Defensive code pattern detection for TypeScript/JavaScript — Practify Protocol Level 1
Maturity: IN DEVELOPMENT — cross-language port of the verified Python scanner. Pattern detection accuracy validated on test files; not yet tested on large production codebases.
Scans TypeScript/JavaScript source code for defensive patterns — code constructs that signal the author was uncertain but chose to hide it rather than declare it.
| Pattern | Severity | What it catches |
|---|---|---|
swallowed-exception |
ERROR | catch {} — exception silently discarded, or catch (e) { console.log(e) } |
bare-except |
ERROR | catch (e: any) / bare catch — catching unknown errors |
missing-anchor |
WARNING | Exported function without @pract.test or @pract.i_dont_know JSDoc annotation |
defensive-null-chain |
WARNING | 3+ chained if (x === null) return null patterns |
trivial-test |
WARNING | Tautological assertions like expect(result).toBe(result) |
vague-todo |
INFO | // TODO without issue tracker reference |
npm install practify-scanner# Scan a file
npx practify-scanner check src/app.ts
# Scan a directory
npx practify-scanner check src/
# Generate a health report
npx practify-scanner report .import { scanFile, scanDirectory, summarize } from "practify-scanner";
// Scan a single file
const patterns = scanFile("src/index.ts");
for (const p of patterns) {
console.log(`[${p.patternType}] ${p.filePath}:${p.lineNumber}`);
console.log(` ${p.suggestion}`);
}
// Scan a directory
const results = scanDirectory("src/");
console.log(`Scanned ${results.size} files`);
// Summarize
const summary = summarize(patterns);
console.log(summary); // { total, byType, bySeverity }practify-scanner is based on a simple insight:
Defensive code patterns expose the author's cognitive state — "I'm not sure about this, but I don't want to say I don't know."
The scanner doesn't judge. It surfaces the patterns so you can decide:
- "Yes, I know this is safe" → document why
- "No, I'm not sure" → add a
@pract.i_dont_knowJSDoc annotation - "Actually, this is a real problem" → fix it
The scanner checks for practify annotations in JSDoc comments:
/**
* Process a list of items, keeping only positive values.
*
* @pract.test "empty list returns empty" — process([]) === []
* @pract.test "keep positives" — process([-1, 0, 3, -5]) === [3]
* @pract.i_dont_know "behavior with massive lists (>1M) not verified"
*/
export function process(data: number[]): number[] {
return data.filter((x) => x > 0);
}Without these annotations, process would trigger a missing-anchor warning — not because it's buggy, but because it has no verifiable evidence of correctness.
| Aspect | Python | TypeScript |
|---|---|---|
| AST parser | ast (stdlib) |
TypeScript Compiler API |
| Anchor detection | Decorators (@pract.test) |
JSDoc annotations (@pract.test) |
| File types | .py |
.ts, .tsx, .js, .jsx, .mjs |
| Maturity | Verified | In Development |
The TypeScript port uses JSDoc annotations instead of decorators because:
- TypeScript decorators are still experimental (Stage 3)
- JSDoc annotations work in both
.tsand.jsfiles - No runtime dependency required — annotations are pure documentation until the anchor system is ported
This is Level 1 of the Practify Protocol — a code verification protocol for vibe coding.
- Level 1 (this package): Scanner — detect defensive patterns
- Level 2+: Anchors + Noise Cards — full verification protocol
For the Python full protocol including @pract.test decorators, noise card tracking, and AI context injection, see the Python practify package.
This port needs practice data:
- Run it on your TypeScript codebase — report false positives
- Compare with the Python scanner — same patterns on equivalent code should produce the same results
- Test on large codebases — we need to validate performance and accuracy at scale
MIT