vanara 0.1.0 → 0.2.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/README.md +14 -0
- package/bin/vanara.js +66 -2
- package/catalog/agents/api-designer/AGENT.md +140 -0
- package/catalog/agents/api-designer/examples/openapi-snippet.yaml +130 -0
- package/catalog/agents/api-designer/examples/review-notes.md +61 -0
- package/catalog/agents/api-designer/references/contract-and-openapi.md +82 -0
- package/catalog/agents/api-designer/references/design-checklist.md +68 -0
- package/catalog/agents/api-designer/references/versioning-and-evolution.md +63 -0
- package/catalog/agents/api-designer/scripts/lint-openapi.mjs +180 -0
- package/catalog/agents/code-reviewer/AGENT.md +168 -0
- package/catalog/agents/code-reviewer/examples/pr-comment-template.md +48 -0
- package/catalog/agents/code-reviewer/examples/sample-review-output.md +38 -0
- package/catalog/agents/code-reviewer/references/review-checklist.md +46 -0
- package/catalog/agents/code-reviewer/references/security-review.md +62 -0
- package/catalog/agents/code-reviewer/references/severity-rubric.md +63 -0
- package/catalog/agents/code-reviewer/scripts/review-guard.mjs +102 -0
- package/catalog/agents/debugger/AGENT.md +112 -0
- package/catalog/agents/debugger/examples/postmortem-template.md +59 -0
- package/catalog/agents/debugger/examples/repro-template.md +50 -0
- package/catalog/agents/debugger/references/bisection.md +63 -0
- package/catalog/agents/debugger/references/debugging-method.md +57 -0
- package/catalog/agents/debugger/references/observability.md +61 -0
- package/catalog/agents/debugger/scripts/parse-stacktrace.mjs +109 -0
- package/catalog/agents/pr-summarizer/AGENT.md +156 -0
- package/catalog/agents/pr-summarizer/examples/sample-pr-summary.md +46 -0
- package/catalog/agents/pr-summarizer/examples/weak-vs-strong-summary.md +68 -0
- package/catalog/agents/pr-summarizer/references/identifying-risk.md +68 -0
- package/catalog/agents/pr-summarizer/references/reading-the-diff.md +68 -0
- package/catalog/agents/pr-summarizer/references/test-plan-and-missing-tests.md +64 -0
- package/catalog/agents/pr-summarizer/references/writing-the-summary.md +63 -0
- package/catalog/agents/pr-summarizer/scripts/diff-risk.mjs +111 -0
- package/catalog/agents/refactoring-specialist/AGENT.md +185 -0
- package/catalog/agents/refactoring-specialist/examples/before-after.md +91 -0
- package/catalog/agents/refactoring-specialist/examples/refactor-plan.md +97 -0
- package/catalog/agents/refactoring-specialist/references/large-scale-refactor.md +50 -0
- package/catalog/agents/refactoring-specialist/references/safe-workflow.md +75 -0
- package/catalog/agents/refactoring-specialist/references/smell-catalog.md +76 -0
- package/catalog/agents/refactoring-specialist/scripts/detect-smells.mjs +150 -0
- package/catalog/agents/security-auditor/AGENT.md +168 -0
- package/catalog/agents/security-auditor/examples/audit-report.md +68 -0
- package/catalog/agents/security-auditor/examples/finding-template.md +31 -0
- package/catalog/agents/security-auditor/references/audit-workflow.md +116 -0
- package/catalog/agents/security-auditor/references/severity-and-reporting.md +56 -0
- package/catalog/agents/security-auditor/references/vuln-classes.md +129 -0
- package/catalog/agents/security-auditor/scripts/scan-secrets.mjs +86 -0
- package/catalog/agents/technical-writer/AGENT.md +163 -0
- package/catalog/agents/technical-writer/examples/how-to-example.md +76 -0
- package/catalog/agents/technical-writer/examples/reference-example.md +75 -0
- package/catalog/agents/technical-writer/references/clarity-and-style.md +52 -0
- package/catalog/agents/technical-writer/references/diataxis-and-doc-types.md +61 -0
- package/catalog/agents/technical-writer/references/structure-and-maintenance.md +59 -0
- package/catalog/agents/technical-writer/scripts/readability.mjs +103 -0
- package/catalog/agents/test-author/AGENT.md +177 -0
- package/catalog/agents/test-author/examples/test-plan.md +54 -0
- package/catalog/agents/test-author/examples/unit-test-example.ts +55 -0
- package/catalog/agents/test-author/references/aaa-and-naming.md +75 -0
- package/catalog/agents/test-author/references/mocking-and-fixtures.md +94 -0
- package/catalog/agents/test-author/references/test-types-and-pyramid.md +60 -0
- package/catalog/agents/test-author/scripts/check-coverage.mjs +145 -0
- package/catalog/agents/threat-modeler/AGENT.md +163 -0
- package/catalog/agents/threat-modeler/examples/dfd-example.md +55 -0
- package/catalog/agents/threat-modeler/examples/threat-model.md +81 -0
- package/catalog/agents/threat-modeler/references/dfd-and-trust-boundaries.md +76 -0
- package/catalog/agents/threat-modeler/references/mitigation-catalog.md +77 -0
- package/catalog/agents/threat-modeler/references/stride-method.md +68 -0
- package/catalog/agents/threat-modeler/scripts/stride-checklist.mjs +183 -0
- package/catalog/agents/vuln-scanner/AGENT.md +146 -0
- package/catalog/agents/vuln-scanner/examples/finding-template.md +57 -0
- package/catalog/agents/vuln-scanner/examples/scan-report.md +49 -0
- package/catalog/agents/vuln-scanner/references/remediation-and-severity.md +68 -0
- package/catalog/agents/vuln-scanner/references/scan-types-and-tools.md +83 -0
- package/catalog/agents/vuln-scanner/references/triage-and-false-positives.md +73 -0
- package/catalog/agents/vuln-scanner/scripts/parse-scan-results.mjs +133 -0
- package/catalog/packs/code-review-pack/PACK.md +100 -0
- package/catalog/packs/code-review-pack/examples/pr-flow-example.md +50 -0
- package/catalog/packs/code-review-pack/references/rollout-guide.md +40 -0
- package/catalog/packs/security-pack/PACK.md +128 -0
- package/catalog/packs/security-pack/examples/owasp-vuln-triage-and-fix.md +66 -0
- package/catalog/packs/security-pack/examples/threat-model-then-audit.md +77 -0
- package/catalog/packs/security-pack/references/choosing-the-right-tool.md +37 -0
- package/catalog/packs/security-pack/references/threat-model-to-remediation.md +69 -0
- package/catalog/packs/security-pack/references/workflow.md +56 -0
- package/catalog/skills/api-pagination/SKILL.md +83 -0
- package/catalog/skills/api-pagination/examples/keyset-queries.sql +38 -0
- package/catalog/skills/api-pagination/examples/response-shapes.md +51 -0
- package/catalog/skills/api-pagination/references/consuming-pages.md +39 -0
- package/catalog/skills/api-pagination/references/counting-and-totals.md +41 -0
- package/catalog/skills/api-pagination/references/cursor-encoding.md +34 -0
- package/catalog/skills/api-pagination/scripts/check-cursor.mjs +51 -0
- package/catalog/skills/caching-strategies/SKILL.md +180 -0
- package/catalog/skills/caching-strategies/examples/http-cache-headers.md +82 -0
- package/catalog/skills/caching-strategies/examples/redis-cache-aside.js +110 -0
- package/catalog/skills/caching-strategies/references/cache-patterns.md +93 -0
- package/catalog/skills/caching-strategies/references/eviction-and-ttl.md +67 -0
- package/catalog/skills/caching-strategies/references/invalidation-and-stampede.md +99 -0
- package/catalog/skills/caching-strategies/scripts/ttl-jitter.mjs +99 -0
- package/catalog/skills/conventional-commits/SKILL.md +135 -0
- package/catalog/skills/conventional-commits/examples/commit-examples.md +60 -0
- package/catalog/skills/conventional-commits/examples/commitlint.config.js +31 -0
- package/catalog/skills/conventional-commits/references/breaking-changes-semver.md +50 -0
- package/catalog/skills/conventional-commits/references/scoping-commits.md +42 -0
- package/catalog/skills/conventional-commits/references/spec.md +78 -0
- package/catalog/skills/conventional-commits/scripts/lint-commit.mjs +97 -0
- package/catalog/skills/database-migrations/SKILL.md +137 -0
- package/catalog/skills/database-migrations/examples/add-column-safe.sql +34 -0
- package/catalog/skills/database-migrations/examples/migration-config.example.json +27 -0
- package/catalog/skills/database-migrations/references/expand-contract.md +74 -0
- package/catalog/skills/database-migrations/references/rollback-and-safety.md +71 -0
- package/catalog/skills/database-migrations/references/zero-downtime-changes.md +84 -0
- package/catalog/skills/database-migrations/scripts/check-migration-reversible.mjs +139 -0
- package/catalog/skills/error-handling-patterns/SKILL.md +136 -0
- package/catalog/skills/error-handling-patterns/examples/express-error-middleware.js +93 -0
- package/catalog/skills/error-handling-patterns/examples/result-pattern.ts +81 -0
- package/catalog/skills/error-handling-patterns/references/error-taxonomy.md +91 -0
- package/catalog/skills/error-handling-patterns/references/language-patterns.md +102 -0
- package/catalog/skills/error-handling-patterns/references/retry-and-backoff.md +76 -0
- package/catalog/skills/error-handling-patterns/scripts/lint-empty-catch.mjs +113 -0
- package/catalog/skills/git-collaboration-workflows/SKILL.md +104 -0
- package/catalog/skills/git-collaboration-workflows/examples/branch-protection.md +48 -0
- package/catalog/skills/git-collaboration-workflows/examples/pr-review-playbook.md +48 -0
- package/catalog/skills/git-collaboration-workflows/references/branching-models.md +52 -0
- package/catalog/skills/git-collaboration-workflows/references/history-hygiene.md +44 -0
- package/catalog/skills/git-collaboration-workflows/scripts/check-branch-hygiene.mjs +62 -0
- package/catalog/skills/owasp-top10/SKILL.md +112 -0
- package/catalog/skills/owasp-top10/examples/sql-injection-fix.md +33 -0
- package/catalog/skills/owasp-top10/examples/xss-fix.md +27 -0
- package/catalog/skills/owasp-top10/references/access-control.md +36 -0
- package/catalog/skills/owasp-top10/references/crypto-failures.md +29 -0
- package/catalog/skills/owasp-top10/references/injection.md +39 -0
- package/catalog/skills/owasp-top10/references/ssrf-and-design.md +35 -0
- package/catalog/skills/owasp-top10/scripts/scan-injection.mjs +73 -0
- package/catalog/skills/prompt-engineering/SKILL.md +100 -0
- package/catalog/skills/prompt-engineering/examples/classification-prompt.md +29 -0
- package/catalog/skills/prompt-engineering/examples/extraction-prompt.md +25 -0
- package/catalog/skills/prompt-engineering/references/prompt-injection.md +34 -0
- package/catalog/skills/prompt-engineering/references/structured-output.md +34 -0
- package/catalog/skills/prompt-engineering/references/techniques.md +33 -0
- package/catalog/skills/prompt-engineering/scripts/validate-output.mjs +71 -0
- package/catalog/skills/readme-writing/SKILL.md +176 -0
- package/catalog/skills/readme-writing/examples/README.template.md +73 -0
- package/catalog/skills/readme-writing/examples/good-readme-example.md +95 -0
- package/catalog/skills/readme-writing/references/anatomy.md +107 -0
- package/catalog/skills/readme-writing/references/maintenance.md +68 -0
- package/catalog/skills/readme-writing/references/writing-style.md +79 -0
- package/catalog/skills/readme-writing/scripts/lint-readme.mjs +117 -0
- package/catalog/skills/refactoring-patterns/SKILL.md +178 -0
- package/catalog/skills/refactoring-patterns/examples/extract-function.before-after.js +57 -0
- package/catalog/skills/refactoring-patterns/examples/replace-conditional-with-polymorphism.before-after.ts +63 -0
- package/catalog/skills/refactoring-patterns/references/code-smells.md +92 -0
- package/catalog/skills/refactoring-patterns/references/refactoring-catalog.md +78 -0
- package/catalog/skills/refactoring-patterns/references/safe-workflow.md +74 -0
- package/catalog/skills/refactoring-patterns/scripts/detect-smells.mjs +137 -0
- package/catalog/skills/rest-api-design/SKILL.md +147 -0
- package/catalog/skills/rest-api-design/examples/error-envelope.json +26 -0
- package/catalog/skills/rest-api-design/examples/orders-api.openapi.yaml +106 -0
- package/catalog/skills/rest-api-design/references/error-handling.md +43 -0
- package/catalog/skills/rest-api-design/references/pagination.md +41 -0
- package/catalog/skills/rest-api-design/references/status-codes.md +32 -0
- package/catalog/skills/rest-api-design/references/versioning.md +31 -0
- package/catalog/skills/rest-api-design/scripts/check-envelope.mjs +62 -0
- package/catalog/skills/secrets-management/SKILL.md +134 -0
- package/catalog/skills/secrets-management/examples/env-example.md +41 -0
- package/catalog/skills/secrets-management/examples/rotation-runbook.md +50 -0
- package/catalog/skills/secrets-management/references/leak-response.md +47 -0
- package/catalog/skills/secrets-management/references/rotation.md +46 -0
- package/catalog/skills/secrets-management/references/secret-managers.md +52 -0
- package/catalog/skills/secrets-management/scripts/detect-hardcoded.mjs +96 -0
- package/catalog/skills/secure-auth/SKILL.md +135 -0
- package/catalog/skills/secure-auth/examples/auth-flow.md +60 -0
- package/catalog/skills/secure-auth/examples/jwt-verify.ts +89 -0
- package/catalog/skills/secure-auth/references/auth-attacks.md +76 -0
- package/catalog/skills/secure-auth/references/oauth2-oidc.md +65 -0
- package/catalog/skills/secure-auth/references/password-hashing.md +70 -0
- package/catalog/skills/secure-auth/references/sessions-vs-jwt.md +69 -0
- package/catalog/skills/secure-auth/scripts/check-password-policy.mjs +114 -0
- package/catalog/skills/sql-index-tuning/SKILL.md +171 -0
- package/catalog/skills/sql-index-tuning/examples/explain-walkthrough.sql +91 -0
- package/catalog/skills/sql-index-tuning/examples/index-ddl.sql +67 -0
- package/catalog/skills/sql-index-tuning/references/btree-internals.md +58 -0
- package/catalog/skills/sql-index-tuning/references/composite-and-covering.md +60 -0
- package/catalog/skills/sql-index-tuning/references/reading-explain.md +54 -0
- package/catalog/skills/sql-index-tuning/references/selectivity-and-cardinality.md +62 -0
- package/catalog/skills/sql-index-tuning/scripts/suggest-index.mjs +213 -0
- package/catalog/skills/test-plan-design/SKILL.md +154 -0
- package/catalog/skills/test-plan-design/examples/test-case-template.md +45 -0
- package/catalog/skills/test-plan-design/examples/test-plan.md +57 -0
- package/catalog/skills/test-plan-design/references/case-design.md +79 -0
- package/catalog/skills/test-plan-design/references/risk-prioritization.md +58 -0
- package/catalog/skills/test-plan-design/references/test-pyramid.md +58 -0
- package/catalog/skills/test-plan-design/scripts/coverage-gaps.mjs +109 -0
- package/free-tier.json +15 -0
- package/package.json +15 -4
- package/premium-index.json +1 -0
- package/src/config.js +31 -1
- package/src/license.js +143 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runnable check: flags maintainability violations from a diff-stat-like JSON so a
|
|
3
|
+
// reviewer (human or agent) can mechanically catch oversized functions and files
|
|
4
|
+
// before the manual pass. Mirrors the --selftest convention of check-envelope.mjs.
|
|
5
|
+
//
|
|
6
|
+
// Input shape (a "diff stat"):
|
|
7
|
+
// {
|
|
8
|
+
// "files": [
|
|
9
|
+
// { "path": "a.js", "lines": 120,
|
|
10
|
+
// "functions": [ { "name": "doThing", "lines": 40 } ] }
|
|
11
|
+
// ]
|
|
12
|
+
// }
|
|
13
|
+
//
|
|
14
|
+
// Usage:
|
|
15
|
+
// node review-guard.mjs '<json>' # check a literal JSON string
|
|
16
|
+
// echo '<json>' | node review-guard.mjs # check from stdin
|
|
17
|
+
// node review-guard.mjs --selftest # run built-in test cases
|
|
18
|
+
|
|
19
|
+
const MAX_FUNC_LINES = 50; // functions should stay under 50 lines
|
|
20
|
+
const MAX_FILE_LINES = 800; // files should stay under 800 lines
|
|
21
|
+
|
|
22
|
+
export function reviewGuard(stat) {
|
|
23
|
+
const violations = [];
|
|
24
|
+
const files = (stat && Array.isArray(stat.files)) ? stat.files : [];
|
|
25
|
+
for (const f of files) {
|
|
26
|
+
const path = f.path ?? '<unknown>';
|
|
27
|
+
if (typeof f.lines === 'number' && f.lines > MAX_FILE_LINES) {
|
|
28
|
+
violations.push({
|
|
29
|
+
severity: 'MEDIUM',
|
|
30
|
+
where: path,
|
|
31
|
+
msg: `file is ${f.lines} lines (> ${MAX_FILE_LINES}); split into smaller modules`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const fns = Array.isArray(f.functions) ? f.functions : [];
|
|
35
|
+
for (const fn of fns) {
|
|
36
|
+
if (typeof fn.lines === 'number' && fn.lines > MAX_FUNC_LINES) {
|
|
37
|
+
violations.push({
|
|
38
|
+
severity: 'MEDIUM',
|
|
39
|
+
where: `${path}:${fn.name ?? '<anon>'}`,
|
|
40
|
+
msg: `function is ${fn.lines} lines (> ${MAX_FUNC_LINES}); extract focused helpers`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { ok: violations.length === 0, violations };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function check(label, json) {
|
|
49
|
+
let stat;
|
|
50
|
+
try { stat = typeof json === 'string' ? JSON.parse(json) : json; }
|
|
51
|
+
catch { console.error(`✗ ${label}: invalid JSON`); return false; }
|
|
52
|
+
const { ok, violations } = reviewGuard(stat);
|
|
53
|
+
if (ok) console.log(`✓ ${label}: no oversized functions or files`);
|
|
54
|
+
else {
|
|
55
|
+
console.error(`✗ ${label}: ${violations.length} violation(s)`);
|
|
56
|
+
violations.forEach((v) => console.error(` [${v.severity}] ${v.where} — ${v.msg}`));
|
|
57
|
+
}
|
|
58
|
+
return ok;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function selftest() {
|
|
62
|
+
const cases = {
|
|
63
|
+
clean: {
|
|
64
|
+
files: [
|
|
65
|
+
{ path: 'ok.js', lines: 120, functions: [{ name: 'small', lines: 20 }] },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
bad_big_function: {
|
|
69
|
+
files: [
|
|
70
|
+
{ path: 'svc.js', lines: 200, functions: [{ name: 'processOrder', lines: 59 }] },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
bad_big_file: {
|
|
74
|
+
files: [
|
|
75
|
+
{ path: 'huge.js', lines: 1200, functions: [{ name: 'fine', lines: 10 }] },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
bad_both: {
|
|
79
|
+
files: [
|
|
80
|
+
{ path: 'monster.js', lines: 900, functions: [{ name: 'god', lines: 300 }] },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
let allExpected = true;
|
|
85
|
+
for (const [name, payload] of Object.entries(cases)) {
|
|
86
|
+
const { ok } = reviewGuard(payload);
|
|
87
|
+
const shouldPass = name === 'clean';
|
|
88
|
+
const correct = ok === shouldPass;
|
|
89
|
+
allExpected &&= correct;
|
|
90
|
+
console.log(`${correct ? '✓' : '✗'} selftest ${name}: ${ok ? 'clean' : 'violations'} (expected ${shouldPass ? 'clean' : 'violations'})`);
|
|
91
|
+
}
|
|
92
|
+
process.exit(allExpected ? 0 : 1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const arg = process.argv[2];
|
|
96
|
+
if (arg === '--selftest') selftest();
|
|
97
|
+
else if (arg) process.exit(check('diff-stat', arg) ? 0 : 1);
|
|
98
|
+
else {
|
|
99
|
+
let buf = '';
|
|
100
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
101
|
+
process.stdin.on('end', () => process.exit(check('stdin', buf) ? 0 : 1));
|
|
102
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debugger
|
|
3
|
+
description: Hypothesis-driven debugging specialist. Use PROACTIVELY when a test fails, an exception or stack trace appears, a build breaks, or behavior is unexpected. Reproduces the failure, bisects to the root cause, fixes the smallest surface, and adds a regression test — never patches symptoms.
|
|
4
|
+
tools: Read, Grep, Glob, Bash, Edit
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
type: agent
|
|
7
|
+
version: 2.0.0
|
|
8
|
+
updated: 2026-06-29
|
|
9
|
+
---
|
|
10
|
+
# Debugger
|
|
11
|
+
|
|
12
|
+
You are a disciplined debugging specialist. You find **root causes, not band-aids**. Debugging is not
|
|
13
|
+
guesswork — it is the scientific method applied to code: observe, hypothesize, predict, test, repeat. You
|
|
14
|
+
change code only *after* a hypothesis is confirmed, and you change as little as possible.
|
|
15
|
+
|
|
16
|
+
The single most important rule: **make the bug reproducible before you try to fix it.** A bug you cannot
|
|
17
|
+
reproduce is a bug you cannot prove you fixed. If you change code while the failure is intermittent, a
|
|
18
|
+
"fix" that coincides with the bug not appearing is indistinguishable from luck.
|
|
19
|
+
|
|
20
|
+
## The method
|
|
21
|
+
|
|
22
|
+
Work this loop in order. Do not skip ahead — most bad fixes come from jumping to step 5 from step 1.
|
|
23
|
+
|
|
24
|
+
1. **Reproduce.** Capture the exact input, environment, and steps that trigger the failure. Reduce it to
|
|
25
|
+
the smallest deterministic repro (see `examples/repro-template.md`). Save the full error text verbatim.
|
|
26
|
+
2. **Read the error.** The message and stack trace usually name the file and line. Read it *top to bottom*
|
|
27
|
+
for the exception type and message; read the frames *top of user code first* for where your code is
|
|
28
|
+
involved. Details in `references/observability.md`.
|
|
29
|
+
3. **Localize / bisect.** Narrow *where* the bad state originates. Binary-search the input, the code path,
|
|
30
|
+
or the commit history (`git bisect`). Halve the search space each step — see `references/bisection.md`.
|
|
31
|
+
4. **Hypothesize.** State the single most likely cause in one falsifiable sentence: "X is null here because
|
|
32
|
+
Y returns undefined when the cache misses." A hypothesis you can't test is not a hypothesis.
|
|
33
|
+
5. **Test the hypothesis.** Confirm with a targeted probe — a log line, a breakpoint, an assertion, a unit
|
|
34
|
+
test — *before* editing the fix. Change one variable at a time so the signal stays clean.
|
|
35
|
+
6. **Fix at the cause.** Edit the smallest surface that addresses the confirmed cause, not the symptom.
|
|
36
|
+
7. **Verify & prevent regression.** Re-run the repro: it must now pass. Add a regression test that fails
|
|
37
|
+
on the old code and passes on the new. Then run the surrounding suite to confirm no new breakage.
|
|
38
|
+
|
|
39
|
+
## Symptom vs root cause
|
|
40
|
+
|
|
41
|
+
The symptom is *where it crashed*; the cause is *why the bad state arose*. A `NullPointerException` on
|
|
42
|
+
line 200 is the symptom — the cause may be a function 50 lines earlier that returned `null` instead of
|
|
43
|
+
throwing. Patching line 200 with a null check hides the real defect and lets corrupt state spread. Always
|
|
44
|
+
trace the bad value back to its **origin**.
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Symptom fix: if (user != null) { ... } // silences the crash, bug still there
|
|
48
|
+
Root fix: // findUser() now throws NotFound instead of returning null on a cache miss
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Reading a stack trace
|
|
52
|
+
|
|
53
|
+
A stack trace is a map. Read the exception type and message first, then find the **topmost frame in your
|
|
54
|
+
own code** (skip framework/library frames) — that is almost always where to start looking.
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
TypeError: Cannot read properties of undefined (reading 'id') <- WHAT failed
|
|
58
|
+
at formatOrder (src/orders/format.js:42:18) <- TOP user frame: START HERE
|
|
59
|
+
at Array.map (<anonymous>) <- library frame, skip
|
|
60
|
+
at renderOrders (src/orders/list.js:88:24) <- caller: how we got here
|
|
61
|
+
at handleRequest (src/server.js:130:9) <- entry point
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Line 42 of `format.js` is where the bad value was *used*; lines 88 and 130 tell you the *path* that
|
|
65
|
+
produced it. Walk down the trace to find which caller passed the undefined value.
|
|
66
|
+
|
|
67
|
+
## Common pitfalls (failure modes)
|
|
68
|
+
|
|
69
|
+
- **Fixing the symptom, not the cause.** A null check that silences a crash leaves the real defect alive.
|
|
70
|
+
- **Changing many things at once.** If you edit five things and the bug disappears, you don't know which
|
|
71
|
+
one mattered — and you may have introduced two new bugs. Change one variable per iteration.
|
|
72
|
+
- **No reproduction.** "It seems fixed" after a non-deterministic failure is not evidence. Get a repro first.
|
|
73
|
+
- **Ignoring the error message.** The message and stack trace usually *say* what's wrong. Read them fully
|
|
74
|
+
before theorizing.
|
|
75
|
+
- **Debugging by coincidence.** Adding random `sleep`/retries/try-catch until it "works" hides race
|
|
76
|
+
conditions and resource bugs instead of fixing them.
|
|
77
|
+
- **Trusting assumptions over observation.** "That can't be null" — prove it with a log, don't assume it.
|
|
78
|
+
- **Skipping the regression test.** Without one, the same bug returns the next time someone refactors.
|
|
79
|
+
|
|
80
|
+
## When NOT to use / boundaries
|
|
81
|
+
|
|
82
|
+
- **Not a feature builder.** This agent diagnoses and fixes defects; for new functionality use a
|
|
83
|
+
planning/TDD workflow instead.
|
|
84
|
+
- **Not a performance profiler.** Slowness without incorrectness is a profiling task, not a bug hunt
|
|
85
|
+
(though the bisection method still applies to perf regressions).
|
|
86
|
+
- **Not a security auditor.** If the "bug" is a vulnerability, hand off to the `security-auditor` agent.
|
|
87
|
+
- **Don't guess-edit when you can't reproduce.** If reproduction is impossible, say so and list exactly
|
|
88
|
+
what you'd need (logs, env, input) rather than editing hopefully.
|
|
89
|
+
- **Don't disable tests or loosen assertions to make red go green** — that destroys the signal that
|
|
90
|
+
caught the bug.
|
|
91
|
+
|
|
92
|
+
## Output format
|
|
93
|
+
|
|
94
|
+
Report in this structure:
|
|
95
|
+
|
|
96
|
+
- **Reproduction** — the minimal steps/input that trigger the failure.
|
|
97
|
+
- **Root cause** — the confirmed cause with `file:line`, distinguished from the symptom.
|
|
98
|
+
- **Fix** — the minimal diff at the cause.
|
|
99
|
+
- **Regression test** — a test that fails before, passes after.
|
|
100
|
+
- **Verification** — what you re-ran to confirm green.
|
|
101
|
+
|
|
102
|
+
## Files in this package
|
|
103
|
+
|
|
104
|
+
- `references/debugging-method.md` — the scientific, hypothesis-driven loop in depth
|
|
105
|
+
- `references/bisection.md` — `git bisect` + binary-search isolation techniques
|
|
106
|
+
- `references/observability.md` — logs/metrics/traces, reading stack traces & core dumps
|
|
107
|
+
- `examples/repro-template.md` — minimal reproduction checklist
|
|
108
|
+
- `examples/postmortem-template.md` — blameless postmortem write-up template
|
|
109
|
+
- `scripts/parse-stacktrace.mjs` — extracts the top user-code frame from a JS/Python stack trace
|
|
110
|
+
|
|
111
|
+
Pairs with the `code-reviewer` agent (review the fix before merge), the `tdd-guide` agent (turn the repro
|
|
112
|
+
into a failing test first), and the `error-handling-patterns` skill (so the cause can't silently recur).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Blameless Postmortem Template
|
|
2
|
+
|
|
3
|
+
Write this after a significant bug or incident is resolved. The goal is **systems, not scapegoats** — ask
|
|
4
|
+
how the process let the defect through, not who typed it.
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
- **Title:** _short description of the failure_
|
|
9
|
+
- **Date / duration:** _when it started, when it was resolved_
|
|
10
|
+
- **Severity / impact:** _who/what was affected, how badly (users, data, revenue)_
|
|
11
|
+
- **Status:** Resolved / Mitigated / Monitoring
|
|
12
|
+
|
|
13
|
+
## Timeline (UTC)
|
|
14
|
+
|
|
15
|
+
| Time | Event |
|
|
16
|
+
|---|---|
|
|
17
|
+
| 14:01 | Deploy of `abc123` |
|
|
18
|
+
| 14:05 | Error rate alarm fires |
|
|
19
|
+
| 14:12 | On-call acknowledges, begins investigation |
|
|
20
|
+
| 14:30 | Root cause identified (see below) |
|
|
21
|
+
| 14:38 | Fix deployed, error rate normal |
|
|
22
|
+
|
|
23
|
+
## Root cause
|
|
24
|
+
|
|
25
|
+
State the **confirmed** cause, distinguished from the symptom, with `file:line` and the commit that
|
|
26
|
+
introduced it. Explain *why the bad state arose*, not just where it crashed.
|
|
27
|
+
|
|
28
|
+
> e.g. `applyDiscount` mutated the shared `cart` array (`src/cart.js:48`), so a retried request applied
|
|
29
|
+
> the discount twice. The symptom was a wrong total at checkout; the cause was shared mutable state.
|
|
30
|
+
|
|
31
|
+
## Detection
|
|
32
|
+
|
|
33
|
+
- How was it found? (alarm / customer report / log)
|
|
34
|
+
- How long until detection? Could a metric or test have caught it sooner?
|
|
35
|
+
|
|
36
|
+
## Resolution & recovery
|
|
37
|
+
|
|
38
|
+
- What fixed it (the minimal change at the cause)?
|
|
39
|
+
- Any cleanup needed (data backfill, cache invalidation)?
|
|
40
|
+
|
|
41
|
+
## The five whys
|
|
42
|
+
|
|
43
|
+
1. Why did the total double? → discount applied twice.
|
|
44
|
+
2. Why twice? → request was retried and the cart was mutated.
|
|
45
|
+
3. Why was it mutated? → `applyDiscount` edited the array in place.
|
|
46
|
+
4. Why in place? → no immutability convention in cart code.
|
|
47
|
+
5. Why no convention? → no lint rule / review check for shared mutation.
|
|
48
|
+
|
|
49
|
+
## Action items
|
|
50
|
+
|
|
51
|
+
| Action | Owner | Type | Status |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| Add regression test for double-retry discount | | prevent | |
|
|
54
|
+
| Make cart operations immutable | | fix-class | |
|
|
55
|
+
| Add alarm on total-mismatch metric | | detect | |
|
|
56
|
+
|
|
57
|
+
## What went well / what was lucky
|
|
58
|
+
|
|
59
|
+
_Separate good process from good fortune — luck isn't repeatable._
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Minimal Reproduction Checklist
|
|
2
|
+
|
|
3
|
+
A bug isn't ready to fix until you can trigger it on demand. Fill this out *before* editing any code.
|
|
4
|
+
|
|
5
|
+
## 1. The failure (verbatim)
|
|
6
|
+
|
|
7
|
+
- **Error message / stack trace:** _paste the full text, unedited_
|
|
8
|
+
- **Where it surfaced:** `file:line` of the top user-code frame
|
|
9
|
+
- **Expected vs actual behavior:** _one line each_
|
|
10
|
+
|
|
11
|
+
## 2. The trigger
|
|
12
|
+
|
|
13
|
+
- **Exact input / steps:**
|
|
14
|
+
1. _step_
|
|
15
|
+
2. _step_
|
|
16
|
+
3. _failure observed_
|
|
17
|
+
- **Frequency:** [ ] every time [ ] intermittent (___ % of runs) [ ] only under load/concurrency
|
|
18
|
+
- **First seen:** _commit / version / date_ — and the last known-good version, if any
|
|
19
|
+
|
|
20
|
+
## 3. The environment
|
|
21
|
+
|
|
22
|
+
| Factor | Value |
|
|
23
|
+
|---|---|
|
|
24
|
+
| OS / runtime version | |
|
|
25
|
+
| Dependency versions | |
|
|
26
|
+
| Config / env vars | |
|
|
27
|
+
| Data / fixtures | |
|
|
28
|
+
|
|
29
|
+
## 4. Reduce to the minimum
|
|
30
|
+
|
|
31
|
+
Strip everything that *doesn't* affect the outcome until you have the smallest case that still fails:
|
|
32
|
+
|
|
33
|
+
- [ ] Removed unrelated code paths
|
|
34
|
+
- [ ] Replaced external calls with fixed stubs/fixtures
|
|
35
|
+
- [ ] Hard-coded the triggering input
|
|
36
|
+
- [ ] Confirmed it **still fails** after reduction
|
|
37
|
+
- [ ] Confirmed it is **deterministic** (or documented the flake rate + suspected race)
|
|
38
|
+
|
|
39
|
+
## 5. The repro artifact
|
|
40
|
+
|
|
41
|
+
A runnable snippet or single failing test is the goal — paste it here:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
# minimal failing command or test
|
|
45
|
+
$ npm test -- checkout.spec.js -t "applies discount once"
|
|
46
|
+
✗ applies discount once (expected 90, got 81)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> If you cannot produce a deterministic repro after a bounded effort, **stop**. Record what you tried and
|
|
50
|
+
> exactly what additional evidence (logs, prod input, env access) you need — do not guess-edit.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Bisection — Binary Search for Bugs
|
|
2
|
+
|
|
3
|
+
When reasoning stalls, *search*. Halving the problem space repeatedly finds the culprit in log₂(N) steps
|
|
4
|
+
instead of N. Bisection applies to three spaces: **history** (which commit), **code** (which statement),
|
|
5
|
+
and **input** (which data).
|
|
6
|
+
|
|
7
|
+
## 1. History: `git bisect`
|
|
8
|
+
|
|
9
|
+
When a bug appeared "sometime in the last 200 commits," let git binary-search them. Mark a known-bad and
|
|
10
|
+
known-good revision; git checks out the midpoint and you tell it good or bad until it names the culprit.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
git bisect start
|
|
14
|
+
git bisect bad # current HEAD is broken
|
|
15
|
+
git bisect good v1.4.0 # this old tag worked
|
|
16
|
+
# git checks out the midpoint; test it, then:
|
|
17
|
+
git bisect good # ...or: git bisect bad
|
|
18
|
+
# repeat until git prints "<sha> is the first bad commit"
|
|
19
|
+
git bisect reset # return to where you started
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Automate it when you have a script that exits 0 (good) / non-zero (bad):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git bisect start HEAD v1.4.0
|
|
26
|
+
git bisect run npm test -- --testPathPattern=checkout
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`git bisect run` will drive the entire search unattended and print the first bad commit. Keep the test
|
|
30
|
+
fast and deterministic, or the search inherits its flakiness.
|
|
31
|
+
|
|
32
|
+
## 2. Code: bisect the execution path
|
|
33
|
+
|
|
34
|
+
Within a single bad function, find *where* the state goes wrong by checking the midpoint. Print or assert
|
|
35
|
+
the suspect value halfway through; if it's already wrong, the bug is in the first half — otherwise the
|
|
36
|
+
second. Repeat on the bad half.
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
function process(x) {
|
|
40
|
+
step A // assert(valid) here -> OK? search lower half
|
|
41
|
+
step B
|
|
42
|
+
step C // <-- midpoint probe: value already corrupt? bug is A/B
|
|
43
|
+
step D
|
|
44
|
+
step E // assert(valid) here -> still OK? bug is D/E
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A "wolf-fence" probe (a single well-placed log of the suspect value) beats scattering ten prints.
|
|
49
|
+
|
|
50
|
+
## 3. Input: bisect the data
|
|
51
|
+
|
|
52
|
+
A 100k-row file that crashes the parser? Don't read 100k rows. Feed the first half — still crashes? The
|
|
53
|
+
offending row is in that half. Halve again. ~17 iterations isolate one row out of 100k. The same works
|
|
54
|
+
for narrowing a failing config to one key, or a failing request to one field.
|
|
55
|
+
|
|
56
|
+
## Pitfalls
|
|
57
|
+
|
|
58
|
+
- **Non-deterministic test → useless bisect.** Flaky good/bad answers send the search to the wrong commit.
|
|
59
|
+
Stabilize the repro first.
|
|
60
|
+
- **Skipping untestable midpoints.** If a midpoint commit won't build, use `git bisect skip` — don't guess.
|
|
61
|
+
- **Forgetting `git bisect reset`** leaves you on a detached checkout.
|
|
62
|
+
- **Too-coarse halving.** Bisect to the *commit*, then bisect *within* it to the statement; don't stop at
|
|
63
|
+
"this 400-line commit did it."
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# The Debugging Method — Hypothesis-Driven
|
|
2
|
+
|
|
3
|
+
Debugging is the scientific method applied to a misbehaving system. You don't "stare at code until you
|
|
4
|
+
see it"; you run controlled experiments that each rule something in or out.
|
|
5
|
+
|
|
6
|
+
## The loop
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
observe ──> hypothesize ──> predict ──> experiment ──> compare
|
|
10
|
+
^ │
|
|
11
|
+
└──────────────────── refine ─────────────────────────┘
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
1. **Observe.** Collect the facts: the exact error, the input, the environment, what changed recently.
|
|
15
|
+
Resist theorizing before you have the message in front of you.
|
|
16
|
+
2. **Hypothesize.** Propose one *falsifiable* cause: "The total is wrong because `applyDiscount` mutates
|
|
17
|
+
the shared `cart` array." A good hypothesis predicts something you can check.
|
|
18
|
+
3. **Predict.** If the hypothesis is true, what must also be true? "Then logging `cart` before and after
|
|
19
|
+
should show it changed."
|
|
20
|
+
4. **Experiment.** Run the smallest test that distinguishes the hypothesis from its alternatives. Change
|
|
21
|
+
**one variable at a time**.
|
|
22
|
+
5. **Compare.** Did the prediction hold? If yes, you've localized the cause. If no, the hypothesis is
|
|
23
|
+
wrong — discard it (don't patch it) and form a new one from what you learned.
|
|
24
|
+
|
|
25
|
+
## Why "one variable at a time" is non-negotiable
|
|
26
|
+
|
|
27
|
+
If you change input, code, and config simultaneously and the bug vanishes, the experiment has no signal:
|
|
28
|
+
you cannot attribute the change. Worse, multi-change "fixes" routinely introduce new defects that hide
|
|
29
|
+
behind the apparent success. Keep a clean control: a known-failing case you re-run after each change.
|
|
30
|
+
|
|
31
|
+
## Rubber-duck and assumption auditing
|
|
32
|
+
|
|
33
|
+
Explain the failing path aloud, line by line, to an imaginary listener. The bug is usually hidden in a
|
|
34
|
+
step you *assumed* was correct and therefore never checked. Make a list of your assumptions and verify the
|
|
35
|
+
cheapest ones first:
|
|
36
|
+
|
|
37
|
+
- "This value is never null." → log it.
|
|
38
|
+
- "This branch always runs." → add a counter.
|
|
39
|
+
- "These two configs match." → diff them.
|
|
40
|
+
|
|
41
|
+
## Confirmation before fixing
|
|
42
|
+
|
|
43
|
+
The most common mistake is editing the fix before confirming the hypothesis. Confirm first with a probe
|
|
44
|
+
(log, assertion, debugger, unit test). Only then edit — and only the smallest surface at the **origin** of
|
|
45
|
+
the bad state, not the place where it finally crashed.
|
|
46
|
+
|
|
47
|
+
## Binary search of everything
|
|
48
|
+
|
|
49
|
+
When you can't reason your way to the cause, *search* for it. Halve the space repeatedly: comment out half
|
|
50
|
+
the code, feed half the input, check the midpoint commit. See `bisection.md`. Each halving turns an
|
|
51
|
+
N-step problem into log₂(N) steps — a 1,000-line search becomes ~10 checks.
|
|
52
|
+
|
|
53
|
+
## Knowing when to stop
|
|
54
|
+
|
|
55
|
+
Stop and escalate (or write down what you need) when: you cannot reproduce after a bounded effort; the
|
|
56
|
+
cause is in a third-party dependency you can't change; or two equally-likely hypotheses both survive
|
|
57
|
+
testing and you need more instrumentation. Guessing past this point produces band-aids.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Observability — Reading the Evidence
|
|
2
|
+
|
|
3
|
+
You can't fix what you can't see. Observability is the practice of making a system's internal state
|
|
4
|
+
inspectable from the outside, through three complementary signals plus the artifacts a crash leaves behind.
|
|
5
|
+
|
|
6
|
+
## The three pillars
|
|
7
|
+
|
|
8
|
+
| Signal | Answers | Use for |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| **Logs** | "What happened, exactly here?" | discrete events, error context, the bad value |
|
|
11
|
+
| **Metrics** | "How much / how often, over time?" | rates, latencies, error %, spotting *when* a regression started |
|
|
12
|
+
| **Traces** | "What was the path across services?" | distributed call graphs, finding the slow/failing hop |
|
|
13
|
+
|
|
14
|
+
Logs give detail, metrics give trends, traces give causality across boundaries. A bug hunt usually starts
|
|
15
|
+
at a metric spike ("errors jumped at 14:05"), zooms to a trace ("the payment service hop failed"), then
|
|
16
|
+
lands on a log line with the exact exception.
|
|
17
|
+
|
|
18
|
+
## Reading a stack trace
|
|
19
|
+
|
|
20
|
+
1. **Exception type + message first** — `TypeError: Cannot read 'id' of undefined` tells you *what*.
|
|
21
|
+
2. **Top user-code frame** — skip framework/stdlib frames; the first frame in *your* files is where to
|
|
22
|
+
start. (Note: Python prints oldest call first / failure **last**; JS prints failure **first**.)
|
|
23
|
+
3. **Walk the callers** — each lower frame shows how execution reached the failure, i.e. who passed the
|
|
24
|
+
bad value.
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
JavaScript (most-recent first): Python (most-recent LAST):
|
|
28
|
+
TypeError: ... 'id' of undefined Traceback (most recent call last):
|
|
29
|
+
at formatOrder (format.js:42) File "list.py", line 88, in render
|
|
30
|
+
at renderOrders (list.js:88) File "format.py", line 42, in format_order
|
|
31
|
+
KeyError: 'id' <- read this line first
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use `scripts/parse-stacktrace.mjs` to extract the top user frame mechanically from either format.
|
|
35
|
+
|
|
36
|
+
## Logging that actually helps
|
|
37
|
+
|
|
38
|
+
- Log the **value and the context**, not "got here": `log('discount', {cartId, before, after})`.
|
|
39
|
+
- Use **structured** logs (JSON) so you can filter/aggregate, not grep prose.
|
|
40
|
+
- Use **levels** deliberately: `error` for failures, `warn` for recovered anomalies, `debug` for probes
|
|
41
|
+
you'll remove. Never leave `console.log` debug spew in committed code.
|
|
42
|
+
- Include a **correlation/request id** so you can stitch one request's logs together.
|
|
43
|
+
|
|
44
|
+
## Core dumps & post-mortem artifacts
|
|
45
|
+
|
|
46
|
+
When a process dies hard, it may leave a snapshot of memory at the moment of death:
|
|
47
|
+
|
|
48
|
+
- **Native (C/C++/Go):** a core file — open with `gdb ./bin core` and `bt` for the backtrace, inspect
|
|
49
|
+
locals with `frame`/`print`.
|
|
50
|
+
- **Node:** run with `--abort-on-uncaught-exception` or use `--heapsnapshot-signal` / `--cpu-prof` for
|
|
51
|
+
memory and CPU forensics.
|
|
52
|
+
- **JVM:** an `hs_err_pid.log` plus a heap dump (`jmap`) you open in a memory analyzer.
|
|
53
|
+
|
|
54
|
+
These let you debug a failure you can observe but not re-trigger live.
|
|
55
|
+
|
|
56
|
+
## Pitfalls
|
|
57
|
+
|
|
58
|
+
- **Logging after the failure.** Put probes *before* the suspect operation so you capture the bad input.
|
|
59
|
+
- **Too much log noise.** Drowning the signal is as bad as none; log the decisive values, not everything.
|
|
60
|
+
- **No timestamps / no ids.** Unordered, uncorrelated logs can't reconstruct a sequence.
|
|
61
|
+
- **Sensitive data in logs.** Never log secrets, tokens, or PII — redact at the boundary.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Extract the top *user-code* frame (file:line) from a JS or Python stack trace.
|
|
3
|
+
// Framework / stdlib / node_modules frames are skipped so you land on YOUR code —
|
|
4
|
+
// the place a bug hunt should start (see references/observability.md).
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node parse-stacktrace.mjs "<trace text>" # parse a literal string
|
|
8
|
+
// cat trace.txt | node parse-stacktrace.mjs # parse from stdin
|
|
9
|
+
// node parse-stacktrace.mjs --selftest # run built-in tests (exit 0/1)
|
|
10
|
+
|
|
11
|
+
// Frames we consider "not user code" and skip when looking for the origin.
|
|
12
|
+
const NOISE = [
|
|
13
|
+
/node_modules[\\/]/,
|
|
14
|
+
/\bnode:internal\//,
|
|
15
|
+
/\b<anonymous>\b/,
|
|
16
|
+
/[\\/](site-packages|dist-packages)[\\/]/,
|
|
17
|
+
/[\\/]lib[\\/]python\d/i,
|
|
18
|
+
/\bin <module>\b.*\b(site-packages|python\d)/i,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const isNoise = (line) => NOISE.some((re) => re.test(line));
|
|
22
|
+
|
|
23
|
+
// JS: " at formatOrder (src/orders/format.js:42:18)" or " at /a/b.js:10:5"
|
|
24
|
+
// Python: ' File "src/format.py", line 42, in format_order'
|
|
25
|
+
function parseFrame(line) {
|
|
26
|
+
const js = line.match(/^\s*at\s+(?:(.*?)\s+\()?(.+?):(\d+)(?::\d+)?\)?\s*$/);
|
|
27
|
+
if (js) {
|
|
28
|
+
return { func: js[1] || '<top>', file: js[2], line: Number(js[3]) };
|
|
29
|
+
}
|
|
30
|
+
const py = line.match(/^\s*File\s+"(.+?)",\s+line\s+(\d+)(?:,\s+in\s+(.*))?\s*$/);
|
|
31
|
+
if (py) {
|
|
32
|
+
return { func: (py[3] || '<module>').trim(), file: py[1], line: Number(py[2]) };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Returns the top user frame. JS traces list the failure FIRST (scan top-down);
|
|
38
|
+
// Python lists it LAST (scan bottom-up). We detect Python by its "File ..." frames.
|
|
39
|
+
export function topUserFrame(trace) {
|
|
40
|
+
const lines = trace.split(/\r?\n/);
|
|
41
|
+
const isPython = lines.some((l) => /^\s*File\s+".+?",\s+line\s+\d+/.test(l));
|
|
42
|
+
const ordered = isPython ? [...lines].reverse() : lines;
|
|
43
|
+
|
|
44
|
+
let firstFrame = null;
|
|
45
|
+
for (const line of ordered) {
|
|
46
|
+
const frame = parseFrame(line);
|
|
47
|
+
if (!frame) continue;
|
|
48
|
+
if (!firstFrame) firstFrame = frame; // fallback if everything is "noise"
|
|
49
|
+
if (!isNoise(line)) return frame; // first non-noise frame = user code
|
|
50
|
+
}
|
|
51
|
+
return firstFrame; // all frames were noise: return the outermost we saw
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function selftest() {
|
|
55
|
+
const jsTrace = [
|
|
56
|
+
"TypeError: Cannot read properties of undefined (reading 'id')",
|
|
57
|
+
' at formatOrder (src/orders/format.js:42:18)',
|
|
58
|
+
' at Array.map (<anonymous>)',
|
|
59
|
+
' at renderOrders (src/orders/list.js:88:24)',
|
|
60
|
+
' at handleRequest (node_modules/express/lib/router.js:12:3)',
|
|
61
|
+
].join('\n');
|
|
62
|
+
|
|
63
|
+
const pyTrace = [
|
|
64
|
+
'Traceback (most recent call last):',
|
|
65
|
+
' File "/usr/lib/python3.11/runpy.py", line 196, in _run_module',
|
|
66
|
+
' File "src/orders/list.py", line 88, in render',
|
|
67
|
+
' File "src/orders/format.py", line 42, in format_order',
|
|
68
|
+
"KeyError: 'id'",
|
|
69
|
+
].join('\n');
|
|
70
|
+
|
|
71
|
+
// A trace where the only frames are in node_modules -> fall back to outermost.
|
|
72
|
+
const noiseOnly = [
|
|
73
|
+
'Error: boom',
|
|
74
|
+
' at foo (node_modules/x/index.js:1:1)',
|
|
75
|
+
].join('\n');
|
|
76
|
+
|
|
77
|
+
const cases = [
|
|
78
|
+
['js top user frame', jsTrace, { file: 'src/orders/format.js', line: 42, func: 'formatOrder' }],
|
|
79
|
+
['python top user frame', pyTrace, { file: 'src/orders/format.py', line: 42, func: 'format_order' }],
|
|
80
|
+
['noise-only fallback', noiseOnly, { file: 'node_modules/x/index.js', line: 1, func: 'foo' }],
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
let ok = true;
|
|
84
|
+
for (const [name, trace, want] of cases) {
|
|
85
|
+
const got = topUserFrame(trace) || {};
|
|
86
|
+
const pass = got.file === want.file && got.line === want.line && got.func === want.func;
|
|
87
|
+
ok &&= pass;
|
|
88
|
+
console.log(`${pass ? '✓' : '✗'} ${name}: ${got.func} @ ${got.file}:${got.line}` +
|
|
89
|
+
(pass ? '' : ` (expected ${want.func} @ ${want.file}:${want.line})`));
|
|
90
|
+
}
|
|
91
|
+
process.exit(ok ? 0 : 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function report(trace) {
|
|
95
|
+
const frame = topUserFrame(trace);
|
|
96
|
+
if (!frame) { console.error('No stack frame found in input.'); return false; }
|
|
97
|
+
console.log(`Top user-code frame: ${frame.func} @ ${frame.file}:${frame.line}`);
|
|
98
|
+
console.log('Start your investigation here.');
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const arg = process.argv[2];
|
|
103
|
+
if (arg === '--selftest') selftest();
|
|
104
|
+
else if (arg) process.exit(report(arg) ? 0 : 1);
|
|
105
|
+
else {
|
|
106
|
+
let buf = '';
|
|
107
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
108
|
+
process.stdin.on('end', () => process.exit(report(buf) ? 0 : 1));
|
|
109
|
+
}
|