@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.
- package/.claude/agents/avesta-acceptance-stage.md +1 -1
- package/.claude/agents/avesta-acceptance-writer.md +2 -1
- package/.claude/agents/avesta-bug-fixer.md +1 -1
- package/.claude/agents/avesta-commit-stage.md +1 -1
- package/.claude/agents/avesta-dependency-reviewer.md +2 -1
- package/.claude/agents/avesta-driver-builder.md +1 -1
- package/.claude/agents/avesta-dsl-builder.md +2 -1
- package/.claude/agents/avesta-enhancer.md +1 -1
- package/.claude/agents/avesta-green.md +1 -1
- package/.claude/agents/avesta-layer-worker.md +1 -1
- package/.claude/agents/avesta-layer.md +1 -1
- package/.claude/agents/avesta-mutation-tester.md +2 -1
- package/.claude/agents/avesta-planner.md +2 -1
- package/.claude/agents/avesta-red.md +1 -1
- package/.claude/agents/avesta-refactorer.md +1 -1
- package/.claude/agents/avesta-release-stage.md +1 -1
- package/.claude/agents/avesta-reviewer.md +2 -1
- package/.claude/agents/avesta-scaffolder.md +2 -1
- package/.claude/agents/avesta-tech-debt-assessor.md +1 -1
- package/.claude/commands/avesta-layer.md +3 -3
- package/CLAUDE.md +5 -2
- package/bin/cli.js +8 -0
- package/bin/lib/hooks.js +83 -3
- package/bin/lib/init.js +5 -3
- package/bin/lib/settings.js +69 -5
- package/bin/lib/utils.js +16 -9
- package/package.json +6 -14
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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**:
|
|
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
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
|
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
|
@@ -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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|