@avesta-hq/prevention 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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/bin/cli.js +8 -0
- package/bin/lib/hooks.js +83 -3
- package/bin/lib/settings.js +11 -1
- package/bin/lib/test-local.js +121 -0
- package/package.json +1 -1
|
@@ -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/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('./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/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,17 @@ 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
|
+
|
|
111
121
|
writeSettings(targetDir, settings);
|
|
112
|
-
console.log(' ✓ Configured workflow enforcement hooks (PreToolUse, UserPromptSubmit)');
|
|
122
|
+
console.log(' ✓ Configured workflow enforcement hooks (PreToolUse, UserPromptSubmit, SubagentStart)');
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
function configureStatusLine(targetDir) {
|
|
@@ -0,0 +1,121 @@
|
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avesta-hq/prevention",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|