opencode-anthropic-fix 0.0.14

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.
@@ -0,0 +1,495 @@
1
+ {
2
+ "manifest_version": "2.0",
3
+ "project": {
4
+ "id": "anti_fragile_contract_watcher",
5
+ "name": "Anti-Fragile Contract-Driven Upstream Watcher (Simplified)",
6
+ "repo": "marco-jardim/opencode-auth-fix",
7
+ "upstream": {
8
+ "npm_package": "@anthropic-ai/claude-code",
9
+ "trigger_event_type": "claude-contract-changed"
10
+ },
11
+ "goals": [
12
+ "Detect upstream contract changes (not cosmetic refactors)",
13
+ "Generate patch via template engine with LLM fallback",
14
+ "Run unit + contract + E2E tests",
15
+ "Create idempotent PR, auto-merge on green gates",
16
+ "Publish new npm version idempotently",
17
+ "Guarantee eventual convergence via reconciliation and retries"
18
+ ],
19
+ "design_decisions": [
20
+ "GitHub Actions only — no external infra (Cloudflare, Durable Objects, KV)",
21
+ "State persisted as JSON artifact in GitHub Actions or branch-stored file",
22
+ "Template-based patching for known contract fields; LLM synth as fallback only",
23
+ "Concurrency control via GitHub Actions concurrency groups (no distributed locks)",
24
+ "npm publish gated by manual approval or @next tag first"
25
+ ]
26
+ },
27
+ "execution_policy": {
28
+ "allow_parallel_within_phase": true,
29
+ "max_parallel_tasks": 3,
30
+ "require_phase_qa_before_advance": true,
31
+ "default_retry_policy": {
32
+ "max_attempts": 3,
33
+ "backoff": "exponential",
34
+ "base_delay_seconds": 60,
35
+ "max_delay_seconds": 900
36
+ },
37
+ "escalation_policy": {
38
+ "on_repeated_failure_attempts": 3,
39
+ "escalate_to": "human_review_required",
40
+ "dead_letter_after_attempts": 6
41
+ }
42
+ },
43
+ "global_constraints": [
44
+ "Contract-driven detection only: do not trigger pipeline on cosmetic or internal refactors unless contract changes.",
45
+ "Idempotency required for all handlers: safe to run multiple times without duplicating PRs or publishes.",
46
+ "Deterministic branch naming: auto/claude-<upstreamVersion>.",
47
+ "npm publish must be idempotent: check if version exists before publishing.",
48
+ "State transitions must be monotonic, persisted as artifact, and protected by GitHub concurrency groups.",
49
+ "All E2E tests must be deterministic and must not depend on external network services.",
50
+ "No Cloudflare, no external databases, no paid infra beyond GitHub Actions minutes."
51
+ ],
52
+ "state_machine": {
53
+ "entity_key": "upstreamVersion:contractHash",
54
+ "storage": "GitHub Actions artifact (state.json) OR branch file (.afcw/state.json)",
55
+ "concurrency": "GitHub Actions concurrency group per entity_key",
56
+ "states": [
57
+ "NOT_OBSERVED",
58
+ "DETECTED",
59
+ "DISPATCHED",
60
+ "PR_CREATED",
61
+ "PR_APPROVED",
62
+ "MERGED",
63
+ "PUBLISHED",
64
+ "FAILED_RETRYABLE",
65
+ "DEAD_LETTER"
66
+ ],
67
+ "terminal_states": ["PUBLISHED", "DEAD_LETTER"],
68
+ "invariants": [
69
+ "PUBLISHED implies MERGED",
70
+ "MERGED implies PR_CREATED",
71
+ "PR_CREATED implies DISPATCHED",
72
+ "At most one PUBLISHED per entity_key",
73
+ "DEAD_LETTER is terminal"
74
+ ],
75
+ "transitions": [
76
+ { "from": "NOT_OBSERVED", "event": "DETECTED", "to": "DETECTED" },
77
+ { "from": "DETECTED", "event": "DISPATCH_OK", "to": "DISPATCHED" },
78
+ { "from": "DETECTED", "event": "DISPATCH_FAIL", "to": "FAILED_RETRYABLE" },
79
+ { "from": "DISPATCHED", "event": "PR_CREATED", "to": "PR_CREATED" },
80
+ { "from": "DISPATCHED", "event": "PR_CREATION_FAIL", "to": "FAILED_RETRYABLE" },
81
+ { "from": "PR_CREATED", "event": "REVIEW_APPROVED", "to": "PR_APPROVED" },
82
+ { "from": "PR_CREATED", "event": "REVIEW_REJECTED", "to": "FAILED_RETRYABLE" },
83
+ { "from": "PR_APPROVED", "event": "MERGED", "to": "MERGED" },
84
+ { "from": "PR_APPROVED", "event": "MERGE_FAIL", "to": "FAILED_RETRYABLE" },
85
+ { "from": "MERGED", "event": "PUBLISH_OK", "to": "PUBLISHED" },
86
+ { "from": "MERGED", "event": "PUBLISH_FAIL", "to": "FAILED_RETRYABLE" },
87
+ { "from": "FAILED_RETRYABLE", "event": "RETRY", "to": "DETECTED" },
88
+ { "from": "FAILED_RETRYABLE", "event": "RETRY_LIMIT_EXCEEDED", "to": "DEAD_LETTER" }
89
+ ]
90
+ },
91
+ "providers": {
92
+ "github": {
93
+ "resources": [
94
+ "GitHub Actions (scheduled cron + repository_dispatch + pull_request + push triggers)",
95
+ "Concurrency groups for distributed locking",
96
+ "Artifacts for state persistence",
97
+ "Branch protection + required status checks"
98
+ ],
99
+ "secrets": [
100
+ "GITHUB_TOKEN (default, for PR/dispatch)",
101
+ "NPM_TOKEN (for publish)",
102
+ "FIREWORKS_API_KEY (for LLM fallback only)"
103
+ ]
104
+ },
105
+ "npm": {
106
+ "resources": ["Package publish rights for target package", "Automation token NPM_TOKEN"]
107
+ },
108
+ "llm": {
109
+ "provider": "fireworks",
110
+ "usage": "fallback only — template patching is primary",
111
+ "models": {
112
+ "synth": "Kimi 2.5 (via Fireworks)",
113
+ "review": "Kimi 2.5 (via Fireworks) OR different model for independence"
114
+ },
115
+ "constraints": [
116
+ "Synth outputs unified diff only",
117
+ "Reviewer outputs JSON verdict only",
118
+ "Only invoked when template patcher cannot handle the contract diff"
119
+ ]
120
+ }
121
+ },
122
+ "tasks": [
123
+ {
124
+ "id": "T00",
125
+ "phase": "P0",
126
+ "name": "Repository baseline audit and contract schema definition",
127
+ "difficulty": "medium",
128
+ "dependencies": [],
129
+ "outputs": [
130
+ "docs/afcw/architecture.md",
131
+ "docs/afcw/paths-allowlist.json",
132
+ "docs/afcw/contract-schema.json",
133
+ "docs/afcw/test-plan.md"
134
+ ],
135
+ "definition_of_done": [
136
+ "File allowlist defined (paths + globs for automation-editable files)",
137
+ "Contract schema JSON drafted (headers, betas, endpoints, system blocks, metadata)",
138
+ "Test plan includes unit/contract/e2e with determinism constraints",
139
+ "Architecture doc includes state machine summary and reconciliation approach"
140
+ ],
141
+ "qa_gate": ["Allowlist excludes .github/workflows by default", "Contract schema covers all known mimese fields"]
142
+ },
143
+ {
144
+ "id": "T01",
145
+ "phase": "P1",
146
+ "name": "Implement npm metadata fetcher + tarball extraction + cli.mjs extractor",
147
+ "difficulty": "medium",
148
+ "dependencies": ["T00"],
149
+ "outputs": ["scripts/fetch-upstream.mjs", "scripts/extract-cli.mjs", "tests/extract-cli.test.mjs"],
150
+ "definition_of_done": [
151
+ "Given package name, fetches dist-tags.latest and tarball URL",
152
+ "Downloads tarball and extracts cli.mjs (fixed path + heuristic fallback)",
153
+ "Handles scoped package encoding and common tarball layouts"
154
+ ],
155
+ "qa_gate": ["Unit tests with fixture tarballs cover multiple path patterns", "Graceful error on missing cli.mjs"]
156
+ },
157
+ {
158
+ "id": "T02",
159
+ "phase": "P1",
160
+ "name": "Implement contract extractor and canonical hashing",
161
+ "difficulty": "heavy",
162
+ "dependencies": ["T01", "T00"],
163
+ "outputs": ["scripts/extract-contract.mjs", "scripts/canonicalize.mjs", "tests/contract-extractor.test.mjs"],
164
+ "definition_of_done": [
165
+ "extract_contract(cliText) returns {contract, contractHash}",
166
+ "Deterministic canonical JSON (sorted keys, sorted arrays, normalized strings)",
167
+ "Same cliText => same contractHash always",
168
+ "Refactor-only changes => same contractHash (validated by fixtures)",
169
+ "Meaningful changes (headers/betas/endpoints) => different contractHash"
170
+ ],
171
+ "qa_gate": ["Snapshot tests for canonical JSON", "At least 1 'no change' and 1 'contract change' fixture"]
172
+ },
173
+ {
174
+ "id": "T03",
175
+ "phase": "P1",
176
+ "name": "Implement contract diff engine with severity scoring",
177
+ "difficulty": "medium",
178
+ "dependencies": ["T02"],
179
+ "outputs": ["scripts/diff-contract.mjs", "tests/diff-contract.test.mjs"],
180
+ "definition_of_done": [
181
+ "diff_contract(old, new) => {changed, severity: low|medium|high, diff}",
182
+ "No change => changed: false",
183
+ "Critical field change (endpoints, auth) => severity: high"
184
+ ],
185
+ "qa_gate": ["Tests cover all severity classifications"]
186
+ },
187
+ {
188
+ "id": "T04",
189
+ "phase": "P2",
190
+ "name": "Implement state manager (JSON artifact + concurrency group)",
191
+ "difficulty": "medium",
192
+ "dependencies": ["T00"],
193
+ "outputs": ["scripts/state-manager.mjs", "tests/state-machine.test.mjs"],
194
+ "definition_of_done": [
195
+ "State record CRUD via GitHub Actions artifact (state.json)",
196
+ "Transitions validated against allowed transitions table",
197
+ "Retry increment and dead-letter logic implemented",
198
+ "Double-apply same event is idempotent"
199
+ ],
200
+ "qa_gate": ["Table-driven tests for all transitions", "Idempotent re-application verified"]
201
+ },
202
+ {
203
+ "id": "T05",
204
+ "phase": "P2",
205
+ "name": "Implement scheduled watcher workflow (detect + diff + dispatch)",
206
+ "difficulty": "heavy",
207
+ "dependencies": ["T01", "T02", "T03", "T04"],
208
+ "outputs": [".github/workflows/watch-upstream.yml", "scripts/watcher.mjs"],
209
+ "definition_of_done": [
210
+ "Cron-triggered GitHub Actions workflow (every 30 min)",
211
+ "Fetches latest upstream, extracts contract, compares hash",
212
+ "No contract change => no dispatch, exits cleanly",
213
+ "Contract change => creates state record DETECTED, sends repository_dispatch, transitions to DISPATCHED",
214
+ "Failures => FAILED_RETRYABLE with retry count incremented"
215
+ ],
216
+ "qa_gate": ["Dry-run mode for local verification", "Concurrency group prevents parallel runs"]
217
+ },
218
+ {
219
+ "id": "T06",
220
+ "phase": "P2",
221
+ "name": "Implement reconciliation workflow (inspect PR/npm status, advance state)",
222
+ "difficulty": "heavy",
223
+ "dependencies": ["T04", "T05"],
224
+ "outputs": [".github/workflows/reconcile.yml", "scripts/reconciler.mjs", "tests/reconciler.test.mjs"],
225
+ "definition_of_done": [
226
+ "Cron-triggered (every 15 min), inspects non-terminal entities",
227
+ "DISPATCHED + PR exists => PR_CREATED",
228
+ "PR_CREATED + merged => MERGED",
229
+ "MERGED + npm version published => PUBLISHED",
230
+ "FAILED_RETRYABLE retry until limit then DEAD_LETTER",
231
+ "Safe when GitHub/npm temporarily unavailable"
232
+ ],
233
+ "qa_gate": ["Simulated failure recovery scenario tests", "Multiple reconciliation runs are idempotent"]
234
+ },
235
+ {
236
+ "id": "T07",
237
+ "phase": "P3",
238
+ "name": "Implement template patcher + LLM synth fallback",
239
+ "difficulty": "heavy",
240
+ "dependencies": ["T03", "T00"],
241
+ "outputs": [
242
+ "scripts/template-patcher.mjs",
243
+ "scripts/llm-synth.mjs",
244
+ "docs/afcw/llm-prompts.md",
245
+ "tests/template-patcher.test.mjs"
246
+ ],
247
+ "definition_of_done": [
248
+ "Template patcher handles known field changes (headers, betas, endpoints, system blocks)",
249
+ "LLM synth invoked only when template patcher returns unsupported diff",
250
+ "Both produce unified diff that only touches allowlist paths",
251
+ "Diff scope validator blocks forbidden files"
252
+ ],
253
+ "qa_gate": [
254
+ "Template patcher covers ≥80% of historical contract diffs",
255
+ "LLM synth tested with mocked Fireworks response"
256
+ ]
257
+ },
258
+ {
259
+ "id": "T08",
260
+ "phase": "P3",
261
+ "name": "Implement LLM reviewer + CI pipeline workflow",
262
+ "difficulty": "medium",
263
+ "dependencies": ["T07"],
264
+ "outputs": ["scripts/llm-review.mjs", "scripts/verify-review.mjs", ".github/workflows/ci.yml"],
265
+ "definition_of_done": [
266
+ "Reviewer outputs JSON: {approve, risk_score, notes[]}",
267
+ "Invalid reviewer output => reject (fail-closed)",
268
+ "CI workflow runs: unit tests, contract tests, e2e tests, lint, review verdict",
269
+ "All checks required for merge"
270
+ ],
271
+ "qa_gate": ["Golden tests for parsing and fail-closed behavior", "CI workflow syntax validated"]
272
+ },
273
+ {
274
+ "id": "T09",
275
+ "phase": "P3",
276
+ "name": "Implement idempotent PR creation + contract/e2e tests",
277
+ "difficulty": "medium",
278
+ "dependencies": ["T07", "T08"],
279
+ "outputs": [
280
+ "scripts/create-or-update-pr.mjs",
281
+ ".github/workflows/on-dispatch.yml",
282
+ "tests/contract.test.mjs",
283
+ "tests/e2e-handshake.test.mjs",
284
+ "tests/mocks/oauth-server.mjs",
285
+ "tests/mocks/api-server.mjs"
286
+ ],
287
+ "definition_of_done": [
288
+ "Branch deterministic: auto/claude-<version>",
289
+ "If branch/PR exists: update; else create",
290
+ "Running twice does not create duplicates",
291
+ "Contract test: impl contract matches upstream contract",
292
+ "E2E test: mock OAuth + API, validate headers/payload/system blocks",
293
+ "E2E is fully offline (no external network)"
294
+ ],
295
+ "qa_gate": ["Dry-run mode for local verification", "Removing required header makes E2E fail"]
296
+ },
297
+ {
298
+ "id": "T10",
299
+ "phase": "P4",
300
+ "name": "Implement release automation + npm publish idempotency",
301
+ "difficulty": "medium",
302
+ "dependencies": ["T09"],
303
+ "outputs": [".github/workflows/release.yml", "scripts/publish-if-needed.mjs"],
304
+ "definition_of_done": [
305
+ "Triggered on push to main after merge",
306
+ "Checks if version already exists on npm before publish",
307
+ "Publishes as @next tag first (promote to @latest via manual step or delay)",
308
+ "Creates GitHub release + git tag",
309
+ "If version already published, exits cleanly and marks PUBLISHED"
310
+ ],
311
+ "qa_gate": ["Dry-run mode documented and tested"]
312
+ },
313
+ {
314
+ "id": "T11",
315
+ "phase": "P5",
316
+ "name": "End-to-end validation + alerting + observability",
317
+ "difficulty": "heavy",
318
+ "dependencies": ["T05", "T06", "T09", "T10"],
319
+ "outputs": ["docs/afcw/e2e-validation.md", "docs/afcw/observability.md", "scripts/alert-dead-letter.mjs"],
320
+ "definition_of_done": [
321
+ "Failure injection scenarios: dispatch fail, LLM fail, CI fail, publish fail",
322
+ "All scenarios converge to terminal state via reconciliation",
323
+ "DEAD_LETTER triggers GitHub issue creation (alert)",
324
+ "State transitions logged with entity_key in workflow logs",
325
+ "At least 5 scenario runs documented with outcomes"
326
+ ],
327
+ "qa_gate": [
328
+ "No scenario leaves system in non-terminal state without retries/reconciliation",
329
+ "Alert test verified"
330
+ ]
331
+ }
332
+ ],
333
+ "phase_gates": [
334
+ {
335
+ "phase": "P0",
336
+ "gate_name": "Baseline Gate",
337
+ "must_pass": ["T00"],
338
+ "evidence": ["Contract schema defined", "Allowlist defined", "Test plan drafted"]
339
+ },
340
+ {
341
+ "phase": "P1",
342
+ "gate_name": "Foundation Gate",
343
+ "must_pass": ["T01", "T02", "T03"],
344
+ "evidence": [
345
+ "Scripts can fetch npm metadata, tarball, extract cli.mjs",
346
+ "Contract extractor stable and tested",
347
+ "Diff engine severity validated"
348
+ ]
349
+ },
350
+ {
351
+ "phase": "P2",
352
+ "gate_name": "Orchestration Gate",
353
+ "must_pass": ["T04", "T05", "T06"],
354
+ "evidence": [
355
+ "State machine enforced with concurrency groups",
356
+ "Watcher detects and dispatches correctly",
357
+ "Reconciler advances states based on PR/npm inspection"
358
+ ]
359
+ },
360
+ {
361
+ "phase": "P3",
362
+ "gate_name": "Pipeline Gate",
363
+ "must_pass": ["T07", "T08", "T09"],
364
+ "evidence": [
365
+ "Template patcher handles known diffs",
366
+ "LLM synth fallback produces scoped patch",
367
+ "LLM review gates merges",
368
+ "PR idempotent flow works",
369
+ "Contract + E2E tests pass"
370
+ ]
371
+ },
372
+ {
373
+ "phase": "P4",
374
+ "gate_name": "Release Gate",
375
+ "must_pass": ["T10"],
376
+ "evidence": ["npm publish idempotent on merge", "Release notes/tag produced"]
377
+ },
378
+ {
379
+ "phase": "P5",
380
+ "gate_name": "System Acceptance Gate",
381
+ "must_pass": ["T11"],
382
+ "evidence": [
383
+ "Failure injection scenarios converge to terminal state",
384
+ "Alerting works",
385
+ "No stuck partial states"
386
+ ]
387
+ }
388
+ ],
389
+ "examples": {
390
+ "watcher_cron": "*/30 * * * *",
391
+ "reconciler_cron": "*/15 * * * *",
392
+ "entity_key_example": "1.4.7:8f23ab9c (upstreamVersion:contractHash)",
393
+ "branch_name_example": "auto/claude-1.4.7",
394
+ "state_artifact_example": {
395
+ "entities": {
396
+ "1.4.7:8f23ab9c": {
397
+ "state": "PR_CREATED",
398
+ "retries": 0,
399
+ "lastEvent": "PR_CREATED",
400
+ "createdAt": "2026-02-20T15:00:00Z",
401
+ "updatedAt": "2026-02-20T15:05:00Z",
402
+ "prNumber": 42,
403
+ "branchName": "auto/claude-1.4.7"
404
+ }
405
+ }
406
+ },
407
+ "repository_dispatch_payload_example": {
408
+ "event_type": "claude-contract-changed",
409
+ "client_payload": {
410
+ "upstreamVersion": "1.4.7",
411
+ "contractHash": "8f23ab9c",
412
+ "severity": "high",
413
+ "diffSummary": { "oauth.tokenEndpoint": { "from": "A", "to": "B" } }
414
+ }
415
+ },
416
+ "review_verdict_example": {
417
+ "approve": true,
418
+ "risk_score": 2,
419
+ "notes": ["Patch updates token endpoint and required header set; no scope creep."]
420
+ }
421
+ },
422
+ "troubleshooting": [
423
+ {
424
+ "symptom": "New npm version published but no workflow triggered",
425
+ "likely_causes": [
426
+ "Watcher cron disabled or workflow file missing",
427
+ "Contract extractor failed (cli.mjs path changed) and marked FAILED_RETRYABLE repeatedly",
428
+ "GitHub Actions runner quota exhausted"
429
+ ],
430
+ "checks": [
431
+ "Check Actions tab for recent watch-upstream runs",
432
+ "Download state.json artifact and inspect latest entity records",
433
+ "Verify GITHUB_TOKEN permissions include contents:write"
434
+ ],
435
+ "fixes": [
436
+ "Adjust cli.mjs extraction heuristics in scripts/extract-cli.mjs",
437
+ "Manually trigger workflow_dispatch to force re-check",
438
+ "Check Actions billing/quota"
439
+ ]
440
+ },
441
+ {
442
+ "symptom": "PR duplicates created",
443
+ "likely_causes": ["Branch naming not deterministic", "PR existence check missing or race condition"],
444
+ "fixes": [
445
+ "Enforce branch name auto/claude-<version>",
446
+ "Search PR by head ref before create in scripts/create-or-update-pr.mjs",
447
+ "Ensure concurrency group is active for on-dispatch workflow"
448
+ ]
449
+ },
450
+ {
451
+ "symptom": "System stuck in MERGED but not PUBLISHED",
452
+ "likely_causes": [
453
+ "npm publish failed silently",
454
+ "release.yml not triggered on push to main",
455
+ "NPM_TOKEN missing/invalid/expired"
456
+ ],
457
+ "fixes": [
458
+ "Verify release.yml triggers on push main",
459
+ "Check scripts/publish-if-needed.mjs dry-run output",
460
+ "Rotate NPM_TOKEN",
461
+ "Reconciler should advance to PUBLISHED if npm already has the version"
462
+ ]
463
+ },
464
+ {
465
+ "symptom": "Template patcher fails on unknown contract diff shape",
466
+ "likely_causes": ["Upstream introduced new contract fields not in template map", "Contract schema out of date"],
467
+ "fixes": [
468
+ "LLM synth fallback should activate automatically",
469
+ "Update contract-schema.json and template-patcher.mjs for new field",
470
+ "Check diff severity — high severity + template fail should alert"
471
+ ]
472
+ },
473
+ {
474
+ "symptom": "LLM synth produces invalid or out-of-scope patch",
475
+ "likely_causes": ["Prompt drift or model regression", "Context too large and truncated poorly"],
476
+ "fixes": [
477
+ "LLM reviewer should reject (fail-closed)",
478
+ "Review and update prompt in docs/afcw/llm-prompts.md",
479
+ "Check diff scope validator is blocking forbidden files"
480
+ ]
481
+ },
482
+ {
483
+ "symptom": "Entity stuck in DEAD_LETTER",
484
+ "likely_causes": [
485
+ "Repeated failures exhausted retry limit",
486
+ "Fundamental incompatibility requires manual intervention"
487
+ ],
488
+ "fixes": [
489
+ "Inspect GitHub issue created by alert-dead-letter.mjs",
490
+ "Manually review contract diff and create PR",
491
+ "After manual fix, update state.json artifact to PUBLISHED"
492
+ ]
493
+ }
494
+ ]
495
+ }
@@ -0,0 +1,69 @@
1
+ import js from "@eslint/js";
2
+
3
+ export default [
4
+ js.configs.recommended,
5
+ {
6
+ languageOptions: {
7
+ ecmaVersion: 2025,
8
+ sourceType: "module",
9
+ globals: {
10
+ // Node.js globals
11
+ console: "readonly",
12
+ process: "readonly",
13
+ setTimeout: "readonly",
14
+ clearTimeout: "readonly",
15
+ setInterval: "readonly",
16
+ clearInterval: "readonly",
17
+ URL: "readonly",
18
+ URLSearchParams: "readonly",
19
+ AbortController: "readonly",
20
+ AbortSignal: "readonly",
21
+ fetch: "readonly",
22
+ Request: "readonly",
23
+ Response: "readonly",
24
+ Headers: "readonly",
25
+ ReadableStream: "readonly",
26
+ TextDecoder: "readonly",
27
+ TextEncoder: "readonly",
28
+ globalThis: "readonly",
29
+ structuredClone: "readonly",
30
+ btoa: "readonly",
31
+ atob: "readonly",
32
+ crypto: "readonly",
33
+ Buffer: "readonly",
34
+ },
35
+ },
36
+ rules: {
37
+ // Relax for our codebase style
38
+ "no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
39
+ "no-console": "off", // CLI tool — console is expected
40
+ "no-constant-condition": ["error", { checkLoops: false }], // while(true) is intentional
41
+ "prefer-const": "error",
42
+ "no-var": "error",
43
+ eqeqeq: ["error", "smart"], // allow == null
44
+ },
45
+ },
46
+ {
47
+ // Test files get additional globals from vitest
48
+ files: ["**/*.test.mjs"],
49
+ languageOptions: {
50
+ globals: {
51
+ describe: "readonly",
52
+ it: "readonly",
53
+ expect: "readonly",
54
+ vi: "readonly",
55
+ beforeEach: "readonly",
56
+ afterEach: "readonly",
57
+ beforeAll: "readonly",
58
+ afterAll: "readonly",
59
+ },
60
+ },
61
+ rules: {
62
+ // Tests often have unused vars from destructuring mocks
63
+ "no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
64
+ },
65
+ },
66
+ {
67
+ ignores: ["dist/", "node_modules/", ".mitm/"],
68
+ },
69
+ ];