tlc-claude-code 2.2.1 → 2.4.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/builder.md +17 -0
- package/.claude/commands/tlc/audit.md +12 -0
- package/.claude/commands/tlc/autofix.md +31 -0
- package/.claude/commands/tlc/build.md +98 -24
- package/.claude/commands/tlc/coverage.md +31 -0
- package/.claude/commands/tlc/discuss.md +31 -0
- package/.claude/commands/tlc/docs.md +31 -0
- package/.claude/commands/tlc/edge-cases.md +31 -0
- package/.claude/commands/tlc/guard.md +9 -0
- package/.claude/commands/tlc/init.md +12 -1
- package/.claude/commands/tlc/plan.md +31 -0
- package/.claude/commands/tlc/quick.md +31 -0
- package/.claude/commands/tlc/review.md +50 -0
- package/.claude/hooks/tlc-session-init.sh +14 -3
- package/CODING-STANDARDS.md +217 -10
- package/bin/setup-autoupdate.js +316 -87
- package/bin/setup-autoupdate.test.js +454 -34
- package/package.json +1 -1
- package/scripts/project-docs.js +1 -1
- package/server/lib/careful-patterns.js +142 -0
- package/server/lib/careful-patterns.test.js +164 -0
- package/server/lib/cli-dispatcher.js +98 -0
- package/server/lib/cli-dispatcher.test.js +249 -0
- package/server/lib/command-router.js +171 -0
- package/server/lib/command-router.test.js +336 -0
- package/server/lib/field-report.js +92 -0
- package/server/lib/field-report.test.js +195 -0
- package/server/lib/orchestration/worktree-manager.js +133 -0
- package/server/lib/orchestration/worktree-manager.test.js +198 -0
- package/server/lib/overdrive-command.js +31 -9
- package/server/lib/overdrive-command.test.js +25 -26
- package/server/lib/prompt-packager.js +98 -0
- package/server/lib/prompt-packager.test.js +185 -0
- package/server/lib/review-fixer.js +107 -0
- package/server/lib/review-fixer.test.js +152 -0
- package/server/lib/routing-command.js +159 -0
- package/server/lib/routing-command.test.js +290 -0
- package/server/lib/scope-checker.js +127 -0
- package/server/lib/scope-checker.test.js +175 -0
- package/server/lib/skill-validator.js +165 -0
- package/server/lib/skill-validator.test.js +289 -0
- package/server/lib/standards/standards-injector.js +6 -0
- package/server/lib/task-router-config.js +142 -0
- package/server/lib/task-router-config.test.js +428 -0
- package/server/lib/test-selector.js +127 -0
- package/server/lib/test-selector.test.js +172 -0
- package/server/setup.sh +271 -271
- package/server/templates/CLAUDE.md +6 -0
- package/server/templates/CODING-STANDARDS.md +356 -10
package/CODING-STANDARDS.md
CHANGED
|
@@ -241,25 +241,64 @@ if (status === 'pending') { ... } // TypeScript will validate
|
|
|
241
241
|
|
|
242
242
|
---
|
|
243
243
|
|
|
244
|
-
## 6. Configuration: No
|
|
244
|
+
## 6. Configuration: Validated, Centralized, No Direct `process.env`
|
|
245
|
+
|
|
246
|
+
### Centralized Config with Validation
|
|
247
|
+
|
|
248
|
+
All configuration MUST go through a validated config module. Never read `process.env` directly in application code.
|
|
245
249
|
|
|
246
250
|
```typescript
|
|
247
|
-
// ❌ NEVER
|
|
251
|
+
// ❌ NEVER: Direct process.env in services/controllers
|
|
248
252
|
class PaymentService {
|
|
249
|
-
private baseUrl =
|
|
250
|
-
private
|
|
253
|
+
private baseUrl = process.env.STRIPE_BASE_URL || 'https://api.stripe.com';
|
|
254
|
+
private apiKey = process.env.STRIPE_API_KEY; // silently undefined if missing
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
//
|
|
254
|
-
// lib/configuration.ts or shared/config/stripe.config.ts
|
|
257
|
+
// ❌ NEVER: Config without validation
|
|
255
258
|
export const stripeConfig = {
|
|
256
259
|
baseUrl: process.env.STRIPE_BASE_URL || 'https://api.stripe.com',
|
|
257
|
-
apiKey: process.env.STRIPE_API_KEY,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
apiKey: process.env.STRIPE_API_KEY, // no validation, no fail-fast
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// ✅ ALWAYS: Validated config module with fail-fast
|
|
264
|
+
// src/config/config.ts
|
|
265
|
+
import { z } from 'zod';
|
|
266
|
+
|
|
267
|
+
const configSchema = z.object({
|
|
268
|
+
stripe: z.object({
|
|
269
|
+
baseUrl: z.string().url().default('https://api.stripe.com'),
|
|
270
|
+
apiKey: z.string().min(1, 'STRIPE_API_KEY is required'),
|
|
271
|
+
timeout: z.coerce.number().int().positive().default(30000),
|
|
272
|
+
}),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
export type AppConfig = z.infer<typeof configSchema>;
|
|
276
|
+
|
|
277
|
+
export function loadConfig(): AppConfig {
|
|
278
|
+
const result = configSchema.safeParse({
|
|
279
|
+
stripe: {
|
|
280
|
+
baseUrl: process.env.STRIPE_BASE_URL,
|
|
281
|
+
apiKey: process.env.STRIPE_API_KEY,
|
|
282
|
+
timeout: process.env.STRIPE_TIMEOUT,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (!result.success) {
|
|
287
|
+
throw new Error(`Config validation failed:\n${result.error.format()}`);
|
|
288
|
+
}
|
|
289
|
+
return result.data;
|
|
290
|
+
}
|
|
260
291
|
```
|
|
261
292
|
|
|
262
|
-
|
|
293
|
+
### Rules
|
|
294
|
+
|
|
295
|
+
1. **All `process.env` access MUST be in the config module** — never in services, controllers, or middleware.
|
|
296
|
+
2. **All required env vars MUST be validated at startup** — fail fast, not at first request.
|
|
297
|
+
3. **Distinguish required vs optional** — required vars throw on boot; optional have explicit defaults.
|
|
298
|
+
4. **No silent empty-string fallbacks** for secrets or connection strings.
|
|
299
|
+
5. **Config is injected via DI or imported from the config module** — never read from env directly.
|
|
300
|
+
|
|
301
|
+
**Rule**: If it could differ between environments, it's config. If it's required, validate it at boot.
|
|
263
302
|
|
|
264
303
|
---
|
|
265
304
|
|
|
@@ -432,6 +471,13 @@ Before committing any code:
|
|
|
432
471
|
- [ ] Typed errors, not generic throws
|
|
433
472
|
- [ ] Tests co-located with module
|
|
434
473
|
- [ ] Build passes (`npm run build`)
|
|
474
|
+
- [ ] **No direct `process.env`** in services/controllers — config module only
|
|
475
|
+
- [ ] **Config validated at startup** with schema (Zod/Joi)
|
|
476
|
+
- [ ] **Ownership checks** on every data-access endpoint
|
|
477
|
+
- [ ] **No secrets in responses** — API keys, tokens are write-only
|
|
478
|
+
- [ ] **Sensitive data hashed** at rest (OTPs, reset tokens)
|
|
479
|
+
- [ ] **All HTML output escaped** — no raw interpolation of user values
|
|
480
|
+
- [ ] **No manual `new Service()`** — use DI container
|
|
435
481
|
|
|
436
482
|
---
|
|
437
483
|
|
|
@@ -723,6 +769,160 @@ function handleResponse<T>(response: ApiResponse<T>): T {
|
|
|
723
769
|
|
|
724
770
|
---
|
|
725
771
|
|
|
772
|
+
## 21. Security: Authorization, Secrets, and Output Encoding
|
|
773
|
+
|
|
774
|
+
### Resource Ownership Checks
|
|
775
|
+
|
|
776
|
+
Every endpoint that accesses a resource MUST verify the requesting user owns or has permission to access that resource. Authentication alone is not enough.
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
// ❌ WRONG: Only checks authentication, not ownership
|
|
780
|
+
@Get('settings/:merchantId')
|
|
781
|
+
@UseGuards(AuthGuard)
|
|
782
|
+
async getSettings(@Param('merchantId') merchantId: string): Promise<Settings> {
|
|
783
|
+
return this.settingsService.findByMerchant(merchantId); // any authed user can read any merchant
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// ✅ CORRECT: Verifies ownership
|
|
787
|
+
@Get('settings/:merchantId')
|
|
788
|
+
@UseGuards(AuthGuard)
|
|
789
|
+
async getSettings(
|
|
790
|
+
@Param('merchantId') merchantId: string,
|
|
791
|
+
@CurrentUser() user: User,
|
|
792
|
+
): Promise<Settings> {
|
|
793
|
+
if (user.merchantId !== merchantId && user.role !== 'admin') {
|
|
794
|
+
throw new ForbiddenError('Cannot access another merchant\'s settings');
|
|
795
|
+
}
|
|
796
|
+
return this.settingsService.findByMerchant(merchantId);
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
**Rules:**
|
|
801
|
+
1. Every data-access endpoint MUST check that the requesting user owns the resource.
|
|
802
|
+
2. Tests MUST prove that user A cannot read/modify user B's data.
|
|
803
|
+
3. Prefer a reusable ownership guard over per-endpoint checks.
|
|
804
|
+
|
|
805
|
+
### Never Expose Secrets in Responses
|
|
806
|
+
|
|
807
|
+
API keys, webhook secrets, tokens, and credentials MUST never appear in API responses or rendered HTML.
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
810
|
+
// ❌ WRONG: Returning secrets to the client
|
|
811
|
+
return {
|
|
812
|
+
merchantId: merchant.id,
|
|
813
|
+
apiKey: merchant.apiKey, // NEVER
|
|
814
|
+
webhookSecret: merchant.webhookSecret, // NEVER
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// ✅ CORRECT: Mask or omit secrets
|
|
818
|
+
return {
|
|
819
|
+
merchantId: merchant.id,
|
|
820
|
+
apiKey: mask(merchant.apiKey), // "sk_live_...4x7f"
|
|
821
|
+
webhookSecret: '••••••••', // write-only, never read back
|
|
822
|
+
};
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
**Rules:**
|
|
826
|
+
1. Secrets are **write-only** — accept on create/update, never return in GET responses.
|
|
827
|
+
2. If display is needed, return masked values (first 4 + last 4 chars).
|
|
828
|
+
3. Audit every response DTO and HTML template for leaked credentials.
|
|
829
|
+
|
|
830
|
+
### Hash Sensitive Data at Rest
|
|
831
|
+
|
|
832
|
+
OTP codes, reset tokens, and session secrets MUST be stored as one-way hashes, never plaintext.
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
// ❌ WRONG: Plaintext OTP
|
|
836
|
+
await db.otpSessions.insert({ code: '123456', expiresAt });
|
|
837
|
+
|
|
838
|
+
// ✅ CORRECT: Hashed OTP
|
|
839
|
+
import { createHash } from 'crypto';
|
|
840
|
+
const hashedCode = createHash('sha256').update(code).digest('hex');
|
|
841
|
+
await db.otpSessions.insert({ codeHash: hashedCode, expiresAt });
|
|
842
|
+
|
|
843
|
+
// Verification: hash the input and compare
|
|
844
|
+
function verifyOtp(input: string, stored: string): boolean {
|
|
845
|
+
const inputHash = createHash('sha256').update(input).digest('hex');
|
|
846
|
+
return timingSafeEqual(Buffer.from(inputHash), Buffer.from(stored));
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### Output Encoding (XSS Prevention)
|
|
851
|
+
|
|
852
|
+
Never interpolate user-controlled values into HTML without escaping. This applies to inline HTML generation, template strings, and server-rendered pages.
|
|
853
|
+
|
|
854
|
+
```typescript
|
|
855
|
+
// ❌ WRONG: Raw interpolation — XSS risk
|
|
856
|
+
const html = `<h1>Welcome, ${user.name}</h1>`;
|
|
857
|
+
const html = `<a href="${redirectUrl}">Continue</a>`;
|
|
858
|
+
|
|
859
|
+
// ✅ CORRECT: Always escape
|
|
860
|
+
import { escapeHtml } from '@/shared/utils/escape';
|
|
861
|
+
|
|
862
|
+
const html = `<h1>Welcome, ${escapeHtml(user.name)}</h1>`;
|
|
863
|
+
const html = `<a href="${escapeHtml(redirectUrl)}">Continue</a>`;
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
// shared/utils/escape.ts
|
|
868
|
+
/**
|
|
869
|
+
* Escapes HTML special characters to prevent XSS.
|
|
870
|
+
*/
|
|
871
|
+
export function escapeHtml(str: string): string {
|
|
872
|
+
return str
|
|
873
|
+
.replace(/&/g, '&')
|
|
874
|
+
.replace(/</g, '<')
|
|
875
|
+
.replace(/>/g, '>')
|
|
876
|
+
.replace(/"/g, '"')
|
|
877
|
+
.replace(/'/g, ''');
|
|
878
|
+
}
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
**Rules:**
|
|
882
|
+
1. **Every** dynamic value in HTML output MUST be escaped.
|
|
883
|
+
2. Prefer a real templating engine over string concatenation for HTML.
|
|
884
|
+
3. Audit merchant-controlled, query-string, and user-input values first.
|
|
885
|
+
4. Do not add new pages using inline HTML string builders — use a proper frontend.
|
|
886
|
+
|
|
887
|
+
---
|
|
888
|
+
|
|
889
|
+
## 22. Dependency Injection: No Manual Instantiation
|
|
890
|
+
|
|
891
|
+
Never manually instantiate services or providers that should be managed by the DI container.
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
// ❌ WRONG: Bypassing DI
|
|
895
|
+
class PaymentService {
|
|
896
|
+
async processPayment(method: string): Promise<void> {
|
|
897
|
+
const provider = method === 'cyberpay'
|
|
898
|
+
? new CyberpayProvider(process.env.CYBERPAY_KEY) // untestable, unmanaged
|
|
899
|
+
: new CodProvider();
|
|
900
|
+
await provider.charge(amount);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// ✅ CORRECT: Provider registry via DI
|
|
905
|
+
@Injectable()
|
|
906
|
+
class PaymentService {
|
|
907
|
+
constructor(
|
|
908
|
+
@Inject('PAYMENT_PROVIDERS') private providers: Map<string, PaymentProvider>,
|
|
909
|
+
) {}
|
|
910
|
+
|
|
911
|
+
async processPayment(method: string): Promise<void> {
|
|
912
|
+
const provider = this.providers.get(method);
|
|
913
|
+
if (!provider) throw new BadRequestError(`Unknown payment method: ${method}`);
|
|
914
|
+
await provider.charge(amount);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
**Rules:**
|
|
920
|
+
1. All providers/services MUST be registered in the DI container.
|
|
921
|
+
2. Never use `new ServiceClass()` in application code — let the framework manage lifecycle.
|
|
922
|
+
3. Use factory providers or provider registries for dynamic selection.
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
726
926
|
## AI Instructions
|
|
727
927
|
|
|
728
928
|
When generating code:
|
|
@@ -744,6 +944,13 @@ When generating code:
|
|
|
744
944
|
15. **Never** let folders exceed 15 files - organize into subfolders
|
|
745
945
|
16. **Never** use `any` type - use `unknown` or proper interfaces
|
|
746
946
|
17. **Always** add explicit return types to functions
|
|
947
|
+
18. **Never** read `process.env` outside the config module
|
|
948
|
+
19. **Always** validate config at startup with a schema
|
|
949
|
+
20. **Always** add ownership/authorization checks on data-access endpoints
|
|
950
|
+
21. **Never** return secrets (API keys, tokens) in API responses or HTML
|
|
951
|
+
22. **Always** hash OTPs, reset tokens, and session secrets before storing
|
|
952
|
+
23. **Always** escape dynamic values in HTML output
|
|
953
|
+
24. **Never** use `new ServiceClass()` — register in DI and inject
|
|
747
954
|
|
|
748
955
|
### Cleanup Tasks
|
|
749
956
|
|