@nklisch/pi-agile-workflow 0.15.3

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.
Files changed (203) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +28 -0
  3. package/CHANGELOG.md +591 -0
  4. package/README.md +107 -0
  5. package/docs/ARCHITECTURE.md +716 -0
  6. package/docs/MIGRATION.md +419 -0
  7. package/docs/PRINCIPLES.md +464 -0
  8. package/docs/ROADMAP.md +95 -0
  9. package/docs/SPEC.md +855 -0
  10. package/docs/VISION.md +106 -0
  11. package/extensions/agile-workflow.test.ts +419 -0
  12. package/extensions/agile-workflow.ts +467 -0
  13. package/hooks/hooks.json +52 -0
  14. package/hooks/scripts/prompt-context.py +628 -0
  15. package/hooks/scripts/substrate-maintainer.py +359 -0
  16. package/hooks/scripts/test_prompt_context.py +757 -0
  17. package/package.json +30 -0
  18. package/scripts/install-work-view.sh +165 -0
  19. package/scripts/tests/agent-metadata.test.sh +75 -0
  20. package/scripts/tests/bump-version.test.sh +552 -0
  21. package/scripts/tests/channel-parity.test.sh +117 -0
  22. package/scripts/tests/convert-content-integrity.test.sh +246 -0
  23. package/scripts/tests/convert-install-routing.test.sh +159 -0
  24. package/scripts/tests/install-work-view.test.sh +411 -0
  25. package/scripts/tests/pi-package-metadata.test.sh +119 -0
  26. package/scripts/tests/work-board-shim.test.sh +239 -0
  27. package/scripts/tests/work-view-dist-version.test.sh +105 -0
  28. package/scripts/work-board.sh +64 -0
  29. package/scripts/work-view.sh +423 -0
  30. package/skills/autopilot/SKILL.md +330 -0
  31. package/skills/autopilot/agents/openai.yaml +6 -0
  32. package/skills/board/SKILL.md +62 -0
  33. package/skills/board/agents/openai.yaml +6 -0
  34. package/skills/bold-refactor/SKILL.md +244 -0
  35. package/skills/bold-refactor/agents/openai.yaml +6 -0
  36. package/skills/bug-scan/SKILL.md +378 -0
  37. package/skills/bug-scan/agents/openai.yaml +6 -0
  38. package/skills/bug-scan/references/async-promises.md +119 -0
  39. package/skills/bug-scan/references/concurrency-races.md +148 -0
  40. package/skills/bug-scan/references/data-layer.md +167 -0
  41. package/skills/bug-scan/references/error-handling.md +197 -0
  42. package/skills/bug-scan/references/gate-item-template.md +94 -0
  43. package/skills/bug-scan/references/language-footguns.md +219 -0
  44. package/skills/bug-scan/references/parked-item-template.md +103 -0
  45. package/skills/bug-scan/references/report-template.md +115 -0
  46. package/skills/bug-scan/references/resource-leaks.md +199 -0
  47. package/skills/bug-scan/references/state-closures.md +148 -0
  48. package/skills/bug-scan/references/time-numbers.md +158 -0
  49. package/skills/convert/SKILL.md +1463 -0
  50. package/skills/convert/agents/openai.yaml +6 -0
  51. package/skills/convert/references/legacy-overlap-migration.md +179 -0
  52. package/skills/deep-code-scan/SKILL.md +378 -0
  53. package/skills/deep-code-scan/agents/openai.yaml +6 -0
  54. package/skills/deep-code-scan/references/artifact-ledger.md +148 -0
  55. package/skills/deep-code-scan/references/consolidation.md +91 -0
  56. package/skills/deep-code-scan/references/decomposition.md +127 -0
  57. package/skills/deep-code-scan/references/item-templates.md +263 -0
  58. package/skills/deep-code-scan/references/lane-catalog.md +129 -0
  59. package/skills/deep-code-scan/references/review-gauntlet.md +108 -0
  60. package/skills/deep-code-scan/references/scanner-brief.md +152 -0
  61. package/skills/e2e-test-design/SKILL.md +517 -0
  62. package/skills/e2e-test-design/references/anti-tautology.md +261 -0
  63. package/skills/e2e-test-design/references/service-mocks.md +234 -0
  64. package/skills/e2e-test-design/references/test-taxonomy.md +211 -0
  65. package/skills/epic-design/SKILL.md +476 -0
  66. package/skills/epicize/SKILL.md +210 -0
  67. package/skills/epicize/agents/openai.yaml +6 -0
  68. package/skills/feature-design/SKILL.md +472 -0
  69. package/skills/fix/SKILL.md +173 -0
  70. package/skills/gate-cruft/SKILL.md +258 -0
  71. package/skills/gate-docs/SKILL.md +278 -0
  72. package/skills/gate-patterns/SKILL.md +389 -0
  73. package/skills/gate-refactor/SKILL.md +408 -0
  74. package/skills/gate-security/SKILL.md +275 -0
  75. package/skills/gate-tests/SKILL.md +367 -0
  76. package/skills/groom/SKILL.md +162 -0
  77. package/skills/groom/agents/openai.yaml +6 -0
  78. package/skills/ideate/SKILL.md +185 -0
  79. package/skills/ideate/agents/openai.yaml +6 -0
  80. package/skills/implement/SKILL.md +288 -0
  81. package/skills/implement-orchestrator/SKILL.md +604 -0
  82. package/skills/park/SKILL.md +111 -0
  83. package/skills/perf-design/SKILL.md +427 -0
  84. package/skills/perf-scout/SKILL.md +404 -0
  85. package/skills/perf-scout/agents/openai.yaml +6 -0
  86. package/skills/perf-scout/references/algorithmic-and-data-structures.md +126 -0
  87. package/skills/perf-scout/references/approximation-and-precomputation.md +136 -0
  88. package/skills/perf-scout/references/caching-and-memoization.md +123 -0
  89. package/skills/perf-scout/references/compiler-runtime-and-language.md +136 -0
  90. package/skills/perf-scout/references/database-and-storage-internals.md +144 -0
  91. package/skills/perf-scout/references/distributed-systems.md +150 -0
  92. package/skills/perf-scout/references/game-engine-and-realtime.md +136 -0
  93. package/skills/perf-scout/references/gpu-and-accelerators.md +130 -0
  94. package/skills/perf-scout/references/idea-ranking.md +75 -0
  95. package/skills/perf-scout/references/io-and-batching.md +135 -0
  96. package/skills/perf-scout/references/memory-and-data-locality.md +137 -0
  97. package/skills/perf-scout/references/parallelism-and-vectorization.md +126 -0
  98. package/skills/perf-scout/references/parked-item-template.md +117 -0
  99. package/skills/perf-scout/references/peer-review-pass.md +150 -0
  100. package/skills/perf-scout/references/report-template.md +148 -0
  101. package/skills/principles/SKILL.md +678 -0
  102. package/skills/principles/references/models.md +191 -0
  103. package/skills/principles/references/subagents.md +158 -0
  104. package/skills/prose-author/SKILL.md +185 -0
  105. package/skills/refactor-conventions-creator/SKILL.md +359 -0
  106. package/skills/refactor-conventions-creator/agents/openai.yaml +6 -0
  107. package/skills/refactor-conventions-creator/references/common-structures.md +91 -0
  108. package/skills/refactor-conventions-creator/references/common-styles.md +90 -0
  109. package/skills/refactor-design/SKILL.md +384 -0
  110. package/skills/release-deploy/SKILL.md +667 -0
  111. package/skills/release-deploy/agents/openai.yaml +6 -0
  112. package/skills/research/SKILL.md +166 -0
  113. package/skills/research/agents/openai.yaml +6 -0
  114. package/skills/review/SKILL.md +267 -0
  115. package/skills/review/references/deep-review.md +98 -0
  116. package/skills/review/references/review-lenses.md +68 -0
  117. package/skills/review/references/substrate-side-effects.md +161 -0
  118. package/skills/review/references/target-resolution.md +52 -0
  119. package/skills/scope/SKILL.md +486 -0
  120. package/work-view/Cargo.lock +486 -0
  121. package/work-view/Cargo.toml +9 -0
  122. package/work-view/crates/cli/.work-view-version +1 -0
  123. package/work-view/crates/cli/Cargo.toml +18 -0
  124. package/work-view/crates/cli/src/actionable.rs +500 -0
  125. package/work-view/crates/cli/src/args.rs +792 -0
  126. package/work-view/crates/cli/src/board/assets/board.css +855 -0
  127. package/work-view/crates/cli/src/board/assets/board.js +259 -0
  128. package/work-view/crates/cli/src/board/assets/card.js +139 -0
  129. package/work-view/crates/cli/src/board/assets/components.css +290 -0
  130. package/work-view/crates/cli/src/board/assets/dependency.js +1193 -0
  131. package/work-view/crates/cli/src/board/assets/detail.js +256 -0
  132. package/work-view/crates/cli/src/board/assets/filters.js +389 -0
  133. package/work-view/crates/cli/src/board/assets/index.html +77 -0
  134. package/work-view/crates/cli/src/board/assets/kanban.js +231 -0
  135. package/work-view/crates/cli/src/board/assets/markdown.js +260 -0
  136. package/work-view/crates/cli/src/board/assets/motion.css +131 -0
  137. package/work-view/crates/cli/src/board/assets/state.js +245 -0
  138. package/work-view/crates/cli/src/board/assets/table.js +304 -0
  139. package/work-view/crates/cli/src/board/assets/tokens.css +602 -0
  140. package/work-view/crates/cli/src/board/assets/views.js +41 -0
  141. package/work-view/crates/cli/src/board/assets.rs +162 -0
  142. package/work-view/crates/cli/src/board/feed.rs +204 -0
  143. package/work-view/crates/cli/src/board/mod.rs +246 -0
  144. package/work-view/crates/cli/src/board/open.rs +144 -0
  145. package/work-view/crates/cli/src/board/server.rs +407 -0
  146. package/work-view/crates/cli/src/main.rs +195 -0
  147. package/work-view/crates/cli/src/render.rs +354 -0
  148. package/work-view/crates/cli/src/scope.rs +157 -0
  149. package/work-view/crates/cli/src/stale.rs +581 -0
  150. package/work-view/crates/cli/tests/board-js/dependency-table.test.mjs +64 -0
  151. package/work-view/crates/cli/tests/board-js/expanded-browsing.test.mjs +79 -0
  152. package/work-view/crates/cli/tests/board-js/harness-smoke.test.mjs +11 -0
  153. package/work-view/crates/cli/tests/board-js/harness.mjs +437 -0
  154. package/work-view/crates/cli/tests/board-js/kanban-detail.test.mjs +75 -0
  155. package/work-view/crates/cli/tests/board-js/markdown-filter.test.mjs +60 -0
  156. package/work-view/crates/cli/tests/fixtures/golden/.work/CONVENTIONS.md +3 -0
  157. package/work-view/crates/cli/tests/fixtures/golden/.work/active/epics/epic-alpha.md +16 -0
  158. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-a.md +16 -0
  159. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-b.md +16 -0
  160. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-alpha-1.md +16 -0
  161. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-research-1.md +19 -0
  162. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-done.md +16 -0
  163. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-shipped.md +18 -0
  164. package/work-view/crates/cli/tests/fixtures/golden/.work/backlog/idea-backlog.md +7 -0
  165. package/work-view/crates/cli/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  166. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/CONVENTIONS.md +6 -0
  167. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-dep-done.md +17 -0
  168. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-design-ready.md +17 -0
  169. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-impl-ready.md +17 -0
  170. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/stories/story-review-ready.md +17 -0
  171. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/CONVENTIONS.md +4 -0
  172. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/epics/malformed-no-closing-fence.md +8 -0
  173. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/features/good-item.md +16 -0
  174. package/work-view/crates/cli/tests/integration.rs +2833 -0
  175. package/work-view/crates/core/Cargo.toml +13 -0
  176. package/work-view/crates/core/src/error.rs +61 -0
  177. package/work-view/crates/core/src/filter.rs +804 -0
  178. package/work-view/crates/core/src/graph.rs +324 -0
  179. package/work-view/crates/core/src/index.rs +590 -0
  180. package/work-view/crates/core/src/lib.rs +54 -0
  181. package/work-view/crates/core/src/model.rs +202 -0
  182. package/work-view/crates/core/src/parse.rs +494 -0
  183. package/work-view/crates/core/tests/fixtures/golden/.work/CONVENTIONS.md +15 -0
  184. package/work-view/crates/core/tests/fixtures/golden/.work/active/epics/epic-core.md +16 -0
  185. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-parser.md +16 -0
  186. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-query.md +16 -0
  187. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/malformed-no-id.md +15 -0
  188. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-parse-unit.md +16 -0
  189. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-security-gate.md +16 -0
  190. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-old.md +16 -0
  191. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-parser.md +17 -0
  192. package/work-view/crates/core/tests/fixtures/golden/.work/backlog/idea-future-board.md +7 -0
  193. package/work-view/crates/core/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  194. package/work-view/crates/core/tests/fixtures/precedence/.work/CONVENTIONS.md +10 -0
  195. package/work-view/crates/core/tests/fixtures/precedence/.work/archive/dup-item.md +18 -0
  196. package/work-view/crates/core/tests/fixtures/precedence/.work/releases/v2.0/dup-item.md +18 -0
  197. package/work-view/crates/core/tests/integration.rs +541 -0
  198. package/work-view/dist/.gitattributes +1 -0
  199. package/work-view/dist/README.md +42 -0
  200. package/work-view/dist/aarch64-apple-darwin/work-view +0 -0
  201. package/work-view/dist/aarch64-unknown-linux-musl/work-view +0 -0
  202. package/work-view/dist/x86_64-apple-darwin/work-view +0 -0
  203. package/work-view/dist/x86_64-unknown-linux-musl/work-view +0 -0
@@ -0,0 +1,378 @@
1
+ ---
2
+ name: bug-scan
3
+ description: >
4
+ Deep multi-angle correctness bug hunt. Use when the user asks to "scan for bugs", "bug hunt", "deep
5
+ bug audit", "find hidden bugs", "find race conditions", or audit lurking correctness issues. Fans
6
+ parallel scanner agents across relevant domains such as concurrency, async, state, resource
7
+ leaks, time/numbers, error handling, data layer, and language footguns. Standalone mode writes a
8
+ scored report; gate mode binds to a release bundle and emits .work/active/stories/ items with
9
+ gate_origin:bugs. Distinct from gate-security, review, perf-design, and repo-eval.
10
+ ---
11
+
12
+ # Bug-Scan
13
+
14
+ You orchestrate a deep, multi-angle hunt for hard-to-find correctness bugs. You detect the
15
+ stack, choose relevant bug domains, dispatch **one deep scanner agent per selected domain in parallel**, and
16
+ either write a scored report (standalone) or produce items in `.work/active/stories/` (gate
17
+ mode). Use a generic sub-agent prompted with the scanner posture from `../principles/references/subagents.md`. Domain selection is the
18
+ scope-size gate: do not spawn a scanner for a domain just because it exists in the table. Each
19
+ scanner loads only its domain's reference —
20
+ that's the progressive-disclosure move that keeps each scanner focused and the
21
+ orchestrator's context lean.
22
+
23
+ Scanner strength is explicit: spawn one source-read-only scanner agent per
24
+ selected domain with high inspection/reviewer reasoning. Use a generic sub-agent prompted with the scanner posture from `../principles/references/subagents.md`. Use extra-high reasoning only for
25
+ concurrency, data-layer, or time bugs in high-risk domains, very large scopes,
26
+ or repeat scans that previously missed issues. These are scanners, not fixers.
27
+ - For the dynamic scanner prompt posture, load
28
+ `../principles/references/subagents.md`. If no suitable scanner subagent adapter is
29
+ available, use the same-host read-only analysis fallback.
30
+
31
+ This skill hunts **correctness** bugs, not vulnerabilities, not perf, not style. Use the
32
+ sibling skills for those.
33
+
34
+ ## Modes
35
+
36
+ ### Standalone (default)
37
+ - `/agile-workflow:bug-scan` — scan the whole repo
38
+ - `/agile-workflow:bug-scan <path>` — scope to a directory or glob (`src/api/`, `**/*.ts`)
39
+ - `/agile-workflow:bug-scan --no-park` — write report only, skip backlog parking (any mode)
40
+
41
+ Output:
42
+ 1. `bug-scan-report.md` at the repo root — domain scores, severity-grouped findings,
43
+ remediation directions, list of parked item ids.
44
+ 2. **One parked backlog item per finding** at `.work/backlog/bug-scan-<slug>.md` — minimal
45
+ frontmatter (`bug_origin: scan`, `bug_severity`, `bug_domain`, `bug_location`) so findings
46
+ are tracked in the substrate alongside the report. The user can elevate any of them later
47
+ via `/agile-workflow:scope`. Idempotent on re-runs (existing findings at the same
48
+ `file:line` are skipped). Suppress with `--no-park`.
49
+
50
+ > Why not write standalone Criticals straight to `.work/active/stories/`? Standalone is
51
+ > exploratory — the user invoked it to *see* what's there, not commit to fixing it. For
52
+ > release-bound, must-fix-before-ship Criticals, use **gate mode**, which writes to active.
53
+
54
+ ### Gate mode
55
+ - `/agile-workflow:bug-scan --release <version>` — scan only files touched by items bound
56
+ to that release
57
+ - Auto-invoked by `/agile-workflow:release-deploy` during the quality-gate stage
58
+
59
+ Output: items in `.work/active/stories/` (Critical/High → `stage: implementing`,
60
+ Medium → `stage: drafting`, Low → backlog), each with `gate_origin: bugs`.
61
+
62
+ The release ships when all bound items (including these) reach `stage: done` — same as
63
+ gate-security.
64
+
65
+ ## Bug domains (and their references)
66
+
67
+ The scanner agents each load exactly one of these. Each reference contains ~15-25 patterns
68
+ with grep-style detection signals, "why hard to find" notes, examples, and fix directions.
69
+
70
+ | Domain | Reference | When to load |
71
+ |---|---|---|
72
+ | Concurrency & races | [references/concurrency-races.md](references/concurrency-races.md) | Code uses threads, processes, goroutines, async runtimes, shared mutable state, locks, channels, atomics |
73
+ | Async / promises | [references/async-promises.md](references/async-promises.md) | Code uses async/await, Promise/Future/Task, coroutines, event loops, non-blocking I/O |
74
+ | State & closures | [references/state-closures.md](references/state-closures.md) | Code uses closures over loop vars, framework reactivity (React/Vue/Svelte/Solid), stores (Redux/Zustand/Pinia), TanStack Query |
75
+ | Resource leaks | [references/resource-leaks.md](references/resource-leaks.md) | Code opens files/sockets/connections, subscribes, registers listeners, schedules timers, spawns threads/processes, acquires locks |
76
+ | Time & numbers | [references/time-numbers.md](references/time-numbers.md) | Code handles dates/timestamps/durations, money, percentages, float comparisons, integer overflow risk |
77
+ | Error handling | [references/error-handling.md](references/error-handling.md) | Code throws/catches/raises, returns Result/Either, uses defer/finally/with, calls fallible operations in sequence |
78
+ | Data layer | [references/data-layer.md](references/data-layer.md) | Code touches a database, ORM, message queue, external service, or coordinates across processes |
79
+ | Language footguns | [references/language-footguns.md](references/language-footguns.md) | Always relevant — the scanner reads only the section matching the detected language |
80
+
81
+ ## Phase 1: Stack discovery
82
+
83
+ Detect what you're scanning:
84
+ - **Languages** — file extensions, manifests (`package.json`, `tsconfig.json`, `pyproject.toml`,
85
+ `Cargo.toml`, `go.mod`, `pom.xml`, `build.gradle`, `*.csproj`, `Gemfile`).
86
+ - **Frameworks** — React, Vue, Svelte, Next, Nuxt, Django, FastAPI, Rails, Spring, Actix, Axum,
87
+ Express, NestJS, Tokio, asyncio.
88
+ - **Data layer** — Prisma, Drizzle, SQLAlchemy, Django ORM, ActiveRecord, Mongoose, Redis,
89
+ Kafka, NATS, SQS.
90
+ - **Concurrency primitives** — goroutines, threads, asyncio.create_task, tokio::spawn, workers.
91
+ - **Entry points** — API routes, CLI mains, background workers, cron, webhook handlers.
92
+
93
+ Summarize the stack in 4-6 lines.
94
+
95
+ ## Phase 2: Domain selection
96
+
97
+ Map detected stack features to the 8 domains. Mark each as **most relevant**, **relevant**,
98
+ or **skip** based on what's in the code and the concrete scan scope. For a small
99
+ path with no async, no data layer, and no external resources, skip those domains
100
+ instead of launching empty scanners. For a broad release bundle or repo-wide
101
+ audit, include every domain with real evidence in Phase 1.
102
+
103
+ ### Standalone mode
104
+ **structured question tool checkpoint** (multi-select): show the 8 domains with relevance annotations,
105
+ recommend 4-6 for a focused scan or all 8 for a full audit. Default to all "most relevant" +
106
+ "relevant" if user doesn't choose.
107
+
108
+ ### Gate mode
109
+ Skip the prompt. Default to all domains that touch any file in the bundle.
110
+
111
+ ## Phase 3: Fan-out scan
112
+
113
+ For each selected domain, spawn **one parallel scanner agent in a single
114
+ message** so they run concurrently.
115
+
116
+ Use a generic sub-agent prompted with the scanner posture from `../principles/references/subagents.md` with high
117
+ inspection/reviewer reasoning; escalate to extra-high only for high-risk
118
+ domains, very large scopes, or repeat scans that previously missed issues. If a
119
+ host cannot spawn the scanner role, use the same-host read-only analysis
120
+ fallback.
121
+
122
+ ### Scope (passed into every scanner)
123
+
124
+ - **Standalone**: the user's path arg, or the whole repo if no arg. Resolve to a concrete file
125
+ list with `git ls-files` (and the path filter, if any).
126
+ - **Gate mode**: only files touched by items bound to the release. `--release`
127
+ auto-widens to ALL tiers (active + archive + releases). Include late-bound
128
+ archived stubs — their bodies may be pruned, but the item id still recovers
129
+ the bundle commits/files. Ignore only the release orchestration item
130
+ (`kind: release`).
131
+ ```bash
132
+ .work/bin/work-view --release <version> --paths | while IFS= read -r item; do
133
+ kind=$(grep -m1 '^kind:' "$item" | awk '{print $2}')
134
+ [ "$kind" = "release" ] && continue
135
+ id=$(grep -m1 '^id:' "$item" | awk '{print $2}')
136
+ git log --grep "$id" --format='%H' | xargs -I{} git diff-tree --no-commit-id --name-only -r {}
137
+ done | sort -u > /tmp/bundle-files-<version>.txt
138
+ ```
139
+ Capture already-tracked findings to feed back as "skip these":
140
+ ```bash
141
+ .work/bin/work-view --release <version> --gate bugs --paths
142
+ ```
143
+
144
+ ### Scanner brief template
145
+
146
+ Each scanner gets:
147
+
148
+ > You are a bug scanner agent for the **<domain>** domain. Use the
149
+ > agile-workflow scanner contract: source-read-only, no fixes, no recursive
150
+ > sub-agents.
151
+ >
152
+ > **Reference (load FIRST)**: `<absolute path to references/<domain>.md>`
153
+ > Read the whole file. It contains the patterns to hunt for in this domain.
154
+ >
155
+ > **Scope** — scan ONLY these files:
156
+ > ```
157
+ > <file list>
158
+ > ```
159
+ >
160
+ > **Stack profile** (from orchestrator):
161
+ > ```
162
+ > <stack summary>
163
+ > ```
164
+ >
165
+ > **Already-tracked findings to skip** (gate mode only):
166
+ > ```
167
+ > <file:line / pattern pairs, or "none">
168
+ > ```
169
+ >
170
+ > **Method**:
171
+ > 1. Load the domain reference. Note the patterns and detection signals.
172
+ > 2. Web-search 1-3 times for current pitfalls in this domain for the specific stack
173
+ > (e.g. "React 19 stale closure useEffect 2025", "Go 1.22 loop variable gotchas").
174
+ > 3. Apply the reference's grep heuristics to the scope. Read flagged files. Confirm matches
175
+ > by reading the code — don't report on grep hits alone.
176
+ > 4. For each confirmed bug, record:
177
+ > - **Title** (one line)
178
+ > - **Pattern** (which named pattern from the reference; or "new" if not in the reference)
179
+ > - **Severity** — see rubric below
180
+ > - **Location** (`file:line`)
181
+ > - **Evidence** (1-5 lines of the offending code, in a fenced block)
182
+ > - **Why it's a bug** (1-2 sentences — the specific failure mode)
183
+ > - **Remediation direction** (a direction, not a finished fix)
184
+ >
185
+ > **Severity rubric** — every finding gets one of:
186
+ > | Severity | Meaning |
187
+ > |---|---|
188
+ > | Critical | Will cause data loss, corruption, hang, or wrong financial outcome under realistic load. Must fix before shipping. |
189
+ > | High | Will cause incorrect behavior or crash under uncommon-but-real conditions (specific input, race window, error path). Must fix before shipping. |
190
+ > | Medium | Real bug, hard to trigger or limited blast radius. Should fix; can defer with explicit acknowledgement. |
191
+ > | Low | Edge case, latent bug, defensive-only. Backlog. |
192
+ > | Info | Not a finding. Don't return Info entries. |
193
+ >
194
+ > **Rules**:
195
+ > - Cite file:line for every finding.
196
+ > - Don't report a pattern unless you read the surrounding code and confirmed the bug applies
197
+ > here. Grep hits alone are not findings.
198
+ > - Don't fabricate. Empty findings list is a valid answer.
199
+ > - Don't implement fixes. Findings only.
200
+ > - Skip findings matching the already-tracked list.
201
+ >
202
+ > **Output format** — return a single markdown document with:
203
+ > ```
204
+ > ## Domain: <name>
205
+ > ## Stack notes: <one-line> what's relevant
206
+ > ## Findings
207
+ >
208
+ > ### Finding 1
209
+ > - **Title**: ...
210
+ > - **Pattern**: ...
211
+ > - **Severity**: Critical | High | Medium | Low
212
+ > - **Location**: `file:line`
213
+ > - **Evidence**:
214
+ > ```<lang>
215
+ > <code>
216
+ > ```
217
+ > - **Why it's a bug**: ...
218
+ > - **Remediation direction**: ...
219
+ > ```
220
+ > Followed by:
221
+ > ```
222
+ > ## Summary
223
+ > - Files scanned: <n>
224
+ > - Patterns applied: <list>
225
+ > - Findings: Critical=<n>, High=<n>, Medium=<n>, Low=<n>
226
+ > ```
227
+
228
+ ### Wave coordination
229
+
230
+ - Dispatch all selected scanners in a **single message** (parallel).
231
+ - If a scanner returns an error, record it as a gap in the report — do not re-run blindly.
232
+ - If a scanner returns >25 findings, ask it (via follow-up message) to keep only the top 25 by
233
+ severity (anything beyond suggests pattern over-matching).
234
+
235
+ ## Phase 4: Aggregate & dedupe
236
+
237
+ Collect every scanner's findings. Then:
238
+
239
+ 1. **Dedupe** — if the same `file:line` appears in two domains, keep the higher-severity entry
240
+ and add a "also flagged by: <domain>" note.
241
+ 2. **Cluster** — group findings by file. A file with 5+ findings probably needs a single
242
+ broader "this module needs rework" item rather than 5 small items.
243
+ 3. **Sort** — by severity (Critical → High → Medium → Low), then by file.
244
+ 4. **Score each domain** 0-10 using the rubric below.
245
+
246
+ ### Domain scoring rubric
247
+
248
+ | Score | Meaning |
249
+ |---|---|
250
+ | 0-1 | Critical findings present, multiple. Codebase is unsafe in this domain. |
251
+ | 2-3 | One Critical or several High. Major work needed. |
252
+ | 4-5 | Several Medium findings; one Critical/High at most. Hygiene gaps. |
253
+ | 6-7 | Clean — only Low findings or one Medium. Standard quality. |
254
+ | 8-9 | No findings + the code shows proactive defensive patterns (transaction boundaries,
255
+ AbortControllers, idempotency keys, explicit timeouts, etc.). |
256
+ | 10 | Zero findings + comprehensive defensive design across the domain. |
257
+
258
+ Floor: a Critical finding caps the domain score at 3. Multiple High caps at 5.
259
+
260
+ Compute an overall score: weighted average where "most relevant" domains weigh 2x, "relevant"
261
+ weigh 1x. Round to one decimal.
262
+
263
+ ## Phase 5: Output
264
+
265
+ ### Standalone mode
266
+
267
+ Two outputs: a markdown report AND parked backlog items (unless `--no-park`).
268
+
269
+ #### 5a. Park findings to backlog (default)
270
+
271
+ Skip this step entirely if invoked with `--no-park`. Otherwise:
272
+
273
+ 1. **Verify substrate**. If `.work/CONVENTIONS.md` is missing, the project isn't on
274
+ agile-workflow yet — skip parking, note it in the report, and tell the user to run
275
+ `/agile-workflow:convert` if they want substrate tracking.
276
+
277
+ 2. **Build the existing-findings index** for idempotency:
278
+ ```bash
279
+ mkdir -p /tmp
280
+ : > /tmp/bugscan-existing.txt
281
+ for f in .work/backlog/bug-scan-*.md; do
282
+ [ -f "$f" ] || continue
283
+ grep -q '^bug_origin: scan$' "$f" || continue
284
+ loc=$(grep -m1 '^bug_location:' "$f" | awk '{print $2}')
285
+ [ -n "$loc" ] && echo "$loc $f" >> /tmp/bugscan-existing.txt
286
+ done
287
+ ```
288
+
289
+ 3. **Park each finding** as one file at `.work/backlog/bug-scan-<slug>.md` using the template
290
+ at [references/parked-item-template.md](references/parked-item-template.md). Skip any
291
+ finding whose `file:line` already appears in `/tmp/bugscan-existing.txt` and tally those
292
+ as "duplicates skipped". On slug collision, suffix `-2`, `-3`.
293
+
294
+ 4. **Commit** all parked files in one commit:
295
+ ```bash
296
+ git add .work/backlog/bug-scan-*.md
297
+ git commit -m "bug-scan: park <N> findings (<scope>)"
298
+ ```
299
+ If working tree was dirty before parking, commit only the new backlog files (`git add`
300
+ with explicit paths — never `git add -A`).
301
+
302
+ #### 5b. Write the report
303
+
304
+ Write to `bug-scan-report.md` in the repo root using the template at
305
+ [references/report-template.md](references/report-template.md). Include:
306
+
307
+ - Overall score (X.X/10)
308
+ - Per-domain score table
309
+ - Findings grouped by severity, each with its parked item id (or "not parked: --no-park"
310
+ or "not parked: no substrate")
311
+ - "Top 3" callout for the most-critical findings
312
+ - Domains skipped + why
313
+ - Park summary: parked count, duplicates skipped, opt-outs
314
+
315
+ #### 5c. Summarize to the user
316
+
317
+ - Overall score
318
+ - Severity counts (Critical / High / Medium / Low)
319
+ - Top 3 critical findings (one line each, with `file:line`)
320
+ - Path to the report
321
+ - Parked count + duplicates skipped (or "parking skipped (--no-park)" / "parking skipped (no substrate)")
322
+ - **structured question tool** (four options):
323
+ - "Elevate criticals to `stage:implementing` via `/agile-workflow:scope`"
324
+ - "Hand top finding to `/agile-workflow:fix` now"
325
+ - "Dive deeper into a specific domain"
326
+ - "Done for now — leave the parked items in backlog"
327
+
328
+ ### Gate mode
329
+
330
+ For each finding above Info, create an item file in `.work/active/stories/` (or backlog for
331
+ Low) using the template at [references/gate-item-template.md](references/gate-item-template.md).
332
+
333
+ Severity → stage mapping:
334
+ - **Critical** / **High** → `stage: implementing`
335
+ - **Medium** → `stage: drafting`
336
+ - **Low** → backlog file at `.work/backlog/`
337
+
338
+ ID convention: `gate-bugs-<short-slug>` where slug describes the finding (e.g.
339
+ `gate-bugs-stale-closure-in-cart-effect`).
340
+
341
+ Commit:
342
+ ```bash
343
+ git add .work/active/stories/ .work/backlog/
344
+ git commit -m "gate-bugs: <N> findings for <version>"
345
+ ```
346
+
347
+ Then report to the user (same format as gate-security):
348
+ - Bundle version
349
+ - Findings by severity
350
+ - Items created (count + new ids)
351
+ - Already-tracked duplicates skipped
352
+ - Next step (release ships when bound items reach `stage: done`)
353
+
354
+ ## Guardrails
355
+
356
+ - **The scanning happens in the scanner agents, not here.** Your job is stack discovery,
357
+ scanner dispatch, and result aggregation. Do not re-do a scanner's work in the
358
+ orchestrator's context — that throws away the progressive-disclosure win.
359
+ - **Fan out selected domains.** Once Phase 2 has selected domains, run one
360
+ scanner per selected domain in parallel. Do not serialize them, and do not
361
+ spawn skipped domains.
362
+ - **Always cite file:line.** Findings without locations are not findings.
363
+ - **Don't fabricate.** A scanner returning zero findings for a domain is a valid result. Score
364
+ it honestly (7, not 10 — absence ≠ active hardening).
365
+ - **Confirm grep hits.** Detection signals are starting points, not findings. Every reported
366
+ bug must have been read and understood in context.
367
+ - **Respect scope.** If the user gave a path, don't expand. If gate mode, scan only the bundle.
368
+ - **No fixes.** This skill produces findings/items only. Remediation goes through
369
+ `/agile-workflow:fix` or `/agile-workflow:implement`.
370
+ - **Idempotent gate re-runs.** Pass already-tracked findings into scanner briefs so they skip
371
+ duplicates; double-check on item-write before creating files.
372
+ - **Idempotent standalone re-runs.** Before parking, scan `.work/backlog/bug-scan-*.md` for
373
+ existing `bug_origin: scan` items, build a `file:line → id` index, and skip findings already
374
+ present. Tally and report the skip count. This keeps repeated `/agile-workflow:bug-scan`
375
+ invocations from flooding backlog with the same items.
376
+ - **Standalone parks to backlog, never to active.** Even Critical findings in standalone mode
377
+ go to `.work/backlog/` — the user explicitly elevates them via `/agile-workflow:scope` if
378
+ they choose. The path to active is gate mode (release-bound, intentional).
@@ -0,0 +1,6 @@
1
+ interface:
2
+ display_name: "Bug Scan"
3
+ short_description: "Use Bug Scan for repo workflows"
4
+ default_prompt: "Use $bug-scan for this repository."
5
+ policy:
6
+ allow_implicit_invocation: false
@@ -0,0 +1,119 @@
1
+ # Async / Promise / Coroutine — Bug Reference
2
+
3
+ > **When to load this reference**: when scanning code with async/await, Promise/Future/Task,
4
+ > coroutines, event loops, or non-blocking I/O. Goroutines/channels live in concurrency-races.md.
5
+
6
+ ## Detection signals
7
+ - JS/TS: `async function`, `\bawait\b`, `Promise\.(all|allSettled|race|any)`, `\.then\(`, `\.catch\(`,
8
+ `setTimeout`, `setInterval`, `new Promise\(`, `\.forEach\(.*async`, `async \(.*\) =>`, top-level `await`.
9
+ - Python: `async def`, `\bawait\b`, `asyncio\.(gather|create_task|run|wait|shield)`, `trio\.open_nursery`,
10
+ `anyio\.`, `loop\.run_in_executor`, `time\.sleep` inside `async def`, `requests\.` inside `async def`.
11
+ - Rust: `\.await`, `tokio::spawn`, `tokio::select!`, `async fn`, `async move`, `block_on`,
12
+ `futures::join!`, `try_join!`, `std::thread::sleep` inside async, `std::sync::Mutex` near `.await`.
13
+ - Swift: `Task \{`, `await `, `actor `, `MainActor`, `withCheckedContinuation`, `Task.detached`.
14
+ - C#: `async Task`, `async void`, `\.Result\b`, `\.Wait\(`, `\.GetAwaiter\(\)\.GetResult`, `ConfigureAwait`.
15
+ - Kotlin: `suspend fun`, `launch \{`, `async \{`, `GlobalScope`, `runBlocking`, `withContext`,
16
+ `SupervisorJob`, `suspendCoroutine` (vs Cancellable).
17
+
18
+ ## Patterns
19
+
20
+ ### 1. Unhandled promise rejection / swallowed error
21
+ - **Signature**: `.then(…)` chain with no `.catch`; `async` function called without `await` or `.catch`; rejected promise stored to variable and ignored.
22
+ - **Why hard to find**: Node logs "UnhandledPromiseRejection" but does not crash by default in browsers / many runtimes. Failure path is the absence of a log.
23
+ - **Where to look**: top-level handlers, event listeners that call async fns, library boundaries.
24
+ - **Example**: `el.addEventListener('click', async () => doStuff())` — rejection is lost.
25
+ - **Fix direction**: wrap with `try/catch` or attach `.catch`; install global `unhandledrejection` handler.
26
+ - **Language variants**: Python `Task` exception lost unless `await`ed or accessed via `.exception()`; Rust `JoinHandle` errors silent until `.await`; C# `async void` throws to `SynchronizationContext` → process crash.
27
+
28
+ ### 2. Fire-and-forget task lost to the void
29
+ - **Signature**: `asyncio.create_task(f())` / `tokio::spawn(f())` / `Task { … }` with no handle retained; no timeout, no cancellation source, no error surface.
30
+ - **Why hard to find**: works fine until load; under pressure tasks accumulate, GC may collect Python tasks (warning only), Rust task leaks live forever.
31
+ - **Where to look**: background workers, "send notification later" code, telemetry flushes.
32
+ - **Example**: `asyncio.create_task(send_email(u))` inside a request handler.
33
+ - **Fix direction**: hold reference (`self._tasks.add(t); t.add_done_callback(self._tasks.discard)`), bound queue, attach timeout + error log.
34
+
35
+ ### 3. Serial `await` in a loop instead of `Promise.all`
36
+ - **Signature**: `for (const x of xs) { await f(x); }` when each call is independent.
37
+ - **Why hard to find**: correct results, just N× slower; only shows up in latency metrics.
38
+ - **Where to look**: hot paths, list-fetching code, migration scripts.
39
+ - **Example**: `for (const id of ids) results.push(await fetch(id))`.
40
+ - **Fix direction**: `Promise.all(ids.map(fetch))`; bound concurrency with `p-limit` / semaphore if N large.
41
+ - **Language variants**: Python `asyncio.gather`, Rust `futures::join_all` / `buffer_unordered`, Kotlin `awaitAll`.
42
+
43
+ ### 4. `forEach` with async callback (promises ignored)
44
+ - **Signature**: `arr.forEach(async x => await f(x))` — `forEach` ignores returned promises.
45
+ - **Why hard to find**: outer function "completes" before any work finishes; downstream sees empty/partial state with no error.
46
+ - **Where to look**: refactors from sync to async, `.forEach` over fetches.
47
+ - **Fix direction**: use `for…of` with `await`, or `Promise.all(arr.map(f))`. ESLint `no-misleading-character-class` / `no-floating-promises` (TS).
48
+
49
+ ### 5. Missing `await` on an async call
50
+ - **Signature**: `const x = fetchUser()` where `fetchUser` returns a Promise; `if (asyncCheck())` — truthy because Promise is truthy.
51
+ - **Why hard to find**: silent in JS without TS-strict / `@typescript-eslint/no-floating-promises`; logs show `[object Promise]` or `<Future pending>`.
52
+ - **Where to look**: conditionals, comparisons, JSON serialization, assignment.
53
+ - **Fix direction**: enable `no-floating-promises`, type the return; Python `RuntimeWarning: coroutine was never awaited` — promote to error in CI.
54
+
55
+ ### 6. `Promise.all` rejecting where partial success was intended
56
+ - **Signature**: `Promise.all([…])` over independent best-effort work.
57
+ - **Why hard to find**: passes happy-path tests; under a single transient failure the whole batch is reported as failed.
58
+ - **Where to look**: dashboard widgets, fan-out reads, notification dispatch.
59
+ - **Fix direction**: `Promise.allSettled` + per-result handling. Python: `asyncio.gather(..., return_exceptions=True)`.
60
+
61
+ ### 7. Race in user-input handler (stale-overwriting-fresh)
62
+ - **Signature**: handler kicks off `await fetch(query)`; later response sets state, but a newer keystroke has already completed.
63
+ - **Why hard to find**: depends on network jitter; results may look right most of the time, then flicker stale.
64
+ - **Where to look**: search-as-you-type, autocomplete, tab switchers, dependent dropdowns.
65
+ - **Fix direction**: capture request id / `AbortController`; on resolve check `if (id !== currentId) return`; in React, cleanup in `useEffect`.
66
+
67
+ ### 8. Cancellation not propagated
68
+ - **Signature**: long-running task with no `AbortSignal` / `CancellationToken` / cooperative `ensure_active()` / `select!` on cancel branch.
69
+ - **Why hard to find**: caller moves on (closed tab, dropped future), worker keeps running, holding locks/DB conns.
70
+ - **Where to look**: streaming endpoints, retry loops, polling intervals, Tokio MPSC `send` (NOT cancel-safe — message lost on drop).
71
+ - **Fix direction**: thread `AbortSignal` through; Python: catch `asyncio.CancelledError` and re-raise after cleanup; Rust: prefer cancel-safe primitives or `tokio::select!` with explicit branches.
72
+
73
+ ### 9. Blocking work inside an async function
74
+ - **Signature**: `time.sleep` / `requests.get` / sync `fs.readFileSync` / `std::thread::sleep` / heavy CPU loop inside `async def` / `async fn`.
75
+ - **Why hard to find**: single-threaded event loop stalls; *all* concurrent tasks pause; symptoms look like network slowness.
76
+ - **Where to look**: legacy lib calls in async handlers, JSON parse of huge payloads, bcrypt/argon2 calls, image processing.
77
+ - **Fix direction**: Python `await asyncio.to_thread(f)` or `run_in_executor`; Node worker thread / `setImmediate`; Rust `tokio::task::spawn_blocking`; C# `Task.Run` for CPU.
78
+
79
+ ### 10. Holding a non-async lock across `await`
80
+ - **Signature**: `threading.Lock` / `std::sync::Mutex` acquired, then `.await` while held.
81
+ - **Why hard to find**: works under low contention; deadlocks/serializes under load; Rust will compile fine but break `Send` for tokio.
82
+ - **Where to look**: caches with locks, singletons, connection pools.
83
+ - **Fix direction**: use `asyncio.Lock` / `tokio::sync::Mutex` / Kotlin `Mutex`; or drop guard before `await`.
84
+
85
+ ### 11. C# blocking on async (`.Result` / `.Wait()`)
86
+ - **Signature**: `someAsync().Result`, `.Wait()`, `.GetAwaiter().GetResult()` from a synchronization-context thread (UI / classic ASP.NET).
87
+ - **Why hard to find**: classic deadlock — continuation needs the captured context, thread is blocked waiting.
88
+ - **Where to look**: legacy callers of async libs, constructors, property getters.
89
+ - **Fix direction**: async-all-the-way; library code uses `ConfigureAwait(false)`; never `async void` except event handlers.
90
+
91
+ ### 12. Swift actor reentrancy assumption
92
+ - **Signature**: actor method reads state, `await`s something, then writes state assuming nothing changed.
93
+ - **Why hard to find**: actor protects against data races, NOT against state mutation across suspension; tests that don't interleave miss it.
94
+ - **Where to look**: balance/quota checks, cache fill ("check-then-load"), counters.
95
+ - **Fix direction**: re-verify invariants after every `await`; pre-store and reuse the in-flight `Task` to dedupe; move state writes before `await`.
96
+
97
+ ### 13. Kotlin: `GlobalScope` / broken structured concurrency
98
+ - **Signature**: `GlobalScope.launch`; manual `Job()` passed to child; `withContext(SupervisorJob())`; `try/catch` swallowing `CancellationException`.
99
+ - **Why hard to find**: parent cancels, child keeps running; or cancellation no longer propagates; zombie coroutines on Android leak ViewModel scopes.
100
+ - **Where to look**: Android ViewModels, repository singletons, retry loops that catch `Throwable`.
101
+ - **Fix direction**: scope to lifecycle (`viewModelScope`); rethrow `CancellationException`; avoid `suspendCoroutine` — prefer `suspendCancellableCoroutine`.
102
+
103
+ ### 14. React/Vue/Svelte: state update after unmount
104
+ - **Signature**: `useEffect(() => { fetch().then(setX) }, [])` — no cleanup; component unmounts before resolve.
105
+ - **Why hard to find**: warning appears intermittently; in production silently leaks closures + may overwrite newer view state.
106
+ - **Where to look**: data-loading effects, subscriptions, intervals.
107
+ - **Fix direction**: `AbortController` in cleanup, or `let cancelled = false; return () => { cancelled = true }`; Svelte: use `onDestroy` / `$effect` cleanup; Vue: `onUnmounted`.
108
+
109
+ ### 15. `Promise.race` leaving losers running
110
+ - **Signature**: `Promise.race([fetch(a), fetch(b)])` used as timeout; loser request keeps running, may resolve with side effects.
111
+ - **Why hard to find**: works functionally; leaks sockets, file handles, may trigger duplicate side effects (writes, analytics).
112
+ - **Where to look**: ad-hoc timeouts, "first available source" patterns.
113
+ - **Fix direction**: pass `AbortSignal` to the losers (`AbortSignal.any`); prefer `Promise.any` for first-success semantics; cancel explicitly.
114
+
115
+ ### 16. `await` in a `finally` masking the original error
116
+ - **Signature**: `try { … } finally { await cleanup() }` where `cleanup` throws.
117
+ - **Why hard to find**: original error vanishes, replaced by cleanup error with unrelated stack.
118
+ - **Where to look**: transaction rollbacks, file close, lock release.
119
+ - **Fix direction**: catch inside `finally`, log+swallow cleanup error, let original propagate; or use `AggregateError`.
@@ -0,0 +1,148 @@
1
+ # Concurrency & Race Conditions — Bug Reference
2
+
3
+ > **When to load this reference**: when scanning code that uses threads, processes, goroutines,
4
+ > async runtimes, shared mutable state, locks, channels, atomics, or any inter-thread/inter-task
5
+ > communication.
6
+
7
+ ## Detection signals (where to start)
8
+ - Synchronization primitives: `sync.Mutex`, `sync.RWMutex`, `Lock(`, `RLock`, `synchronized`, `Arc<Mutex`, `parking_lot`, `std::mutex`, `pthread_`, `NSLock`, `DispatchQueue`.
9
+ - Task spawn / scheduling: `go func`, `tokio::spawn`, `thread::spawn`, `Thread(`, `threading.`, `multiprocessing.`, `asyncio.gather`, `asyncio.create_task`, `Promise.all`, `Promise.race`, `new Worker(`, `worker_threads`, `ExecutorService`, `CompletableFuture`.
10
+ - Channels / queues: `make(chan`, `<-`, `select {`, `mpsc::`, `crossbeam`, `queue.Queue`, `BlockingQueue`.
11
+ - Atomics / memory: `atomic.`, `AtomicInteger`, `AtomicReference`, `volatile`, `static mut`, `memory_order_`, `Ordering::Relaxed`, `unsafe`.
12
+ - Shared state markers: `Send`, `Sync`, `@GuardedBy`, `@ThreadSafe`, `dispatch_once`, `__thread`, module-level mutables.
13
+ - Hot files: caches, session managers, connection pools, rate limiters, schedulers, leader-election code, file-write helpers.
14
+
15
+ ## Patterns
16
+
17
+ ### 1. Check-then-act (TOCTOU)
18
+ - **Signature**: `if !exists(x) { create(x) }`, `if cache.get(k) is None: cache[k] = ...`, `os.path.exists` then `open`, `SELECT ... then INSERT`.
19
+ - **Why hard to find**: looks like ordinary control flow; only one window of vulnerability between two innocuous calls.
20
+ - **Where to look**: file create helpers, "ensure_" / "get_or_create" / "find_or_init", auth/session bootstrap, idempotent endpoints, DB upserts written as two statements.
21
+ - **Example**:
22
+ ```python
23
+ if user_id not in sessions: # check
24
+ sessions[user_id] = Session() # act — two threads can both pass the check
25
+ ```
26
+ - **Fix direction**: collapse to one atomic op (`setdefault`, `LoadOrStore`, `putIfAbsent`, DB unique constraint + `ON CONFLICT`, `O_CREAT|O_EXCL`).
27
+ - **Language variants**: Go → `sync.Map.LoadOrStore`; Java → `ConcurrentHashMap.computeIfAbsent`; Rust → `entry().or_insert_with`; Node → `fs.writeFile(... 'wx')`; SQL → unique index + upsert.
28
+
29
+ ### 2. Non-atomic read-modify-write on shared state
30
+ - **Signature**: `counter++`, `total += x`, `list.append` on a module-level list (post-GIL Python), `map[k] = map[k] + 1`.
31
+ - **Why hard to find**: source line looks atomic; bug surfaces only under contention or load tests.
32
+ - **Where to look**: metrics counters, in-memory tallies, free-list updates, retry counters, ID generators.
33
+ - **Example**:
34
+ ```go
35
+ var hits int
36
+ go func() { hits++ }() // racy — read, increment, write are three ops
37
+ ```
38
+ - **Fix direction**: use atomics (`atomic.AddInt64`, `AtomicInteger.incrementAndGet`, `fetch_add`), a lock, or message-pass through a channel/actor.
39
+ - **Language variants**: Python 3.13 free-threading (PEP 703) removes the GIL safety net — `+=`, `dict[k]=...`, and `list.append` are no longer guaranteed atomic; audit shared module state.
40
+
41
+ ### 3. Double-checked locking / unsafe publication
42
+ - **Signature**: `if instance is None: with lock: if instance is None: instance = X()`, lazy singletons.
43
+ - **Why hard to find**: looks correct; partially-constructed object may be visible to a reader without happens-before.
44
+ - **Where to look**: singleton getters, lazy caches, JNI/CGo glue, ObjC `+sharedInstance` rolled by hand.
45
+ - **Example**:
46
+ ```java
47
+ if (cfg == null) { synchronized(L) { if (cfg == null) cfg = new Cfg(); } } // cfg must be volatile
48
+ ```
49
+ - **Fix direction**: declare the field `volatile` / `AtomicReference` (Java), use `OnceLock`/`OnceCell` (Rust), `sync.Once` (Go), `dispatch_once` (Swift/ObjC), module-import time init (Python).
50
+
51
+ ### 4. Memory visibility / missing happens-before
52
+ - **Signature**: a writer thread sets a `done`/`ready` flag; reader spins on it without `volatile`, `atomic`, or a release/acquire pair.
53
+ - **Why hard to find**: works on x86 due to strong ordering; fails on ARM/Apple Silicon or under aggressive JIT/compiler reordering.
54
+ - **Where to look**: shutdown flags, "ready" signals, `while (!stopped)` loops, lock-free queues, custom spinlocks, `static mut` in Rust.
55
+ - **Example**:
56
+ ```cpp
57
+ bool ready=false; int v=0;
58
+ // Thread A: v=42; ready=true;
59
+ // Thread B: while(!ready); use(v); // both must be std::atomic with acquire/release
60
+ ```
61
+ - **Fix direction**: use language-blessed atomics with appropriate ordering (`SeqCst` default, `Acquire`/`Release` if you know the model); in Java make fields `volatile` or use `j.u.c.atomic`; in Go use `atomic.LoadX`/`StoreX` or a channel.
62
+
63
+ ### 5. Lock ordering / cyclical deadlock
64
+ - **Signature**: function F locks A then B; function G locks B then A.
65
+ - **Why hard to find**: only deadlocks when both paths run concurrently; tests usually take one path at a time.
66
+ - **Where to look**: any module with ≥2 mutexes; account/transfer code, parent↔child object graphs, bidirectional relationships, "swap" or "merge" operations.
67
+ - **Example**:
68
+ ```go
69
+ // transfer(a,b): a.Lock(); b.Lock(); transfer(b,a) inverts — deadlock
70
+ ```
71
+ - **Fix direction**: impose a global lock order (sort by address/ID), use a single coarser lock, use `try_lock` with back-off, or restructure to lock-free messaging.
72
+
73
+ ### 6. Lock held during callback / I/O / await
74
+ - **Signature**: `lock.acquire(); callback(); lock.release()`, `mutex.lock(); http.get(...)`, holding a Tokio `MutexGuard` across `.await`.
75
+ - **Why hard to find**: callback may reenter, block, or call back into the same module, producing deadlock or pathological latency.
76
+ - **Where to look**: event emitters, observer/listener fan-out, plugin systems, async functions that take a `std::sync::Mutex`, Swift `@MainActor` work that calls completion handlers.
77
+ - **Example**:
78
+ ```rust
79
+ let g = m.lock().unwrap();
80
+ client.get(url).await; // holds lock across await — task starvation / deadlock
81
+ ```
82
+ - **Fix direction**: copy needed data out, drop the guard before the slow op, or use `tokio::sync::Mutex` (still avoid long holds); never invoke unknown user code under a lock.
83
+
84
+ ### 7. Iteration during concurrent mutation
85
+ - **Signature**: `for k,v in map.items():` while another thread writes; `range m` in Go without a lock; `for (Entry e : map.entrySet())` on a non-concurrent map.
86
+ - **Why hard to find**: throws `ConcurrentModificationException` only sometimes; in Go produces silent corruption or panic.
87
+ - **Where to look**: stats dumpers, snapshot endpoints, cache eviction sweeps, GC-like cleanup loops.
88
+ - **Fix direction**: copy under lock then iterate the copy, use `ConcurrentHashMap`/`sync.Map`, or use snapshot iterators (`CopyOnWriteArrayList`).
89
+
90
+ ### 8. Wait/notify / condition-variable misuse
91
+ - **Signature**: `if cond: wait()` (should be `while`), `notify_one()` when multiple waiters need wake, missing predicate check after wake.
92
+ - **Why hard to find**: spurious wakeups are rare; missed signals only fire under specific interleavings.
93
+ - **Where to look**: hand-rolled bounded queues, producer/consumer code, `Condvar`, `Object.wait/notify`, `threading.Condition`, `pthread_cond_t`.
94
+ - **Fix direction**: always `while (!predicate) wait()`, prefer `notify_all` when in doubt, or replace with a channel / `BlockingQueue` / semaphore.
95
+
96
+ ### 9. Atomicity violation across multiple atomic ops
97
+ - **Signature**: `if map.contains(k) { map.get(k) }`, `atomic.Load` followed by `atomic.Store`, `ConcurrentHashMap.get` then `put`.
98
+ - **Why hard to find**: each individual op is atomic, so static analysis and review give a false sense of safety.
99
+ - **Where to look**: "get or update" code, balance/inventory math on atomics, two-step state transitions.
100
+ - **Fix direction**: use a CAS loop (`compareAndSet`, `compare_exchange`), `compute`/`merge` style atomic combinators, or take a lock spanning the full sequence.
101
+
102
+ ### 10. Cancellation / interruption leaving partial state
103
+ - **Signature**: `try: work() except CancelledError: ...` without rollback; `ctx.Done()` mid-write; Go `context` cancel during multi-step DB write; Swift `Task.cancel`.
104
+ - **Why hard to find**: only fires when a timeout/cancel races with a critical section.
105
+ - **Where to look**: HTTP handlers with deadlines, batch jobs, transactional flows that mix DB + cache + external API, `defer`/`finally` blocks that themselves do I/O.
106
+ - **Fix direction**: make critical sections atomic (single tx), guard cleanup with `shield`/`uninterruptible`, persist intent before doing work so cancel-recovery is well-defined.
107
+
108
+ ### 11. Channel deadlock / goroutine leak via blocked send/receive
109
+ - **Signature**: send to unbuffered channel with no receiver; receive from a channel that is never closed; `select` whose only ready case is `<-ctx.Done()` that never fires.
110
+ - **Why hard to find**: goroutines leak silently; only visible via pprof, RSS growth, or `runtime.NumGoroutine`.
111
+ - **Where to look**: fan-out workers without `WaitGroup`, request-scoped goroutines without `context`, "fire and forget" launches, Rust `tokio::spawn` whose `JoinHandle` is dropped.
112
+ - **Fix direction**: always pair sends with bounded receivers, close channels from the sender side, propagate `context.Context`, and use `errgroup` / `JoinSet` for structured concurrency.
113
+
114
+ ### 12. `select` with `default` busy-loop / starvation
115
+ - **Signature**: Go `select { case x:=<-ch: ... ; default: }` inside a `for`; Rust `try_recv` polled in a tight loop; JS `while(!done)` with `setImmediate`.
116
+ - **Why hard to find**: code "works" but pins a CPU; starves siblings on the same runtime.
117
+ - **Where to look**: hand-rolled pollers, retry loops, scheduler shims, custom event loops.
118
+ - **Fix direction**: drop `default` and block on the channel, add a `time.After` / `tokio::time::sleep`, or restructure as event-driven.
119
+
120
+ ### 13. Caching: stampede / thundering herd / double-fill
121
+ - **Signature**: many concurrent misses for the same key each trigger a backend fetch; `if cache.miss: load_from_db()` with no in-flight coalescing.
122
+ - **Why hard to find**: behaves fine under low load; collapses the backing store under traffic spikes or cold start.
123
+ - **Where to look**: memoization decorators, HTTP middleware caches, CDN-fronted endpoints, session/JWT lookups.
124
+ - **Fix direction**: single-flight / request coalescing (`singleflight.Group`, `asyncio` future cache, Caffeine `AsyncLoadingCache`), jittered TTLs, lock-on-key, probabilistic early expiration.
125
+
126
+ ### 14. Send/Sync violations and !Send across await (Rust)
127
+ - **Signature**: `Rc<...>` shared across threads; `RefCell` behind `Arc`; `MutexGuard`, `*mut T`, or non-`Send` future held across `.await` on a multi-threaded runtime.
128
+ - **Why hard to find**: compiler usually catches `!Send`, but trait-object boundaries (`Box<dyn Future>`), conditional compilation, and `unsafe impl Send` can hide it.
129
+ - **Where to look**: `unsafe impl Send for ...`, `tokio::spawn` of large async blocks, FFI handles, `Pin<Box<dyn Future>>` returned from traits, async-trait macros.
130
+ - **Fix direction**: replace `Rc/RefCell` with `Arc/Mutex` (or `tokio::sync::Mutex` if held across await), restrict to `LocalSet` / single-thread runtime, narrow guard scope, audit every `unsafe impl Send`.
131
+
132
+ ### 15. Reentrancy and re-entering the same lock
133
+ - **Signature**: a method takes a lock then calls a virtual/overridden method that takes the same lock; recursive locking on non-reentrant mutex (`std::sync::Mutex`, `pthread_mutex_t` default).
134
+ - **Why hard to find**: works if the second call ends up on a different instance; deadlocks only on certain object graphs.
135
+ - **Where to look**: framework templates, ORM lifecycle hooks, ObjC KVO observers, signals/slots, `Drop` impls that lock.
136
+ - **Fix direction**: split critical sections so callbacks run outside; use a re-entrant mutex only as a stopgap; restructure ownership.
137
+
138
+ ### 16. Background task lifetime escapes parent scope
139
+ - **Signature**: spawning a task that captures `self` / request-scoped state then outlives it; `setTimeout(fn, 0)` in a handler that finishes; `tokio::spawn` not joined.
140
+ - **Why hard to find**: works in tests because the process stays alive; production crashes are use-after-free or stale-data writes.
141
+ - **Where to look**: request handlers, React `useEffect` async work, Swift `Task { ... }` not stored, fire-and-forget logging/metrics flushes.
142
+ - **Fix direction**: structured concurrency (`errgroup`, `TaskGroup`, `JoinSet`, `AbortController`), explicit join/cancel on parent exit, tie task lifetime to a `context.Context`.
143
+
144
+ ### 17. Lost update on optimistic concurrency without version check
145
+ - **Signature**: `row = SELECT ...; row.x += 1; UPDATE SET x = row.x WHERE id = ?` with no `version`/`updated_at` predicate.
146
+ - **Why hard to find**: classic last-writer-wins; only manifests as silent data drift.
147
+ - **Where to look**: counters, inventory, balances, "edit settings" endpoints, ETL merge jobs.
148
+ - **Fix direction**: add a `version` column + `WHERE version = ?` and retry on 0 rows; or `UPDATE ... SET x = x + 1`; or `SELECT ... FOR UPDATE`.