@directive-run/claude-plugin 1.16.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-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hardening-ai-systems
|
|
3
|
+
description: "Add guardrails (input/output validation), memory strategies, token budgets, PII detection, prompt injection prevention, and circuit breakers to AI orchestrators. Use when enforcing content policies, managing costs, preventing security vulnerabilities, adding resilience, or implementing agent memory across turns."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hardening AI Systems
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
This skill applies when the project uses `@directive-run/ai`. If not found in `package.json`, suggest installing it: `npm install @directive-run/ai`.
|
|
11
|
+
|
|
12
|
+
## When Claude Should Use This Skill
|
|
13
|
+
|
|
14
|
+
## Auto-Invoke Triggers
|
|
15
|
+
- User asks about input/output validation for LLM calls
|
|
16
|
+
- User wants to prevent harmful content or prompt injection
|
|
17
|
+
- User needs to control token spending or set cost limits
|
|
18
|
+
- User asks about agent memory, conversation history, or context management
|
|
19
|
+
- User mentions `createGuardrail`, `GuardrailError`, `createAgentMemory`
|
|
20
|
+
- User wants retry logic, circuit breakers, or failure resilience
|
|
21
|
+
|
|
22
|
+
## Exclusions
|
|
23
|
+
- Do NOT invoke for basic orchestrator/agent setup – use `building-ai-orchestrators`
|
|
24
|
+
- Do NOT invoke for streaming or provider setup – use `building-ai-agents`
|
|
25
|
+
- Do NOT invoke for test mocking – use `testing-ai-systems`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Reference
|
|
30
|
+
|
|
31
|
+
## Decision Tree: Which Hardening Feature?
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
What do you need?
|
|
35
|
+
├── Block bad inputs before LLM call → Input guardrail
|
|
36
|
+
├── Validate/filter LLM output → Output guardrail
|
|
37
|
+
├── Remember context across turns → createAgentMemory
|
|
38
|
+
├── Limit cost / token spend → maxTokenBudget
|
|
39
|
+
├── Detect PII in prompts → PII guardrail
|
|
40
|
+
├── Prevent prompt injection → Injection guardrail
|
|
41
|
+
└── Handle provider failures gracefully → Circuit breaker + retry
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Guardrails
|
|
47
|
+
|
|
48
|
+
Guardrails are pure validators. They check content and throw `GuardrailError` on violation. They must NOT modify the content they check.
|
|
49
|
+
|
|
50
|
+
### Input + Output Guardrails
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { createGuardrail, GuardrailError } from "@directive-run/ai";
|
|
54
|
+
|
|
55
|
+
// Input guardrail – runs before each LLM call
|
|
56
|
+
const lengthGuardrail = createGuardrail({
|
|
57
|
+
name: "input-length",
|
|
58
|
+
type: "input",
|
|
59
|
+
check: async (input) => {
|
|
60
|
+
if (input.prompt.length > 10_000) {
|
|
61
|
+
throw new GuardrailError({
|
|
62
|
+
code: "INPUT_TOO_LONG",
|
|
63
|
+
message: `Input exceeds 10,000 characters (got ${input.prompt.length})`,
|
|
64
|
+
guardrail: "input-length",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// No return value needed – guardrails only validate
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Output guardrail – runs after each LLM response
|
|
72
|
+
const toxicityGuardrail = createGuardrail({
|
|
73
|
+
name: "toxicity-check",
|
|
74
|
+
type: "output",
|
|
75
|
+
check: async (output) => {
|
|
76
|
+
const score = await getToxicityScore(output.text);
|
|
77
|
+
|
|
78
|
+
if (score > 0.8) {
|
|
79
|
+
throw new GuardrailError({
|
|
80
|
+
code: "TOXIC_OUTPUT",
|
|
81
|
+
message: `Toxicity score ${score} exceeds threshold`,
|
|
82
|
+
guardrail: "toxicity-check",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Attach to orchestrator
|
|
89
|
+
const orchestrator = createAgentOrchestrator({
|
|
90
|
+
runner: createAnthropicRunner({ model: "claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
91
|
+
guardrails: [lengthGuardrail, toxicityGuardrail],
|
|
92
|
+
factsSchema: {
|
|
93
|
+
input: t.string(),
|
|
94
|
+
output: t.string().optional(),
|
|
95
|
+
status: t.string<"idle" | "done" | "blocked">(),
|
|
96
|
+
violationCode: t.string().optional(),
|
|
97
|
+
},
|
|
98
|
+
init: (facts) => {
|
|
99
|
+
facts.status = "idle";
|
|
100
|
+
},
|
|
101
|
+
resolvers: {
|
|
102
|
+
process: {
|
|
103
|
+
requirement: "PROCESS",
|
|
104
|
+
resolve: async (req, context) => {
|
|
105
|
+
try {
|
|
106
|
+
const result = await context.runner.run({ prompt: context.facts.input });
|
|
107
|
+
context.facts.output = result.text;
|
|
108
|
+
context.facts.status = "done";
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error instanceof GuardrailError) {
|
|
111
|
+
context.facts.status = "blocked";
|
|
112
|
+
context.facts.violationCode = error.code;
|
|
113
|
+
} else {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Security Guardrails
|
|
126
|
+
|
|
127
|
+
### PII Detection
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const piiGuardrail = createGuardrail({
|
|
131
|
+
name: "pii-check",
|
|
132
|
+
type: "input",
|
|
133
|
+
check: async (input) => {
|
|
134
|
+
const patterns: Record<string, RegExp> = {
|
|
135
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/,
|
|
136
|
+
creditCard: /\b\d{4}[\s-]\d{4}[\s-]\d{4}[\s-]\d{4}\b/,
|
|
137
|
+
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const detected = Object.entries(patterns)
|
|
141
|
+
.filter(([, re]) => re.test(input.prompt))
|
|
142
|
+
.map(([type]) => type);
|
|
143
|
+
|
|
144
|
+
if (detected.length > 0) {
|
|
145
|
+
throw new GuardrailError({
|
|
146
|
+
code: "PII_DETECTED",
|
|
147
|
+
message: `Input contains PII: ${detected.join(", ")}`,
|
|
148
|
+
guardrail: "pii-check",
|
|
149
|
+
metadata: { piiTypes: detected },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Prompt Injection Prevention
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const injectionGuardrail = createGuardrail({
|
|
160
|
+
name: "injection-prevention",
|
|
161
|
+
type: "input",
|
|
162
|
+
check: async (input) => {
|
|
163
|
+
const patterns = [
|
|
164
|
+
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
165
|
+
/you\s+are\s+now\s+(?:in\s+)?(?:developer|admin|god)\s+mode/i,
|
|
166
|
+
/disregard\s+(?:your\s+)?(?:system\s+)?instructions/i,
|
|
167
|
+
/<\|im_start\|>/i,
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const matched = patterns.some((re) => re.test(input.prompt));
|
|
171
|
+
|
|
172
|
+
if (matched) {
|
|
173
|
+
throw new GuardrailError({
|
|
174
|
+
code: "PROMPT_INJECTION",
|
|
175
|
+
message: "Potential prompt injection detected",
|
|
176
|
+
guardrail: "injection-prevention",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Agent Memory
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { createAgentMemory } from "@directive-run/ai";
|
|
189
|
+
|
|
190
|
+
// Sliding window: keep last N messages
|
|
191
|
+
const memory = createAgentMemory({
|
|
192
|
+
strategy: "sliding-window",
|
|
193
|
+
maxMessages: 20,
|
|
194
|
+
maxTokens: 8000,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Summary: compress old context with a cheap LLM
|
|
198
|
+
const summaryMemory = createAgentMemory({
|
|
199
|
+
strategy: "summary",
|
|
200
|
+
maxMessages: 50,
|
|
201
|
+
summarizeAfter: 30,
|
|
202
|
+
summaryRunner: createAnthropicRunner({ model: "claude-haiku-4-5", apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Semantic: retrieve relevant past messages by similarity
|
|
206
|
+
const semanticMemory = createAgentMemory({
|
|
207
|
+
strategy: "semantic",
|
|
208
|
+
maxRetrieved: 5,
|
|
209
|
+
embedder: myEmbeddingFunction,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Usage in resolver
|
|
213
|
+
resolvers: {
|
|
214
|
+
respond: {
|
|
215
|
+
requirement: "RESPOND",
|
|
216
|
+
resolve: async (req, context) => {
|
|
217
|
+
const result = await context.runner.run({
|
|
218
|
+
prompt: context.facts.userMessage,
|
|
219
|
+
messages: context.memory.getMessages(), // inject history
|
|
220
|
+
});
|
|
221
|
+
context.facts.response = result.text;
|
|
222
|
+
// Memory updates automatically after each run
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Token Budget Management
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const orchestrator = createAgentOrchestrator({
|
|
234
|
+
runner: createAnthropicRunner({ model: "claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
235
|
+
budget: {
|
|
236
|
+
maxTokenBudget: 100_000, // Hard limit – halts execution
|
|
237
|
+
budgetWarningThreshold: 0.8, // Fire warning at 80%
|
|
238
|
+
onBudgetWarning: (used, max) => {
|
|
239
|
+
console.warn(`Budget at ${Math.round((used / max) * 100)}%`);
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
factsSchema: {
|
|
243
|
+
input: t.string(),
|
|
244
|
+
output: t.string().optional(),
|
|
245
|
+
tokensUsed: t.number(),
|
|
246
|
+
},
|
|
247
|
+
init: (facts) => {
|
|
248
|
+
facts.tokensUsed = 0;
|
|
249
|
+
},
|
|
250
|
+
resolvers: {
|
|
251
|
+
process: {
|
|
252
|
+
requirement: "PROCESS",
|
|
253
|
+
resolve: async (req, context) => {
|
|
254
|
+
if (!context.budget.canAfford(estimateTokens(context.facts.input))) {
|
|
255
|
+
throw new Error("Insufficient token budget");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const result = await context.runner.run({ prompt: context.facts.input });
|
|
259
|
+
context.facts.output = result.text;
|
|
260
|
+
context.facts.tokensUsed += result.usage.totalTokens;
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
function estimateTokens(text: string): number {
|
|
267
|
+
return Math.ceil(text.length / 4); // ~4 chars per token
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Resilience: Circuit Breaker + Retry
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { createCircuitBreaker } from "@directive-run/ai";
|
|
277
|
+
|
|
278
|
+
const breaker = createCircuitBreaker({
|
|
279
|
+
failureThreshold: 5, // Open after 5 consecutive failures
|
|
280
|
+
recoveryTimeout: 30_000, // Retry after 30s
|
|
281
|
+
halfOpenRequests: 2,
|
|
282
|
+
onStateChange: (from, to) => console.log(`Circuit: ${from} → ${to}`),
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Retry config on the resolver
|
|
286
|
+
resolvers: {
|
|
287
|
+
callLLM: {
|
|
288
|
+
requirement: "CALL_LLM",
|
|
289
|
+
retry: {
|
|
290
|
+
attempts: 3,
|
|
291
|
+
backoff: "exponential",
|
|
292
|
+
initialDelay: 500,
|
|
293
|
+
maxDelay: 10_000,
|
|
294
|
+
retryOn: (error) => {
|
|
295
|
+
return error.message.includes("rate_limit") ||
|
|
296
|
+
error.message.includes("overloaded");
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
resolve: async (req, context) => {
|
|
300
|
+
const result = await context.runner.run({ prompt: context.facts.input });
|
|
301
|
+
context.facts.output = result.text;
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Critical Anti-Patterns
|
|
310
|
+
|
|
311
|
+
## Guardrails that modify content
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// WRONG – guardrails must not transform input
|
|
315
|
+
const bad = createGuardrail({
|
|
316
|
+
name: "sanitizer",
|
|
317
|
+
type: "input",
|
|
318
|
+
check: async (input) => {
|
|
319
|
+
input.prompt = input.prompt.replace(/badword/g, "***"); // NEVER mutate
|
|
320
|
+
return input; // NEVER return content
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// CORRECT – only validate, throw on violation
|
|
325
|
+
const good = createGuardrail({
|
|
326
|
+
name: "content-check",
|
|
327
|
+
type: "input",
|
|
328
|
+
check: async (input) => {
|
|
329
|
+
if (input.prompt.includes("badword")) {
|
|
330
|
+
throw new GuardrailError({ code: "PROHIBITED", message: "Prohibited content", guardrail: "content-check" });
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Not setting token budgets in production
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// WRONG – runaway loops = runaway costs
|
|
340
|
+
const orchestrator = createAgentOrchestrator({ runner, /* no budget */ });
|
|
341
|
+
|
|
342
|
+
// CORRECT
|
|
343
|
+
const orchestrator = createAgentOrchestrator({
|
|
344
|
+
runner,
|
|
345
|
+
budget: { maxTokenBudget: 50_000, budgetWarningThreshold: 0.8 },
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Catching GuardrailError silently
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// WRONG – violations disappear
|
|
353
|
+
} catch (e) { /* swallowed */ }
|
|
354
|
+
|
|
355
|
+
// CORRECT
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error instanceof GuardrailError) {
|
|
358
|
+
context.facts.status = "blocked";
|
|
359
|
+
context.facts.violationCode = error.code;
|
|
360
|
+
} else {
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Resolver parameter naming
|
|
367
|
+
Always use `(req, context)` – never `(req, ctx)` or `(request, context)`.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Reference Files
|
|
372
|
+
|
|
373
|
+
- `ai-guardrails-memory.md` – Full guardrail API, all memory strategies, configuration options
|
|
374
|
+
- `ai-budget-resilience.md` – Budget configuration, circuit breaker, retry policies, estimateTokens
|
|
375
|
+
- `ai-security.md` – PII patterns, injection detection, security best practices
|
|
376
|
+
- `examples/auth-flow.ts` – Auth-gated orchestrator with security guardrails
|
|
377
|
+
- `examples/fraud-analysis.ts` – Budget-aware multi-agent fraud detection
|