@xenos1996/usat 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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +335 -0
  3. package/USAT.md +596 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +438 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/config.d.ts +7 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +72 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/detect/index.d.ts +58 -0
  13. package/dist/detect/index.d.ts.map +1 -0
  14. package/dist/detect/index.js +329 -0
  15. package/dist/detect/index.js.map +1 -0
  16. package/dist/engine/audit.d.ts +21 -0
  17. package/dist/engine/audit.d.ts.map +1 -0
  18. package/dist/engine/audit.js +295 -0
  19. package/dist/engine/audit.js.map +1 -0
  20. package/dist/engine/diff.d.ts +24 -0
  21. package/dist/engine/diff.d.ts.map +1 -0
  22. package/dist/engine/diff.js +136 -0
  23. package/dist/engine/diff.js.map +1 -0
  24. package/dist/engine/evaluate.d.ts +19 -0
  25. package/dist/engine/evaluate.d.ts.map +1 -0
  26. package/dist/engine/evaluate.js +377 -0
  27. package/dist/engine/evaluate.js.map +1 -0
  28. package/dist/engine/gate.d.ts +19 -0
  29. package/dist/engine/gate.d.ts.map +1 -0
  30. package/dist/engine/gate.js +47 -0
  31. package/dist/engine/gate.js.map +1 -0
  32. package/dist/engine/loader.d.ts +22 -0
  33. package/dist/engine/loader.d.ts.map +1 -0
  34. package/dist/engine/loader.js +360 -0
  35. package/dist/engine/loader.js.map +1 -0
  36. package/dist/engine/maturity.d.ts +20 -0
  37. package/dist/engine/maturity.d.ts.map +1 -0
  38. package/dist/engine/maturity.js +157 -0
  39. package/dist/engine/maturity.js.map +1 -0
  40. package/dist/engine/score.d.ts +19 -0
  41. package/dist/engine/score.d.ts.map +1 -0
  42. package/dist/engine/score.js +141 -0
  43. package/dist/engine/score.js.map +1 -0
  44. package/dist/engine/sections.d.ts +14 -0
  45. package/dist/engine/sections.d.ts.map +1 -0
  46. package/dist/engine/sections.js +50 -0
  47. package/dist/engine/sections.js.map +1 -0
  48. package/dist/index.d.ts +23 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +17 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/report/markdown.d.ts +16 -0
  53. package/dist/report/markdown.d.ts.map +1 -0
  54. package/dist/report/markdown.js +488 -0
  55. package/dist/report/markdown.js.map +1 -0
  56. package/dist/types.d.ts +282 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +16 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/util/glob.d.ts +11 -0
  61. package/dist/util/glob.d.ts.map +1 -0
  62. package/dist/util/glob.js +86 -0
  63. package/dist/util/glob.js.map +1 -0
  64. package/dist/util/project.d.ts +72 -0
  65. package/dist/util/project.d.ts.map +1 -0
  66. package/dist/util/project.js +586 -0
  67. package/dist/util/project.js.map +1 -0
  68. package/dist/util/yaml.d.ts +28 -0
  69. package/dist/util/yaml.d.ts.map +1 -0
  70. package/dist/util/yaml.js +48 -0
  71. package/dist/util/yaml.js.map +1 -0
  72. package/docs/ARCHITECTURE.md +119 -0
  73. package/docs/README.md +27 -0
  74. package/docs/adr/0001-record-architecture-decisions.md +35 -0
  75. package/docs/adr/0002-typescript-engine-over-python.md +49 -0
  76. package/docs/adr/0003-rules-are-data-not-code.md +50 -0
  77. package/docs/adr/0004-markdown-only-output.md +41 -0
  78. package/docs/adr/0005-maturity-dampens-severity.md +56 -0
  79. package/docs/adr/0006-severity-status-two-axes.md +48 -0
  80. package/docs/adr/0007-suppressions-are-visible-and-expiring.md +46 -0
  81. package/docs/adr/0008-report-trailer-and-diff.md +48 -0
  82. package/docs/adr/0009-fail-closed-on-malformed-input.md +56 -0
  83. package/docs/adr/0010-detector-design.md +54 -0
  84. package/docs/adr/0011-coexist-with-deep-scanners.md +52 -0
  85. package/docs/adr/README.md +32 -0
  86. package/docs/agent-integration.md +129 -0
  87. package/docs/ci-integration.md +163 -0
  88. package/docs/concepts.md +196 -0
  89. package/docs/configuration.md +105 -0
  90. package/docs/detectors.md +186 -0
  91. package/docs/getting-started.md +99 -0
  92. package/docs/maturity-profiles.md +156 -0
  93. package/docs/reference/api.md +193 -0
  94. package/docs/reference/cli.md +412 -0
  95. package/docs/rule-packs.md +278 -0
  96. package/docs/standards-mapping.md +171 -0
  97. package/package.json +96 -0
  98. package/rules/core/architecture.yaml +190 -0
  99. package/rules/core/cicd.yaml +212 -0
  100. package/rules/core/code-quality.yaml +261 -0
  101. package/rules/core/dependencies.yaml +132 -0
  102. package/rules/core/documentation.yaml +119 -0
  103. package/rules/core/future-readiness.yaml +91 -0
  104. package/rules/core/release.yaml +131 -0
  105. package/rules/core/repo.yaml +303 -0
  106. package/rules/core/security.yaml +494 -0
  107. package/rules/core/supply-chain.yaml +357 -0
  108. package/rules/core/testing.yaml +260 -0
  109. package/rules/detectors.yaml +2255 -0
  110. package/rules/index.yaml +41 -0
  111. package/rules/profiles/maturity.yaml +135 -0
  112. package/rules/stacks/ai-era.yaml +193 -0
  113. package/rules/stacks/api-backend.yaml +121 -0
  114. package/rules/stacks/cli.yaml +105 -0
  115. package/rules/stacks/compliance.yaml +157 -0
  116. package/rules/stacks/containers.yaml +124 -0
  117. package/rules/stacks/data.yaml +165 -0
  118. package/rules/stacks/go.yaml +134 -0
  119. package/rules/stacks/iac.yaml +128 -0
  120. package/rules/stacks/jvm.yaml +150 -0
  121. package/rules/stacks/ml-ai.yaml +140 -0
  122. package/rules/stacks/mobile.yaml +121 -0
  123. package/rules/stacks/node-typescript.yaml +166 -0
  124. package/rules/stacks/python.yaml +175 -0
  125. package/rules/stacks/rust.yaml +129 -0
  126. package/rules/stacks/solidity.yaml +139 -0
  127. package/rules/stacks/web-frontend.yaml +159 -0
  128. package/templates/AGENTS.audit.md +135 -0
  129. package/templates/AUDIT_REPORT.md +155 -0
@@ -0,0 +1,105 @@
1
+ # Configuration — `.usat.yaml`
2
+
3
+ Generate one with `usat init`. Every field is optional; a missing file is fine.
4
+
5
+ ```yaml
6
+ version: 1
7
+
8
+ # ── Lifecycle stage ────────────────────────────────────────────────────────
9
+ # Override auto-detection: prototype | mvp | beta | production | legacy
10
+ maturity: production
11
+
12
+ # ── Rule packs ─────────────────────────────────────────────────────────────
13
+ include: [stacks/solidity] # force on, regardless of detection
14
+ exclude: [stacks/mobile] # force off
15
+
16
+ # ── Per-rule overrides ─────────────────────────────────────────────────────
17
+ rules:
18
+ DOC-003:
19
+ severity: LOW
20
+ reason: 'Docs live in Notion, not the repo (decision: ADR-014)'
21
+ SEC-042:
22
+ disabled: true
23
+ reason: 'No user-facing auth in this worker'
24
+ CQ-010:
25
+ weight: 4
26
+
27
+ # ── Accepted risk ──────────────────────────────────────────────────────────
28
+ suppressions:
29
+ - rule: PERF-005
30
+ reason: 'Known N+1 in the admin panel; 40 rows max.'
31
+ until: '2026-12-31'
32
+
33
+ # ── Indexing ───────────────────────────────────────────────────────────────
34
+ ignore: # extra globs, on top of .gitignore + USAT defaults
35
+ - 'generated/**'
36
+ - 'vendor/**'
37
+
38
+ # ── Facts ──────────────────────────────────────────────────────────────────
39
+ facts: # assert what detection could not infer
40
+ - 'has:database'
41
+ - 'platform:server'
42
+ ```
43
+
44
+ ## Field reference
45
+
46
+ | Field | Type | Effect |
47
+ | --------------------- | ----------------------- | ------------------------------------------------------------------------------ |
48
+ | `version` | `1` | Schema version |
49
+ | `maturity` | stage | Overrides auto-detection; changes dampening and the expected band |
50
+ | `include` | pack ids | Force packs on even when `skip_when` says no |
51
+ | `exclude` | pack ids | Force packs off |
52
+ | `rules.<id>.severity` | severity | Re-grade one rule (must be on the ladder, else ignored with a warning) |
53
+ | `rules.<id>.weight` | number | Change how much it moves the score (finite, ≥ 0, else ignored with a warning) |
54
+ | `rules.<id>.disabled` | bool | Skip entirely (still listed as ➖ SKIPPED) |
55
+ | `rules.<id>.reason` | string | **Required in practice** — an override with no reason is an unaudited decision |
56
+ | `suppressions[]` | `{rule, reason, until}` | Excluded from the score, listed under Accepted Risk |
57
+ | `ignore` | globs | Extra paths to keep out of the index |
58
+ | `facts` | fact strings | Assert detection facts manually (`ns:value`) |
59
+ | `sections` | section ids | Restrict the report to these sections |
60
+
61
+ ## What is indexed
62
+
63
+ The file index skips, in order:
64
+
65
+ 1. `.git/`
66
+ 2. `DEFAULT_IGNORES` — `node_modules/`, `dist/`, `build/`, `.next/`, `venv/`, `target/`, media, archives, binaries
67
+ 3. Your `.gitignore`
68
+ 4. `ignore:` from `.usat.yaml`
69
+
70
+ Lockfiles **are** indexed (a rule asking "is a lockfile committed?" has to see them)
71
+ but are never grepped — they are huge and full of false positives. Files over 2 MB
72
+ and anything with a NUL byte in the first 8 KB are skipped as binary.
73
+
74
+ Build-output checks therefore use `tracked_absent`, which asks git what is tracked
75
+ rather than what is on disk:
76
+
77
+ ```yaml
78
+ - id: REPO-007
79
+ check:
80
+ kind: tracked_absent
81
+ patterns: ['dist/**', 'build/**', 'node_modules/**']
82
+ ```
83
+
84
+ ## Rules of thumb
85
+
86
+ **Prefer `facts:` over `ignore:`.** If detection is wrong, say what is true:
87
+
88
+ ```yaml
89
+ facts: ['has:database', 'orm:prisma']
90
+ ```
91
+
92
+ **Prefer a suppression over disabling a rule.** A suppression stays visible in the
93
+ report under Accepted Risk; a disabled rule disappears and takes its knowledge with it.
94
+
95
+ **Waivers expire — and expiry fails closed.** An `until` date in the past, or
96
+ one that cannot be parsed as a date, excludes the suppression with a warning
97
+ and the finding reports normally. Use unambiguous ISO dates (`2026-12-31`).
98
+ An audit must never silently honour dead risk acceptances (see ADR-0007).
99
+
100
+ **`include` forces packs on wholesale.** A force-loaded pack applies all its
101
+ rules regardless of depth and `applies_when` — that is the documented
102
+ contract, and the operator is responsible for what they force on.
103
+
104
+ **Keep `.usat.yaml` in the repo.** It is the audit trail for every exception you
105
+ have taken. A reviewer should be able to read it and understand what the team decided.
@@ -0,0 +1,186 @@
1
+ # Detectors — the fact catalogue
2
+
3
+ Detection is entirely declarative: [`rules/detectors.yaml`](../rules/detectors.yaml)
4
+ maps file patterns to **facts**, and rule packs select themselves on those facts.
5
+ Add a signal and every pack that cares starts applying — no TypeScript required.
6
+
7
+ ```bash
8
+ usat detect . # what USAT thinks your project is
9
+ ```
10
+
11
+ ## How a detector works
12
+
13
+ ```yaml
14
+ - fact: 'fw:next'
15
+ category: framework
16
+ title: 'Next.js'
17
+ match:
18
+ any_of:
19
+ - file_exists: ['next.config.js', 'next.config.mjs', 'next.config.ts']
20
+ - manifest: { file: 'package.json', key: 'dependencies.next' }
21
+ ```
22
+
23
+ | Primitive | Meaning |
24
+ | ---------------------------------- | ------------------------------------------------ |
25
+ | `any_file: [glob]` | at least one file matches |
26
+ | `file_exists: [glob]` | path present |
27
+ | `dir_exists: [path]` | directory present |
28
+ | `content: {include, pattern}` | regex found in matching files |
29
+ | `manifest: {file, key, contains?}` | dotted key resolves (JSON, or TOML/YAML section) |
30
+ | `metric: {name, min\|max}` | numeric fact comparison |
31
+ | `any_of` / `all_of` | boolean composition |
32
+ | `implies: [fact]` | derived — all listed facts must hold |
33
+
34
+ Several detectors may emit the same fact; the fact is set if **any** of them fires
35
+ (OR semantics). `implies` chains resolve to a fixed point (bounded by
36
+ detector count + 1 passes, early exit), so chains of any length resolve
37
+ regardless of declaration order.
38
+
39
+ Request-serving frameworks imply their platform: `fw:express` …
40
+ `fw:sveltekit` (24 frameworks) each derive `platform:server`, so a bare
41
+ framework app with no Dockerfile still gets the platform-gated rules
42
+ (SEC-003 et al.). Task queues, static builders, and desktop/mobile packs
43
+ deliberately imply nothing — see the comment block in
44
+ `rules/detectors.yaml`.
45
+
46
+ Manifest queries are section-scoped where sections exist: a dotted key
47
+ (`tool.poetry.dependencies`) matches a full header segment
48
+ (case-insensitive; `[dev-dependencies]` is not `[dependencies]`), the leaf
49
+ matches on word boundaries, and `contains` is checked against the matched
50
+ block — never the whole file. Flat files with no headers keep whole-text
51
+ search. See ADR-0010.
52
+
53
+ ## Prose, tests, and examples are excluded from `content` scans
54
+
55
+ A repo whose README mentions Postgres does not have a Postgres dependency. Every
56
+ `content:` scan automatically skips:
57
+
58
+ ```
59
+ **/*.md **/*.mdx **/*.rst README* docs/** doc/** examples/**
60
+ example/** templates/** fixtures/** __mocks__/** test/** tests/**
61
+ __tests__/** spec/** *.test.* *.spec.* *.stories.* test_*
62
+ *_test.* LICENSE* CHANGELOG*
63
+ ```
64
+
65
+ Deliberately **not** excluded: `**/*.txt` — `requirements.txt` is a
66
+ manifest, not prose. This list mirrors `CONTENT_EXCLUDES` in
67
+ `src/detect/index.ts`; if they drift, the code wins and this doc is a bug —
68
+ file it.
69
+
70
+ Everything else in the tree is fair game. If a project's own config comments are
71
+ still producing noise, add the file to `ignore:` in `.usat.yaml` — that is what
72
+ [`../.usat.yaml`](../.usat.yaml) does with `rules/`.
73
+
74
+ ## Fact namespaces
75
+
76
+ ### `lang:*` — language
77
+
78
+ `typescript` `javascript` `python` `go` `rust` `java` `kotlin` `swift` `ruby` `php`
79
+ `csharp` `cpp` `scala` `dart` `elixir` `lua` `zig` `solidity` `shell` `haskell`
80
+
81
+ ### `pm:*` — package manager
82
+
83
+ `node` `npm` `pnpm` `yarn` `bun` `pip` `poetry` `uv` `pipenv` `cargo` `go-mod`
84
+ `maven` `gradle` `composer` `nuget` `swiftpm` `pub` `mix` `foundry` `hardhat`
85
+
86
+ ### `fw:*` — framework
87
+
88
+ Web: `react` `vue` `angular` `svelte` `solid` `next` `nuxt` `remix` `sveltekit`
89
+ `astro` `vite` `tailwind`
90
+ Node backend: `express` `fastify` `nest` `hono` `koa`
91
+ Python: `django` `flask` `fastapi` `celery` `airflow` `streamlit`
92
+ Go: `gin` `echo` `fiber` `chi` · Rust: `axum` `actix` `rocket`
93
+ JVM: `spring` `quarkus` · Ruby: `rails` · PHP: `laravel` `symfony` · .NET: `dotnet`
94
+ Mobile/desktop: `react-native` `expo` `flutter` `electron` `tauri`
95
+
96
+ ### `project:*` — archetype
97
+
98
+ `library` `cli` `monorepo` `api` `ml` `blockchain` `iac` `data-pipeline` `game` `docs`
99
+
100
+ ### `platform:*` — runtime
101
+
102
+ `web` `server` `desktop` `mobile` `ios` `android` `evm` `embedded`
103
+
104
+ ### `db:*` — datastore
105
+
106
+ `postgres` `mysql` `sqlite` `mongodb` `redis` `clickhouse` `elasticsearch` `vector`
107
+ `dynamodb` · plus `has:database`
108
+
109
+ ### `orm:*`
110
+
111
+ `prisma` `drizzle` `typeorm` `sequelize` `mongoose` `sqlalchemy` `gorm` `sqlx`
112
+ `diesel` `hibernate` `activerecord`
113
+
114
+ ### `auth:*`
115
+
116
+ `jwt` `oauth` `session` `apikey` `passport` `nextauth` `supabase` `firebase` `auth0`
117
+
118
+ ### `api:*` · `obs:*`
119
+
120
+ `api:graphql` `api:grpc` `api:trpc` `api:websocket`
121
+ `obs:sentry` `obs:otel` `obs:prometheus` `obs:datadog` `obs:grafana` · `has:monitoring`
122
+
123
+ ### `test:*`
124
+
125
+ `vitest` `jest` `mocha` `pytest` `gotest` `cargotest` `junit` `rspec` `phpunit`
126
+ `playwright` `cypress` · plus `has:tests` `has:e2e` `has:perf-tests`
127
+
128
+ ### `ci:*` · `infra:*`
129
+
130
+ `ci:github-actions` `ci:gitlab` `ci:jenkins` `ci:circleci` `ci:buildkite` `ci:azure`
131
+ · `has:ci`
132
+
133
+ ### `ai:*`
134
+
135
+ `ai:llm-sdk` `ai:prompts` `ai:agents` `ai:mcp` `ai:rag` `ai:evals`
136
+
137
+ ### `doc:*`
138
+
139
+ `readme` `license` `contributing` `code-of-conduct` `security-policy` `changelog`
140
+ `architecture` `adr` `api-reference` `runbook` `onboarding` `env-example`
141
+ `gitignore` `codeowners` `pr-template` `issue-templates` `agents`
142
+
143
+ ### `has:*` — cross-cutting capabilities
144
+
145
+ `has:lint` `has:format` `has:typecheck` `has:precommit` `has:dependabot`
146
+ `has:renovate` `has:changesets` `has:release-automation` `has:editorconfig`
147
+ `has:containers` `has:kubernetes` `has:terraform` `has:serverless` `has:sbom`
148
+ `has:secrets-scan` `has:sast` `has:dep-scan` `has:codecov` `has:monitoring`
149
+ `has:crypto` `has:file-upload` `has:payments` `has:pii` `has:csp` `has:rate-limit`
150
+ `has:database` `has:tests` `has:e2e` `has:perf-tests` `has:ci`
151
+
152
+ ### `practice:*`
153
+
154
+ `conventional-commits` `codeowners` `signed-commits` `branch-protection-doc`
155
+ `pinned-versions` `i18n` `feature-flags` `queue` `cache`
156
+ `monorepo-tool-turborepo` `monorepo-tool-nx`
157
+
158
+ ### `maturity:*` and metrics
159
+
160
+ `maturity:prototype` `maturity:mvp` `maturity:beta` `maturity:production` `maturity:legacy`
161
+
162
+ Metrics, referenced as `metric:<name>` in predicates:
163
+
164
+ | Metric | Source |
165
+ | --------------------- | --------------------------- |
166
+ | `commits` | `git rev-list --count HEAD` |
167
+ | `contributors` | `git shortlog -sn --all` |
168
+ | `tags` | `git tag --list` |
169
+ | `branches` | `git branch --list` |
170
+ | `files` | indexed file count |
171
+ | `daysSinceLastCommit` | `git log -1 --format=%ct` |
172
+
173
+ ## Adding a detector
174
+
175
+ 1. Add an entry to `rules/detectors.yaml` with a clear `fact:` name.
176
+ 2. Use `manifest:` where possible — it is far more reliable than a content regex
177
+ (`dependencies.hono` beats grepping for `hono` in source).
178
+ 3. Verify against a project that uses the technology and one that does not:
179
+
180
+ ```bash
181
+ usat detect ~/code/uses-hono
182
+ usat detect ~/code/does-not
183
+ ```
184
+
185
+ 4. If detection is right but a project still eludes it, users can assert facts in
186
+ `.usat.yaml` — but if you find yourself doing that often, the detector is wrong.
@@ -0,0 +1,99 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npx @xenos1996/usat audit . # zero-install
7
+ npm i -g @xenos1996/usat # or globally
8
+ npm i -D @xenos1996/usat # or per project, so CI and laptops agree
9
+ ```
10
+
11
+ Requires Node 20+. No network access is needed at audit time, and USAT never
12
+ uploads anything — it reads files and writes one Markdown file.
13
+
14
+ ## Your first audit
15
+
16
+ ```bash
17
+ cd ~/code/my-project
18
+ usat detect . # 1. check what it thinks you are
19
+ usat audit . # 2. audit it
20
+ open AUDIT.md
21
+ ```
22
+
23
+ Start with `detect`. If the detected facts are wrong, the report will be wrong —
24
+ and fixing it takes one line of config:
25
+
26
+ ```yaml
27
+ # .usat.yaml
28
+ facts: ['has:database'] # assert anything detection missed
29
+ ```
30
+
31
+ ## Reading the report
32
+
33
+ **1 · Look at the band, not the number.**
34
+
35
+ ```
36
+ Overall Health Score: 71.4/100
37
+ Expected band for Beta / Growing: 60–85 — within the expected band 👍
38
+ ```
39
+
40
+ A 55/100 on a prototype is healthy. The same 55/100 on a production service is a
41
+ problem. USAT tells you which you are looking at.
42
+
43
+ **2 · Read Immediate Action Required.** CRITICAL and security-HIGH findings, with
44
+ location and fix. Everything else waits.
45
+
46
+ **3 · Check confidence before celebrating.**
47
+
48
+ | Dimension | Score | Confidence |
49
+ | ------------------ | ----- | ---------- |
50
+ | S10 · Dependencies | 10/10 | 25% |
51
+
52
+ A 10/10 at 25% confidence means one of four applicable rules could be checked
53
+ automatically. The other three are in the judgement queue. Sections where
54
+ _nothing_ could be verified say **"— not verified"** and are excluded from the
55
+ total rather than quietly scoring 10.
56
+
57
+ **4 · Work the judgement queue.** These are the checks grep cannot settle. Take them
58
+ to an agent or a reviewer. Every row says what to look for and what evidence to
59
+ record. **Do not mark anything ✅ without evidence.**
60
+
61
+ **5 · Ship the roadmap.** SPRINT 0 → SPRINT 1 → SPRINT 2 → BACKLOG, generated from
62
+ severity. `DEFERRED` lists what the maturity profile says you should deliberately
63
+ ignore _at this stage_.
64
+
65
+ ## Common flags
66
+
67
+ ```bash
68
+ usat audit . --depth deep # include deep-only rules (cycles, duplication, complexity)
69
+ usat audit . --profile production # grade against the full bar regardless of age
70
+ usat audit . --fail-on high # exit 1 on HIGH+ (for CI gates)
71
+ usat audit . --out reports/2026-09.md # dated reports, so you can diff them later
72
+ usat audit . --allow-commands # run shell checks (npm audit, depcheck, dpdm)
73
+ usat audit . --include stacks/solidity # force a pack on
74
+ usat rules --section S2 # list rules
75
+ usat explain SEC-001 # everything about one rule
76
+ ```
77
+
78
+ `--allow-commands` is **off by default** because it shells out. Those rules appear
79
+ as ❓ NEEDS REVIEW until you enable it — which is the point: they are real checks,
80
+ they just need your permission to run.
81
+
82
+ ## Compare two audits
83
+
84
+ ```bash
85
+ usat audit . --out reports/2026-06.md
86
+ # ... three months of work ...
87
+ usat audit . --out reports/2026-09.md
88
+ usat diff reports/2026-06.md reports/2026-09.md
89
+ ```
90
+
91
+ The diff reads the YAML trailer embedded in every report — so a Markdown report is
92
+ still the only artefact you need to keep.
93
+
94
+ ## Next steps
95
+
96
+ - [Concepts](concepts.md) — how severity, status, and scoring actually work
97
+ - [Configuration](configuration.md) — `.usat.yaml` reference
98
+ - [Rule packs](rule-packs.md) — write your own rules
99
+ - [Agent integration](agent-integration.md) — drive USAT from Claude, Cursor, Codex
@@ -0,0 +1,156 @@
1
+ # Maturity profiles
2
+
3
+ The same repository deserves a different report at age three days and age three years.
4
+ Grading a prototype against a production bar produces a wall of noise nobody reads;
5
+ grading production with prototype standards produces a false all-clear.
6
+
7
+ USAT detects a lifecycle stage and **dampens** severity to match.
8
+
9
+ ## How the stage is detected
10
+
11
+ A score out of 7.5, from signals that are hard to fake:
12
+
13
+ | Signal | Points |
14
+ | ---------------------- | ------ |
15
+ | Tests present | +1 |
16
+ | CI configured | +1 |
17
+ | CHANGELOG maintained | +1 |
18
+ | Release tags published | +1 |
19
+ | ≥50 commits | +1 |
20
+ | SECURITY.md published | +1 |
21
+ | Containerised | +0.5 |
22
+ | Monitoring configured | +0.5 |
23
+ | CONTRIBUTING guide | +0.5 |
24
+
25
+ Then:
26
+
27
+ | Condition | Stage |
28
+ | ----------------------------- | -------------- |
29
+ | No commits for ~18 months | **legacy** |
30
+ | ~12 months idle **and** no CI | **legacy** |
31
+ | Score ≥ 7 **and** tags exist | **production** |
32
+ | Score ≥ 5 | **beta** |
33
+ | Score ≥ 2.5 | **mvp** |
34
+ | Otherwise | **prototype** |
35
+
36
+ A `0.x` version with no tags is never promoted to _production_ — it downgrades to
37
+ _beta_ with the reason recorded in the report.
38
+
39
+ Boundary semantics, stated exactly because auditors must not hedge: staleness
40
+ is exclusive (`> 365` and `> 540` days — exactly-365-days-stale counts as
41
+ fresh); production needs score ≥ 7 **and** at least one tag (7.0 with zero
42
+ tags is beta); prerelease detection reads the **root** `package.json` only,
43
+ so monorepos and polyglot version files (`pyproject.toml`, `Cargo.toml`,
44
+ `go.mod`) should assert maturity explicitly via `facts:` or `maturity:` if
45
+ the auto-detection misreads them.
46
+
47
+ Every signal and the resulting score are printed in the report appendix, so the
48
+ classification can be argued with.
49
+
50
+ ## Dampening
51
+
52
+ Steps move down the ladder `FUTURE < LOW < MEDIUM < HIGH < CRITICAL`.
53
+
54
+ | Class | 🌱 Prototype | 🚀 MVP | 🧪 Beta | 🏭 Production | 🏚️ Legacy |
55
+ | ----------------- | ------------ | ------ | ------- | ------------- | --------- |
56
+ | `security` | −1 | −0 | −0 | −0 | −0 |
57
+ | `supply-chain` | −1 | −1 | −0 | −0 | −0 |
58
+ | `correctness` | −1 | −1 | −0 | −0 | −0 |
59
+ | `maintainability` | −2 | −1 | −1 | −0 | −1 |
60
+ | `operations` | −2 | −1 | −1 | −0 | −0 |
61
+ | `performance` | −2 | −1 | −1 | −0 | −1 |
62
+ | `compliance` | −2 | −1 | −1 | −0 | −0 |
63
+ | `documentation` | −2 | −2 | −1 | −0 | −1 |
64
+ | `style` | −2 | −2 | −1 | −0 | −1 |
65
+
66
+ > ### 🔴 CRITICAL is never dampened, at any stage.
67
+ >
68
+ > A leaked credential in a weekend prototype is still a leaked credential.
69
+ > A committed `.env` in an MVP is still a committed `.env`.
70
+ > Everything else is negotiable with the calendar. This one is not, and it is
71
+ > enforced in code (`src/engine/maturity.ts`), not just documented here.
72
+
73
+ Every dampened finding says so in the report, so a growing prototype can see what
74
+ will climb on its own:
75
+
76
+ ```
77
+ - 🚫 **LICENSE present** `REPO-005`
78
+ - 🪶 Downgraded HIGH → LOW by the Prototype / Spike profile
79
+ ```
80
+
81
+ ## Expected score bands
82
+
83
+ | Stage | Band | Reading |
84
+ | ------------- | ----- | ---------------------------------------------------------------------------- |
85
+ | 🌱 Prototype | 30–65 | "Above band" here means you started with good hygiene, not that you are done |
86
+ | 🚀 MVP | 45–75 | Real users, real data — the security basics must exist |
87
+ | 🧪 Beta | 60–85 | Close the process gaps before they compound |
88
+ | 🏭 Production | 75–95 | Full bar; nothing dampened |
89
+ | 🏚️ Legacy | 40–70 | Graded on risk containment, not modernisation ambition |
90
+
91
+ The report always prints the score **and** the band and a verdict:
92
+
93
+ ```
94
+ Overall Health Score: 71.4/100
95
+ Expected band for Beta / Growing: 60–85 — within the expected band 👍
96
+ ```
97
+
98
+ ## Per-stage guidance
99
+
100
+ Each profile in [`rules/profiles/maturity.yaml`](../rules/profiles/maturity.yaml)
101
+ carries a `focus` list (what actually matters now) and a `defer` list (what to
102
+ deliberately ignore). Both are rendered in the report's roadmap:
103
+
104
+ ### 🌱 Prototype
105
+
106
+ **Focus:** secret hygiene and `.gitignore` · a README that says what this is and how
107
+ to run it · one smoke test so the happy path is known-good.
108
+ **Defer:** Kubernetes, multi-region, formal ADRs, 80% coverage gates.
109
+
110
+ ### 🚀 MVP
111
+
112
+ **Focus:** input validation and output encoding on every user-facing path · automated
113
+ backup plus a restore you have actually performed · CI running lint + tests ·
114
+ dependency scanning before the graph grows.
115
+ **Defer:** formal threat model, multi-region, chaos engineering, SBOM/VEX.
116
+
117
+ ### 🧪 Beta
118
+
119
+ **Focus:** branch protection with mandatory review · structured logging with
120
+ correlation IDs and real alerting · integration tests around critical flows ·
121
+ documented rollback.
122
+ **Defer:** full DR site, formal verification, mutation testing.
123
+
124
+ ### 🏭 Production
125
+
126
+ **Focus:** close every HIGH · provenance/attestation on release artifacts (SLSA) ·
127
+ tested DR with a real RTO/RPO · observability that answers "is it broken?" in under
128
+ five minutes.
129
+
130
+ ### 🏚️ Legacy
131
+
132
+ **Focus:** inventory EOL dependencies with a replacement date · confirm backups
133
+ restore and that someone still knows how · monitoring, alerting, and a named owner per
134
+ service · document well enough to hand over or retire.
135
+ **Defer:** rewrites without a strangler plan; framework migrations for their own sake.
136
+
137
+ ## Overriding
138
+
139
+ ```bash
140
+ usat audit . --profile production # grade against the full bar
141
+ ```
142
+
143
+ ```yaml
144
+ # .usat.yaml
145
+ maturity: production
146
+ ```
147
+
148
+ Use `--profile production` on a younger project when you want the
149
+ _"what would it take to ship this?"_ view. Expect the score to drop and the CRITICAL
150
+ list to stay exactly the same length — that is the point.
151
+
152
+ ## Customising
153
+
154
+ Edit [`rules/profiles/maturity.yaml`](../rules/profiles/maturity.yaml) — no code
155
+ changes needed. Add a stage, change the dampening table, adjust the expected bands, or
156
+ rewrite the focus/defer lists to match how your organisation actually ships.