@qball-inc/the-bulwark 1.0.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 +43 -0
- package/agents/bulwark-fix-validator.md +633 -0
- package/agents/bulwark-implementer.md +391 -0
- package/agents/bulwark-issue-analyzer.md +308 -0
- package/agents/bulwark-standards-reviewer.md +221 -0
- package/agents/plan-creation-architect.md +323 -0
- package/agents/plan-creation-eng-lead.md +352 -0
- package/agents/plan-creation-po.md +300 -0
- package/agents/plan-creation-qa-critic.md +334 -0
- package/agents/product-ideation-competitive-analyzer.md +298 -0
- package/agents/product-ideation-idea-validator.md +268 -0
- package/agents/product-ideation-market-researcher.md +292 -0
- package/agents/product-ideation-pattern-documenter.md +308 -0
- package/agents/product-ideation-segment-analyzer.md +303 -0
- package/agents/product-ideation-strategist.md +259 -0
- package/agents/statusline-setup.md +97 -0
- package/hooks/hooks.json +59 -0
- package/package.json +45 -0
- package/scripts/hooks/cleanup-stale.sh +13 -0
- package/scripts/hooks/enforce-quality.sh +166 -0
- package/scripts/hooks/implementer-quality.sh +256 -0
- package/scripts/hooks/inject-protocol.sh +52 -0
- package/scripts/hooks/suggest-pipeline.sh +175 -0
- package/scripts/hooks/track-pipeline-start.sh +37 -0
- package/scripts/hooks/track-pipeline-stop.sh +52 -0
- package/scripts/init-rules.sh +35 -0
- package/scripts/init.sh +151 -0
- package/skills/anthropic-validator/SKILL.md +607 -0
- package/skills/anthropic-validator/references/agents-checklist.md +131 -0
- package/skills/anthropic-validator/references/commands-checklist.md +102 -0
- package/skills/anthropic-validator/references/hooks-checklist.md +151 -0
- package/skills/anthropic-validator/references/mcp-checklist.md +136 -0
- package/skills/anthropic-validator/references/plugins-checklist.md +148 -0
- package/skills/anthropic-validator/references/skills-checklist.md +85 -0
- package/skills/assertion-patterns/SKILL.md +296 -0
- package/skills/bug-magnet-data/SKILL.md +284 -0
- package/skills/bug-magnet-data/context/cli-args.md +91 -0
- package/skills/bug-magnet-data/context/db-query.md +104 -0
- package/skills/bug-magnet-data/context/file-contents.md +103 -0
- package/skills/bug-magnet-data/context/http-body.md +91 -0
- package/skills/bug-magnet-data/context/process-spawn.md +123 -0
- package/skills/bug-magnet-data/data/booleans/boundaries.yaml +143 -0
- package/skills/bug-magnet-data/data/collections/arrays.yaml +114 -0
- package/skills/bug-magnet-data/data/collections/objects.yaml +123 -0
- package/skills/bug-magnet-data/data/concurrency/race-conditions.yaml +118 -0
- package/skills/bug-magnet-data/data/concurrency/state-machines.yaml +115 -0
- package/skills/bug-magnet-data/data/dates/boundaries.yaml +137 -0
- package/skills/bug-magnet-data/data/dates/invalid.yaml +132 -0
- package/skills/bug-magnet-data/data/dates/timezone.yaml +118 -0
- package/skills/bug-magnet-data/data/encoding/charset.yaml +79 -0
- package/skills/bug-magnet-data/data/encoding/normalization.yaml +105 -0
- package/skills/bug-magnet-data/data/formats/email.yaml +154 -0
- package/skills/bug-magnet-data/data/formats/json.yaml +187 -0
- package/skills/bug-magnet-data/data/formats/url.yaml +165 -0
- package/skills/bug-magnet-data/data/language-specific/javascript.yaml +182 -0
- package/skills/bug-magnet-data/data/language-specific/python.yaml +174 -0
- package/skills/bug-magnet-data/data/language-specific/rust.yaml +148 -0
- package/skills/bug-magnet-data/data/numbers/boundaries.yaml +161 -0
- package/skills/bug-magnet-data/data/numbers/precision.yaml +89 -0
- package/skills/bug-magnet-data/data/numbers/special.yaml +69 -0
- package/skills/bug-magnet-data/data/strings/boundaries.yaml +109 -0
- package/skills/bug-magnet-data/data/strings/injection.yaml +208 -0
- package/skills/bug-magnet-data/data/strings/special-chars.yaml +190 -0
- package/skills/bug-magnet-data/data/strings/unicode.yaml +139 -0
- package/skills/bug-magnet-data/references/external-lists.md +115 -0
- package/skills/bulwark-brainstorm/SKILL.md +563 -0
- package/skills/bulwark-brainstorm/references/at-teammate-prompts.md +60 -0
- package/skills/bulwark-brainstorm/references/role-critical-analyst.md +78 -0
- package/skills/bulwark-brainstorm/references/role-development-lead.md +66 -0
- package/skills/bulwark-brainstorm/references/role-product-delivery-lead.md +79 -0
- package/skills/bulwark-brainstorm/references/role-product-manager.md +62 -0
- package/skills/bulwark-brainstorm/references/role-project-sme.md +59 -0
- package/skills/bulwark-brainstorm/references/role-technical-architect.md +66 -0
- package/skills/bulwark-research/SKILL.md +298 -0
- package/skills/bulwark-research/references/viewpoint-contrarian.md +63 -0
- package/skills/bulwark-research/references/viewpoint-direct-investigation.md +62 -0
- package/skills/bulwark-research/references/viewpoint-first-principles.md +65 -0
- package/skills/bulwark-research/references/viewpoint-practitioner.md +62 -0
- package/skills/bulwark-research/references/viewpoint-prior-art.md +66 -0
- package/skills/bulwark-scaffold/SKILL.md +330 -0
- package/skills/bulwark-statusline/SKILL.md +161 -0
- package/skills/bulwark-statusline/scripts/statusline.sh +144 -0
- package/skills/bulwark-verify/SKILL.md +519 -0
- package/skills/code-review/SKILL.md +428 -0
- package/skills/code-review/examples/anti-patterns/linting.ts +181 -0
- package/skills/code-review/examples/anti-patterns/security.ts +91 -0
- package/skills/code-review/examples/anti-patterns/standards.ts +195 -0
- package/skills/code-review/examples/anti-patterns/type-safety.ts +108 -0
- package/skills/code-review/examples/recommended/linting.ts +195 -0
- package/skills/code-review/examples/recommended/security.ts +154 -0
- package/skills/code-review/examples/recommended/standards.ts +231 -0
- package/skills/code-review/examples/recommended/type-safety.ts +181 -0
- package/skills/code-review/frameworks/angular.md +218 -0
- package/skills/code-review/frameworks/django.md +235 -0
- package/skills/code-review/frameworks/express.md +207 -0
- package/skills/code-review/frameworks/flask.md +298 -0
- package/skills/code-review/frameworks/generic.md +146 -0
- package/skills/code-review/frameworks/react.md +152 -0
- package/skills/code-review/frameworks/vue.md +244 -0
- package/skills/code-review/references/linting-patterns.md +221 -0
- package/skills/code-review/references/security-patterns.md +125 -0
- package/skills/code-review/references/standards-patterns.md +246 -0
- package/skills/code-review/references/type-safety-patterns.md +130 -0
- package/skills/component-patterns/SKILL.md +131 -0
- package/skills/component-patterns/references/pattern-cli-command.md +118 -0
- package/skills/component-patterns/references/pattern-database.md +166 -0
- package/skills/component-patterns/references/pattern-external-api.md +139 -0
- package/skills/component-patterns/references/pattern-file-parser.md +168 -0
- package/skills/component-patterns/references/pattern-http-server.md +162 -0
- package/skills/component-patterns/references/pattern-process-spawner.md +133 -0
- package/skills/continuous-feedback/SKILL.md +327 -0
- package/skills/continuous-feedback/references/collect-instructions.md +81 -0
- package/skills/continuous-feedback/references/specialize-code-review.md +82 -0
- package/skills/continuous-feedback/references/specialize-general.md +98 -0
- package/skills/continuous-feedback/references/specialize-test-audit.md +81 -0
- package/skills/create-skill/SKILL.md +359 -0
- package/skills/create-skill/references/agent-conventions.md +194 -0
- package/skills/create-skill/references/agent-template.md +195 -0
- package/skills/create-skill/references/content-guidance.md +291 -0
- package/skills/create-skill/references/decision-framework.md +124 -0
- package/skills/create-skill/references/template-pipeline.md +217 -0
- package/skills/create-skill/references/template-reference-heavy.md +111 -0
- package/skills/create-skill/references/template-research.md +210 -0
- package/skills/create-skill/references/template-script-driven.md +172 -0
- package/skills/create-skill/references/template-simple.md +80 -0
- package/skills/create-subagent/SKILL.md +353 -0
- package/skills/create-subagent/references/agent-conventions.md +268 -0
- package/skills/create-subagent/references/content-guidance.md +232 -0
- package/skills/create-subagent/references/decision-framework.md +134 -0
- package/skills/create-subagent/references/template-single-agent.md +192 -0
- package/skills/fix-bug/SKILL.md +241 -0
- package/skills/governance-protocol/SKILL.md +116 -0
- package/skills/init/SKILL.md +341 -0
- package/skills/issue-debugging/SKILL.md +385 -0
- package/skills/issue-debugging/references/anti-patterns.md +245 -0
- package/skills/issue-debugging/references/debug-report-schema.md +227 -0
- package/skills/mock-detection/SKILL.md +511 -0
- package/skills/mock-detection/references/false-positive-prevention.md +402 -0
- package/skills/mock-detection/references/stub-patterns.md +236 -0
- package/skills/pipeline-templates/SKILL.md +215 -0
- package/skills/pipeline-templates/references/code-change-workflow.md +277 -0
- package/skills/pipeline-templates/references/code-review.md +336 -0
- package/skills/pipeline-templates/references/fix-validation.md +421 -0
- package/skills/pipeline-templates/references/new-feature.md +335 -0
- package/skills/pipeline-templates/references/research-brainstorm.md +161 -0
- package/skills/pipeline-templates/references/research-planning.md +257 -0
- package/skills/pipeline-templates/references/test-audit.md +389 -0
- package/skills/pipeline-templates/references/test-execution-fix.md +238 -0
- package/skills/plan-creation/SKILL.md +497 -0
- package/skills/product-ideation/SKILL.md +372 -0
- package/skills/product-ideation/references/analysis-frameworks.md +161 -0
- package/skills/session-handoff/SKILL.md +139 -0
- package/skills/session-handoff/references/examples.md +223 -0
- package/skills/setup-lsp/SKILL.md +312 -0
- package/skills/setup-lsp/references/server-registry.md +85 -0
- package/skills/setup-lsp/references/troubleshooting.md +135 -0
- package/skills/subagent-output-templating/SKILL.md +415 -0
- package/skills/subagent-output-templating/references/examples.md +440 -0
- package/skills/subagent-prompting/SKILL.md +364 -0
- package/skills/subagent-prompting/references/examples.md +342 -0
- package/skills/test-audit/SKILL.md +531 -0
- package/skills/test-audit/references/known-limitations.md +41 -0
- package/skills/test-audit/references/priority-classification.md +30 -0
- package/skills/test-audit/references/prompts/deep-mode-detection.md +83 -0
- package/skills/test-audit/references/prompts/synthesis.md +57 -0
- package/skills/test-audit/references/rewrite-instructions.md +46 -0
- package/skills/test-audit/references/schemas/audit-output.yaml +100 -0
- package/skills/test-audit/references/schemas/diagnostic-output.yaml +49 -0
- package/skills/test-audit/scripts/data-flow-analyzer.ts +509 -0
- package/skills/test-audit/scripts/integration-mock-detector.ts +462 -0
- package/skills/test-audit/scripts/package.json +20 -0
- package/skills/test-audit/scripts/skip-detector.ts +211 -0
- package/skills/test-audit/scripts/verification-counter.ts +295 -0
- package/skills/test-classification/SKILL.md +310 -0
- package/skills/test-fixture-creation/SKILL.md +295 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coding Standards Recommended Patterns
|
|
3
|
+
*
|
|
4
|
+
* These examples demonstrate good coding standards.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// CS1: Single Responsibility - Each function does one thing
|
|
8
|
+
async function validateOrder(order: Order): Promise<void> {
|
|
9
|
+
if (!order.items.length) {
|
|
10
|
+
throw new OrderValidationError('Empty order');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function calculateOrderTotal(order: Order): number {
|
|
15
|
+
return order.items.reduce((sum, item) => sum + item.price, 0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function saveOrder(order: Order): Promise<void> {
|
|
19
|
+
await database.orders.save(order);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function notifyOrderConfirmation(order: Order): Promise<void> {
|
|
23
|
+
await emailService.send(order.customer.email, 'Order confirmed');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function trackOrderProcessed(order: Order): Promise<void> {
|
|
27
|
+
await analytics.track('order_processed', order.id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Composition of single-responsibility functions
|
|
31
|
+
async function processOrderClean(order: Order): Promise<Order> {
|
|
32
|
+
await validateOrder(order);
|
|
33
|
+
order.total = calculateOrderTotal(order);
|
|
34
|
+
await saveOrder(order);
|
|
35
|
+
await notifyOrderConfirmation(order);
|
|
36
|
+
await trackOrderProcessed(order);
|
|
37
|
+
return order;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// CS2: Named Constants - No Magic Values
|
|
41
|
+
const DISCOUNT_THRESHOLDS = {
|
|
42
|
+
MINIMUM_FOR_DISCOUNT: 100,
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
const DISCOUNT_RATES = {
|
|
46
|
+
GOLD: 0.2,
|
|
47
|
+
SILVER: 0.1,
|
|
48
|
+
DEFAULT: 0.05,
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
type CustomerType = 'gold' | 'silver' | 'bronze';
|
|
52
|
+
|
|
53
|
+
function calculateDiscountClean(total: number, customerType: CustomerType): number {
|
|
54
|
+
if (total <= DISCOUNT_THRESHOLDS.MINIMUM_FOR_DISCOUNT) {
|
|
55
|
+
return total * DISCOUNT_RATES.DEFAULT;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const rate = customerType === 'gold'
|
|
59
|
+
? DISCOUNT_RATES.GOLD
|
|
60
|
+
: customerType === 'silver'
|
|
61
|
+
? DISCOUNT_RATES.SILVER
|
|
62
|
+
: DISCOUNT_RATES.DEFAULT;
|
|
63
|
+
|
|
64
|
+
return total * rate;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// CS2: Explicit Transformation - Return new object instead of mutation
|
|
68
|
+
function normalizeUserClean(user: User): NormalizedUser {
|
|
69
|
+
return {
|
|
70
|
+
...user,
|
|
71
|
+
email: user.email.toLowerCase(),
|
|
72
|
+
name: user.name.trim(),
|
|
73
|
+
normalizedAt: new Date(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// CS2: Explicit Dependencies - Pass state as parameter
|
|
78
|
+
interface Transaction {
|
|
79
|
+
id: string;
|
|
80
|
+
amount: number;
|
|
81
|
+
status: 'pending' | 'processing' | 'completed';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function processPaymentClean(
|
|
85
|
+
transaction: Transaction,
|
|
86
|
+
amount: number
|
|
87
|
+
): Transaction {
|
|
88
|
+
return {
|
|
89
|
+
...transaction,
|
|
90
|
+
amount,
|
|
91
|
+
status: 'processing',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// CS2: Pure Getters - No side effects
|
|
96
|
+
class UserProfileClean {
|
|
97
|
+
private _views = 0;
|
|
98
|
+
private readonly id: string;
|
|
99
|
+
|
|
100
|
+
get viewCount(): number {
|
|
101
|
+
return this._views;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Side effect is explicit method, not hidden in getter
|
|
105
|
+
recordView(): void {
|
|
106
|
+
this._views++;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Analytics tracking is separate concern
|
|
110
|
+
trackProfileView(): void {
|
|
111
|
+
analytics.track('profile_viewed', this.id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// CS3: Fail Fast - Propagate errors with context
|
|
116
|
+
async function fetchDataClean(id: string): Promise<Data> {
|
|
117
|
+
try {
|
|
118
|
+
return await api.getData(id);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// Log for debugging
|
|
121
|
+
console.error(`Failed to fetch data for id=${id}:`, error);
|
|
122
|
+
// Re-throw with context
|
|
123
|
+
throw new DataFetchError(`Failed to fetch data: ${id}`, { cause: error });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// CS3: Fail on Invalid Input
|
|
128
|
+
function parseConfigClean(raw: string): Config {
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(raw);
|
|
131
|
+
return validateConfig(parsed);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new ConfigParseError('Invalid configuration', { cause: error });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// CS3: Input Validation at Boundaries
|
|
138
|
+
function calculateAgeClean(birthYear: number): number {
|
|
139
|
+
const currentYear = new Date().getFullYear();
|
|
140
|
+
|
|
141
|
+
if (!Number.isInteger(birthYear)) {
|
|
142
|
+
throw new ValidationError('Birth year must be an integer');
|
|
143
|
+
}
|
|
144
|
+
if (birthYear < 1900) {
|
|
145
|
+
throw new ValidationError('Birth year too far in the past');
|
|
146
|
+
}
|
|
147
|
+
if (birthYear > currentYear) {
|
|
148
|
+
throw new ValidationError('Birth year cannot be in the future');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return currentYear - birthYear;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// CS4: Clean Imports - Only import what's used
|
|
155
|
+
import { neededHelper } from './utils';
|
|
156
|
+
import type { Config, Data } from './types';
|
|
157
|
+
|
|
158
|
+
// CS4: All Variables Used
|
|
159
|
+
function processItemsClean(items: Item[]): string[] {
|
|
160
|
+
// Only declare what's needed
|
|
161
|
+
return items.map((item) => item.name);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// CS4: No Commented-Out Code - Delete unused code
|
|
165
|
+
function handleRequestClean(req: Request) {
|
|
166
|
+
// Old implementation deleted, not commented
|
|
167
|
+
return processNewWay(req);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// CS4: No Dead Code - All paths reachable
|
|
171
|
+
function processClean(value: number): number {
|
|
172
|
+
if (value > 0) {
|
|
173
|
+
return value * 2;
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Calculate a complex weighted metric from data points.
|
|
180
|
+
*
|
|
181
|
+
* @param data - Array of metric data points to analyze
|
|
182
|
+
* @param weights - Weight for each data point (must match data length)
|
|
183
|
+
* @param normalize - Whether to normalize the result to 0-1 range
|
|
184
|
+
* @param threshold - Minimum value to include in calculation
|
|
185
|
+
* @returns Calculated metric with metadata
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* const result = calculateComplexMetric(
|
|
190
|
+
* [{ value: 10 }, { value: 20 }],
|
|
191
|
+
* [0.3, 0.7],
|
|
192
|
+
* true,
|
|
193
|
+
* 5
|
|
194
|
+
* );
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export function calculateComplexMetricClean(
|
|
198
|
+
data: MetricData[],
|
|
199
|
+
weights: number[],
|
|
200
|
+
normalize: boolean,
|
|
201
|
+
threshold: number
|
|
202
|
+
): ComplexResult {
|
|
203
|
+
validateInputs(data, weights, threshold);
|
|
204
|
+
return internalCalculation(data, weights, normalize, threshold);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Consistent Error Handling
|
|
208
|
+
async function fetchAllClean(): Promise<FetchResult> {
|
|
209
|
+
// Consistent try-catch for all operations
|
|
210
|
+
try {
|
|
211
|
+
const [users, orders, products] = await Promise.all([
|
|
212
|
+
fetch('/users').then((r) => r.json()),
|
|
213
|
+
fetch('/orders').then((r) => r.json()),
|
|
214
|
+
fetch('/products').then((r) => r.json()),
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
return { users, orders, products, success: true };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Failed to fetch data:', error);
|
|
220
|
+
throw new DataFetchError('Failed to load data', { cause: error });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Consistent Async Style
|
|
225
|
+
async function loadDataClean(): Promise<ProcessedData> {
|
|
226
|
+
// Consistent await throughout
|
|
227
|
+
const config = await loadConfig();
|
|
228
|
+
const response = await fetch(config.url);
|
|
229
|
+
const data = await response.json();
|
|
230
|
+
return processData(data);
|
|
231
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Safety Recommended Patterns
|
|
3
|
+
*
|
|
4
|
+
* These examples demonstrate type-safe coding practices.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Proper typing instead of `any`
|
|
8
|
+
interface ProcessableData {
|
|
9
|
+
id: string;
|
|
10
|
+
payload: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function processDataSafe(data: ProcessableData) {
|
|
14
|
+
// Type-safe access
|
|
15
|
+
return data.payload;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Typed return with proper interface
|
|
19
|
+
interface User {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
email: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchUserSafe(id: string): Promise<User> {
|
|
26
|
+
const response = await fetch(`/api/users/${id}`);
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
|
|
29
|
+
// Validate shape before returning
|
|
30
|
+
if (!isUser(data)) {
|
|
31
|
+
throw new Error('Invalid user data');
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Type guard for runtime validation
|
|
37
|
+
function isUser(data: unknown): data is User {
|
|
38
|
+
return (
|
|
39
|
+
typeof data === 'object' &&
|
|
40
|
+
data !== null &&
|
|
41
|
+
'id' in data &&
|
|
42
|
+
'name' in data &&
|
|
43
|
+
'email' in data
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// JSON parsing with validation
|
|
48
|
+
import { z } from 'zod';
|
|
49
|
+
|
|
50
|
+
const ConfigSchema = z.object({
|
|
51
|
+
apiUrl: z.string().url(),
|
|
52
|
+
timeout: z.number().positive(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
type Config = z.infer<typeof ConfigSchema>;
|
|
56
|
+
|
|
57
|
+
function loadConfigSafe(): Config {
|
|
58
|
+
const raw = JSON.parse(process.env.CONFIG ?? '{}');
|
|
59
|
+
return ConfigSchema.parse(raw); // Throws if invalid
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Explicit parameter types
|
|
63
|
+
function calculateSafe(a: number, b: number): number {
|
|
64
|
+
return a + b;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Safe null handling with type narrowing
|
|
68
|
+
interface UserProfile {
|
|
69
|
+
name: string;
|
|
70
|
+
profile?: {
|
|
71
|
+
avatar?: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getAvatarSafe(user: UserProfile | null): string {
|
|
76
|
+
if (!user) {
|
|
77
|
+
return 'default-avatar.png';
|
|
78
|
+
}
|
|
79
|
+
return user.profile?.avatar ?? 'default-avatar.png';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Consistent optional chaining
|
|
83
|
+
function getNameSafe(user: UserProfile | null): string {
|
|
84
|
+
// All access uses optional chaining consistently
|
|
85
|
+
return user?.profile?.avatar ?? 'default';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Nullish coalescing for valid falsy values
|
|
89
|
+
function getCountSafe(items?: number): number {
|
|
90
|
+
// Uses ?? to only default on null/undefined, not 0
|
|
91
|
+
return items ?? 10;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Type narrowing instead of assertion
|
|
95
|
+
function convertSafe(value: string): number {
|
|
96
|
+
const parsed = parseInt(value, 10);
|
|
97
|
+
if (isNaN(parsed)) {
|
|
98
|
+
throw new Error(`Cannot convert "${value}" to number`);
|
|
99
|
+
}
|
|
100
|
+
return parsed;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Type guard for API responses
|
|
104
|
+
interface ApiResponse<T> {
|
|
105
|
+
success: boolean;
|
|
106
|
+
data?: T;
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseResponseSafe<T>(
|
|
111
|
+
response: ApiResponse<T>,
|
|
112
|
+
guard: (data: unknown) => data is T
|
|
113
|
+
): T {
|
|
114
|
+
if (!response.success || !response.data) {
|
|
115
|
+
throw new Error(response.error ?? 'Request failed');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!guard(response.data)) {
|
|
119
|
+
throw new Error('Invalid response shape');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return response.data;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Safe array access with validation
|
|
126
|
+
async function getFirstUserSafe(): Promise<User> {
|
|
127
|
+
const users = await fetchUsers();
|
|
128
|
+
|
|
129
|
+
if (users.length === 0) {
|
|
130
|
+
throw new Error('No users found');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return users[0]; // Now guaranteed to exist
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Typed catch clause
|
|
137
|
+
async function fetchDataSafe() {
|
|
138
|
+
try {
|
|
139
|
+
return await fetch('/api/data');
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof Error) {
|
|
142
|
+
console.log(error.message);
|
|
143
|
+
} else {
|
|
144
|
+
console.log('Unknown error:', error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Proper generic constraints
|
|
150
|
+
interface Identifiable {
|
|
151
|
+
id: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function wrapSafe<T extends Identifiable>(value: T): { wrapped: T; id: string } {
|
|
155
|
+
return { wrapped: value, id: value.id };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Discriminated unions for exhaustive checking
|
|
159
|
+
type Result<T> =
|
|
160
|
+
| { success: true; data: T }
|
|
161
|
+
| { success: false; error: string };
|
|
162
|
+
|
|
163
|
+
function handleResult<T>(result: Result<T>): T {
|
|
164
|
+
if (result.success) {
|
|
165
|
+
return result.data;
|
|
166
|
+
}
|
|
167
|
+
throw new Error(result.error);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Branded types for semantic safety
|
|
171
|
+
type UserId = string & { readonly __brand: 'UserId' };
|
|
172
|
+
type OrderId = string & { readonly __brand: 'OrderId' };
|
|
173
|
+
|
|
174
|
+
function createUserId(id: string): UserId {
|
|
175
|
+
return id as UserId;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getUserById(id: UserId): Promise<User> {
|
|
179
|
+
// Can only be called with UserId, not OrderId
|
|
180
|
+
return fetch(`/api/users/${id}`).then((r) => r.json());
|
|
181
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Angular Framework Patterns
|
|
2
|
+
|
|
3
|
+
Security and quality patterns specific to Angular applications.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Security Patterns
|
|
8
|
+
|
|
9
|
+
### XSS Prevention
|
|
10
|
+
|
|
11
|
+
| Pattern | Risk | Detection |
|
|
12
|
+
|---------|------|-----------|
|
|
13
|
+
| `innerHTML` binding | High | `[innerHTML]="userInput"` without sanitization |
|
|
14
|
+
| `bypassSecurityTrust*` | Critical | Bypassing Angular sanitizer |
|
|
15
|
+
| Template injection | High | Dynamic template construction |
|
|
16
|
+
|
|
17
|
+
**Safe Pattern:**
|
|
18
|
+
```typescript
|
|
19
|
+
// BAD
|
|
20
|
+
@Component({
|
|
21
|
+
template: `<div [innerHTML]="userComment"></div>`
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// GOOD - Angular sanitizes by default for interpolation
|
|
25
|
+
@Component({
|
|
26
|
+
template: `<div>{{userComment}}</div>`
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// If HTML is needed, sanitize explicitly
|
|
30
|
+
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
31
|
+
|
|
32
|
+
@Component({
|
|
33
|
+
template: `<div [innerHTML]="sanitizedContent"></div>`
|
|
34
|
+
})
|
|
35
|
+
export class CommentComponent {
|
|
36
|
+
sanitizedContent: SafeHtml;
|
|
37
|
+
|
|
38
|
+
constructor(private sanitizer: DomSanitizer) {}
|
|
39
|
+
|
|
40
|
+
setContent(html: string) {
|
|
41
|
+
// Only use when HTML is from trusted source
|
|
42
|
+
this.sanitizedContent = this.sanitizer.sanitize(SecurityContext.HTML, html);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### URL Safety
|
|
48
|
+
|
|
49
|
+
| Pattern | Risk | Detection |
|
|
50
|
+
|---------|------|-----------|
|
|
51
|
+
| Dynamic href | High | `[href]="userUrl"` |
|
|
52
|
+
| Router navigate with user input | Medium | `router.navigate([userInput])` |
|
|
53
|
+
|
|
54
|
+
**Safe Pattern:**
|
|
55
|
+
```typescript
|
|
56
|
+
// Validate URLs before using
|
|
57
|
+
validateUrl(url: string): boolean {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = new URL(url);
|
|
60
|
+
return ['http:', 'https:'].includes(parsed.protocol);
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### HTTP Security
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Always use HttpClient (includes XSRF protection)
|
|
71
|
+
import { HttpClient } from '@angular/common/http';
|
|
72
|
+
|
|
73
|
+
// Configure XSRF
|
|
74
|
+
@NgModule({
|
|
75
|
+
imports: [
|
|
76
|
+
HttpClientModule,
|
|
77
|
+
HttpClientXsrfModule.withOptions({
|
|
78
|
+
cookieName: 'XSRF-TOKEN',
|
|
79
|
+
headerName: 'X-XSRF-TOKEN'
|
|
80
|
+
})
|
|
81
|
+
]
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Type Safety Patterns
|
|
88
|
+
|
|
89
|
+
### Component Inputs/Outputs
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// BAD
|
|
93
|
+
@Input() data: any;
|
|
94
|
+
@Output() changed = new EventEmitter<any>();
|
|
95
|
+
|
|
96
|
+
// GOOD
|
|
97
|
+
interface UserData {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Input() data!: UserData;
|
|
103
|
+
@Output() changed = new EventEmitter<UserData>();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Observable Typing
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// BAD
|
|
110
|
+
users$: Observable<any>;
|
|
111
|
+
|
|
112
|
+
// GOOD
|
|
113
|
+
users$: Observable<User[]>;
|
|
114
|
+
|
|
115
|
+
// With error handling
|
|
116
|
+
users$ = this.http.get<User[]>('/api/users').pipe(
|
|
117
|
+
catchError(error => {
|
|
118
|
+
console.error('Failed to fetch users:', error);
|
|
119
|
+
return of([]);
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Form Typing
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// BAD
|
|
128
|
+
form = new FormGroup({
|
|
129
|
+
name: new FormControl(''),
|
|
130
|
+
email: new FormControl('')
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// GOOD - Typed forms (Angular 14+)
|
|
134
|
+
interface UserForm {
|
|
135
|
+
name: FormControl<string>;
|
|
136
|
+
email: FormControl<string>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
form = new FormGroup<UserForm>({
|
|
140
|
+
name: new FormControl('', { nonNullable: true }),
|
|
141
|
+
email: new FormControl('', { nonNullable: true })
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Linting Patterns
|
|
148
|
+
|
|
149
|
+
### Component Size
|
|
150
|
+
|
|
151
|
+
| Metric | Threshold | Action |
|
|
152
|
+
|--------|-----------|--------|
|
|
153
|
+
| Template lines | >100 | Split to sub-components |
|
|
154
|
+
| Component methods | >10 | Extract to service |
|
|
155
|
+
| Constructor params | >5 | Review dependencies |
|
|
156
|
+
|
|
157
|
+
### Subscription Management
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// BAD - Memory leak
|
|
161
|
+
ngOnInit() {
|
|
162
|
+
this.service.getData().subscribe(data => this.data = data);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// GOOD - Automatic cleanup
|
|
166
|
+
data$ = this.service.getData();
|
|
167
|
+
// Use async pipe in template: {{ data$ | async }}
|
|
168
|
+
|
|
169
|
+
// Or manual cleanup
|
|
170
|
+
private destroy$ = new Subject<void>();
|
|
171
|
+
|
|
172
|
+
ngOnInit() {
|
|
173
|
+
this.service.getData()
|
|
174
|
+
.pipe(takeUntil(this.destroy$))
|
|
175
|
+
.subscribe(data => this.data = data);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ngOnDestroy() {
|
|
179
|
+
this.destroy$.next();
|
|
180
|
+
this.destroy$.complete();
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Coding Standards
|
|
187
|
+
|
|
188
|
+
### Naming Conventions
|
|
189
|
+
|
|
190
|
+
| Element | Convention | Example |
|
|
191
|
+
|---------|------------|---------|
|
|
192
|
+
| Component | PascalCase + Component suffix | `UserProfileComponent` |
|
|
193
|
+
| Service | PascalCase + Service suffix | `AuthService` |
|
|
194
|
+
| Directive | PascalCase + Directive suffix | `HighlightDirective` |
|
|
195
|
+
| Pipe | PascalCase + Pipe suffix | `CurrencyPipe` |
|
|
196
|
+
|
|
197
|
+
### File Organization
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
feature/
|
|
201
|
+
feature.component.ts
|
|
202
|
+
feature.component.html
|
|
203
|
+
feature.component.scss
|
|
204
|
+
feature.component.spec.ts
|
|
205
|
+
feature.module.ts
|
|
206
|
+
feature-routing.module.ts
|
|
207
|
+
services/
|
|
208
|
+
models/
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## What to Skip
|
|
214
|
+
|
|
215
|
+
- Angular CLI generated code
|
|
216
|
+
- Zone.js related patterns
|
|
217
|
+
- Test bed setup boilerplate
|
|
218
|
+
- Module declarations
|