@ssheleg/sheleg-dev 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,120 @@ All notable changes to this project are documented here.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
  Versioning: [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.9.0 — 2026-08-24
8
+
9
+ ### The pack that wires integrations had nothing about knowing when they break
10
+
11
+ Six skills covered taking money, tracking conversions, signing people in and
12
+ page speed. None covered the layer that says any of it stopped working: a `grep`
13
+ for `sentry` across the whole family returned two incidental mentions.
14
+
15
+ `error-tracking` is written from a measured baseline rather than an imagined
16
+ one. An agent solving this exact task without the skill, in one session, made
17
+ four failures that were recorded as they happened:
18
+
19
+ - **Called "get a DSN" a manual step needing an account.** The account existed
20
+ and the CLI was already authenticated. Error tracking stayed off for no reason.
21
+ - **Wrote a `before_send` scrubber without knowing `EventScrubber` exists** and
22
+ runs by default — so it could not say which layer covered what, or whether its
23
+ own code was redundant or load-bearing.
24
+ - **Did not know the MCP is OAuth**, and would have declared it in the machine's
25
+ shared gateway, where an OAuth flow cannot complete.
26
+ - **Would have set a DSN and stopped**, leaving every issue attached to a release
27
+ with no commits and suspect-commit attribution permanently dead.
28
+
29
+ ### The measurement the skill is built on
30
+
31
+ `sentry-sdk` 2.19.2, run 2026-08-24. The built-in `EventScrubber` is enabled by
32
+ default and matches on KEY NAMES against a 32-entry denylist. It does not inspect
33
+ string values, and neither `dsn` nor `database_url` is on that list.
34
+
35
+ ```python
36
+ event = {"message": "could not connect to postgresql://u:SUPERSECRET@host/db"}
37
+ EventScrubber().scrub_event(event)
38
+ assert "SUPERSECRET" in str(event) # passes — not scrubbed
39
+ ```
40
+
41
+ A service that writes a database URL near an error therefore starts forwarding
42
+ that password to a third party the moment Sentry is added, with wider reach than
43
+ the log had. The skill ships both layers — and, more usefully, the test that
44
+ asserts layer one alone still leaks, so the day Sentry starts scrubbing values a
45
+ red test says the second layer may now be redundant.
46
+
47
+ ### Also in this release
48
+
49
+ - The 403 that reads as an auth failure and is not: `allowMemberProjectCreation:
50
+ false` blocks `project create` **even for an org owner**, because the policy is
51
+ evaluated against the token's scopes and the device-flow token carries no
52
+ `org:write`. The team-scoped endpoint fails identically. Measured, with the
53
+ three ways out ranked.
54
+ - Two different tools are called `sentry`. Commands from one silently do not
55
+ exist in the other, and they install into prefixes where the older copy tends
56
+ to win — 0.38.0 shadowing 0.43.0, observed here. The shadow check is one line.
57
+ - `$schema` was missing from `plugin.json` and from the marketplace entry. Added.
58
+
59
+ ## v0.8.0 — 2026-08-20
60
+
61
+ **The self-test that proved the money invariants was measuring one thing and claiming
62
+ another.** `--self-test` deleted a rule, ran an invariant, and asked whether the invariant
63
+ went red. Twelve invariants carried **45 `assert.` calls**, and the verdict line called
64
+ them *"12 assertions, each watched failing"* — so any assertion that was not the sole
65
+ discriminator of a rule could be replaced by `assert.ok(true)` and nothing anywhere would
66
+ notice. Measured against the shipped v0.7.0 pack: three neutered assertions (the
67
+ `clawbacks.map` deepEqual, the `mirror.periodEnd` equality, the `response.body` deepEqual)
68
+ left `--self-test` at exit 0 and `npm test` at 0. The same hole in `ad-tracking`, in the PII
69
+ guard `references/meta-linkedin.md` advertises by name.
70
+
71
+ The `assert` an invariant receives is now a recorder: a failure is remembered instead of
72
+ thrown, so the assertions after it still run, and each call site is one measurement
73
+ identified by its line. **37 of 45 watched failing, one call site at a time. Eight declared
74
+ `assert.unmutated`** — an assertion no rule in the pack varies — **and measured unbreakable,
75
+ with the reason at the call site.** Both directions: an `unmutated` assertion a mutant does
76
+ break is a failure too, or the escape hatch is the bypass. Four assertions that were being
77
+ pre-empted by a `TypeError` on the line above them became measurable rather than declared
78
+ away. `test/fixtures_test.js` grew three plants, one of them the NON-first assertion inside
79
+ a multi-assert invariant — the plant that used to pass.
80
+
81
+ Then the sweep that started there and did not stop:
82
+
83
+ - **The validator's check count did not count checks.** `checks = 10 + len(skill_dirs)`, so
84
+ adding a skill moved the number and adding a check did not — and five ledger rows read it
85
+ as evidence a guard had arrived. Every check is registered; the verdict prints the
86
+ registry's length, and the ledger's quoted copy of that line is compared against it.
87
+ - **The ledger described an artifact nobody ships.** Its shipped block was headed `v0.6.0`
88
+ while `v0.7.0` was tagged and on npm, and 37 rows read *verified locally · unreleased*
89
+ under four blocks closing *"Nothing was released."* The heading is now compared against
90
+ `git describe --tags` and against `package.json`.
91
+ - **The `mcp__.*` half of the manual gate had no guard.** Deleting that entry from
92
+ `hooks/hooks.json` left all three suites green, while the decision module does refuse
93
+ `mcp__…__create_refund` by name. And `README.md`'s copy-channel snippet registered one
94
+ matcher against the plugin's two — a weaker gate handed out by the document that exists
95
+ because the channel has none. Both are now checked; the snippet by matcher set.
96
+ - **This repository measured no body budget at all**, so `crypto-payments/SKILL.md` sat past
97
+ the house working limit and was caught by another repository's auditor. Measured here now,
98
+ and the skill was **split rather than trimmed**: `references/callback-route-hardening.md`
99
+ and `references/testing-and-local-dev.md`, 4894 → 4387 tokens. All six skills `0 GAP`.
100
+ - **Counted numbers became computed ones.** Seven in `SECURITY.md` (all four correct on the
101
+ day they were written, three of them moved by this change) and two in
102
+ `docs/evals/stripe-billing.md`, which restated `4994` tokens / `441` lines / `0 GAP, 13
103
+ PASS` against a measured 4747 / 409 / `0 GAP, 14 PASS`.
104
+ - **A document that ships now resolves against the tarball, not only against a clone.**
105
+ `SECURITY.md` sent a reader to two documents `npm pack` does not contain.
106
+ - **`docs/` joined the checked corpus**, with every document in it classified as live or as
107
+ a dated record — `docs/AGENT_SYNC.md` had been naming six paths this repository does not
108
+ have.
109
+ - **`.claude/agent-sync.json` pointed at a file that did not exist** and guarded the ledger
110
+ without guarding the board.
111
+ - **`CONTRIBUTING.md` called the gate two suites and it runs three**; the PR template asked
112
+ for one third of it. Both derive the list from `package.json` → `scripts.test` now.
113
+ - **`install.sh` — the channel that `rm -rf`s each destination — printed no notice** that
114
+ the gate does not travel with it, while the npm installer did.
115
+
116
+ Fourteen negative self-tests were added, one per guard, and all **42** were watched refusing
117
+ their plants on a green tree. CI run `32293489020` at `6f66255` — 39 steps, 39 `success`,
118
+ 28 of 28 negatives — retires the *"CI has not seen any of this"* limitation the ledger had
119
+ recorded four separate times.
120
+
7
121
  ## v0.7.0 — 2026-08-19
8
122
 
9
123
  **The pack that takes money had the weakest controls in the family, and every one of them
package/README.md CHANGED
@@ -7,7 +7,7 @@ Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
7
7
 
8
8
  ---
9
9
 
10
- ## The six skills
10
+ ## The seven skills
11
11
 
12
12
  | Skill | Answers |
13
13
  |---|---|
@@ -17,13 +17,15 @@ Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
17
17
  | **`google-signin`** | how do I let people sign in with Google without handing someone their account |
18
18
  | **`google-auth`** | how does my *server* authenticate to Google — OAuth, ADC, service accounts, federation |
19
19
  | **`frontend-performance`** | why is the Lighthouse score bad and which fix actually moves it |
20
+ | **`error-tracking`** | how do I know it broke, without mailing my own credentials to Sentry |
20
21
 
21
22
  Each carries its own references and loads them only when the work reaches them.
22
23
 
23
24
  **Two of them ship a suite rather than a warning.** `stripe-billing` and `ad-tracking`
24
25
  carry a `fixtures/` directory — real provider webhook bodies, an assertion pack, and a
25
- `--self-test` that deletes one rule at a time so you can watch every assertion fail. Copy
26
- the directory in and run it:
26
+ `--self-test` that deletes one rule at a time so you can watch every assertion fail — one
27
+ call site at a time, which is not the same thing as one invariant at a time. Copy the
28
+ directory in and run it:
27
29
 
28
30
  ```bash
29
31
  node plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs --self-test
@@ -88,7 +90,7 @@ a Lighthouse score.
88
90
  /plugin install sheleg-dev@sheleg-dev
89
91
  ```
90
92
 
91
- **npm installer** — copies all six skills into `~/.claude/skills/`:
93
+ **npm installer** — copies all seven skills into `~/.claude/skills/`:
92
94
 
93
95
  ```bash
94
96
  npx @ssheleg/sheleg-dev
@@ -158,17 +160,29 @@ settings, by design.
158
160
  with the plugin and the hook is live as soon as the plugin is enabled. Nothing further
159
161
  to do, and no per-hook switch: enablement is the whole control.
160
162
  - **Installed by `npx @ssheleg/sheleg-dev`, `install.sh` or `npx skills add`** — those
161
- copy the six skills into `~/.claude/skills/` and **carry no hook**. The gate is prose
163
+ copy the seven skills into `~/.claude/skills/` and **carry no hook**. The gate is prose
162
164
  again until you register it yourself, in `~/.claude/settings.json` or a project's
163
165
  `.claude/settings.json`:
164
166
 
165
167
  ```json
166
- { "hooks": { "PreToolUse": [ { "matcher": "Bash",
168
+ { "hooks": { "PreToolUse": [
169
+ { "matcher": "Bash",
167
170
  "hooks": [ { "type": "command",
168
171
  "command": "node \"$HOME/.claude/skills/.sheleg-dev-hooks/money-gate.js\"",
169
- "timeout": 15 } ] } ] } }
172
+ "timeout": 15 } ] },
173
+ { "matcher": "mcp__.*",
174
+ "hooks": [ { "type": "command",
175
+ "command": "node \"$HOME/.claude/skills/.sheleg-dev-hooks/money-gate.js\"",
176
+ "timeout": 15 } ] }
177
+ ] } }
170
178
  ```
171
179
 
180
+ **Both entries, not just the first.** A refund does not care which door it came
181
+ through: `create_refund` on an MCP server never touches a shell, and the table above
182
+ advertises it. Until 2026-08-20 this snippet carried the `Bash` matcher alone — a
183
+ weaker gate than the plugin's, handed out by the document that exists because the copy
184
+ channels have no gate at all. `test/validate.py` now compares the two matcher sets.
185
+
172
186
  Copy `plugins/sheleg-dev/hooks/` to that path first. `npx @ssheleg/sheleg-dev` prints
173
187
  this reminder; **nothing enforces it**, which is why the plugin channel is the
174
188
  recommended one.
@@ -219,8 +233,9 @@ matter does not error — hosts truncate it silently, which is worse); and
219
233
  dangling link nor a file nobody loads can ship.
220
234
 
221
235
  Plus, for the money fixtures, that every assertion has been watched failing and that no
222
- two rules are covering for each other — `test/fixtures_test.js` neuters three assertions
223
- and requires each pack's `--self-test` to notice.
236
+ two rules are covering for each other — `test/fixtures_test.js` neuters assertions,
237
+ including one that is **not** the first inside a multi-assert invariant, and requires each
238
+ pack's `--self-test` to notice. That last plant is the one that used to pass.
224
239
 
225
240
  CI plants a defect against every one of those guards and requires the validator
226
241
  to fail. A green from a check nobody has watched fail is not evidence.
package/SECURITY.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Security
2
2
 
3
- `sheleg-dev` is six skills about the layer a product reaches once it has users.
3
+ `sheleg-dev` is seven skills about the layer a product reaches once it has users.
4
4
  Five of them touch other people's money or identity — Stripe billing, crypto
5
5
  payments, ad and conversion tracking, Google sign-in, server-side Google auth —
6
- and the sixth is front-end performance. That subject is the reason this document
6
+ the sixth is front-end performance and the seventh is error tracking. Money is the
7
+ reason this document
7
8
  has to be exact about the pack itself.
8
9
 
9
10
  Everything below is checkable from a clone, and the commands at the end are the
@@ -16,8 +17,8 @@ documents name that does not exist here.
16
17
 
17
18
  ## What ships, and what of it executes
18
19
 
19
- `git ls-files plugins` returns **50 files: 28 markdown, one plugin manifest, three
20
- files of the manual gate, and 18 files of money fixtures.** Two of the six skills now
20
+ `git ls-files plugins` returns **56 files: 34 markdown, one plugin manifest, three
21
+ files of the manual gate, and 18 files of money fixtures.** Two of the seven skills now
21
22
  ship code you can run — the assertion packs under `fixtures/` — and that is a change
22
23
  from every release before v0.7.0, so it is stated here rather than left to be noticed.
23
24
  They run only when you invoke them, read only the JSON fixtures beside them, and open
@@ -26,19 +27,23 @@ no socket; the greps below prove all three.
26
27
  | Component | Count | Runtime behavior |
27
28
  |---|---|---|
28
29
  | `SKILL.md`, one per skill | 6 | Text. Read by the agent, executes nothing. |
29
- | `references/` files, loaded on demand | 20 | Text. Same. |
30
+ | `references/` files, loaded on demand | 25 | Text. Same. |
30
31
  | `plugins/sheleg-dev/.claude-plugin/plugin.json` | 1 | Manifest read by Claude Code. |
31
32
  | `plugins/sheleg-dev/hooks/hooks.json` | 1 | Hook manifest read by Claude Code. Declares one `PreToolUse` hook and no `if` filter. |
32
33
  | `plugins/sheleg-dev/hooks/money-gate.js` | 1 | Runs on a tool call, when the plugin is enabled. Requires `path` and its own decision module — nothing else. Reads stdin, writes one JSON line, exits 0 on every path. |
33
34
  | `plugins/sheleg-dev/hooks/lib/moneygate.js` | 1 | Pure. Payload and environment in, verdict out. No `require` at all, no filesystem, no clock. |
34
35
  | `bin/sheleg-dev.js` — the npm installer | 1 | Runs only when you invoke it. Node built-ins only: `fs`, `path`, `os`. |
35
- | `install.sh` — the shell installer | 1 | Runs only when you invoke it. Coreutils only: `cd`, `pwd`, `dirname`, `basename`, `mkdir`, `rm`, `cp`, `echo`. |
36
+ | `install.sh` — the shell installer | 1 | **Not in the tarball** — `files` ships `bin/` and `plugins/`, so this one reaches you only through a clone. Runs when you invoke it. Coreutils only: `cd`, `pwd`, `dirname`, `basename`, `mkdir`, `rm`, `cp`, `echo`. It is the destructive channel: `rm -rf` per skill, then `cp -R`. |
36
37
  | `plugins/sheleg-dev/skills/*/fixtures/*.json` — webhook payloads | 12 | Data. Placeholder ids only; no key, token, signing secret or real customer id. |
37
38
  | `plugins/sheleg-dev/skills/*/fixtures/*.mjs` — the assertion packs and their reference implementations | 4 | Runs only when you invoke it. Reads the JSON beside it with `readFileSync` and nothing else: no `child_process`, no `fetch`, no write, no `process.env`. |
38
39
  | `plugins/sheleg-dev/skills/*/fixtures/manifest.json` and its `README.md` | 4 | Text. The invariant-to-fixture-to-document map, which `test/validate.py` reads. |
39
40
 
40
- The published tarball is those plus `README.md`, `CHANGELOG.md`, `SECURITY.md`,
41
- `LICENSE` and `package.json` — 56 files, listed by `npm pack --dry-run`.
41
+ The published tarball is `bin/` and `plugins/` plus `README.md`, `CHANGELOG.md`,
42
+ `SECURITY.md`, `LICENSE` and `package.json` — 62 files, listed by
43
+ `npm pack --dry-run`. `install.sh`, `test/`, `docs/` and `CONTRIBUTING.md` are **not**
44
+ in it; `test/validate.py` now refuses a path these shipped documents name that the
45
+ tarball does not carry, because resolving in a clone is the wrong question for a
46
+ document that ships.
42
47
 
43
48
  **The gate can refuse a command and can never run one.** `money-gate.js` requires
44
49
  `path` and the decision module; the decision module requires nothing. There is no
@@ -55,11 +60,11 @@ nothing but the CLI you typed.
55
60
 
56
61
  ## What the installers read and write
57
62
 
58
- Both do one thing: copy the six skill directories out of the package into
63
+ Both do one thing: copy the seven skill directories out of the package into
59
64
  `~/.claude/skills/<name>`.
60
65
 
61
66
  - **Read:** only files inside the package.
62
- - **Write:** only `~/.claude/skills/` — six directories, named for the six skills.
67
+ - **Write:** only `~/.claude/skills/` — seven directories, named for the seven skills.
63
68
  Nothing else on the filesystem, nothing outside `$HOME`.
64
69
  - **Network: none.** Neither file opens a socket or resolves a hostname.
65
70
  `bin/sheleg-dev.js` requires `fs`, `path` and `os` and nothing else — no
@@ -71,7 +76,7 @@ Both do one thing: copy the six skill directories out of the package into
71
76
 
72
77
  **One destructive difference between the two installers.** `bin/sheleg-dev.js`
73
78
  skips a skill that is already installed and says so, unless you pass `--force`
74
- (`bin/sheleg-dev.js:45-46`). `install.sh` does not ask: for each of the six names
79
+ (`bin/sheleg-dev.js:45-46`). `install.sh` does not ask: for each of the seven names
75
80
  it runs `rm -rf` on the destination and re-copies, every time
76
81
  (`install.sh:21-22`). If you have hand-edited an installed skill, the shell
77
82
  installer deletes your edits and the node installer does not.
@@ -136,12 +141,15 @@ environment variables, never in source and never in logs:
136
141
 
137
142
  ## Where the real risk is
138
143
 
139
- Not the bytes — the advice. These six skills describe Stripe, Heleket and BTCPay,
144
+ Not the bytes — the advice. These seven skills describe Stripe, Heleket and BTCPay,
140
145
  Google Sign-In, Workload Identity Federation, four ad platforms and browser
141
146
  defaults, and every one of those changes. **A claim here that a provider has since
142
147
  changed is a security defect in this pack**, not merely a stale doc, and it is the
143
- exposure `docs/evidence/verification.md` names as the largest one this repository
144
- does not test. `CONTRIBUTING.md` requires a claim about what a provider does
148
+ exposure the verification ledger names as the largest one this repository does not
149
+ test. **Neither that ledger nor the contributor guide is in this tarball** —
150
+ `docs/evidence/verification.md` and `CONTRIBUTING.md` live in the git repository at
151
+ <https://github.com/ssheleg/sheleg-dev>, and until 2026-08-20 this paragraph named
152
+ both as if you had them. The guide requires a claim about what a provider does
145
153
  *today* to carry the date it was true. If you find one that has rotted, report it
146
154
  with what the correct claim is and what backs it.
147
155
 
@@ -161,7 +169,7 @@ git clone https://github.com/ssheleg/sheleg-dev && cd sheleg-dev
161
169
  # self-describing documents name -- this file included.
162
170
  python3 test/validate.py
163
171
 
164
- # The shipped payload: 50 files.
172
+ # The shipped payload: 56 files.
165
173
  git ls-files plugins
166
174
 
167
175
  # 22 lines: the plugin manifest, the three files of the manual gate, and the 18
@@ -176,11 +184,13 @@ grep -nE "\brequire\(" plugins/sheleg-dev/hooks/money-gate.js plugins/sheleg-dev
176
184
  grep -nE "child_process|\bfetch\(|require\('(http|https|net|fs|dns|tls)'\)|spawn\(|execFile|writeFile|appendFile|unlink" \
177
185
  plugins/sheleg-dev/hooks/money-gate.js plugins/sheleg-dev/hooks/lib/moneygate.js
178
186
 
179
- # Both directions of the gate, watched: 65 fixtures, refusals and allow-plants.
187
+ # Both directions of the gate, watched: every refusal and every false-positive
188
+ # plant. The command prints its own count -- this document does not restate it.
180
189
  node test/moneygate_test.js
181
190
 
182
191
  # The money fixtures: both assertion packs against their reference implementation, both
183
- # --self-test runs, and three plants that neuter an assertion. 13 checks.
192
+ # --self-test runs (per ASSERTION, not per invariant), and the plants that neuter one.
193
+ # The command prints its own count.
184
194
  node test/fixtures_test.js
185
195
 
186
196
  # What the assertion packs cannot reach: NO OUTPUT, and grep exits 1 because it
@@ -196,6 +206,6 @@ grep -nE "require|child_process|exec|spawn|fetch|socket|rm -rf|cp -R|copyFileSyn
196
206
  # Live-key shapes across the skill payload: one line, and it is a placeholder.
197
207
  grep -rnE "sk_live_[A-Za-z0-9]|rk_live_[A-Za-z0-9]|whsec_[A-Za-z0-9]{8}|BEGIN [A-Z ]*PRIVATE KEY" plugins
198
208
 
199
- # What npm publishes, and nothing else: 56 files.
209
+ # What npm publishes, and nothing else: 62 files.
200
210
  npm pack --dry-run
201
211
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/sheleg-dev",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Six integration skills for the layer a product reaches once it has users: Stripe subscription billing, crypto payments, ad and conversion tracking under Consent Mode v2, Google sign-in and the wider Google auth surface, and Core Web Vitals. This package is the installer CLI.",
5
5
  "bin": {
6
6
  "sheleg-dev": "bin/sheleg-dev.js"
@@ -43,6 +43,8 @@
43
43
  "skill"
44
44
  ],
45
45
  "scripts": {
46
- "test": "python3 test/validate.py && node test/moneygate_test.js && node test/fixtures_test.js"
46
+ "test": "python3 test/validate.py && node test/moneygate_test.js && node test/fixtures_test.js",
47
+ "test:negatives": "python3 test/negatives.py",
48
+ "test:all": "python3 test/validate.py && node test/moneygate_test.js && node test/fixtures_test.js && python3 test/negatives.py"
47
49
  }
48
50
  }
@@ -1,8 +1,9 @@
1
1
  {
2
+ "$schema": "https://json.schemastore.org/claude-code-plugin.json",
2
3
  "name": "sheleg-dev",
3
4
  "displayName": "Sheleg Dev",
4
- "description": "The integration layer a product needs once it has users: Stripe subscription billing reconciled into your own database, crypto payments that survive over-payment and duplicate webhooks, ad and conversion tracking under Consent Mode v2, Google sign-in with the account-linking guards, the wider Google auth surface, and Core Web Vitals work that moves the score.",
5
- "version": "0.7.0",
5
+ "description": "The integration layer a product needs once it has users: Stripe subscription billing reconciled into your own database, crypto payments that survive over-payment and duplicate webhooks, ad and conversion tracking under Consent Mode v2, Google sign-in with the account-linking guards, the wider Google auth surface, and Core Web Vitals work that moves the score, and Sentry error tracking wired so it does not forward your own credentials to a third party and so a stack trace names the commit that caused it.",
6
+ "version": "0.9.0",
6
7
  "author": {
7
8
  "name": "ssheleg",
8
9
  "url": "https://x.com/sshlg93"
@@ -23,6 +24,9 @@
23
24
  "oauth2",
24
25
  "core-web-vitals",
25
26
  "claude-code",
26
- "cursor"
27
+ "cursor",
28
+ "sentry",
29
+ "error-tracking",
30
+ "observability"
27
31
  ]
28
32
  }
@@ -3,12 +3,13 @@
3
3
  **Copy this whole directory into your repository.** It is the executable half of what
4
4
  `SKILL.md` → *E-commerce* and `references/meta-linkedin.md` →
5
5
  *Deduplication with the Conversions API* state in prose: the three payloads one purchase
6
- produces, six assertions a correct emitter must pass, and a self-test that breaks the
7
- emitter one rule at a time so you can watch every assertion fail.
6
+ produces, six invariants a correct emitter must hold — 12 assertions between them — and a
7
+ self-test that breaks the emitter one rule at a time so you can watch each assertion fail
8
+ on its own.
8
9
 
9
10
  ```bash
10
- node assert-dedup-contract.mjs # 6 assertions against the reference emitter
11
- node assert-dedup-contract.mjs --self-test # break one rule at a time; every assertion must go red
11
+ node assert-dedup-contract.mjs # 6 invariants / 12 assertions
12
+ node assert-dedup-contract.mjs --self-test # break one rule at a time; EACH ASSERTION must go red
12
13
  ```
13
14
 
14
15
  No dependencies, no network, **no access token anywhere in this directory** — the sink