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.
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/SECURITY.md +46 -0
- package/SKILL.md +198 -0
- package/dist/ast.d.ts +55 -0
- package/dist/ast.js +186 -0
- package/dist/ast.js.map +1 -0
- package/dist/checks/async-no-log.d.ts +19 -0
- package/dist/checks/async-no-log.js +77 -0
- package/dist/checks/async-no-log.js.map +1 -0
- package/dist/checks/decision-router.d.ts +16 -0
- package/dist/checks/decision-router.js +112 -0
- package/dist/checks/decision-router.js.map +1 -0
- package/dist/checks/fast-path-with-io.d.ts +19 -0
- package/dist/checks/fast-path-with-io.js +58 -0
- package/dist/checks/fast-path-with-io.js.map +1 -0
- package/dist/checks/index.d.ts +21 -0
- package/dist/checks/index.js +90 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/checks/log-schema.d.ts +18 -0
- package/dist/checks/log-schema.js +124 -0
- package/dist/checks/log-schema.js.map +1 -0
- package/dist/checks/slow-path-no-log.d.ts +16 -0
- package/dist/checks/slow-path-no-log.js +71 -0
- package/dist/checks/slow-path-no-log.js.map +1 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.js +186 -0
- package/dist/cli.js.map +1 -0
- package/dist/column.d.ts +10 -0
- package/dist/column.js +30 -0
- package/dist/column.js.map +1 -0
- package/dist/fix.d.ts +34 -0
- package/dist/fix.js +110 -0
- package/dist/fix.js.map +1 -0
- package/dist/hook-install.d.ts +36 -0
- package/dist/hook-install.js +94 -0
- package/dist/hook-install.js.map +1 -0
- package/dist/lib/instrument.d.ts +49 -0
- package/dist/lib/instrument.js +53 -0
- package/dist/lib/instrument.js.map +1 -0
- package/dist/patterns.d.ts +36 -0
- package/dist/patterns.js +72 -0
- package/dist/patterns.js.map +1 -0
- package/dist/runner.d.ts +35 -0
- package/dist/runner.js +104 -0
- package/dist/runner.js.map +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +32 -0
- package/dist/util.js +105 -0
- package/dist/util.js.map +1 -0
- 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.
|