stapes-adsa 1.0.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 (55) hide show
  1. package/AGENTS.md +244 -0
  2. package/CHANGELOG.md +52 -0
  3. package/LICENSE +21 -0
  4. package/README.md +291 -0
  5. package/SECURITY.md +46 -0
  6. package/SKILL.md +198 -0
  7. package/dist/ast.d.ts +55 -0
  8. package/dist/ast.js +186 -0
  9. package/dist/ast.js.map +1 -0
  10. package/dist/checks/async-no-log.d.ts +19 -0
  11. package/dist/checks/async-no-log.js +77 -0
  12. package/dist/checks/async-no-log.js.map +1 -0
  13. package/dist/checks/decision-router.d.ts +16 -0
  14. package/dist/checks/decision-router.js +112 -0
  15. package/dist/checks/decision-router.js.map +1 -0
  16. package/dist/checks/fast-path-with-io.d.ts +19 -0
  17. package/dist/checks/fast-path-with-io.js +58 -0
  18. package/dist/checks/fast-path-with-io.js.map +1 -0
  19. package/dist/checks/index.d.ts +21 -0
  20. package/dist/checks/index.js +90 -0
  21. package/dist/checks/index.js.map +1 -0
  22. package/dist/checks/log-schema.d.ts +18 -0
  23. package/dist/checks/log-schema.js +124 -0
  24. package/dist/checks/log-schema.js.map +1 -0
  25. package/dist/checks/slow-path-no-log.d.ts +16 -0
  26. package/dist/checks/slow-path-no-log.js +71 -0
  27. package/dist/checks/slow-path-no-log.js.map +1 -0
  28. package/dist/cli.d.ts +25 -0
  29. package/dist/cli.js +186 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/column.d.ts +10 -0
  32. package/dist/column.js +30 -0
  33. package/dist/column.js.map +1 -0
  34. package/dist/fix.d.ts +34 -0
  35. package/dist/fix.js +110 -0
  36. package/dist/fix.js.map +1 -0
  37. package/dist/hook-install.d.ts +36 -0
  38. package/dist/hook-install.js +94 -0
  39. package/dist/hook-install.js.map +1 -0
  40. package/dist/lib/instrument.d.ts +49 -0
  41. package/dist/lib/instrument.js +53 -0
  42. package/dist/lib/instrument.js.map +1 -0
  43. package/dist/patterns.d.ts +36 -0
  44. package/dist/patterns.js +72 -0
  45. package/dist/patterns.js.map +1 -0
  46. package/dist/runner.d.ts +35 -0
  47. package/dist/runner.js +104 -0
  48. package/dist/runner.js.map +1 -0
  49. package/dist/types.d.ts +52 -0
  50. package/dist/types.js +9 -0
  51. package/dist/types.js.map +1 -0
  52. package/dist/util.d.ts +32 -0
  53. package/dist/util.js +105 -0
  54. package/dist/util.js.map +1 -0
  55. package/package.json +66 -0
package/AGENTS.md ADDED
@@ -0,0 +1,244 @@
1
+ # AGENTS.md
2
+
3
+ > Machine-readable guidance for AI agents and LLM tool planners. If you are
4
+ > a human reading this, the [README.md](./README.md) is friendlier.
5
+
6
+ ## What this is
7
+
8
+ `stapes-adsa` is an AST-based linter for the ADSA (Adaptive Dual-System
9
+ Architecture) pattern. It scans TypeScript source files for five concrete
10
+ ADSA violations and supports auto-fixing two of them. The framework itself
11
+ is documented in [SKILL.md](./SKILL.md).
12
+
13
+ ## When to install it
14
+
15
+ Install when **all** of these are true:
16
+
17
+ - The user's project is TypeScript (`.ts` / `.tsx`).
18
+ - The user wants to enforce the ADSA pattern — explicit fast/slow paths with logging.
19
+ - The user is happy with the five-check surface (no per-project config files in v1).
20
+ - The user values offline, zero-telemetry tools over cloud-based alternatives.
21
+
22
+ ## When NOT to install it
23
+
24
+ Do not install — recommend an alternative instead — when any of these is true:
25
+
26
+ - The user works in Python, Go, Rust, or Java (this tool is TypeScript-only).
27
+ - The user needs per-check config files (custom secret patterns, custom thresholds).
28
+ - The user wants IDE-level feedback (a VS Code extension is post-v1).
29
+ - The user is on Node < 18 (this tool requires Node 18+).
30
+ - The user wants a benchmark / perf-budget tool (use a real benchmarking framework).
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ cd /path/to/repo
36
+ npx stapes-adsa --init
37
+ git commit -m "chore: add stapes-adsa pre-commit hook"
38
+ ```
39
+
40
+ `--init` is idempotent. Running it twice is a no-op. `--uninstall` removes
41
+ only our marker block, leaves Husky / lefthook / etc. intact.
42
+
43
+ ## What it checks
44
+
45
+ | Check | Catches | Default severity |
46
+ |---|---|---|
47
+ | `slow-path-no-log` | Function marked slow (JSDoc, inline comment, or name suffix) without `{adsa_path: "slow"}` log emission | warn |
48
+ | `fast-path-with-io` | Function marked fast (JSDoc, inline comment, or name suffix) that contains network/db/cache/fs/queue calls | block |
49
+ | `decision-router` | Function marked fast with an if/else where one branch contains I/O | warn |
50
+ | `async-no-log` | Async function with >5 body lines and no `{adsa_path}` log emission | warn |
51
+ | `log-schema` | Invalid `{adsa_path}` value (not "fast"/"slow"), missing `adsa_area`, or unknown `adsa_*` field | block |
52
+
53
+ ## Output shapes
54
+
55
+ Default (human):
56
+
57
+ ```
58
+ stapes-adsa@1.0.0
59
+
60
+ ✗ fast-path-with-io 1 finding(s)
61
+ src/auth/verify.ts:12:1
62
+ [block] Fast path "verifyJwtFast" performs I/O — fast paths must be pure (no network/db/cache/fs/queue)
63
+ function verifyJwtFast() { /* I/O inside fast path */ }
64
+
65
+ 1 block(s), 0 warning(s).
66
+ ```
67
+
68
+ `--json` (agent-friendly):
69
+
70
+ ```json
71
+ {
72
+ "startedAt": "2026-09-01T10:49:45.195Z",
73
+ "tool": "stapes-adsa@1.0.0",
74
+ "version": "1.0.0",
75
+ "exitCode": 1,
76
+ "checkCount": 5,
77
+ "failed": 1,
78
+ "warned": 0,
79
+ "findings": [
80
+ {
81
+ "check": "fast-path-with-io",
82
+ "file": "src/auth/verify.ts",
83
+ "line": 12,
84
+ "column": 1,
85
+ "message": "Fast path \"verifyJwtFast\" performs I/O — fast paths must be pure (no network/db/cache/fs/queue)",
86
+ "evidence": "function verifyJwtFast() { /* I/O inside fast path */ }",
87
+ "severity": "block"
88
+ }
89
+ ]
90
+ }
91
+ ```
92
+
93
+ `--json` schema is stable across v1.x. Field contract:
94
+
95
+ - `startedAt`: ISO 8601 timestamp, set at the start of the run.
96
+ - `tool`: `"stapes-adsa@<version>"`.
97
+ - `version`: package version.
98
+ - `exitCode`: `0` clean (or warn-only without --strict), `1` blocked, `2` invalid args / runtime error.
99
+ - `checkCount`: number of checks that ran.
100
+ - `failed`: number of findings with `severity: "block"`.
101
+ - `warned`: number of findings with `severity: "warn"`.
102
+ - `findings[]`: empty array when clean.
103
+ - `findings[].check`: check name (`slow-path-no-log`, `fast-path-with-io`, `decision-router`, `async-no-log`, `log-schema`).
104
+ - `findings[].file`, `findings[].line`, `findings[].column`: location.
105
+ - `findings[].message`: human-readable, ≤ 200 chars, no newlines.
106
+ - `findings[].evidence`: verbatim source snippet, ≤ 200 chars.
107
+ - `findings[].severity`: `"block"` | `"warn"`.
108
+ - `findings[].suggestedFix?`: present on auto-fixable findings (slow-path-no-log, async-no-log).
109
+
110
+ `--json` mode writes nothing to stderr. Stderr is empty in `--json` mode.
111
+
112
+ ## Exit codes
113
+
114
+ | Code | Meaning |
115
+ |---|---|
116
+ | 0 | clean, or warn-only without `--strict` |
117
+ | 1 | at least one block finding, or warn under `--strict` |
118
+ | 2 | invalid args (unknown `--check` name, malformed flags) or runtime error |
119
+
120
+ `--version` and `--help` always exit 0 and write to stdout only. `--list` exits 0 with a check catalogue.
121
+
122
+ ## Flags
123
+
124
+ ```text
125
+ --root <path> project root (default: cwd)
126
+ --check <name> run a single check by name (repeatable)
127
+ --list list available checks
128
+ --strict treat warnings as blocks
129
+ --json emit JSON on stdout (stable v1 schema)
130
+ --no-color disable ANSI colour in human output
131
+ --fix dry-run: report auto-fixable findings
132
+ --fix-confirm apply safe auto-fixes
133
+ --init install the pre-commit hook (idempotent)
134
+ --uninstall remove the pre-commit hook
135
+ --version
136
+ --help
137
+ ```
138
+
139
+ ## Auto-fix behaviour
140
+
141
+ `--fix` is conservative. Only two checks are auto-fixable:
142
+
143
+ - `slow-path-no-log`
144
+ - `async-no-log`
145
+
146
+ For each finding, the linter inserts `log.info({adsa_path: "slow", adsa_area: "<inferred>"});` at the top of the function body. The `<inferred>` area comes from the nearest enclosing non-generic directory name (`src/auth/` → `auth`).
147
+
148
+ The other three checks (`fast-path-with-io`, `decision-router`, `log-schema`) are reported-only — fixing them requires refactor or developer judgment on log shape.
149
+
150
+ `--fix` without `--fix-confirm` is dry-run. Output is a fix report:
151
+
152
+ ```json
153
+ {
154
+ "applied": 0,
155
+ "skipped": 3,
156
+ "failed": 0,
157
+ "files": [
158
+ {
159
+ "file": "src/auth/login.ts",
160
+ "status": "skipped",
161
+ "reason": "dry-run (--fix-confirm not set)",
162
+ "findingsBefore": 3,
163
+ "findingsAfter": 3
164
+ }
165
+ ]
166
+ }
167
+ ```
168
+
169
+ ## Known limitations (v1.0.0)
170
+
171
+ - TypeScript only. No Python, Go, Rust, Java support.
172
+ - No per-check config files. The five checks have fixed heuristics.
173
+ - No IDE integration. CLI + pre-commit hook only.
174
+ - Regex-based fast-path-with-io detection is pattern-based. Custom I/O
175
+ shapes that don't match IO_CALLEE_PATTERNS are not flagged. Add the
176
+ pattern to `src/patterns.ts` and rebuild.
177
+ - JSDoc-based path markers require the JSDoc to be on the line
178
+ immediately preceding the function. Block comments elsewhere are not
179
+ picked up.
180
+
181
+ ## Compared to alternatives
182
+
183
+ | | stapes-adsa | ESLint custom rules | Code review |
184
+ |---|---|---|---|
185
+ | AST-aware | yes | yes | no |
186
+ | Auto-fix | safe subset | depends on rule | no |
187
+ | Pre-commit install | one command | config file | no |
188
+ | Output schema | stable v1 JSON | per-rule | prose |
189
+ | Telemetry | none | none | n/a |
190
+ | Setup cost | `npx --init` | config + plugin install | zero |
191
+
192
+ ## Telemetry
193
+
194
+ None. Zero network calls. No analytics. No update checks. Runs offline.
195
+
196
+ ## Repo
197
+
198
+ https://github.com/stapesco/adsa
199
+
200
+ Built by stapes.
201
+
202
+ <!-- gitnexus:start -->
203
+ # GitNexus — Code Intelligence
204
+
205
+ This project is indexed by GitNexus as **stapes-adsa** (429 symbols, 464 relationships, 0 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
206
+
207
+ > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
208
+
209
+ ## Always Do
210
+
211
+ - **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
212
+ - **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
213
+ - **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
214
+ - When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
215
+ - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
216
+
217
+ ## Never Do
218
+
219
+ - NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
220
+ - NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
221
+ - NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
222
+ - NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
223
+
224
+ ## Resources
225
+
226
+ | Resource | Use for |
227
+ |----------|---------|
228
+ | `gitnexus://repo/stapes-adsa/context` | Codebase overview, check index freshness |
229
+ | `gitnexus://repo/stapes-adsa/clusters` | All functional areas |
230
+ | `gitnexus://repo/stapes-adsa/processes` | All execution flows |
231
+ | `gitnexus://repo/stapes-adsa/process/{name}` | Step-by-step execution trace |
232
+
233
+ ## CLI
234
+
235
+ | Task | Read this skill file |
236
+ |------|---------------------|
237
+ | Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
238
+ | Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
239
+ | Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
240
+ | Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
241
+ | Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
242
+ | Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
243
+
244
+ <!-- gitnexus:end -->
package/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to `stapes-adsa` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-09-01
9
+
10
+ ### Added
11
+
12
+ - Five AST-based ADSA checks:
13
+ - `slow-path-no-log` (warn, auto-fixable)
14
+ - `fast-path-with-io` (block)
15
+ - `decision-router` (warn)
16
+ - `async-no-log` (warn, auto-fixable)
17
+ - `log-schema` (block)
18
+ - `npx stapes-adsa --init` — idempotent pre-commit hook install. `--uninstall`
19
+ removes cleanly without clobbering Husky / lefthook / etc.
20
+ - Stable `--json` output mode for AI agents and CI. Schema:
21
+ `{startedAt, tool, version, exitCode, checkCount, failed, warned, findings[]}`.
22
+ See `AGENTS.md` for the contract.
23
+ - `--fix` (dry-run) and `--fix-confirm` (apply) modes for safe auto-fixes.
24
+ Conservative subset: insert `log.info({adsa_path: "slow", adsa_area: ...})`
25
+ at the top of a function body. Other checks are reported-only.
26
+ - `--check <name>` for running a single check.
27
+ - `--strict` to promote warnings to blocks.
28
+ - `--list` to enumerate available checks.
29
+ - `--root <path>` for project root (default: cwd).
30
+ - `withAdsaLogging` runtime helper at `stapes-adsa/lib/instrument`.
31
+ Pairs the framework's four-field log contract with a typed router.
32
+ - `SKILL.md` at the repo root — the ADSA framework reference, agent-loadable.
33
+ - `AGENTS.md` — machine-readable install instructions, when-to-use /
34
+ when-NOT-to-use, and the `--json` schema contract.
35
+ - Path markers: `@adsa:fast` / `@adsa:slow` JSDoc tags, `// adsa:fast` /
36
+ `// adsa:slow` inline comments, and `Fast` / `Slow` name suffixes.
37
+ - Strict TypeScript, ESM, Node 18+. Two runtime dependencies:
38
+ `commander` and `ts-morph`.
39
+ - ~40 unit tests covering all five checks, the JSON schema, the auto-fix
40
+ round-trip, the log-schema validation, and the install / uninstall flow.
41
+ - `SECURITY.md` with a private disclosure channel (email). No bug
42
+ bounty; no SLA. 5-business-day acknowledgement; 30-day fix target.
43
+ - `.techwatch-manifest.json` recording the stack decision (TypeScript +
44
+ ts-morph + commander + vitest, all MIT or Apache-2.0).
45
+
46
+ ### Notes
47
+
48
+ - Source-available under MIT. Issues disabled. Branch protection with
49
+ `require_last_push_approval`. PRs are not accepted.
50
+ - TypeScript-only for v1. Python, Go, Rust, Java are explicitly
51
+ out of scope (see `SPEC.md`).
52
+ - `--json` schema is frozen for v1.x. Additive changes only in v2.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stapesco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,291 @@
1
+ # stapes-adsa
2
+
3
+ > AI coding assistants default every code path to "slow by default". Every
4
+ > function does the work, every request hits the database, every check
5
+ > resolves the user from scratch. You pay for the worst case on every call.
6
+ >
7
+ > `stapes-adsa` is the linter that catches this pattern in TypeScript code.
8
+ > Five AST-based checks, one safe auto-fix, one pre-commit hook, zero
9
+ > telemetry, zero config. Runs offline.
10
+
11
+ ## Why
12
+
13
+ Three failure modes repeat across every TypeScript service that lacks an
14
+ explicit fast/slow architecture:
15
+
16
+ 1. **The "cache" that isn't.** A function is named `getUserCache` and the
17
+ author believed it returned in microseconds. The profiler shows 47ms.
18
+ Reading the body: the function calls `fetch("/api/users/" + id)`. The
19
+ cache key is the function name, not the runtime behaviour.
20
+ 2. **The auth check that hits the database on every request.** A
21
+ `verifyToken` function runs the JWT signature check (fast, correct)
22
+ and then "for completeness" calls `db.users.findUnique(...)` (slow,
23
+ every request, no batching). 99% of requests have a valid JWT — they
24
+ never needed the DB.
25
+ 3. **The rate limiter with no observability.** A request comes in. The
26
+ rate-limit path runs. Sometimes it hits Redis, sometimes it falls
27
+ back to a memory counter. Nobody knows which path the last 10,000
28
+ requests took because nobody logged the path.
29
+
30
+ `stapes-adsa` flags all three. The linter scans every `.ts`/`.tsx` file,
31
+ identifies functions marked fast or slow, and verifies they actually
32
+ behave that way — and emit the log line that proves it.
33
+
34
+ The framework is described in [SKILL.md](./SKILL.md). The linter is the
35
+ enforcement.
36
+
37
+ ## How
38
+
39
+ ```bash
40
+ cd /path/to/your/repo
41
+ npx stapes-adsa --init
42
+ git commit -m "chore: add ADSA linter"
43
+ ```
44
+
45
+ `--init` writes a marker-block shell script to `.git/hooks/pre-commit`.
46
+ On every commit, the linter runs against staged files. Blocks stop the
47
+ commit. Warnings print and let the commit pass.
48
+
49
+ ```text
50
+ $ npx stapes-adsa
51
+ stapes-adsa@1.0.0
52
+
53
+ ✗ fast-path-with-io 1 finding(s)
54
+ src/auth/verify.ts:12:1
55
+ [block] Fast path "verifyJwtFast" performs I/O — fast paths must be pure
56
+ function verifyJwtFast() { /* I/O inside fast path */ }
57
+
58
+ ⚠ slow-path-no-log 1 finding(s)
59
+ src/auth/load.ts:31:1
60
+ [warn] Function "loadUserPermissions" marked slow has no {adsa_path: "slow"} log emission
61
+ async function loadUserPermissions() { /* slow path, no log */ }
62
+
63
+ 1 block(s), 1 warning(s).
64
+ ```
65
+
66
+ For AI agents and CI scripts, `--json` gives a stable, parseable shape:
67
+
68
+ ```bash
69
+ npx stapes-adsa --json | jq '.exitCode, .findings[].message'
70
+ ```
71
+
72
+ ```json
73
+ {
74
+ "startedAt": "2026-09-01T10:49:45.195Z",
75
+ "tool": "stapes-adsa@1.0.0",
76
+ "version": "1.0.0",
77
+ "exitCode": 1,
78
+ "checkCount": 5,
79
+ "failed": 1,
80
+ "warned": 1,
81
+ "findings": [
82
+ {
83
+ "check": "fast-path-with-io",
84
+ "file": "src/auth/verify.ts",
85
+ "line": 12,
86
+ "column": 1,
87
+ "message": "Fast path \"verifyJwtFast\" performs I/O — fast paths must be pure",
88
+ "evidence": "function verifyJwtFast() { /* I/O inside fast path */ }",
89
+ "severity": "block"
90
+ },
91
+ {
92
+ "check": "slow-path-no-log",
93
+ "file": "src/auth/load.ts",
94
+ "line": 31,
95
+ "column": 1,
96
+ "message": "Function \"loadUserPermissions\" marked slow has no {adsa_path: \"slow\"} log emission",
97
+ "evidence": "async function loadUserPermissions() { /* slow path, no log */ }",
98
+ "severity": "warn",
99
+ "suggestedFix": {
100
+ "description": "Insert slow-path log emission at top of loadUserPermissions",
101
+ "replacement": "log.info({ adsa_path: \"slow\", adsa_area: \"auth\" });\n",
102
+ "line": 32,
103
+ "column": 1
104
+ }
105
+ }
106
+ ]
107
+ }
108
+ ```
109
+
110
+ Stderr is always empty in `--json` mode. Exit codes are the contract:
111
+ `0` clean, `1` blocked, `2` invalid args. That's the whole protocol.
112
+
113
+ ## What
114
+
115
+ The five checks:
116
+
117
+ | Check | What it catches | Default severity | Auto-fixable |
118
+ |---|---|---|---|
119
+ | `slow-path-no-log` | Function marked slow (JSDoc, inline comment, or name suffix) without `{adsa_path: "slow"}` log emission | warn | yes |
120
+ | `fast-path-with-io` | Function marked fast (JSDoc, inline comment, or name suffix) that contains network / db / cache / fs / queue calls | block | no |
121
+ | `decision-router` | Function marked fast with an if/else where one branch contains I/O | warn | no |
122
+ | `async-no-log` | Async function with >5 body lines and no `{adsa_path}` log emission | warn | yes |
123
+ | `log-schema` | Invalid `{adsa_path}` value (not `"fast"` / `"slow"`), missing `adsa_area`, or unknown `adsa_*` field | block | no |
124
+
125
+ Flags:
126
+
127
+ ```text
128
+ --root <path> project root (default: cwd)
129
+ --check <name> run a single check by name (repeatable)
130
+ --list list available checks
131
+ --strict treat warnings as blocks
132
+ --json emit JSON on stdout (stable v1 schema)
133
+ --no-color disable ANSI colour in human output
134
+ --fix dry-run: report auto-fixable findings
135
+ --fix-confirm apply safe auto-fixes
136
+ --init install the pre-commit hook (idempotent)
137
+ --uninstall remove the pre-commit hook
138
+ --version
139
+ --help
140
+ ```
141
+
142
+ Idempotent. `npx stapes-adsa --init` run twice is a no-op. `--uninstall`
143
+ removes only our marker block — Husky, lefthook, or other pre-commit
144
+ hooks continue to run.
145
+
146
+ ### Path markers
147
+
148
+ You can declare a path explicitly so the linter can verify it:
149
+
150
+ | Form | Effect |
151
+ |---|---|
152
+ | `/** @adsa:fast */` or `/** @adsa:slow */` JSDoc | Function classified |
153
+ | `// adsa:fast` or `// adsa:slow` inline comment | Function classified |
154
+ | Name suffix `Fast` / `Slow` (e.g. `verifyJwtFast`, `resolveSlow`) | Function classified |
155
+
156
+ ### Worked example — auth
157
+
158
+ ```ts
159
+ import { withAdsaLogging } from "stapes-adsa/lib/instrument";
160
+
161
+ // @adsa:fast — pure, no I/O
162
+ function verifyTokenFast(token: string): UserClaims | null {
163
+ try {
164
+ return jwt.verify(token, PUBLIC_KEY) as UserClaims;
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+
170
+ // @adsa:slow — DB lookup
171
+ async function resolveSessionSlow(claims: UserClaims): Promise<Session> {
172
+ return db.sessions.findUnique({ where: { userId: claims.sub } });
173
+ }
174
+
175
+ // Router
176
+ export async function authenticate(token: string) {
177
+ return withAdsaLogging({
178
+ area: "auth",
179
+ predicate: async () => verifyTokenFast(token),
180
+ slowPath: async () => {
181
+ const claims = jwt.decode(token);
182
+ if (!claims) throw new AuthError("invalid");
183
+ return resolveSessionSlow(claims as UserClaims);
184
+ },
185
+ });
186
+ }
187
+ ```
188
+
189
+ The linter checks that `verifyTokenFast` does not contain `fetch`,
190
+ `db.*`, `redis.*`, `fs.*`, etc. — and flags it as a `fast-path-with-io`
191
+ violation if it does. It checks that `resolveSessionSlow` contains a
192
+ `log.*({adsa_path: "slow"...})` call — and flags it as
193
+ `slow-path-no-log` if it does not.
194
+
195
+ ### Agent-native
196
+
197
+ This tool is designed for AI agent runtimes and CI scripts:
198
+
199
+ - **Zero telemetry.** No network calls. No analytics. No update checks.
200
+ - **Zero config.** No files to write (besides the pre-commit hook on `--init`). No env vars to set.
201
+ - **`--json`** emits parseable output with a stable schema (see `AGENTS.md` for the contract).
202
+ - **Exit codes are stable.** `0` clean, `1` blocked, `2` invalid args. No other codes from the normal flow.
203
+ - **Idempotent install.** `--init` is safe to re-run. `--uninstall` removes cleanly.
204
+ - **Runs offline.** No API keys. No service to log into. Source files never leave the machine.
205
+
206
+ ### Verify it yourself (5 lines)
207
+
208
+ ```bash
209
+ # 1. Zero network calls during a run
210
+ npx stapes-adsa --json >/dev/null && \
211
+ lsof -p $$ -i 2>/dev/null | grep -E "node|npx" || \
212
+ echo "no outgoing TCP from this shell"
213
+
214
+ # 2. Idempotent init
215
+ npx stapes-adsa --init && \
216
+ npx stapes-adsa --init # second is a no-op
217
+
218
+ # 3. Stable JSON shape across runs
219
+ npx stapes-adsa --json | jq '{tool, version, exitCode, checkCount}'
220
+ ```
221
+
222
+ ### Telemetry
223
+
224
+ None. Zero network calls. The only filesystem writes are
225
+ `.git/hooks/pre-commit` during `--init` and source files modified
226
+ during `--fix-confirm`.
227
+
228
+ ## Compared to
229
+
230
+ | Tool | What you trade away by choosing it |
231
+ |---|---|
232
+ | **ESLint custom rules** | The ESLint ecosystem is rich but you maintain the config, the plugins, and the rule definitions. `stapes-adsa` is one fixed check set, one install, zero config. |
233
+ | **Code review** | Humans catch context. They also miss the third "fetch inside the cache function" because they're tired on Friday afternoon. The linter doesn't get tired. |
234
+ | **Custom framework documentation** | "Just write ADSA in your head and trust the reviewers" — this is the current state. It scales to one team of five. It does not scale past that. |
235
+ | **OpenTelemetry spans** | OTEL tracks the *time* a path took. It does not enforce that the path was *classified* as fast or slow. Different layer. |
236
+
237
+ ## What we measured
238
+
239
+ On 2026-09-01 we ran `stapes-adsa@1.0.0` against two production TypeScript services
240
+ before announcing it. Both repos had not adopted ADSA yet.
241
+
242
+ | Repo | .ts files | LoC | Findings | Density |
243
+ |---|---|---|---|---|
244
+ | `strat_nav` | 45 | 8,244 | 32 async-no-log | 0.71 / file |
245
+ | `risk_automation` | 60 | 5,051 | 132 async-no-log | 2.20 / file |
246
+
247
+ Findings breakdown: **100% async-no-log, zero blocks**. Every flagged function is
248
+ observability-dark today — no `adsa_path` log line is emitted. Installing the
249
+ linter closes the observability gap immediately: every flagged function gets a
250
+ structured log emission that ops can grep on.
251
+
252
+ For latency, the structural estimate is **30–60% per-request reduction on hot
253
+ paths once refactored**. The model:
254
+
255
+ - ~30% of findings are fast/slow candidates (cache lookup, JWT verify,
256
+ indexed search). On those paths, fast path 1–2ms vs slow path 30–100ms,
257
+ hit rate 95–99%. Per-call latency reduction on those paths: **~95%**.
258
+ - ~50% are pure slow paths with no fast variant — observability only.
259
+ - ~20% are already-fast false positives — observability only.
260
+
261
+ Code cost: each ADSA point adds ~30–50 LoC (predicate + slowPath + router).
262
+ For 8 ADSA points on a 5–10 kLoC service: **3–6% code growth**.
263
+
264
+ Trade ratio: roughly 1 LoC added → 5–15% latency saved on the hot path.
265
+
266
+ These are **structural estimates, not benchmarks**. No traffic traces were
267
+ captured. The 30–60% range assumes typical ratios from industry
268
+ cache/JWT/search patterns. The only guaranteed, immediate outcome is the
269
+ 100% observability closure — the latency claim is conditional on engineering
270
+ work per flagged function. See [SAVINGS-NOTE.md](./SAVINGS-NOTE.md) for the
271
+ full model and the scan artifacts.
272
+
273
+ ## Source
274
+
275
+ Visible at https://github.com/stapesco/adsa. Built by stapes. Read the
276
+ code. Fork it. PRs are not accepted.
277
+
278
+ See [SKILL.md](./SKILL.md) for the ADSA framework reference.
279
+ See [AGENTS.md](./AGENTS.md) for the machine-readable install contract
280
+ and the stable `--json` schema.
281
+
282
+ ## Issues
283
+
284
+ If `stapes-adsa` doesn't work for you, the most likely fix is in one
285
+ of the five files under `src/checks/`. Read them, fork, patch.
286
+
287
+ For security disclosures, see [SECURITY.md](./SECURITY.md).
288
+
289
+ ## License
290
+
291
+ MIT
package/SECURITY.md ADDED
@@ -0,0 +1,46 @@
1
+ # Security
2
+
3
+ If you've found a security vulnerability in `stapes-adsa`, please report
4
+ it privately before public disclosure.
5
+
6
+ ## How to report
7
+
8
+ Email **github@stapes.co.za** with:
9
+
10
+ - A short description of the issue
11
+ - Reproduction steps (or a minimal PoC)
12
+ - The version affected (`stapes-adsa --version`)
13
+ - Your expected disclosure timeline
14
+
15
+ Please **do not** open a public GitHub issue for security bugs.
16
+
17
+ ## What to expect
18
+
19
+ - Acknowledgement within **5 business days**
20
+ - A fix or a clear timeline for a fix within **30 days** of acknowledgement
21
+ - Public disclosure coordinated with you, after the fix ships
22
+
23
+ There is **no bug bounty programme** and **no reward** for disclosure.
24
+ We fix issues because they're bugs, not because we're buying silence.
25
+
26
+ ## Out of scope
27
+
28
+ - Lack of a feature (open a discussion, not a security report)
29
+ - Performance issues
30
+ - Compatibility issues with specific TypeScript versions, Node versions, or shells
31
+ - Reports about the dependency supply chain that are about a transitive dep rather than `stapes-adsa` itself
32
+
33
+ If you're not sure whether something is a security issue, report it.
34
+ We'll triage and let you know.
35
+
36
+ ## Why this tool touches sensitive data
37
+
38
+ `stapes-adsa` reads TypeScript source files and inspects their AST to
39
+ detect ADSA pattern violations. It also reads the contents of any
40
+ `{adsa_path}` log object literals to validate them against the
41
+ four-field contract. The tool itself does not phone home, does not log
42
+ file contents to disk, and does not retain them. But because it sees
43
+ source code, a malicious or compromised supply-chain dep could leak
44
+ content through the AST traversal. If you discover a way to exfiltrate
45
+ data through the linter or the pre-commit hook it installs, please
46
+ report through the channel above.