@avesta-hq/prevention 0.4.1 → 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.
@@ -2,8 +2,8 @@
2
2
  name: avesta-acceptance-stage
3
3
  description: Set up acceptance stage workflow with version-based test execution
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"acceptance-stage\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-acceptance-stage
@@ -2,8 +2,9 @@
2
2
  name: avesta-acceptance-writer
3
3
  description: Write Executable Specifications in problem-domain language
4
4
  model: inherit
5
- tools: Read, Write, Edit, Glob, Grep
5
+ disallowedTools: Bash
6
6
  maxTurns: 25
7
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"acceptance-writer\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
8
  ---
8
9
 
9
10
  # avesta-acceptance-writer
@@ -2,8 +2,8 @@
2
2
  name: avesta-bug-fixer
3
3
  description: Bug fix workflow - reproduce, failing test, fix, verify
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"bug-fixer\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-bug-fixer
@@ -2,8 +2,8 @@
2
2
  name: avesta-commit-stage
3
3
  description: Set up commit stage CI/CD pipeline with test pyramid enforcement
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"commit-stage\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-commit-stage
@@ -2,8 +2,9 @@
2
2
  name: avesta-dependency-reviewer
3
3
  description: Review dependencies and generate gradual update plan
4
4
  model: inherit
5
- tools: Read, Bash, Glob, Grep
5
+ disallowedTools: Write, Edit
6
6
  maxTurns: 20
7
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"dependency-reviewer\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
8
  ---
8
9
 
9
10
  # avesta-dependency-reviewer
@@ -2,8 +2,8 @@
2
2
  name: avesta-driver-builder
3
3
  description: Implement Protocol Driver for acceptance tests
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"driver-builder\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-driver-builder
@@ -2,8 +2,9 @@
2
2
  name: avesta-dsl-builder
3
3
  description: Implement Domain Specific Language for acceptance tests
4
4
  model: inherit
5
- tools: Read, Write, Edit, Glob, Grep
5
+ disallowedTools: Bash
6
6
  maxTurns: 25
7
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"dsl-builder\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
8
  ---
8
9
 
9
10
  # avesta-dsl-builder
@@ -2,8 +2,8 @@
2
2
  name: avesta-enhancer
3
3
  description: Enhancement workflow - plan, ATDD, TDD, driver, review
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"enhancer\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-enhancer
@@ -2,8 +2,8 @@
2
2
  name: avesta-green
3
3
  description: TDD Green Phase - Write MINIMAL code to pass the failing test
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 25
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"green\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-green
@@ -2,8 +2,8 @@
2
2
  name: avesta-layer-worker
3
3
  description: Implement a single Clean Architecture layer with TDD (all tests first, then all code)
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"layer-worker\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-layer-worker
@@ -2,8 +2,8 @@
2
2
  name: avesta-layer
3
3
  description: Implement a full layer - all tests first, then all production code
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 50
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"layer\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-layer
@@ -2,8 +2,9 @@
2
2
  name: avesta-mutation-tester
3
3
  description: Analyze test effectiveness with mutation testing patterns
4
4
  model: inherit
5
- tools: Read, Bash, Glob, Grep
5
+ disallowedTools: Write, Edit
6
6
  maxTurns: 25
7
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"mutation-tester\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
8
  ---
8
9
 
9
10
  # avesta-mutation-tester
@@ -2,8 +2,9 @@
2
2
  name: avesta-planner
3
3
  description: Create implementation plans using Example Mapping and behavioral analysis
4
4
  model: inherit
5
- tools: Read, Write, Edit, Glob, Grep
5
+ disallowedTools: Bash
6
6
  maxTurns: 30
7
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"planner\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
8
  ---
8
9
 
9
10
  # avesta-planner
@@ -2,8 +2,8 @@
2
2
  name: avesta-red
3
3
  description: TDD Red Phase - Write ONE failing test
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 25
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"red\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-red
@@ -2,8 +2,8 @@
2
2
  name: avesta-refactorer
3
3
  description: TDD Refactor Phase - Improve code structure while keeping tests green
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 25
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"refactorer\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-refactorer
@@ -2,8 +2,8 @@
2
2
  name: avesta-release-stage
3
3
  description: Set up release stage CI/CD pipeline with contract verification
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"release-stage\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-release-stage
@@ -2,8 +2,9 @@
2
2
  name: avesta-reviewer
3
3
  description: Review code for XP/CD best practices and Clean Architecture
4
4
  model: inherit
5
- tools: Read, Glob, Grep
5
+ disallowedTools: Write, Edit, Bash
6
6
  maxTurns: 20
7
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"reviewer\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
8
  ---
8
9
 
9
10
  # avesta-reviewer
@@ -2,8 +2,9 @@
2
2
  name: avesta-scaffolder
3
3
  description: Scaffold project structure, dependencies, and config files
4
4
  model: haiku
5
- tools: Read, Write, Bash, Glob
5
+ disallowedTools: Edit
6
6
  maxTurns: 15
7
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"scaffolder\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
8
  ---
8
9
 
9
10
  # avesta-scaffolder
@@ -2,8 +2,8 @@
2
2
  name: avesta-tech-debt-assessor
3
3
  description: Tech debt workflow - approval tests, refactor, verify
4
4
  model: inherit
5
- tools: Read, Write, Edit, Bash, Glob, Grep
6
5
  maxTurns: 30
6
+ initialPrompt: "FIRST: Call avesta_get_prompt(\"tech-debt-assessor\") to load your instructions — include project_type in context. Then call avesta_get_skill for EVERY skill in skills_to_load with _source=workflow. File writes are BLOCKED by the PreToolUse hook until all skills are loaded."
7
7
  ---
8
8
 
9
9
  # avesta-tech-debt-assessor
@@ -8,13 +8,13 @@ If blocked: Show the `error_message` to the user and stop.
8
8
 
9
9
  If `gate_check.allowed` is true, orchestrate per-layer workers:
10
10
 
11
- 1. **Parse arguments** from `$ARGUMENTS`:
11
+ 1. **Parse arguments and dispatch response**:
12
12
  - **Layer(s)**: If a specific layer is named (`domain`, `application`, `infrastructure`, `presentation`), implement only that layer. If `all` or no layer specified, implement all four in order: domain, application, infrastructure, presentation.
13
- - **Project type**: If `backend` or `frontend` is specified, use it. Default: `backend`.
13
+ - **Project type**: Use the `project_type` field from the dispatch response. This comes from the workflow state and reflects what the user set during `/avesta-init`. Do NOT default to backend — always use the dispatch response value.
14
14
 
15
15
  2. **For each layer** (in the determined order), spawn a subagent:
16
16
  - Use the `avesta-layer-worker` agent
17
- - Pass a structured prompt: `"LAYER: <layer_name>\nPROJECT_TYPE: <project_type>\nFEATURE: $ARGUMENTS"`
17
+ - Pass a structured prompt: `"LAYER: <layer_name>\nPROJECT_TYPE: <project_type from dispatch>\nFEATURE: $ARGUMENTS"`
18
18
  - Wait for the worker to complete before starting the next layer
19
19
  - After each worker completes, briefly report which layer finished and how many tests passed
20
20
 
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
@@ -16,9 +16,11 @@ Commands:
16
16
  init First-time setup: commands, agents, MCP server, hooks, CLAUDE.md
17
17
  update Update everything to latest version (commands, agents, MCP server, hooks)
18
18
  serve Start the MCP server (used by Claude Code)
19
+ test-local [dir] Set up a project to use local source (no npm publish needed)
19
20
  session-start Output session context (used by SessionStart hook)
20
21
  hook pre-tool-use Workflow enforcement hook (blocks unauthorized edits)
21
22
  hook prompt-submit Context injection hook (injects workflow state)
23
+ hook subagent-start Skill-loading injection hook (fires when agents spawn)
22
24
 
23
25
  Examples:
24
26
  cd my-project
@@ -44,6 +46,9 @@ switch (command) {
44
46
  case 'serve':
45
47
  require('./lib/init').serve();
46
48
  break;
49
+ case 'test-local':
50
+ require('../scripts/lib/test-local').testLocal(args[1]);
51
+ break;
47
52
  case 'session-start':
48
53
  require('./lib/session-start').sessionStart();
49
54
  break;
@@ -55,6 +60,9 @@ switch (command) {
55
60
  case 'prompt-submit':
56
61
  require('./lib/hooks').hookPromptSubmit();
57
62
  break;
63
+ case 'subagent-start':
64
+ require('./lib/hooks').hookSubagentStart();
65
+ break;
58
66
  default:
59
67
  console.error(`Unknown hook: ${args[1]}`);
60
68
  process.exit(1);
package/bin/lib/hooks.js CHANGED
@@ -109,6 +109,14 @@ function extractBashWriteContent(command) {
109
109
 
110
110
  // ── PreToolUse Hook ───────────────────────────────────────────────
111
111
 
112
+ function readWorkflowState(cwd) {
113
+ const statePath = path.join(cwd, '.avesta', 'workflow-state.json');
114
+ if (!fs.existsSync(statePath)) return null;
115
+ try {
116
+ return JSON.parse(fs.readFileSync(statePath, 'utf-8'));
117
+ } catch { return null; }
118
+ }
119
+
112
120
  function hookPreToolUse() {
113
121
  try {
114
122
  const data = JSON.parse(fs.readFileSync(0, 'utf8'));
@@ -187,6 +195,22 @@ function hookPreToolUse() {
187
195
  }
188
196
  }
189
197
 
198
+ // State-based fallback: block git commit/push when policy is missing
199
+ // but workflow state exists (e.g., policy file deleted or stale)
200
+ if (!policy && /\bgit\s+(commit|push)\b/.test(command)) {
201
+ const state = readWorkflowState(cwd);
202
+ // Bypass for scaffold, spike, or idle (no feature in progress)
203
+ const bypass = state?.needs_scaffold || state?.mode === 'spike' || state?.current_phase === 'idle';
204
+ if (state && !bypass && !state.gates?.review_approved) {
205
+ const verb = /\bgit\s+commit\b/.test(command) ? 'commit' : 'push';
206
+ process.stderr.write(
207
+ `⛔ Prevention: Cannot ${verb} without completing code review.\n` +
208
+ `Run /avesta-code-review first.\n`
209
+ );
210
+ process.exit(2);
211
+ }
212
+ }
213
+
190
214
  // Secret scanning for Bash commands that write file content
191
215
  const inlineContent = extractBashWriteContent(command);
192
216
  if (inlineContent) {
@@ -226,8 +250,13 @@ function hookPromptSubmit() {
226
250
  process.exit(0);
227
251
  }
228
252
 
229
- if (policy.prompt_context) {
230
- process.stdout.write(JSON.stringify({ additionalContext: policy.prompt_context }));
253
+ const parts = [];
254
+ if (policy.prompt_context) parts.push(policy.prompt_context);
255
+ if (policy.project_type) parts.push(`Project type: ${policy.project_type}`);
256
+ if (policy.current_agent) parts.push(`Active agent: avesta-${policy.current_agent}`);
257
+
258
+ if (parts.length > 0) {
259
+ process.stdout.write(JSON.stringify({ additionalContext: parts.join(' | ') }));
231
260
  }
232
261
  process.exit(0);
233
262
  } catch {
@@ -235,4 +264,55 @@ function hookPromptSubmit() {
235
264
  }
236
265
  }
237
266
 
238
- module.exports = { hookPreToolUse, hookPromptSubmit };
267
+ // ── SubagentStart Hook ───────────────────────────────────────────
268
+
269
+ function hookSubagentStart() {
270
+ try {
271
+ const data = JSON.parse(fs.readFileSync(0, 'utf8'));
272
+ const { agent_type, cwd = process.cwd() } = data;
273
+
274
+ if (!agent_type) {
275
+ process.exit(0);
276
+ }
277
+
278
+ // Read workflow state to get project_type and current agent's required skills
279
+ const statePath = path.join(cwd, '.avesta', 'workflow-state.json');
280
+ if (!fs.existsSync(statePath)) {
281
+ process.exit(0);
282
+ }
283
+
284
+ let state;
285
+ try {
286
+ state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
287
+ } catch {
288
+ process.exit(0);
289
+ }
290
+
291
+ const requiredSkills = state.required_skills_for_agent || [];
292
+ if (requiredSkills.length === 0) {
293
+ // Agent has no skills to load (committer, shipper, etc.)
294
+ process.exit(0);
295
+ }
296
+
297
+ const projectType = state.project?.type || 'backend';
298
+ const agentName = state.current_agent || agent_type.replace(/^avesta-/, '');
299
+
300
+ process.stdout.write(JSON.stringify({
301
+ hookSpecificOutput: {
302
+ hookEventName: "SubagentStart",
303
+ additionalContext:
304
+ `[Prevention] MANDATORY — Skills must be loaded before writing any files.\n` +
305
+ `The PreToolUse hook will BLOCK all file writes until skills are loaded.\n` +
306
+ `1. Call avesta_get_prompt("${agentName}", { context: { project_type: "${projectType}" } })\n` +
307
+ `2. Call avesta_get_skill for EVERY skill in skills_to_load with _source="workflow"\n` +
308
+ `Required skills (${requiredSkills.length}): ${requiredSkills.join(', ')}\n` +
309
+ `Project type: ${projectType}`
310
+ }
311
+ }));
312
+ process.exit(0);
313
+ } catch {
314
+ process.exit(0); // Fail-open
315
+ }
316
+ }
317
+
318
+ module.exports = { hookPreToolUse, hookPromptSubmit, hookSubagentStart };
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); }
@@ -87,6 +87,7 @@ function configureEnforcementHooks(targetDir) {
87
87
 
88
88
  const preToolUseCmd = findCliCommand('hook pre-tool-use');
89
89
  const promptSubmitCmd = findCliCommand('hook prompt-submit');
90
+ const subagentStartCmd = findCliCommand('hook subagent-start');
90
91
 
91
92
  // PreToolUse: remove old prevention hooks, add fresh ones
92
93
  const existing = (settings.hooks.PreToolUse || []).filter(entry =>
@@ -108,8 +109,30 @@ function configureEnforcementHooks(targetDir) {
108
109
  settings.hooks.UserPromptSubmit = promptHooks;
109
110
  }
110
111
 
112
+ // SubagentStart: inject skill-loading context when agents spawn
113
+ const subagentHooks = (settings.hooks.SubagentStart || []).filter(entry =>
114
+ !(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('prevention hook')))
115
+ );
116
+ subagentHooks.push(
117
+ { matcher: '', hooks: [{ type: 'command', command: subagentStartCmd }] },
118
+ );
119
+ settings.hooks.SubagentStart = subagentHooks;
120
+
121
+ writeSettings(targetDir, settings);
122
+ console.log(' ✓ Configured workflow enforcement hooks (PreToolUse, UserPromptSubmit, SubagentStart)');
123
+ }
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
+
111
135
  writeSettings(targetDir, settings);
112
- console.log(' ✓ Configured workflow enforcement hooks (PreToolUse, UserPromptSubmit)');
113
136
  }
114
137
 
115
138
  function configureStatusLine(targetDir) {
@@ -121,7 +144,7 @@ function configureStatusLine(targetDir) {
121
144
  writeSettings(targetDir, settings);
122
145
  }
123
146
 
124
- function configureMcpServer(targetDir, binaryPath) {
147
+ function configureMcpServer(targetDir, binaryPath, options = {}) {
125
148
  const mcpJsonPath = path.join(targetDir, '.mcp.json');
126
149
  let mcpConfig = {};
127
150
  if (fs.existsSync(mcpJsonPath)) {
@@ -130,11 +153,51 @@ function configureMcpServer(targetDir, binaryPath) {
130
153
 
131
154
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
132
155
 
133
- if (binaryPath) {
134
- 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;
135
172
  } else {
136
- 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 {}
137
194
  }
195
+ const headers = authToken ? { Authorization: authToken } : {};
196
+ mcpConfig.mcpServers['prevention-content'] = {
197
+ type: 'http',
198
+ url: remoteUrl,
199
+ headers,
200
+ };
138
201
 
139
202
  fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
140
203
  }
@@ -167,6 +230,7 @@ function ensureClaudeMdSection(targetDir) {
167
230
  module.exports = {
168
231
  configureSessionStartHook,
169
232
  configureEnforcementHooks,
233
+ configurePermissions,
170
234
  configureStatusLine,
171
235
  configureMcpServer,
172
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.4.1",
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"