@avesta-hq/prevention 0.6.0-pre.13 → 0.6.0-pre.14
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.
|
@@ -99,7 +99,7 @@ process.stdin.on("end", () => {
|
|
|
99
99
|
|
|
100
100
|
// Gate progress
|
|
101
101
|
const GATE_ORDER = [
|
|
102
|
-
"vision_approved", "plan_approved", "atdd_approved",
|
|
102
|
+
"vision_approved", "shape_approved", "plan_approved", "atdd_approved",
|
|
103
103
|
"characterization_complete", "tdd_complete",
|
|
104
104
|
"driver_complete", "review_approved",
|
|
105
105
|
];
|
package/bin/lib/settings.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const { isInsideNodeModules, escapeRegExp } = require(
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { isInsideNodeModules, escapeRegExp } = require("./utils");
|
|
4
4
|
|
|
5
|
-
const PREVENTION_SECTION_START =
|
|
6
|
-
const PREVENTION_SECTION_END =
|
|
5
|
+
const PREVENTION_SECTION_START = "<!-- prevention:start -->";
|
|
6
|
+
const PREVENTION_SECTION_END = "<!-- prevention:end -->";
|
|
7
7
|
|
|
8
8
|
const PREVENTION_CLAUDE_MD_SECTION = `${PREVENTION_SECTION_START}
|
|
9
9
|
## Prevention — MCP Server Integration
|
|
@@ -36,23 +36,23 @@ ${PREVENTION_SECTION_END}`;
|
|
|
36
36
|
// ── Helpers ───────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
38
|
function readSettings(targetDir) {
|
|
39
|
-
const settingsPath = path.join(targetDir,
|
|
39
|
+
const settingsPath = path.join(targetDir, ".claude", "settings.json");
|
|
40
40
|
if (!fs.existsSync(settingsPath)) return {};
|
|
41
41
|
try {
|
|
42
|
-
return JSON.parse(fs.readFileSync(settingsPath,
|
|
42
|
+
return JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
43
43
|
} catch {
|
|
44
44
|
return {};
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function writeSettings(targetDir, settings) {
|
|
49
|
-
const claudeDir = path.join(targetDir,
|
|
49
|
+
const claudeDir = path.join(targetDir, ".claude");
|
|
50
50
|
if (!fs.existsSync(claudeDir)) {
|
|
51
51
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
52
52
|
}
|
|
53
53
|
fs.writeFileSync(
|
|
54
|
-
path.join(claudeDir,
|
|
55
|
-
JSON.stringify(settings, null, 2) +
|
|
54
|
+
path.join(claudeDir, "settings.json"),
|
|
55
|
+
JSON.stringify(settings, null, 2) + "\n",
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -60,26 +60,29 @@ function findCliCommand(subcommand) {
|
|
|
60
60
|
if (isInsideNodeModules()) {
|
|
61
61
|
return `npx @avesta-hq/prevention ${subcommand}`;
|
|
62
62
|
}
|
|
63
|
-
return `node ${path.resolve(__dirname,
|
|
63
|
+
return `node ${path.resolve(__dirname, "..", "cli.js")} ${subcommand}`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// ── Configurators ─────────────────────────────────────────────────
|
|
67
67
|
|
|
68
68
|
function isPreventionHook(h) {
|
|
69
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
h.command &&
|
|
71
|
+
(h.command.includes("prevention") || h.command.includes("avesta"))
|
|
72
|
+
);
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
function configureSessionStartHook(targetDir) {
|
|
73
76
|
const settings = readSettings(targetDir);
|
|
74
77
|
if (!settings.hooks) settings.hooks = {};
|
|
75
78
|
|
|
76
|
-
const command = findCliCommand(
|
|
79
|
+
const command = findCliCommand("session-start");
|
|
77
80
|
|
|
78
81
|
// Remove all existing prevention SessionStart hooks, then add one fresh
|
|
79
|
-
const nonPrevention = (settings.hooks.SessionStart || []).filter(
|
|
80
|
-
!(entry.hooks && entry.hooks.some(isPreventionHook))
|
|
82
|
+
const nonPrevention = (settings.hooks.SessionStart || []).filter(
|
|
83
|
+
(entry) => !(entry.hooks && entry.hooks.some(isPreventionHook)),
|
|
81
84
|
);
|
|
82
|
-
nonPrevention.push({ matcher:
|
|
85
|
+
nonPrevention.push({ matcher: "", hooks: [{ type: "command", command }] });
|
|
83
86
|
settings.hooks.SessionStart = nonPrevention;
|
|
84
87
|
|
|
85
88
|
writeSettings(targetDir, settings);
|
|
@@ -89,36 +92,47 @@ function configureEnforcementHooks(targetDir) {
|
|
|
89
92
|
const settings = readSettings(targetDir);
|
|
90
93
|
if (!settings.hooks) settings.hooks = {};
|
|
91
94
|
|
|
92
|
-
const preToolUseCmd = findCliCommand(
|
|
93
|
-
const promptSubmitCmd = findCliCommand(
|
|
94
|
-
const subagentStartCmd = findCliCommand(
|
|
95
|
+
const preToolUseCmd = findCliCommand("hook pre-tool-use");
|
|
96
|
+
const promptSubmitCmd = findCliCommand("hook prompt-submit");
|
|
97
|
+
const subagentStartCmd = findCliCommand("hook subagent-start");
|
|
95
98
|
|
|
96
99
|
// PreToolUse: remove all prevention hooks, add fresh ones
|
|
97
|
-
const preToolUse = (settings.hooks.PreToolUse || []).filter(
|
|
98
|
-
!(entry.hooks && entry.hooks.some(isPreventionHook))
|
|
100
|
+
const preToolUse = (settings.hooks.PreToolUse || []).filter(
|
|
101
|
+
(entry) => !(entry.hooks && entry.hooks.some(isPreventionHook)),
|
|
99
102
|
);
|
|
100
103
|
preToolUse.push(
|
|
101
|
-
{
|
|
102
|
-
|
|
104
|
+
{
|
|
105
|
+
matcher: "Edit|Write",
|
|
106
|
+
hooks: [{ type: "command", command: preToolUseCmd }],
|
|
107
|
+
},
|
|
108
|
+
{ matcher: "Bash", hooks: [{ type: "command", command: preToolUseCmd }] },
|
|
103
109
|
);
|
|
104
110
|
settings.hooks.PreToolUse = preToolUse;
|
|
105
111
|
|
|
106
112
|
// UserPromptSubmit: remove all prevention hooks, add one fresh
|
|
107
|
-
const promptHooks = (settings.hooks.UserPromptSubmit || []).filter(
|
|
108
|
-
!(entry.hooks && entry.hooks.some(isPreventionHook))
|
|
113
|
+
const promptHooks = (settings.hooks.UserPromptSubmit || []).filter(
|
|
114
|
+
(entry) => !(entry.hooks && entry.hooks.some(isPreventionHook)),
|
|
109
115
|
);
|
|
110
|
-
promptHooks.push({
|
|
116
|
+
promptHooks.push({
|
|
117
|
+
matcher: "",
|
|
118
|
+
hooks: [{ type: "command", command: promptSubmitCmd }],
|
|
119
|
+
});
|
|
111
120
|
settings.hooks.UserPromptSubmit = promptHooks;
|
|
112
121
|
|
|
113
122
|
// SubagentStart: remove all prevention hooks, add one fresh
|
|
114
|
-
const subagentHooks = (settings.hooks.SubagentStart || []).filter(
|
|
115
|
-
!(entry.hooks && entry.hooks.some(isPreventionHook))
|
|
123
|
+
const subagentHooks = (settings.hooks.SubagentStart || []).filter(
|
|
124
|
+
(entry) => !(entry.hooks && entry.hooks.some(isPreventionHook)),
|
|
116
125
|
);
|
|
117
|
-
subagentHooks.push({
|
|
126
|
+
subagentHooks.push({
|
|
127
|
+
matcher: "",
|
|
128
|
+
hooks: [{ type: "command", command: subagentStartCmd }],
|
|
129
|
+
});
|
|
118
130
|
settings.hooks.SubagentStart = subagentHooks;
|
|
119
131
|
|
|
120
132
|
writeSettings(targetDir, settings);
|
|
121
|
-
console.log(
|
|
133
|
+
console.log(
|
|
134
|
+
" ✓ Configured workflow enforcement hooks (PreToolUse, UserPromptSubmit, SubagentStart)",
|
|
135
|
+
);
|
|
122
136
|
}
|
|
123
137
|
|
|
124
138
|
function configurePermissions(targetDir) {
|
|
@@ -126,7 +140,7 @@ function configurePermissions(targetDir) {
|
|
|
126
140
|
if (!settings.permissions) settings.permissions = {};
|
|
127
141
|
if (!settings.permissions.allow) settings.permissions.allow = [];
|
|
128
142
|
|
|
129
|
-
const rule =
|
|
143
|
+
const rule = "mcp__prevention__*";
|
|
130
144
|
if (!settings.permissions.allow.includes(rule)) {
|
|
131
145
|
settings.permissions.allow.push(rule);
|
|
132
146
|
}
|
|
@@ -135,19 +149,23 @@ function configurePermissions(targetDir) {
|
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
function configureStatusLine(targetDir) {
|
|
138
|
-
const statusLineSrc = path.join(targetDir,
|
|
152
|
+
const statusLineSrc = path.join(targetDir, ".avesta", "statusLine.cjs");
|
|
139
153
|
if (!fs.existsSync(statusLineSrc)) return;
|
|
140
154
|
|
|
141
155
|
const settings = readSettings(targetDir);
|
|
142
|
-
settings.statusLine = { type:
|
|
156
|
+
settings.statusLine = { type: "command", command: `node ${statusLineSrc}` };
|
|
143
157
|
writeSettings(targetDir, settings);
|
|
144
158
|
}
|
|
145
159
|
|
|
146
160
|
function configureMcpServer(targetDir, binaryPath, options = {}) {
|
|
147
|
-
const mcpJsonPath = path.join(targetDir,
|
|
161
|
+
const mcpJsonPath = path.join(targetDir, ".mcp.json");
|
|
148
162
|
let mcpConfig = {};
|
|
149
163
|
if (fs.existsSync(mcpJsonPath)) {
|
|
150
|
-
try {
|
|
164
|
+
try {
|
|
165
|
+
mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, "utf8"));
|
|
166
|
+
} catch {
|
|
167
|
+
mcpConfig = {};
|
|
168
|
+
}
|
|
151
169
|
}
|
|
152
170
|
|
|
153
171
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
@@ -155,36 +173,42 @@ function configureMcpServer(targetDir, binaryPath, options = {}) {
|
|
|
155
173
|
// Local MCP server: workflow orchestration tools
|
|
156
174
|
// Pass through API/MCP URLs so the server process can reach them
|
|
157
175
|
const env = {};
|
|
158
|
-
if (process.env.AVESTA_API_URL)
|
|
159
|
-
|
|
176
|
+
if (process.env.AVESTA_API_URL)
|
|
177
|
+
env.AVESTA_API_URL = process.env.AVESTA_API_URL;
|
|
178
|
+
if (process.env.AVESTA_MCP_URL)
|
|
179
|
+
env.AVESTA_MCP_URL = process.env.AVESTA_MCP_URL;
|
|
160
180
|
const hasEnv = Object.keys(env).length > 0;
|
|
161
181
|
|
|
162
182
|
if (options.localSource) {
|
|
163
183
|
// Dev mode: run from source via tsx
|
|
164
|
-
const entry = { command:
|
|
184
|
+
const entry = { command: "npx", args: ["tsx", options.localSource] };
|
|
165
185
|
if (hasEnv) entry.env = env;
|
|
166
|
-
mcpConfig.mcpServers[
|
|
186
|
+
mcpConfig.mcpServers["prevention"] = entry;
|
|
167
187
|
} else if (binaryPath) {
|
|
168
188
|
const entry = { command: path.resolve(binaryPath) };
|
|
169
189
|
if (hasEnv) entry.env = env;
|
|
170
|
-
mcpConfig.mcpServers[
|
|
190
|
+
mcpConfig.mcpServers["prevention"] = entry;
|
|
171
191
|
} else {
|
|
172
|
-
const entry = { command:
|
|
192
|
+
const entry = { command: "npx", args: ["@avesta-hq/prevention", "serve"] };
|
|
173
193
|
if (hasEnv) entry.env = env;
|
|
174
|
-
mcpConfig.mcpServers[
|
|
194
|
+
mcpConfig.mcpServers["prevention"] = entry;
|
|
175
195
|
}
|
|
176
196
|
|
|
177
197
|
// Remote MCP server: content delivery (skills, prompts, catalog)
|
|
178
198
|
// Pre-populate auth token from existing session if available
|
|
179
|
-
const remoteUrl = process.env.AVESTA_MCP_URL ||
|
|
180
|
-
const existingRemote = mcpConfig.mcpServers[
|
|
199
|
+
const remoteUrl = process.env.AVESTA_MCP_URL || "http://localhost:3000/mcp";
|
|
200
|
+
const existingRemote = mcpConfig.mcpServers["prevention-content"];
|
|
181
201
|
const existingToken = existingRemote?.headers?.Authorization;
|
|
182
|
-
let authToken = existingToken ||
|
|
202
|
+
let authToken = existingToken || "";
|
|
183
203
|
if (!authToken) {
|
|
184
204
|
try {
|
|
185
|
-
const sessionPath = path.join(
|
|
205
|
+
const sessionPath = path.join(
|
|
206
|
+
require("os").homedir(),
|
|
207
|
+
".avesta",
|
|
208
|
+
"session.json",
|
|
209
|
+
);
|
|
186
210
|
if (fs.existsSync(sessionPath)) {
|
|
187
|
-
const session = JSON.parse(fs.readFileSync(sessionPath,
|
|
211
|
+
const session = JSON.parse(fs.readFileSync(sessionPath, "utf8"));
|
|
188
212
|
if (session.access_token) {
|
|
189
213
|
authToken = `Bearer ${session.access_token}`;
|
|
190
214
|
}
|
|
@@ -192,38 +216,53 @@ function configureMcpServer(targetDir, binaryPath, options = {}) {
|
|
|
192
216
|
} catch {}
|
|
193
217
|
}
|
|
194
218
|
const headers = authToken ? { Authorization: authToken } : {};
|
|
195
|
-
mcpConfig.mcpServers[
|
|
196
|
-
type:
|
|
219
|
+
mcpConfig.mcpServers["prevention-content"] = {
|
|
220
|
+
type: "http",
|
|
197
221
|
url: remoteUrl,
|
|
198
222
|
headers,
|
|
199
223
|
};
|
|
200
224
|
|
|
201
|
-
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) +
|
|
225
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
202
226
|
}
|
|
203
227
|
|
|
204
228
|
function ensureClaudeMdSection(targetDir) {
|
|
205
|
-
const claudeMdPath = path.join(targetDir,
|
|
229
|
+
const claudeMdPath = path.join(targetDir, "CLAUDE.md");
|
|
206
230
|
|
|
207
231
|
if (!fs.existsSync(claudeMdPath)) {
|
|
208
|
-
fs.writeFileSync(
|
|
209
|
-
|
|
232
|
+
fs.writeFileSync(
|
|
233
|
+
claudeMdPath,
|
|
234
|
+
PREVENTION_CLAUDE_MD_SECTION + "\n",
|
|
235
|
+
"utf-8",
|
|
236
|
+
);
|
|
237
|
+
console.log(" ✓ Created CLAUDE.md with Prevention section");
|
|
210
238
|
return;
|
|
211
239
|
}
|
|
212
240
|
|
|
213
|
-
const content = fs.readFileSync(claudeMdPath,
|
|
241
|
+
const content = fs.readFileSync(claudeMdPath, "utf-8");
|
|
214
242
|
|
|
215
243
|
if (content.includes(PREVENTION_SECTION_START)) {
|
|
216
244
|
const regex = new RegExp(
|
|
217
|
-
escapeRegExp(PREVENTION_SECTION_START) +
|
|
245
|
+
escapeRegExp(PREVENTION_SECTION_START) +
|
|
246
|
+
"[\\s\\S]*?" +
|
|
247
|
+
escapeRegExp(PREVENTION_SECTION_END),
|
|
248
|
+
"m",
|
|
218
249
|
);
|
|
219
|
-
fs.writeFileSync(
|
|
220
|
-
|
|
250
|
+
fs.writeFileSync(
|
|
251
|
+
claudeMdPath,
|
|
252
|
+
content.replace(regex, PREVENTION_CLAUDE_MD_SECTION),
|
|
253
|
+
"utf-8",
|
|
254
|
+
);
|
|
255
|
+
console.log(" ✓ Updated Prevention section in CLAUDE.md");
|
|
221
256
|
return;
|
|
222
257
|
}
|
|
223
258
|
|
|
224
|
-
const separator = content.endsWith(
|
|
225
|
-
fs.writeFileSync(
|
|
226
|
-
|
|
259
|
+
const separator = content.endsWith("\n") ? "\n" : "\n\n";
|
|
260
|
+
fs.writeFileSync(
|
|
261
|
+
claudeMdPath,
|
|
262
|
+
content + separator + PREVENTION_CLAUDE_MD_SECTION + "\n",
|
|
263
|
+
"utf-8",
|
|
264
|
+
);
|
|
265
|
+
console.log(" ✓ Added Prevention section to CLAUDE.md");
|
|
227
266
|
}
|
|
228
267
|
|
|
229
268
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avesta-hq/prevention",
|
|
3
|
-
"version": "0.6.0-pre.
|
|
3
|
+
"version": "0.6.0-pre.14",
|
|
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",
|