sanook-cli 0.4.0 → 0.5.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.
Files changed (235) hide show
  1. package/.env.example +19 -0
  2. package/CHANGELOG.md +144 -0
  3. package/README.md +153 -20
  4. package/README.th.md +136 -0
  5. package/dist/agentContext.js +4 -0
  6. package/dist/approval.js +6 -0
  7. package/dist/bin.js +394 -51
  8. package/dist/brain.js +92 -59
  9. package/dist/brand.js +47 -0
  10. package/dist/checkpoint.js +37 -0
  11. package/dist/commands.js +86 -6
  12. package/dist/compaction.js +76 -5
  13. package/dist/config.js +100 -12
  14. package/dist/cost.js +60 -3
  15. package/dist/doctor.js +92 -0
  16. package/dist/gateway/auth.js +2 -2
  17. package/dist/gateway/ledger.js +2 -2
  18. package/dist/gateway/scheduler.js +1 -0
  19. package/dist/gateway/serve.js +6 -4
  20. package/dist/gateway/server.js +10 -2
  21. package/dist/git.js +11 -2
  22. package/dist/hooks.js +43 -17
  23. package/dist/knowledge.js +48 -49
  24. package/dist/loop.js +182 -66
  25. package/dist/lsp/client.js +173 -0
  26. package/dist/lsp/framing.js +56 -0
  27. package/dist/lsp/index.js +138 -0
  28. package/dist/lsp/servers.js +82 -0
  29. package/dist/mcp-server.js +244 -0
  30. package/dist/mcp.js +184 -29
  31. package/dist/memory-store.js +559 -0
  32. package/dist/memory.js +143 -29
  33. package/dist/orchestrate.js +150 -0
  34. package/dist/providers/codex.js +2 -2
  35. package/dist/providers/keys.js +3 -2
  36. package/dist/providers/registry.js +133 -1
  37. package/dist/repomap.js +93 -0
  38. package/dist/search/chunk.js +158 -0
  39. package/dist/search/embed-store.js +187 -0
  40. package/dist/search/engine.js +203 -0
  41. package/dist/search/fuse.js +35 -0
  42. package/dist/search/index-core.js +187 -0
  43. package/dist/search/indexer.js +241 -0
  44. package/dist/search/store.js +77 -0
  45. package/dist/session.js +42 -8
  46. package/dist/skill-install.js +10 -10
  47. package/dist/skills.js +12 -9
  48. package/dist/summarize.js +31 -0
  49. package/dist/tools/bash.js +21 -2
  50. package/dist/tools/diagnostics.js +41 -0
  51. package/dist/tools/edit.js +29 -7
  52. package/dist/tools/index.js +8 -1
  53. package/dist/tools/list.js +7 -2
  54. package/dist/tools/permission.js +90 -9
  55. package/dist/tools/read.js +23 -4
  56. package/dist/tools/remember.js +1 -1
  57. package/dist/tools/sandbox.js +61 -0
  58. package/dist/tools/search.js +105 -4
  59. package/dist/tools/task.js +195 -29
  60. package/dist/tools/timeout.js +35 -0
  61. package/dist/tools/util.js +10 -0
  62. package/dist/tools/write.js +6 -4
  63. package/dist/trust.js +89 -0
  64. package/dist/ui/app.js +218 -27
  65. package/dist/ui/banner.js +4 -9
  66. package/dist/ui/history.js +30 -0
  67. package/dist/ui/mentions.js +44 -0
  68. package/dist/ui/setup.js +6 -5
  69. package/dist/ui/useEditor.js +83 -0
  70. package/dist/update.js +114 -0
  71. package/dist/worktree.js +173 -0
  72. package/package.json +11 -5
  73. package/scripts/postinstall.mjs +33 -0
  74. package/second-brain/.agents/_Index.md +30 -0
  75. package/second-brain/.agents/skills/_Index.md +30 -0
  76. package/second-brain/.agents/workflows/_Index.md +30 -0
  77. package/second-brain/AGENTS.md +4 -4
  78. package/second-brain/Acceptance/_Index.md +30 -0
  79. package/second-brain/Acceptance/golden-case-template.md +39 -0
  80. package/second-brain/Areas/_Index.md +30 -0
  81. package/second-brain/Bugs/System-OS/_Index.md +30 -0
  82. package/second-brain/Bugs/_Index.md +30 -0
  83. package/second-brain/CLAUDE.md +4 -1
  84. package/second-brain/Checklists/_Index.md +30 -0
  85. package/second-brain/Checklists/preflight-postflight-template.md +29 -0
  86. package/second-brain/Distillations/_Index.md +30 -0
  87. package/second-brain/Entities/_Index.md +30 -0
  88. package/second-brain/Entities/entity-template.md +33 -0
  89. package/second-brain/Evals/_Index.md +30 -0
  90. package/second-brain/Evals/correction-pairs.md +24 -0
  91. package/second-brain/Evals/failure-taxonomy.md +24 -0
  92. package/second-brain/Evals/golden-set.md +25 -0
  93. package/second-brain/Evals/quality-ledger.md +23 -0
  94. package/second-brain/Evals/self-eval-rubric.md +23 -0
  95. package/second-brain/GEMINI.md +4 -4
  96. package/second-brain/Goals/_Index.md +30 -0
  97. package/second-brain/Handoffs/_Index.md +30 -0
  98. package/second-brain/Home.md +7 -0
  99. package/second-brain/Intake/Raw Sources/_Index.md +30 -0
  100. package/second-brain/Intake/_Index.md +30 -0
  101. package/second-brain/Intake/_Quarantine/_Index.md +30 -0
  102. package/second-brain/Learning/_Index.md +30 -0
  103. package/second-brain/Playbooks/_Index.md +30 -0
  104. package/second-brain/Playbooks/playbook-template.md +23 -0
  105. package/second-brain/Projects/_Index.md +30 -0
  106. package/second-brain/Prompts/_Index.md +30 -0
  107. package/second-brain/README.md +2 -1
  108. package/second-brain/Research/_Index.md +30 -0
  109. package/second-brain/Retrospectives/_Index.md +30 -0
  110. package/second-brain/Reviews/_Index.md +30 -0
  111. package/second-brain/Runbooks/_Index.md +30 -0
  112. package/second-brain/Runbooks/eval-loop.md +24 -0
  113. package/second-brain/Sessions/_Index.md +30 -0
  114. package/second-brain/Shared/AI-Context-Index.md +20 -0
  115. package/second-brain/Shared/AI-Threads/_Index.md +30 -0
  116. package/second-brain/Shared/Archive/_Index.md +30 -0
  117. package/second-brain/Shared/Assets/_Index.md +30 -0
  118. package/second-brain/Shared/Context-Packs/_Index.md +30 -0
  119. package/second-brain/Shared/Context7-Docs/_Index.md +30 -0
  120. package/second-brain/Shared/Coordination/NOW.md +28 -0
  121. package/second-brain/Shared/Coordination/_Index.md +30 -0
  122. package/second-brain/Shared/Coordination/agent-registry.md +24 -0
  123. package/second-brain/Shared/Coordination/task-board/_Index.md +30 -0
  124. package/second-brain/Shared/Coordination/task-board/task-template.md +43 -0
  125. package/second-brain/Shared/Coordination/task-board.md +32 -0
  126. package/second-brain/Shared/Core-Facts/_Index.md +30 -0
  127. package/second-brain/Shared/Decision-Memory/_Index.md +30 -0
  128. package/second-brain/Shared/Glossary/_Index.md +30 -0
  129. package/second-brain/Shared/Memory-Inbox/_Index.md +30 -0
  130. package/second-brain/Shared/Operating-State/_Index.md +30 -0
  131. package/second-brain/Shared/Prompting/_Index.md +30 -0
  132. package/second-brain/Shared/Provenance/_Index.md +30 -0
  133. package/second-brain/Shared/Rules/_Index.md +30 -0
  134. package/second-brain/Shared/Rules/contextual-note-rule.md +30 -0
  135. package/second-brain/Shared/Rules/frontmatter-standard.md +10 -0
  136. package/second-brain/Shared/Rules/memory-write-protocol.md +28 -0
  137. package/second-brain/Shared/Rules/procedural-runbook-header.md +40 -0
  138. package/second-brain/Shared/Rules/review-and-staleness-policy.md +22 -0
  139. package/second-brain/Shared/Rules/rules-formatting.md +34 -0
  140. package/second-brain/Shared/Scripts/_Index.md +30 -0
  141. package/second-brain/Shared/Scripts-Archive/_Index.md +30 -0
  142. package/second-brain/Shared/Tech-Standards/_Index.md +30 -0
  143. package/second-brain/Shared/Tech-Standards/verification-standard.md +40 -0
  144. package/second-brain/Shared/User-Memory/_Index.md +30 -0
  145. package/second-brain/Shared/User-Persona/_Index.md +30 -0
  146. package/second-brain/Shared/User-Persona/owner-profile.md +25 -0
  147. package/second-brain/Shared/Working-Memory/_Index.md +30 -0
  148. package/second-brain/Shared/_Index.md +30 -0
  149. package/second-brain/Shared/mcp-servers/_Index.md +30 -0
  150. package/second-brain/Skills/_Index.md +30 -0
  151. package/second-brain/Templates/_Index.md +30 -0
  152. package/second-brain/Templates/bug.md +2 -0
  153. package/second-brain/Templates/handoff.md +2 -0
  154. package/second-brain/Templates/session.md +2 -0
  155. package/second-brain/Tools/_Index.md +30 -0
  156. package/second-brain/Traces/_Index.md +30 -0
  157. package/second-brain/Vault Structure Map.md +33 -1
  158. package/second-brain/copilot/_Index.md +30 -0
  159. package/skills/audit-license-compliance/SKILL.md +117 -0
  160. package/skills/author-codemod/SKILL.md +110 -0
  161. package/skills/build-audit-logging/SKILL.md +112 -0
  162. package/skills/build-cdc-streaming-pipeline/SKILL.md +123 -0
  163. package/skills/build-cli-tool/SKILL.md +108 -0
  164. package/skills/build-data-table/SKILL.md +141 -0
  165. package/skills/build-native-mobile-ui/SKILL.md +154 -0
  166. package/skills/build-offline-first-sync/SKILL.md +118 -0
  167. package/skills/build-realtime-channel/SKILL.md +122 -0
  168. package/skills/build-vector-search/SKILL.md +131 -0
  169. package/skills/compose-local-dev-stack/SKILL.md +149 -0
  170. package/skills/configure-bundler-build/SKILL.md +166 -0
  171. package/skills/configure-dns-tls/SKILL.md +142 -0
  172. package/skills/configure-reverse-proxy-lb/SKILL.md +129 -0
  173. package/skills/configure-security-headers-csp/SKILL.md +122 -0
  174. package/skills/contract-testing/SKILL.md +140 -0
  175. package/skills/datetime-timezone-correctness/SKILL.md +125 -0
  176. package/skills/debug-ci-pipeline-failure/SKILL.md +134 -0
  177. package/skills/debug-flaky-tests/SKILL.md +128 -0
  178. package/skills/defend-llm-prompt-injection/SKILL.md +110 -0
  179. package/skills/deliver-webhooks/SKILL.md +116 -0
  180. package/skills/design-api-pagination/SKILL.md +144 -0
  181. package/skills/design-authorization-model/SKILL.md +119 -0
  182. package/skills/design-backup-dr-recovery/SKILL.md +113 -0
  183. package/skills/design-event-sourcing-cqrs/SKILL.md +143 -0
  184. package/skills/design-multi-tenancy/SKILL.md +100 -0
  185. package/skills/design-protobuf-grpc-service/SKILL.md +146 -0
  186. package/skills/design-relational-schema/SKILL.md +129 -0
  187. package/skills/design-search-index-infra/SKILL.md +151 -0
  188. package/skills/design-state-machine/SKILL.md +108 -0
  189. package/skills/design-token-system/SKILL.md +109 -0
  190. package/skills/distributed-locks-leases/SKILL.md +120 -0
  191. package/skills/encrypt-sensitive-data/SKILL.md +148 -0
  192. package/skills/feature-flags-rollout/SKILL.md +130 -0
  193. package/skills/file-upload-object-storage/SKILL.md +107 -0
  194. package/skills/fuzz-dynamic-security-test/SKILL.md +111 -0
  195. package/skills/harden-llm-app-reliability/SKILL.md +126 -0
  196. package/skills/i18n-localization-setup/SKILL.md +113 -0
  197. package/skills/idempotency-keys/SKILL.md +107 -0
  198. package/skills/implement-push-notifications/SKILL.md +142 -0
  199. package/skills/ingest-webhook-secure/SKILL.md +120 -0
  200. package/skills/integrate-oauth-oidc/SKILL.md +126 -0
  201. package/skills/load-stress-test/SKILL.md +129 -0
  202. package/skills/map-privacy-data-gdpr/SKILL.md +146 -0
  203. package/skills/model-nosql-data/SKILL.md +118 -0
  204. package/skills/money-decimal-arithmetic/SKILL.md +123 -0
  205. package/skills/monitor-ml-drift/SKILL.md +109 -0
  206. package/skills/numeric-precision-units/SKILL.md +144 -0
  207. package/skills/optimize-llm-cost-latency/SKILL.md +103 -0
  208. package/skills/optimize-react-rerenders/SKILL.md +124 -0
  209. package/skills/orchestrate-agent-workflow/SKILL.md +100 -0
  210. package/skills/payments-billing-integration/SKILL.md +114 -0
  211. package/skills/pin-toolchain-versions/SKILL.md +116 -0
  212. package/skills/plan-strangler-migration/SKILL.md +95 -0
  213. package/skills/property-based-testing/SKILL.md +108 -0
  214. package/skills/publish-package-registry/SKILL.md +130 -0
  215. package/skills/recover-git-state/SKILL.md +119 -0
  216. package/skills/remediate-web-vulnerabilities/SKILL.md +125 -0
  217. package/skills/resilience-timeouts-retries/SKILL.md +104 -0
  218. package/skills/resolve-merge-rebase-conflict/SKILL.md +97 -0
  219. package/skills/rewrite-git-history/SKILL.md +109 -0
  220. package/skills/scaffold-cross-platform-app/SKILL.md +137 -0
  221. package/skills/schema-evolution-compatibility/SKILL.md +121 -0
  222. package/skills/send-transactional-email/SKILL.md +126 -0
  223. package/skills/serve-deploy-ml-model/SKILL.md +107 -0
  224. package/skills/setup-cdn-edge-waf/SKILL.md +107 -0
  225. package/skills/setup-devcontainer-env/SKILL.md +131 -0
  226. package/skills/setup-lint-format-precommit/SKILL.md +140 -0
  227. package/skills/setup-monorepo-tooling/SKILL.md +125 -0
  228. package/skills/ship-mobile-app-store-release/SKILL.md +137 -0
  229. package/skills/structured-output-llm/SKILL.md +86 -0
  230. package/skills/supply-chain-sbom-provenance/SKILL.md +120 -0
  231. package/skills/test-data-factories/SKILL.md +158 -0
  232. package/skills/threat-model-stride/SKILL.md +123 -0
  233. package/skills/train-evaluate-ml-model/SKILL.md +109 -0
  234. package/skills/unicode-text-correctness/SKILL.md +109 -0
  235. package/skills/visual-regression-testing/SKILL.md +120 -0
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: recover-git-state
3
+ description: Recovers lost or broken git state — restores dropped commits/branches/stashes via reflog and fsck, pins a regression with git bisect, and safely undoes a bad reset/rebase/merge with revert or reset --soft/--mixed/--hard — without destroying still-recoverable objects.
4
+ when_to_use: Work appears gone after a reset --hard, bad rebase, deleted branch, dropped stash, or detached HEAD, or you need to pin which commit introduced a bug. NOT intentional history editing (rewrite-git-history), conflict-marker resolution (resolve-merge-rebase-conflict), or diagnosing a non-git code failure (debug-root-cause).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Reach for this when work *appears* gone or HEAD landed somewhere wrong — the commits almost always still exist as unreachable objects:
10
+
11
+ - "I lost my commits" / "my branch is empty" after a `reset --hard`, bad `rebase`, or force-fetch
12
+ - "I deleted the wrong branch" (`git branch -D feature`)
13
+ - "I'm in detached HEAD and made commits — did I lose them?"
14
+ - "I `git stash drop`'d / `stash pop`'d into a conflict and lost a stash"
15
+ - "Which commit broke this?" — a regression to pin across a known-good..bad range
16
+ - A merge/rebase/`reset` made things worse and you want it back exactly as it was
17
+
18
+ NOT this skill:
19
+ - *Intentionally* rewriting history (squash, reword, rebase to clean up, strip a secret) → rewrite-git-history
20
+ - Resolving the conflict markers from an in-progress merge/rebase/cherry-pick → resolve-merge-rebase-conflict
21
+ - The code itself is failing (test/crash/wrong output) and git history is fine → debug-root-cause
22
+ - The file was never committed and isn't in any stash → git can't recover it; check editor local history / backups
23
+
24
+ ## Steps
25
+
26
+ 1. **STOP and snapshot before touching anything.** Recovery commands can themselves overwrite refs. Freeze current state so you can't make it worse:
27
+ ```bash
28
+ git stash list; git status; git log --oneline -5 # what do we actually have?
29
+ git branch _backup_$(date +%s) # pin current HEAD as a named ref
30
+ ```
31
+ Never run `reset --hard`, `checkout`, `rebase`, or `gc` until you've located the SHA you want back.
32
+
33
+ 2. **Find the lost SHA — reflog is the safety net.** Every move of HEAD (and of each branch) is logged for ~90 days, even after `reset --hard` or branch deletion:
34
+ ```bash
35
+ git reflog # every HEAD move: HEAD@{0}, HEAD@{1}, ...
36
+ git reflog show feature # moves of one branch ref specifically
37
+ git reflog --date=relative | grep -iE 'commit|reset|rebase|checkout'
38
+ ```
39
+ The entry *just before* the bad operation is your target (e.g. `HEAD@{2}` = "before the reset"). Copy its SHA.
40
+
41
+ 3. **Recover by creating a ref — never `checkout` a loose SHA bare.** Pick the action by what you lost:
42
+
43
+ | Lost | Command | Note |
44
+ |---|---|---|
45
+ | A deleted branch | `git branch feature <sha>` | `<sha>` from `git reflog show feature` or `fsck` |
46
+ | Commits after a `reset --hard` | `git reset --hard HEAD@{1}` | moves current branch back; **discards** anything since |
47
+ | Commits, but keep current work | `git branch rescue <sha>` | safe — inspect/`cherry-pick` from `rescue`, then delete it |
48
+ | Detached-HEAD commits | `git branch keep <sha>` *before* you `checkout` away | bare checkout-away orphans them |
49
+
50
+ Default to `git branch rescue <sha>` — it's non-destructive. Use `reset --hard HEAD@{n}` only when you're sure everything after it is garbage.
51
+
52
+ 4. **Recover a dropped/popped stash via fsck.** A dropped stash isn't in `reflog` but lives as an unreachable commit:
53
+ ```bash
54
+ git fsck --no-reflog --unreachable | grep commit | awk '{print $3}' \
55
+ | xargs -I{} git log -1 --format='%H %ci %s' {} | grep -i 'WIP on'
56
+ git stash apply <sha> # or: git branch stash_recovered <sha>
57
+ ```
58
+ A stash commit's subject starts with `WIP on <branch>:`. `git fsck --lost-found` also drops them under `.git/lost-found/`.
59
+
60
+ 5. **Undo by audience: published → `revert`, local → `reset`.** This is the one rule that prevents a second disaster:
61
+
62
+ | Situation | Use | Why |
63
+ |---|---|---|
64
+ | Bad commit already pushed / shared | `git revert <sha>` | adds an inverse commit — no history rewrite, safe for collaborators |
65
+ | Local mistake, want it gone | `git reset` (see below) | rewrites your local branch; never on shared history |
66
+
67
+ `reset` mode, decided by where you want the changes to land:
68
+ - `--soft HEAD~1` → undo the commit, keep changes **staged** (re-commit cleanly)
69
+ - `--mixed HEAD~1` (default) → undo commit, keep changes in **working tree**, unstaged
70
+ - `--hard HEAD~1` → undo commit **and discard** the changes — destructive; only after step 1's backup
71
+
72
+ 6. **Restore working-tree files (not commits) with `git restore`.** Discarded edits or a wrong file version:
73
+ ```bash
74
+ git restore path/to/file # revert working-tree file to HEAD (uncommitted edits gone)
75
+ git restore --source=<sha> file # pull one file's content from a specific commit
76
+ git restore --staged path # unstage only (keep working-tree edits)
77
+ ```
78
+ `git checkout -- file` is the old spelling; prefer `restore`.
79
+
80
+ 7. **Pin a regression with `git bisect`.** Binary-search the first bad commit across a known-good..bad range:
81
+ ```bash
82
+ git bisect start
83
+ git bisect bad # current HEAD is broken
84
+ git bisect good v1.4.0 # last known-good tag/sha
85
+ # git checks out the midpoint; test it, then mark good/bad and repeat
86
+ git bisect good # or: git bisect bad
87
+ ```
88
+ Automate it — let git drive every step with an exit-coded script:
89
+ ```bash
90
+ git bisect run ./test.sh # script: exit 0 = good, 1..124 = bad, 125 = skip (untestable)
91
+ ```
92
+ When done it prints `<sha> is the first bad commit`. **Always** `git bisect reset` to return to your original HEAD.
93
+
94
+ 8. **Run `git gc` only after you've recovered and verified.** Unreachable objects survive until garbage collection. Don't run `git gc --prune=now` or `git reflog expire` while a recovery is pending — that's what permanently deletes the SHAs you're hunting.
95
+
96
+ ## Common Errors
97
+
98
+ - **`checkout`ing a loose SHA, making commits, then leaving — they're orphaned again.** Always `git branch <name> <sha>` *first*; commit onto a real ref.
99
+ - **`reset --hard HEAD@{1}` to "go back," wiping current uncommitted work.** `--hard` discards the working tree. Stash or `git branch _backup` first (step 1); use `git branch rescue` instead when unsure.
100
+ - **`reflog` is empty / "ambiguous argument".** You're in a fresh clone or a different repo — reflog is per-local-clone and not pushed. Use `git fsck --lost-found` to find unreachable commits by content instead.
101
+ - **`revert`ing a merge commit fails or reverts the wrong side.** Pass the parent: `git revert -m 1 <merge-sha>` (mainline = parent 1). Reverting a merge has its own re-merge gotcha; for shared history that's still safer than rewriting.
102
+ - **`stash pop` hit a conflict and you assumed the stash was lost.** A *conflicted* `pop` does **not** drop the stash — it's still in `git stash list`. Resolve, then `git stash drop`.
103
+ - **Ran `git gc` / `git reflog expire --expire=now --all` and now the SHA is gone.** Pruning deleted the unreachable objects. Recover the ref *before* any gc; check `.git/lost-found/` and cloned mirrors.
104
+ - **`bisect` mislabels because the build is broken at some midpoints.** Have the script `exit 125` (skip) for uncompilable commits so bisect routes around them instead of falsely marking bad.
105
+ - **Forgot `git bisect reset`.** You're left in detached HEAD on a midpoint commit, confusing later work. Reset returns you to the pre-bisect branch.
106
+ - **Using `reset` to undo *pushed* commits, then force-pushing.** Rewrites shared history and breaks teammates. On published commits use `git revert`; rewriting is rewrite-git-history's job, not a recovery.
107
+
108
+ ## Verify
109
+
110
+ Recovery is complete and correct when:
111
+
112
+ 1. **The recovered ref points at the right tree.** `git diff <known-good-sha> <recovered-ref>` is empty (or shows exactly the intended delta) — confirms you grabbed the right SHA, not a neighbor.
113
+ 2. **The expected commits are reachable.** `git log --oneline <recovered-ref>` shows the commits that were "lost," and `git status` is clean (no surprise staged/modified files).
114
+ 3. **A recovered stash applied cleanly.** `git stash show -p <sha>` matches the work you expected; after `apply` the files contain the WIP changes.
115
+ 4. **An `undo` did what its audience requires.** After `revert`: a new inverse commit exists and `git log` history is intact (nothing rewritten). After `reset`: `git status` shows the changes in the intended state (staged for `--soft`, unstaged for `--mixed`, gone for `--hard`).
116
+ 5. **Bisect named one culprit and cleaned up.** Output ended with `<sha> is the first bad commit`, `git show <sha>` plausibly explains the regression, and `git bisect reset` returned HEAD to the starting branch (`git status` confirms the original branch, not detached).
117
+ 6. **Nothing was pruned mid-recovery.** No `git gc`/`reflog expire` ran before the ref was secured; the `_backup` branch from step 1 still exists as a fallback.
118
+
119
+ Done = the recovered commits/branch/stash are on a named ref whose tree matches the known-good SHA (`git diff` empty), any undo matched its published-vs-local audience (revert vs reset mode), and bisect (if used) named the first bad commit with `git bisect reset` leaving HEAD on the original branch.
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: remediate-web-vulnerabilities
3
+ description: Fixes specific web vulnerability classes — SQL/command injection, XSS, CSRF, SSRF, IDOR/broken access, insecure deserialization — by applying the canonical hardening (parameterized queries, args-array exec, context-aware output encoding + CSP, SameSite + synchronizer tokens, egress allowlists, per-owner authorization, safe deserialization) and proving each fix with a regression test that replays the exploit.
4
+ when_to_use: A specific vuln was found (review, scan, pentest) or an input/output path needs proactive hardening. Distinct from security-review (finds and reports vulns, does not fix), design-authorization-model (authZ architecture, not a single IDOR patch), and defend-llm-prompt-injection (LLM/prompt-specific, not classic web).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - "We got a finding for SQL injection / XSS / SSRF — fix it"
10
+ - "A pentest flagged IDOR on `GET /orders/:id` — anyone can read any order"
11
+ - "Sanitize this user input before it hits the shell / the DB / the page"
12
+ - "Harden this URL-fetch endpoint so it can't hit our metadata service"
13
+ - "Stop us deserializing untrusted JSON/pickle/YAML into objects"
14
+ - Proactively hardening a newly added input or output path before ship
15
+
16
+ NOT this skill:
17
+ - *Finding* and reporting vulns in a diff (no specific one yet) → security-review
18
+ - Designing the overall authZ model (roles, policies, tenancy) instead of patching one missing ownership check → design-authorization-model
19
+ - Prompt injection / tool-abuse / jailbreaks against an LLM → defend-llm-prompt-injection
20
+ - Stress-finding *new* input bugs by mutation/fuzzing → fuzz-dynamic-security-test
21
+ - Moving plaintext secrets out of code/IaC and rotating them → secrets-management
22
+ - WAF rules / managed rulesets at the edge as a *compensating* control → setup-cdn-edge-waf (a WAF is defense-in-depth, never the fix)
23
+
24
+ Fix the **class, not the instance**: when you find one concatenated query, grep the codebase and fix every sibling. A WAF rule or input blocklist is a band-aid — remove the unsafe construct.
25
+
26
+ ## Steps
27
+
28
+ 1. **Identify the class, then apply its canonical fix.** Do not invent ad-hoc escaping — each class has one correct construct:
29
+
30
+ | Class | Root cause | Canonical fix (do this) | Never (the band-aid) |
31
+ |---|---|---|---|
32
+ | **SQLi** | String-built query | Parameterized query / bound params; ORM with bindings | Escaping quotes, blocklisting `'`/`;`/`--` |
33
+ | **Command injection** | Shell-interpreted string | No shell: exec with **args array**; allowlist binaries/flags | `escapeshellarg`-then-concat into `sh -c` |
34
+ | **XSS** | Untrusted data in HTML | Framework auto-escaping + **context-aware** encoding + CSP; sanitize HTML with DOMPurify | Regex strip of `<script>`, `innerHTML` of user data |
35
+ | **CSRF** | Ambient cookie auth | `SameSite=Lax/Strict` + **synchronizer token** on state-changers | Checking `Referer` only |
36
+ | **SSRF** | User-controlled fetch URL | **Allowlist** dest hosts; block link-local/metadata/private ranges; pin resolved IP | Blocklisting `localhost`/`127.0.0.1` strings |
37
+ | **IDOR / broken access** | authN ≠ authZ | Authorize **every object by owner/tenant**, server-side, on the resolved row | Hiding the ID in the UI; UUIDs as "security" |
38
+ | **Insecure deserialization** | Untrusted bytes → objects | Don't deserialize untrusted data; use data-only formats (JSON) + schema validate | `pickle.loads`, `yaml.load`, Java native, `unserialize()` on input |
39
+
40
+ 2. **SQLi — parameterize, never concatenate.** Pass user data as bound parameters so it is never parsed as SQL. Identifiers (table/column names) can't be parameters — allowlist them against a fixed set.
41
+
42
+ ```python
43
+ # BAD: db.execute(f"SELECT * FROM users WHERE email = '{email}'")
44
+ db.execute("SELECT * FROM users WHERE email = %s", (email,)) # psycopg
45
+ # ORM: User.query.filter_by(email=email) # SQLAlchemy binds automatically
46
+ # Dynamic column: pick from an allowlist, don't interpolate the raw value
47
+ ALLOWED = {"name", "created_at"}
48
+ if sort not in ALLOWED: raise ValueError("bad sort column") # reject, don't interpolate
49
+ ```
50
+
51
+ 3. **Command injection — drop the shell, pass an args array.** The shell is the vuln; `shell=True` / `sh -c` / `os.system` interpret metacharacters. Pass argv as a list so the OS execs the binary directly with no parsing.
52
+
53
+ ```python
54
+ # BAD: subprocess.run(f"convert {path} out.png", shell=True)
55
+ subprocess.run(["convert", path, "out.png"], shell=False, check=True) # path is one argv element, never re-parsed
56
+ ```
57
+ If a value must be a flag, allowlist it (`if mode not in {"fast","hq"}: reject`). Never build a flag string from user input.
58
+
59
+ 4. **XSS — encode for the output context, add CSP, sanitize HTML only with a vetted lib.** Escaping for HTML body ≠ for an attribute ≠ for JS ≠ for a URL. Let the template engine auto-escape (Jinja `autoescape`, React `{}` text nodes) and don't defeat it (`| safe`, `dangerouslySetInnerHTML`, `v-html`, `innerHTML`). If you must render user HTML, run it through **DOMPurify** first:
60
+
61
+ ```js
62
+ el.textContent = userInput; // default: text, auto-safe
63
+ el.innerHTML = DOMPurify.sanitize(userHtml); // ONLY when HTML is required
64
+ // Never build href/src from raw input — reject non-http(s) schemes (blocks javascript:)
65
+ ```
66
+ Add a strict CSP as defense-in-depth: `Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'none'` — no `unsafe-inline`/`unsafe-eval`. CSP is a second wall, not the fix.
67
+
68
+ 5. **CSRF — `SameSite` cookies + synchronizer token.** Set session cookies `SameSite=Lax` (or `Strict`), `Secure`, `HttpOnly`. For every state-changing request (`POST/PUT/PATCH/DELETE`), require a per-session CSRF token (double-submit cookie or server-stored), compared with a constant-time check. Pure token-auth APIs (`Authorization: Bearer`, no cookies) are not CSRF-prone — don't bolt tokens onto those.
69
+
70
+ 6. **SSRF — allowlist egress, block internal ranges, fetch the pinned IP.** If the destination is user-controlled, default-deny:
71
+ - Allowlist the exact hostnames/domains you intend to reach; reject everything else.
72
+ - Resolve the host, then **reject** `127.0.0.0/8`, `::1`, `10/8`, `172.16/12`, `192.168/16`, `169.254.0.0/16` (link-local → cloud metadata `169.254.169.254`), `fc00::/7`, and `0.0.0.0`.
73
+ - Resolve once, validate that IP, and connect to **that IP** (pass it explicitly / pin it) to kill DNS-rebinding TOCTOU.
74
+ - Disable redirects, or re-validate the target on each hop. Never follow a redirect to an unvalidated host.
75
+
76
+ 7. **IDOR / broken access — authorize every object by owner, server-side.** Authentication tells you *who*; you still must check *they own this row*. Scope the query or assert ownership on the resolved object — never trust an ID from the request as proof of access.
77
+
78
+ ```python
79
+ # BAD: order = Order.get(request.params["id"]) # any id -> anyone's order
80
+ order = Order.get(id=request.params["id"], owner_id=current_user.id) # scope to caller
81
+ if order is None: raise NotFound() # 404, not 403 — don't confirm the row exists
82
+ ```
83
+ This is the single-instance patch. If you're (re)designing roles/policies/multi-tenancy, that's design-authorization-model.
84
+
85
+ 8. **Insecure deserialization — never deserialize untrusted bytes.** Object-deserializers (`pickle`, `yaml.load`, Java `ObjectInputStream`, PHP `unserialize`, .NET `BinaryFormatter`) can execute code or instantiate arbitrary types. Accept only data formats and validate against a schema:
86
+
87
+ ```python
88
+ # BAD: obj = pickle.loads(body) / data = yaml.load(body)
89
+ data = json.loads(body) # data only, no code execution
90
+ yaml.safe_load(body) # if YAML is required
91
+ Payload.model_validate(data) # pydantic: enforce shape/types
92
+ ```
93
+ If signed objects are unavoidable, verify an HMAC over the bytes *before* deserializing.
94
+
95
+ 9. **Sweep the class and write the regression test.** Grep for every sibling of the fixed pattern (`grep -rn "shell=True"`, `f"SELECT`, `innerHTML =`, `pickle.loads`, `yaml.load(`, `dangerouslySetInnerHTML`). Then write a test that sends the **actual exploit payload** and asserts it no longer triggers (Verify).
96
+
97
+ ## Common Errors
98
+
99
+ - **Escaping/blocklisting instead of parameterizing.** Quote-escaping and `'`/`;` blocklists are bypassable (unicode, comments, encoding). Use bound parameters — fix the construct.
100
+ - **`shell=True` "but I escaped it".** `escapeshellarg`-then-concat still goes through the shell and gets bypassed. Pass an argv array with `shell=False`; no shell, no metacharacters.
101
+ - **Sanitizing on input, rendering elsewhere.** Input sanitization can't know the output context. Encode at the point of output (HTML vs attr vs JS vs URL); store data raw.
102
+ - **Trusting a custom XSS regex.** Hand-rolled HTML filters miss `onerror=`, `javascript:`, SVG, mutation XSS. Use DOMPurify; never `innerHTML` raw input.
103
+ - **CSP with `unsafe-inline`/`unsafe-eval`.** Negates the protection against injected `<script>`. Use nonces/hashes; if you can't, you haven't fixed the XSS — CSP was only the backstop.
104
+ - **SSRF fix that blocks strings, not IPs.** Blocking `"localhost"` misses `0`, `0x7f.1`, `[::1]`, decimal IPs, and DNS-rebinding. Resolve and check the **IP** against CIDR ranges, then connect to that resolved IP.
105
+ - **Following redirects after SSRF validation.** Validated host 302-redirects to `169.254.169.254`. Disable redirects or re-validate every hop.
106
+ - **IDOR "fixed" by switching to UUIDs / hiding the ID.** Obscurity isn't authorization. The fix is the server-side owner/tenant check on the resolved object.
107
+ - **Adding authN where authZ is missing.** Logged-in ≠ authorized for *this* object. Scope the lookup to the caller.
108
+ - **`yaml.load` left as the "safe" one.** Plain `yaml.load` constructs arbitrary objects. Use `yaml.safe_load`. Likewise `pickle`/`BinaryFormatter`/`unserialize` on input are never safe — switch to JSON + schema.
109
+ - **Fixing the reported instance, leaving the class.** The scanner found one; the same pattern lives in ten other files. Grep and fix all, or it regresses next sprint.
110
+ - **Calling a WAF rule the fix.** A blocked payload at the edge while the unsafe code remains is unfixed — the next encoding gets through. WAF is defense-in-depth (setup-cdn-edge-waf), not remediation.
111
+
112
+ ## Verify
113
+
114
+ A fix is proven only when an automated test reproduces the original exploit and asserts it's now inert:
115
+
116
+ 1. **SQLi:** Send `' OR '1'='1` / `'; DROP TABLE--` as the input → response is a normal empty/auth-fail result, no extra rows, no error leaking SQL; query log shows it ran as a bound parameter.
117
+ 2. **Command injection:** Send `; id`, `$(id)`, `` `id` ``, `| cat /etc/passwd` as the value → no extra process runs, no command output in the response; a sentinel file the payload tries to create does not exist.
118
+ 3. **XSS:** Submit `<img src=x onerror=alert(1)>` and `javascript:alert(1)` → response renders them as **escaped text** (`<img...`), not live markup; assert the raw `<script>`/`onerror` byte sequence is absent from the HTML. Confirm the `Content-Security-Policy` header is present and free of `unsafe-inline`.
119
+ 4. **CSRF:** Replay a state-changing `POST` with a valid session cookie but **no/forged** CSRF token → rejected (`403`); the same request with a valid token → succeeds.
120
+ 5. **SSRF:** Request each blocked target — `http://169.254.169.254/`, `http://127.0.0.1`, `http://[::1]`, a decimal-IP form, and a host that 30x-redirects to `169.254.169.254` → all rejected before any socket to an internal range opens; only an explicitly allowlisted host returns `200`. Assert the connection to the internal range was never opened.
121
+ 6. **IDOR:** As user A, request user B's object ID → `404` (not the row, not a `403` that confirms existence); as the owner → `200`. Run it for every object-scoped route you touched.
122
+ 7. **Deserialization:** Feed a malicious pickle/`!!python/object` YAML/gadget payload → rejected at schema validation, no object instantiated, no code executed (sentinel side-effect absent).
123
+ 8. **Class sweep:** The grep for the unsafe construct returns zero remaining hits in app code (excluding the test fixtures that hold the payloads).
124
+
125
+ Done = the exploit-replay test for the fixed class **failed before** the change and **passes after**, the canonical construct (not a blocklist/WAF rule) is in place, the class-wide grep is clean, and no Critical regression remains.
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: resilience-timeouts-retries
3
+ description: Makes calls to flaky dependencies (DBs, HTTP/RPC APIs, queues) survive failure without amplifying it — bounded timeouts on connect/read/total/per-attempt, deadline propagation across the call chain, exponential backoff with FULL jitter, retry budgets/caps, circuit breakers (closed/open/half-open), bulkheads, backpressure + load-shedding (429/503 + Retry-After), and hedged requests for tail latency. Retries only idempotent ops; never retries 4xx except 408/429; library-specific for resilience4j, Polly, tenacity, failsafe-go/gobreaker, JS AbortSignal+p-retry, gRPC deadlines, and Envoy/Istio outlier-detection.
4
+ when_to_use: User is calling a network dependency that can be slow/down (HTTP API, DB, RPC, queue) and needs it to fail fast, retry safely, or stop hammering a sick service — or is debugging retry storms, thundering-herd, hung pools, cascading timeouts. Distinct from rate-limiting (limits inbound traffic *you* receive; this protects *your* outbound calls) and async-concurrency-correctness (in-process task/lock/cancellation correctness, not network failure policy). For making the retried write itself safe, pair with idempotency-keys.
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Reach for this skill when your code crosses a network boundary to something that can be slow, flaky, or down:
10
+
11
+ - "This call to the payments API / DB sometimes hangs forever and pins all our threads"
12
+ - "Add retries to this HTTP/RPC client" (and you need them to not make an outage worse)
13
+ - "One slow downstream is taking down the whole service" (cascading failure)
14
+ - "We had a retry storm / thundering herd after the dependency recovered"
15
+ - "Tail latency (p99) is terrible even though p50 is fine"
16
+ - "Should we retry this 500? this timeout? this POST?"
17
+
18
+ NOT this skill:
19
+ - Limiting inbound traffic *you serve* (per-user/IP quotas, token bucket) → rate-limiting. This skill governs the *outbound* calls you make and shedding load when *you* are overwhelmed.
20
+ - In-process deadlocks, leaked tasks, locks across `await`, channel backpressure → async-concurrency-correctness. That's correctness of concurrency; this is policy for network failure.
21
+ - Making the operation you retry safe to run twice (dedup key, exactly-once effect) → idempotency-keys. Retry without idempotency = duplicate charges.
22
+ - Delivering *your* outbound webhooks with retry/backoff/DLQ → deliver-webhooks (this skill is the primitive it builds on).
23
+
24
+ First principle: **every retry adds load to a system that is already failing.** Default to *fewer* retries with jitter and a budget, never *more*.
25
+
26
+ ## Steps
27
+
28
+ 1. **Put a bound on every wait — no unbounded blocking, ever.** A missing timeout is the root cause of most "the whole service hung" incidents: one stuck call holds a connection/thread until the pool is empty. Set all four:
29
+
30
+ | Timeout | What it caps | Typical |
31
+ |---|---|---|
32
+ | connect | TCP/TLS handshake | 1–3s |
33
+ | read/socket | gap between bytes | 2–10s |
34
+ | per-attempt total | one try end-to-end | derived from p99 + margin |
35
+ | overall/deadline | whole op incl. retries | < the caller's own deadline |
36
+
37
+ Per-language: JS `fetch(url, { signal: AbortSignal.timeout(ms) })` (default fetch has NO timeout); Python `httpx.Timeout(connect=, read=, write=, pool=)` or `requests` `timeout=(connect, read)` (a bare `timeout=5` is read-only — connect can still hang); Go `http.Client{Timeout}` + a per-request `context.WithTimeout`; Java set both `connectTimeout` and `requestTimeout`. Never leave a driver/client on its infinite default.
38
+
39
+ 2. **Propagate a deadline (time budget), don't restart the clock per layer.** A 5s timeout at three nested layers = up to 15s of real wait. Compute an absolute deadline once at the edge and pass it down; each hop spends from the *remaining* budget. Go: pass `ctx` (carries `WithDeadline`) into every call — `ctx, _ := context.WithTimeout(parent, remaining)`. gRPC: set a **deadline** on the client call (`grpc.WithTimeout`/`context` deadline), and servers must check `ctx.Err()` / `context.Deadline()` and stop work when it's blown. Reserve a slice of the budget for retries — don't let a single attempt consume all of it.
40
+
41
+ 3. **Retry ONLY idempotent/safe operations.** GET/PUT/DELETE are safe; a raw POST is not — a retried "create order" can double-charge. Either restrict retries to safe verbs, or make the write idempotent with an **idempotency key** the server dedupes on (→ idempotency-keys) and only then retry it. Treat "I don't know if it ran" (timeout after send) as **possibly executed** — never blind-retry a non-idempotent write on timeout.
42
+
43
+ 4. **Retry the right errors, fail fast on the rest.**
44
+
45
+ | Outcome | Retry? |
46
+ |---|---|
47
+ | connection refused / reset / DNS / connect timeout | yes |
48
+ | read timeout (idempotent op only) | yes |
49
+ | 502, 503, 504 | yes |
50
+ | 429, 408 | yes — and honor `Retry-After` |
51
+ | 500 | usually no (often a deterministic bug — same input, same failure) |
52
+ | 400, 401, 403, 404, 409, 422 | **no** — retrying a client error just burns budget |
53
+
54
+ gRPC: retry `UNAVAILABLE`/`DEADLINE_EXCEEDED`/`RESOURCE_EXHAUSTED`; not `INVALID_ARGUMENT`/`NOT_FOUND`/`PERMISSION_DENIED`. When `Retry-After` is present, obey it instead of your backoff.
55
+
56
+ 5. **Back off exponentially with FULL jitter.** Fixed delays (or exponential with *no* jitter) make every client that failed at T retry in lockstep — a synchronized retry storm that re-knocks-over the recovering dependency (thundering herd). Use full jitter: `sleep = random_uniform(0, min(cap, base * 2**attempt))`.
57
+
58
+ ```python
59
+ delay = random.uniform(0, min(cap, base * (2 ** attempt))) # base=0.1s, cap=10s
60
+ ```
61
+ "Equal jitter" (`half + rand(half)`) is acceptable; "no jitter" is the bug. Add `Retry-After` override when the server sent one.
62
+
63
+ 6. **Cap retries and add a retry budget — keep nesting from multiplying load.** Limit attempts (typically **2–3 total**, not 5+) AND a max elapsed (the overall deadline from step 2). Crucial at scale: a per-client **retry budget** (e.g. retries ≤ 10–20% of total requests). Retrying at *every* layer multiplies: 3 layers × 3 retries = 27× load on the bottom service. **Retry at exactly one layer** (usually the lowest, closest to the dependency) and have outer layers fail fast.
64
+
65
+ 7. **Add a circuit breaker so a dead dependency fails instantly.** Stop sending into a hole; give it room to recover. States: **closed** (pass through, count failures) → **open** (fail fast immediately, no call) → **half-open** (after a cooldown, allow a few probes; success → closed, failure → open). Trip on **error-rate over a rolling window** (e.g. >50% of ≥20 calls) rather than raw consecutive failures (less noisy under low traffic). Per-dependency breaker — never one global breaker for all downstreams.
66
+
67
+ 8. **Bulkhead: isolate pools so one sick dependency can't drown the rest.** Give each downstream its *own* connection pool / thread pool / semaphore with a bounded max. Without it, slow calls to dep A consume every worker and requests to healthy dep B also fail. resilience4j `Bulkhead`/`ThreadPoolBulkhead`; a per-dependency `Semaphore(N)`; separate HTTP clients with separate `maxConnsPerHost`. Bound the pool *and* the wait-for-a-slot timeout.
68
+
69
+ 9. **Backpressure & load-shed when *you're* the one overwhelmed.** Bounded queues only — an unbounded queue just hides the overload until OOM and inflates latency past every deadline. When the queue/pool is full, **reject early**: return `503` (or `429`) with `Retry-After` and shed load *before* doing expensive work. Fast rejection beats slow timeout. (Inbound *policy* limits → rate-limiting; this is shedding under acute overload.)
70
+
71
+ 10. **Choose fail-fast vs fail-open/degrade per call deliberately.** On exhausted retries / open breaker: **fail-fast** (propagate the error) for must-be-correct calls (payment authorize); **fail-open / degrade** for optional ones — return a cached/stale value, a default, or a partial response so one non-critical dep can't take down the page. Decide and document it; never default to "throw 500".
72
+
73
+ 11. **Hedge requests for tail latency (read-only/idempotent only).** If p99 ≫ p50, send a second (parallel) request after a delay (e.g. at the p95 latency mark), take whichever returns first, cancel the loser. Cuts tail latency at the cost of extra load — gate it (e.g. ≤5% hedged) so it doesn't amplify during an incident. gRPC supports hedging policy natively. Never hedge non-idempotent writes.
74
+
75
+ 12. **Use the battle-tested library, not a hand-rolled `for` loop.**
76
+
77
+ | Stack | Use | Notes |
78
+ |---|---|---|
79
+ | Java/Kotlin | **resilience4j** | `Retry` + `CircuitBreaker` + `Bulkhead` + `TimeLimiter`, composed; order matters (TimeLimiter inside Retry) |
80
+ | .NET | **Polly** | `ResiliencePipeline`: `AddRetry` (with jitter) + `AddCircuitBreaker` + `AddTimeout` |
81
+ | Python | **tenacity** or **backoff** | `@retry(wait=wait_random_exponential(max=10), stop=stop_after_attempt(3), retry=retry_if_exception_type(...))` |
82
+ | Go | **failsafe-go** / **sony/gobreaker** | gobreaker for the breaker; failsafe-go for retry+backoff+circuit composed |
83
+ | JS/TS | **p-retry** + `AbortSignal.timeout` | p-retry for backoff; `opossum` for the circuit breaker |
84
+ | gRPC | service-config `retryPolicy` + `hedgingPolicy` | declarative; set deadlines on the client |
85
+ | Mesh | **Envoy / Istio** | `retryPolicy` (`retry_on: 5xx,connect-failure,retriable-4xx`) + `numRetries` + **outlier detection** (the mesh's circuit breaker — ejects bad hosts) |
86
+
87
+ Push retry/breaker policy to the mesh (Envoy/Istio) when you can — it's per-route, consistent across languages, and observable. Keep app-level resilience for finer control (idempotency-aware retries, fallbacks).
88
+
89
+ 13. **Make all of it observable.** Emit metrics per dependency: attempt count, retry count, breaker state transitions, timeout count, shed/rejected count, p99 latency. A breaker that's silently open looks identical to a healthy-but-idle dependency — you must be able to see it. Alert on breaker-open and on retry-budget exhaustion.
90
+
91
+ ## Anti-Patterns
92
+
93
+ | Anti-pattern | Why it bites | Fix |
94
+ |---|---|---|
95
+ | Retrying a non-idempotent POST | Double charge / duplicate record | Idempotency key (→ idempotency-keys) or don't retry |
96
+ | Backoff with no jitter | Synchronized retry storm hammers the recovering dep | Full jitter |
97
+ | Unbounded retries (`while`/no cap) | Burns budget, melts the dependency | Cap 2–3 + overall deadline |
98
+ | Retries nested at every layer | 3×3×3 = 27× load multiplication | Retry at ONE layer only |
99
+ | Retrying inside a retry (library + manual) | Hidden multiplication, double the attempts | Pick one place to own retries |
100
+ | No timeout / infinite default | One hung call drains the whole pool → service down | Bound connect+read+total |
101
+ | Retrying 4xx (400/401/404) | Same input, same failure — pure waste | Only retry 5xx/408/429/connect errors |
102
+ | One global circuit breaker | One bad dep opens the breaker for all deps | Per-dependency breaker + bulkhead |
103
+ | Unbounded queue for backpressure | Latency blows past deadlines, then OOM | Bounded queue + reject early (503/Retry-After) |
104
+ | Same timeout at every layer | Inner timeout ≥ outer → outer fires first, inner work wasted | Propagate a shrinking deadline |
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: resolve-merge-rebase-conflict
3
+ description: Resolves non-trivial merge, rebase, and cherry-pick conflicts by reading both sides' intent and combining hunks — handling rename/delete/add-add/binary conflicts, enabling rerere for repeats, and verifying the result builds and tests green rather than just clearing markers.
4
+ when_to_use: A merge/rebase/cherry-pick halts on conflicts (large or semantic), or the same conflict keeps recurring. Not clean commits/PRs (git-commit-pr), intentional history editing (rewrite-git-history), or recovering lost work (recover-git-state).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - "`git merge`/`git rebase`/`git cherry-pick` stopped with conflicts and I need to finish it correctly."
10
+ - "Big conflict across many files after a long-lived branch — which side wins where?"
11
+ - "The same conflict keeps coming back every rebase."
12
+ - "`<<<<<<<`/`=======`/`>>>>>>>` markers everywhere and the two sides changed the *same* logic differently."
13
+ - "rename/rename, modify/delete, add/add, or a binary asset conflicted and `--ours`/`--theirs` isn't obvious."
14
+
15
+ NOT this skill:
16
+ - Writing the commit message / opening the PR once the tree is clean → git-commit-pr
17
+ - Deliberately reshaping history (squash, reorder, split, drop commits) when there's no conflict to resolve → rewrite-git-history
18
+ - A rebase/merge that ate your work, detached HEAD, or you need a commit back via reflog → recover-git-state
19
+
20
+ ## Steps
21
+
22
+ 1. **Identify the operation first — `ours`/`theirs` MEANING FLIPS.** The same flag points opposite directions depending on what's running. Check `git status` (it names the op) before touching anything:
23
+
24
+ | Operation | `HEAD` / `--ours` is | `MERGE_HEAD` / `--theirs` is | Mental model |
25
+ |---|---|---|---|
26
+ | `git merge X` | your current branch | the branch X you're merging in | ours = where you are |
27
+ | `git rebase X` | **X (upstream)** — replayed onto | **your commit** being reapplied | **inverted**: "ours" is the base you're moving onto, "theirs" is your own change |
28
+ | `git cherry-pick C` | your current branch | commit C being applied | like merge |
29
+ | `git revert C` | your current branch | the inverse of C | like merge |
30
+
31
+ In a rebase, blindly taking `--ours` throws away *your own* work. Never run `checkout --ours/--theirs` on autopilot during a rebase.
32
+
33
+ 2. **Turn on zdiff3 so you can see the base.** Default `merge` style hides the common ancestor, so you can't tell which side actually changed what:
34
+
35
+ ```sh
36
+ git config --global merge.conflictStyle zdiff3 # adds a ||||||| base section between the two sides
37
+ git config --global rerere.enabled true # record+replay resolutions (step 7)
38
+ ```
39
+
40
+ With zdiff3 a hunk shows `<<<<<<< ours` / `||||||| base` / `======= theirs` / `>>>>>>>`. Compare each side *against base*: the side that differs from base is the one that changed; if both differ, you must merge them by hand.
41
+
42
+ 3. **Survey the whole conflict before editing one file.** `git status` lists unmerged paths; `git diff --diff-filter=U` shows only conflicted hunks. Classify each path: content conflict, or a *tree* conflict (rename/delete/add). Resolve content hunks by **intent**, not by picking a side:
43
+ - If both sides differ from base, you almost always want **both changes combined**, not one discarded. Read what each side was trying to do, write the union that satisfies both, delete all markers.
44
+ - Only take one side wholesale when the other is genuinely superseded (e.g. theirs deleted a function ours merely reformatted).
45
+ - For a single file you've decided entirely belongs to one side: `git checkout --ours -- <path>` / `--theirs -- <path>` (remember the rebase flip), then `git add <path>`.
46
+
47
+ 4. **Handle tree conflicts deliberately — markers won't appear:**
48
+ - **modify/delete** (`deleted by us`/`deleted by them`): git can't merge a hunk into a missing file. Decide: keep the file and reapply the other side's change (`git add <path>`), or honor the delete (`git rm <path>`). Default: keep it if the surviving side still calls into it.
49
+ - **rename/rename** (same file renamed to two names, or both edited one rename): git leaves multiple paths. Pick the intended final name, move the merged content there, `git rm` the stray path, `git add` the keeper.
50
+ - **add/add** (both branches created the same path with different content): treat exactly like a content conflict — merge the two versions into one file, `git add`.
51
+ - **binary / generated** (images, lockfiles, compiled assets): no line merge possible. Choose a side on purpose — `git checkout --theirs -- yarn.lock && git add yarn.lock` — then **regenerate** rather than trust either copy (e.g. re-run `yarn install` / `npm install` and commit the result, never hand-stitch a lockfile).
52
+
53
+ 5. **Continue, don't re-stage by hand at the end.** After every conflicted path is `git add`-ed (or `git rm`-ed):
54
+ - merge → `git commit` (uses the prepared merge message)
55
+ - rebase → `git rebase --continue`
56
+ - cherry-pick → `git cherry-pick --continue`
57
+ Repeat per replayed commit — a rebase can stop again on the next commit; resolve each in turn.
58
+
59
+ 6. **Know the three exits.** Don't thrash a bad resolution — bail and retry with a cleaner head:
60
+
61
+ | Command | Effect | Use when |
62
+ |---|---|---|
63
+ | `--continue` | accept current resolution, proceed | you resolved correctly |
64
+ | `--abort` | restore the pre-operation state exactly | resolution is going sideways; start over |
65
+ | `--skip` | drop the current commit being applied (rebase/cherry-pick only) | this commit is already upstream / empty after resolution — **never** to dodge a hard conflict |
66
+
67
+ `git merge --abort` / `git rebase --abort` / `git cherry-pick --abort` always returns you to safety. Prefer abort + a fresh attempt over forcing a tangled hunk.
68
+
69
+ 7. **Let rerere kill the repeats.** With `rerere.enabled true` (step 2), git records each manual resolution and **auto-replays** it the next time the identical conflict appears — invaluable when rebasing a long branch onto a moving main, or re-running a merge. Inspect with `git rerere status`/`git rerere diff`; if rerere replayed a *wrong* prior resolution, `git rerere forget <path>` and redo it.
70
+
71
+ 8. **Build and test BEFORE declaring done — clearing markers is not resolving.** A tree with zero markers can still compile to wrong behavior: you may have dropped one side's logic, or combined two valid hunks into a contradiction. Run the project's real build + test (`npm test`, `cargo build && cargo test`, `pytest`, `make`) on the resolved tree. Fix failures at the merge seams, not by weakening assertions. Only then commit/continue.
72
+
73
+ 9. **Reduce future conflicts (avoidance > resolution).** Keep PRs small and short-lived; `git fetch && git rebase origin/main` (or merge main in) frequently so you resolve a little, often, against fresh base instead of a giant divergence later; agree on formatting (run the formatter on both sides) so whitespace/reflow doesn't manufacture conflicts.
74
+
75
+ ## Common Errors
76
+
77
+ - **`checkout --ours`/`--theirs` during a rebase, expecting merge semantics.** The labels are inverted — `--ours` is upstream, `--theirs` is your own commit. Result: you silently delete your own changes. Confirm the op via `git status` first; in rebase, *theirs* is usually what you want to keep.
78
+ - **Committing with conflict markers still in the file.** `<<<<<<<`/`=======`/`>>>>>>>` left behind ship as literal source and break the build. Grep the whole tree before committing (see Verify) — don't trust your eyes per-file.
79
+ - **Default `merge` conflictStyle, guessing which side changed.** Without the `|||||||` base you can't see the common ancestor and pick wrong. Set `merge.conflictStyle=zdiff3` once, globally.
80
+ - **Blindly taking one side to "make it go away."** `checkout --theirs` on a content conflict where both sides added needed logic drops half the work with a clean exit code. Conflicts where both differ from base almost always want the *union*.
81
+ - **Treating modify/delete as nothing to do.** No markers appear, so it looks resolved — but you must explicitly `git add` (keep) or `git rm` (delete). Leaving it unstaged stalls `--continue`.
82
+ - **Hand-merging a lockfile or binary.** `package-lock.json`/`yarn.lock`/`Cargo.lock` and images can't be line-merged sanely. Pick a side, then regenerate (`npm install`) or re-export — a stitched lockfile installs a phantom dependency graph.
83
+ - **`git rebase --skip` to escape a hard conflict.** Skip *drops the entire commit*, silently losing that change. Only skip a commit already present upstream or empty after resolution; otherwise resolve it.
84
+ - **Declaring done at zero markers without building.** A syntactically clean merge can be semantically broken (dropped branch, double-applied change). Always run build + tests on the resolved tree.
85
+ - **rerere replaying a stale wrong resolution.** Once enabled it auto-applies your *previous* answer even if that answer was the bug. If an auto-resolved hunk looks off, `git rerere forget <path>` and redo.
86
+ - **Resolving one conflict in a multi-commit rebase and assuming you're done.** Rebase stops per commit; `--continue` may immediately halt on the next. Loop until `git status` reports no rebase in progress.
87
+
88
+ ## Verify
89
+
90
+ 1. **No markers anywhere:** `git grep -nE '^(<{7}|={7}|>{7}|\|{7})' -- ':!*.md'` returns nothing (also catches the zdiff3 `|||||||` base line). Empty output is mandatory.
91
+ 2. **No unmerged paths:** `git status --porcelain` shows no `UU`/`AA`/`DU`/`UD`/`DD`/`AU`/`UA` entries; `git diff --diff-filter=U` is empty.
92
+ 3. **Operation actually finished:** `git status` reports no merge/rebase/cherry-pick in progress (no `.git/MERGE_HEAD`, `.git/rebase-merge`, or `CHERRY_PICK_HEAD`).
93
+ 4. **Build + tests green on the resolved tree** — the project's real commands (`npm test` / `cargo test` / `pytest` / `make`), not a marker check standing in for verification.
94
+ 5. **Both sides' intended changes are present:** diff the result against each parent and confirm neither side's needed logic was dropped. For a finished merge: `git diff HEAD^1 HEAD` (your side) and `git diff HEAD^2 HEAD` (the merged-in side). For a rebase, `git range-diff @{upstream}...HEAD` shows your commits survived intact.
95
+ 6. **rerere recorded reusable resolutions** (if conflicts may recur): `git rerere status` is clean and future identical conflicts auto-resolve.
96
+
97
+ Done = zero conflict markers, no unmerged paths, the operation is complete, build + tests pass on the resolved tree, and a diff against both parents confirms each side's intended change is present.
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: rewrite-git-history
3
+ description: Rewrites git history safely — interactive rebase (squash/split/reorder/reword/edit), amend, and git filter-repo/BFG to purge a committed secret or large file — using force-with-lease and explicit shared-branch safeguards.
4
+ when_to_use: Cleaning up a feature branch before merge, purging a committed secret or large blob from all history, or splitting/squashing commits. Distinct from git-commit-pr (normal commit/PR), resolve-merge-rebase-conflict (fixing conflicts), and recover-git-state (recovering lost commits).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Reach for this skill when you must **change commits that already exist**, not create new ones:
10
+
11
+ - "Squash these 6 WIP commits into one before merging"
12
+ - "Reword that commit message / fix the author/email on old commits"
13
+ - "Split this giant commit into logical pieces"
14
+ - "Reorder / drop a commit from my branch"
15
+ - "I committed an API key / `.env` / a 400 MB binary — scrub it from the whole history"
16
+ - "Amend the last commit, I forgot a file"
17
+
18
+ NOT this skill:
19
+ - Writing a normal commit message or opening a PR on un-rewritten work → git-commit-pr
20
+ - A rebase/merge that stopped on `CONFLICT` markers → resolve-merge-rebase-conflict
21
+ - Lost a commit/branch after a bad rebase/reset, need it back → recover-git-state
22
+ - Moving plaintext secrets into a vault, rotating, or scanning for leaks → secrets-management (run *after* the purge here)
23
+
24
+ ## Steps
25
+
26
+ 1. **Apply the golden rule first — is this branch shared?** Rewriting changes every downstream SHA; anyone who pulled the old history gets a divergent tree and can re-push the secret you deleted.
27
+
28
+ | Branch state | Rewrite? | How to push |
29
+ |---|---|---|
30
+ | Local-only, never pushed | Yes, freely | normal `git push` (first push) |
31
+ | Pushed, **only you** pull it (personal feature branch) | Yes | `git push --force-with-lease` |
32
+ | Shared / `main` / release / others have pulled | **No — coordinate first** | announce → everyone stops → rewrite → `--force-with-lease` → everyone re-clones or `git reset --hard origin/<branch>` |
33
+
34
+ Default: rewrite only local/unshared branches. For `main`, the answer is almost always "don't" — the only exception is purging a secret, and then only with team sign-off.
35
+
36
+ 2. **Record the safety net before touching anything.** Copy the current tip so you can undo: `git rev-parse HEAD`, and confirm `git status` is clean — stash or commit dirty work first; rebase refuses to start otherwise. The reflog (`git reflog`) keeps the old tip ~90 days regardless, but a written-down SHA is faster.
37
+
38
+ 3. **Interactive rebase for squash/reword/reorder/drop/edit.** Rebase the commits *since* the base, not your whole history:
39
+
40
+ ```bash
41
+ git rebase -i origin/main # or: git rebase -i HEAD~5
42
+ ```
43
+ In the todo editor, set the verb on each line (top = oldest), and **reorder by moving whole lines**:
44
+
45
+ | Verb | Effect |
46
+ |---|---|
47
+ | `pick` | keep as-is |
48
+ | `reword` | keep changes, edit the message |
49
+ | `edit` | stop here to amend content or split (step 4) |
50
+ | `squash` | fold into the commit above, **combine both messages** |
51
+ | `fixup` | fold into the commit above, **discard this message** (use for "oops typo" commits) |
52
+ | `drop` | delete the commit entirely (or just delete the line) |
53
+
54
+ Save and close. Resolve any conflicts (→ resolve-merge-rebase-conflict), then `git rebase --continue`. Abort cleanly at any point with `git rebase --abort` — it restores the pre-rebase tip exactly.
55
+
56
+ 4. **Split one commit into several.** Mark it `edit` in the rebase todo; when rebase stops on it:
57
+ ```bash
58
+ git reset HEAD^ # un-commit, keep changes in working tree (mixed reset)
59
+ git add -p # stage hunk-by-hunk for the first piece
60
+ git commit -m "first logical change"
61
+ git add -p && git commit -m "second logical change" # repeat until clean
62
+ git rebase --continue
63
+ ```
64
+ `git reset HEAD^` (mixed) keeps your work in the tree; never `--hard` here or you lose it.
65
+
66
+ 5. **Amend only the last commit** (no rebase needed): `git commit --amend` (edit message + fold staged changes), or `git commit --amend --no-edit` to silently add forgotten files. If already pushed to your own branch, follow with `git push --force-with-lease`.
67
+
68
+ 6. **Purge a file/secret across ALL history — `git filter-repo` (preferred, BFG fallback).** `git filter-branch` is deprecated and slow; do not use it.
69
+ ```bash
70
+ pip install git-filter-repo # or: brew install git-filter-repo
71
+ # Remove a path everywhere (history rewritten in place):
72
+ git filter-repo --path config/secrets.yml --invert-paths
73
+ # Or redact a literal string/regex from every blob:
74
+ printf 'AKIAIOSFODNN7EXAMPLE==>REDACTED\n' > replacements.txt
75
+ git filter-repo --replace-text replacements.txt
76
+ ```
77
+ BFG alternative for big blobs: `bfg --delete-files '*.zip'` or `bfg --replace-text replacements.txt`, then `git reflog expire --expire=now --all && git gc --prune=now --aggressive`. `filter-repo` runs that cleanup for you and removes the `origin` remote on purpose — re-add it before pushing.
78
+
79
+ 7. **ROTATE the leaked secret — rewriting history does NOT un-leak it.** Anyone who cloned, any fork, any CI cache, and GitHub's own unreachable-commit cache still hold it. The history scrub is step 1 of 2; the real fix is: **revoke + reissue the key/token/password at the provider** (then → secrets-management). Treat the credential as compromised the moment it was pushed.
80
+
81
+ 8. **Force-push with `--force-with-lease`, never bare `--force`.**
82
+ ```bash
83
+ git push --force-with-lease origin <branch>
84
+ ```
85
+ `--force-with-lease` refuses the push if the remote moved since your last fetch — it catches the case where a teammate pushed in the meantime, which bare `--force` would silently obliterate. For a full-history purge you must push every ref: `git push --force-with-lease --all && git push --force-with-lease --tags`.
86
+
87
+ ## Common Errors
88
+
89
+ - **Force-pushing a shared/`main` branch without coordination.** Everyone else's next pull diverges, and someone re-pushes the deleted secret. Confirm the branch is unshared (step 1) or get explicit sign-off first.
90
+ - **Using bare `git push --force`.** Overwrites whatever is on the remote with zero safety check, including a teammate's just-pushed commits. Always `--force-with-lease`.
91
+ - **Thinking the rewrite removed the secret.** It only removed it from *your* refs. Forks, clones, CI caches, and GitHub's cached unreachable commits still expose it — rotate the credential (step 7). This is the single most common and most damaging mistake.
92
+ - **`git reset --hard HEAD^` when splitting a commit.** Discards the very changes you were trying to re-commit. Use `git reset HEAD^` (mixed) to keep them in the working tree.
93
+ - **Rebasing onto the wrong base** (`HEAD~10` swallows commits already on `main`). Rebase against the tracking base, e.g. `git rebase -i origin/main`, so you only touch your own commits.
94
+ - **Reordering by editing SHAs or text instead of moving whole lines.** The rebase todo is line-ordered; cut/paste entire lines to reorder. Editing a hash breaks the rebase.
95
+ - **`squash` when you meant `fixup`** (or vice versa). `squash` opens an editor to combine both messages; `fixup` drops the second message. Picking wrong leaves "WIP"/"typo" text in your final message.
96
+ - **Running `git filter-repo` on a dirty or non-fresh clone.** It refuses non-fresh clones by default for safety; work on a fresh `git clone` (or pass `--force` only when you understand why), and commit/stash everything first.
97
+ - **Forgetting `filter-repo` dropped the remote.** It removes `origin` deliberately so you can't push to the wrong place by reflex. Re-add it (`git remote add origin <url>`) before the force-push.
98
+ - **Letting purged objects linger.** After a manual `filter-branch`/BFG run, the blobs survive in reflog/packs until `git reflog expire --expire=now --all && git gc --prune=now`. Without it, `git cat-file` still serves the secret locally.
99
+
100
+ ## Verify
101
+
102
+ 1. **Target commits are correct.** `git log --oneline` (and `--stat`/`-p`) shows exactly the intended squash/split/reword/reorder — commit count and each message match the plan.
103
+ 2. **No unintended commits dropped.** Diff the rewritten branch against the saved pre-rewrite SHA from step 2: `git range-diff <old-sha> HEAD` (or `git diff <old-sha> HEAD` to confirm the *tree* is identical when you only re-shaped messages/structure, not content). Cross-check `git reflog` for the old tip.
104
+ 3. **Secret is gone from every ref, not just `HEAD`.** `git log --all -p -S 'AKIA'` returns nothing, and `git grep -I 'AKIA' $(git rev-list --all)` finds no hits across all history. For a removed path: `git log --all --oneline -- config/secrets.yml` is empty.
105
+ 4. **No dangling reachable copy.** After cleanup, `git rev-list --objects --all | grep <blob-sha>` is empty and `git count-objects -v` shows the pack shrank.
106
+ 5. **Credential actually rotated.** The old key returns 401/403 from the provider (not just deleted from git). If it still authenticates, you are not done.
107
+ 6. **Remote matches and was pushed safely.** `git push --force-with-lease` succeeded (not refused), and `git log origin/<branch> --oneline` equals local. Teammates on a shared branch confirmed re-sync.
108
+
109
+ Done = intended commits are exactly reshaped with zero unintended drops (verified against the pre-rewrite SHA/reflog), the secret is absent from every ref and every history blob, the leaked credential is rotated and dead at the provider, and the push used `--force-with-lease` on a branch that was either unshared or coordinated.