backend-skeleton 1.0.0-beta.1

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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -0
  3. package/bin/bskel.mjs +2384 -0
  4. package/contracts/completeness.mjs +176 -0
  5. package/contracts/emit.mjs +287 -0
  6. package/contracts/export.mjs +325 -0
  7. package/contracts/openapi.mjs +869 -0
  8. package/contracts/validate.mjs +147 -0
  9. package/handles/_engine.mjs +281 -0
  10. package/handles/codec.mjs +119 -0
  11. package/handles/conformance.mjs +74 -0
  12. package/handles/providers/java-spring/ast-bridge.mjs +59 -0
  13. package/handles/providers/java-spring/ast-helper/build.gradle +34 -0
  14. package/handles/providers/java-spring/ast-helper/gradle/wrapper/gradle-wrapper.jar +0 -0
  15. package/handles/providers/java-spring/ast-helper/gradle/wrapper/gradle-wrapper.properties +9 -0
  16. package/handles/providers/java-spring/ast-helper/gradlew +248 -0
  17. package/handles/providers/java-spring/ast-helper/gradlew.bat +82 -0
  18. package/handles/providers/java-spring/ast-helper/settings.gradle +1 -0
  19. package/handles/providers/java-spring/ast-helper/src/main/java/com/backendskeleton/asthelper/Main.java +178 -0
  20. package/handles/providers/java-spring/emit.mjs +232 -0
  21. package/handles/providers/java-spring/patch-strategy.mjs +229 -0
  22. package/handles/providers/java-spring/plan.mjs +377 -0
  23. package/handles/providers/java-spring/templates/HandleAspect.java.tmpl +125 -0
  24. package/handles/providers/java-spring/templates/HandleCodec.java.tmpl +150 -0
  25. package/handles/providers/java-spring/templates/HandleController.java.tmpl +177 -0
  26. package/handles/providers/java-spring/templates/HandleRegistry.java.tmpl +107 -0
  27. package/handles/providers/java-spring/templates/HandleRegistryRepository.java.tmpl +8 -0
  28. package/handles/providers/java-spring/templates/HandleService.java.tmpl +95 -0
  29. package/handles/providers/java-spring/templates/HandleSnapshot.java.tmpl +75 -0
  30. package/handles/providers/java-spring/templates/HandleSnapshotRepository.java.tmpl +20 -0
  31. package/handles/providers/java-spring/templates/RecordHandleSnapshot.java.tmpl +50 -0
  32. package/handles/providers/java-spring/templates/ResourceResolver.java.tmpl +50 -0
  33. package/handles/providers/java-spring/templates/ResourceResolverStub.java.tmpl +77 -0
  34. package/handles/providers/java-spring/templates/migration.sql.tmpl +34 -0
  35. package/handles/providers/java-spring.mjs +21 -0
  36. package/handles/providers/python-fastapi/emit.mjs +171 -0
  37. package/handles/providers/python-fastapi/plan.mjs +186 -0
  38. package/handles/providers/python-fastapi/templates/__init__.py.tmpl +1 -0
  39. package/handles/providers/python-fastapi/templates/codec.py.tmpl +122 -0
  40. package/handles/providers/python-fastapi/templates/handle_service.py.tmpl +96 -0
  41. package/handles/providers/python-fastapi/templates/migration.sql.tmpl +35 -0
  42. package/handles/providers/python-fastapi/templates/record_snapshot.py.tmpl +155 -0
  43. package/handles/providers/python-fastapi/templates/registry.py.tmpl +37 -0
  44. package/handles/providers/python-fastapi/templates/resolver.py.tmpl +59 -0
  45. package/handles/providers/python-fastapi/templates/resolvers_init.py.tmpl +13 -0
  46. package/handles/providers/python-fastapi/templates/router.py.tmpl +140 -0
  47. package/handles/providers/python-fastapi/templates/tables.py.tmpl +66 -0
  48. package/handles/providers/python-fastapi.mjs +22 -0
  49. package/handles/providers/typescript-express/emit.mjs +128 -0
  50. package/handles/providers/typescript-express/plan.mjs +234 -0
  51. package/handles/providers/typescript-express/templates/codec.ts.tmpl +116 -0
  52. package/handles/providers/typescript-express/templates/registry.ts.tmpl +39 -0
  53. package/handles/providers/typescript-express/templates/resolver.ts.tmpl +55 -0
  54. package/handles/providers/typescript-express/templates/resolvers_index.ts.tmpl +11 -0
  55. package/handles/providers/typescript-express/templates/router.ts.tmpl +122 -0
  56. package/handles/providers/typescript-express.mjs +20 -0
  57. package/handles/registry.mjs +90 -0
  58. package/lib/cli.mjs +430 -0
  59. package/lib/doctor.mjs +200 -0
  60. package/lib/exit-codes.mjs +67 -0
  61. package/lib/featureid.mjs +55 -0
  62. package/lib/featurelifecycle.mjs +205 -0
  63. package/lib/fsutil.mjs +50 -0
  64. package/lib/gate-definitions.mjs +293 -0
  65. package/lib/gates.mjs +263 -0
  66. package/lib/handles-manifest.mjs +92 -0
  67. package/lib/lock.mjs +68 -0
  68. package/lib/patch-approvals.mjs +56 -0
  69. package/lib/paths.mjs +21 -0
  70. package/lib/repo.mjs +44 -0
  71. package/lib/schema-validate.mjs +56 -0
  72. package/lib/state.mjs +124 -0
  73. package/lib/template.mjs +35 -0
  74. package/lib/verify.mjs +206 -0
  75. package/lib/workflow.mjs +142 -0
  76. package/new/fastapi.mjs +165 -0
  77. package/new/index.mjs +62 -0
  78. package/new/params.mjs +233 -0
  79. package/new/spring.mjs +198 -0
  80. package/new/templates/fastapi/README.md +26 -0
  81. package/new/templates/fastapi/app/__init__.py +0 -0
  82. package/new/templates/fastapi/app/main.py +8 -0
  83. package/new/templates/fastapi/gitignore +6 -0
  84. package/new/templates/fastapi/pyproject.toml +14 -0
  85. package/package.json +50 -0
  86. package/scanners/adapters/_express-shared.mjs +238 -0
  87. package/scanners/adapters/_java-spring-analyzer.mjs +273 -0
  88. package/scanners/adapters/generic-grep.mjs +128 -0
  89. package/scanners/adapters/java-spring.mjs +301 -0
  90. package/scanners/adapters/javascript-express.mjs +422 -0
  91. package/scanners/adapters/python-fastapi.mjs +348 -0
  92. package/scanners/adapters/typescript-express.mjs +299 -0
  93. package/scanners/capabilities.mjs +90 -0
  94. package/scanners/conformance.mjs +59 -0
  95. package/scanners/db/introspect.mjs +109 -0
  96. package/scanners/db/migrations.mjs +126 -0
  97. package/scanners/index.mjs +281 -0
  98. package/scanners/registry.mjs +130 -0
  99. package/scanners/render.mjs +136 -0
  100. package/scanners/text-util.mjs +8 -0
  101. package/schemas/adapter.schema.json +23 -0
  102. package/schemas/agent-envelope.schema.json +21 -0
  103. package/schemas/contract-resolution.schema.json +28 -0
  104. package/schemas/feature-contract.schema.json +78 -0
  105. package/schemas/feature-index.schema.json +25 -0
  106. package/schemas/feature.schema.json +17 -0
  107. package/schemas/gate-event.schema.json +19 -0
  108. package/schemas/handles-plan.schema.json +31 -0
  109. package/schemas/handles-provider.schema.json +26 -0
  110. package/schemas/patch-approvals.schema.json +28 -0
  111. package/schemas/scan-report.schema.json +102 -0
  112. package/schemas/stack-choice.schema.json +89 -0
  113. package/schemas/stack-record.schema.json +20 -0
  114. package/schemas/state.schema.json +43 -0
  115. package/scripts/preflight-base-ref.sh +226 -0
  116. package/stack/apply.mjs +159 -0
  117. package/stack/bootstrap/_lib.sh +73 -0
  118. package/stack/bootstrap/ngrok.sh +90 -0
  119. package/stack/catalog/ngrok.yml +63 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 popixoxipop
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,284 @@
1
+ # backend-skeleton
2
+
3
+ Spec-driven backend scaffolding for brownfield (and greenfield) Java/Spring Boot, Python/FastAPI,
4
+ and TypeScript/JavaScript Express repos: a brownfield-collision gate before any spec/plan step,
5
+ feature_id-scoped machine-readable contracts, UUID-addressable field handles, and stack-choice
6
+ (e.g. ngrok) wiring — all enforced by disk `content-hash` gates, not prompt instructions a future
7
+ session could ignore.
8
+
9
+ `bskel` exists because a previous ad-hoc agent-driven scaffolding attempt branched a worktree 658
10
+ commits behind the real default branch and never noticed. Every gate in this tool is a regression
11
+ check for a specific failure mode found the same way — see `DECISIONS.md` for the full record.
12
+
13
+ ## Status: beta
14
+
15
+ This is the first public release, and it's deliberately labeled a beta rather than `1.0.0`
16
+ stable. Split by actual maturity, not by feature list:
17
+
18
+ - **`scan`, `contract` (including `export`), and `new`** are the most exercised paths — real,
19
+ measured verification against a real production Spring Boot repo (see `DECISIONS.md`), plus a
20
+ synthetic fixture corpus for every adapter, run in CI on every change.
21
+ - **`handles`** (the UUID-addressable resolver/codec/router codegen) is functionally complete and
22
+ tested the same way, but has never been deployed to a real production repo, and two named gaps
23
+ stay open specifically because of that: generated `fetch()`/`patch()` paths never check
24
+ `HandleRegistry` for revocation, and generated authorization is inferred from a single
25
+ `@PreAuthorize(hasRole(...))` shape rather than a real policy contract. Both are tracked as `O3`
26
+ and `O5` in `CATALOG.md`, explicitly deferred until handles are actually used somewhere real --
27
+ treat `handles emit`'s output as a scaffold to finish by hand, not a production-ready subsystem,
28
+ until that work lands.
29
+
30
+ Version numbers, install instructions, and a real feedback path will firm up as this gets used
31
+ against more real repos.
32
+
33
+ ## Quickstart
34
+
35
+ ```bash
36
+ npm install -g backend-skeleton@beta # or: npx backend-skeleton@beta <command>
37
+ cd <target-repo> # must be a git repository
38
+
39
+ bskel doctor # what's on PATH, which scanner adapter detects this repo, and why
40
+ bskel preflight # confirms HEAD is actually based on the real default branch,
41
+ # not a stale/abandoned one -- required before anything else
42
+
43
+ bskel feature init --slug organization-management
44
+ bskel scan --feature 001-organization-management --terms organization
45
+ # brownfield-collision scan; refuses to proceed silently if this
46
+ # module already exists elsewhere in the codebase
47
+ bskel scan disposition --feature 001-organization-management --mode reuse --note "..."
48
+ # required once scan finds a collision/adjacent match
49
+
50
+ bskel contract emit --feature 001-organization-management
51
+ # feature_id-scoped JSON Schema contract, from real source annotations
52
+ bskel handles plan --feature 001-organization-management
53
+ bskel handles emit --feature 001-organization-management
54
+ # UUID-addressable field handles + generated resolver code
55
+
56
+ bskel verify --feature 001-organization-management --build
57
+ # aggregates every gate's current status; --build also runs the
58
+ # target repo's own build wrapper (gradlew/mvnw/npm), if present
59
+ ```
60
+
61
+ ### Starting from nothing (greenfield)
62
+
63
+ Every command above assumes an existing Spring Boot or FastAPI repo. If you don't have one yet:
64
+
65
+ ```bash
66
+ bskel new --stack spring --slug my-service # calls start.spring.io (network required), or:
67
+ bskel new --stack fastapi --slug my-service # a local starter template, no network call
68
+
69
+ cd my-service
70
+ # create a remote you own and push to it (e.g. `gh repo create --private --source=. --push`),
71
+ # then: git remote set-head origin --auto
72
+ bskel preflight # now resolvable -- picks up from the Quickstart above
73
+ ```
74
+
75
+ `bskel new` deliberately never creates a remote itself and never auto-chains into `preflight` --
76
+ `preflight` requires a real `origin` remote with a resolvable default branch, which a brand-new
77
+ local-only repo doesn't have yet. See `D-greenfield-bootstrap` in `DECISIONS.md`.
78
+
79
+ Both stacks accept `--name`, `--description` and `--project-version` (the *generated project's* own
80
+ version -- `--version` is a global flag that prints `bskel`'s). Beyond that the parameters differ,
81
+ because the two ecosystems do:
82
+
83
+ ```bash
84
+ bskel new --stack spring --slug my-service \
85
+ --group-id com.acme --artifact-id billing --package-name com.acme.billing \
86
+ --java-version 21 --packaging war --add-dependencies actuator,postgresql
87
+
88
+ bskel new --stack fastapi --slug my-service \
89
+ --python-version 3.12 --port 9000 --license MIT --database postgres
90
+ ```
91
+
92
+ - **`--add-dependencies` extends** the baseline (`web, data-jpa, security, validation, lombok`).
93
+ **`--dependencies` REPLACES it** -- and if the result drops `web`, `data-jpa` or `validation`, you
94
+ get a specific stderr warning naming what stops working downstream, then it scaffolds anyway.
95
+ - **`--group-id`/`--package-name`/`--artifact-id` are validated locally** against the Java package
96
+ grammar. That isn't belt-and-braces: `start.spring.io` accepts `groupId=com.new` (a reserved word)
97
+ and `groupId=has space` with HTTP 200 and hands back a project that cannot compile.
98
+ - **`--java-version` is checked against `start.spring.io`'s own live metadata**, fetched on demand
99
+ only when you pass a non-default value, never cached to disk -- for the same reason: `javaVersion=99`
100
+ returns HTTP 200 and writes `JavaLanguageVersion.of(99)` straight into `build.gradle`.
101
+ - **`--database` pins a driver and nothing else** -- no engine, session or connection code is
102
+ generated, because that would be `bskel` inventing your domain.
103
+ - `--type`, `--language` and `--boot-version` are deliberately **refused** with a specific reason
104
+ each (Maven/Kotlin scaffolds break this tool's own scanner and codegen assumptions; a bad
105
+ `bootVersion` gets an unusable HTTP 500). Run `bskel new --stack spring --type maven-project` to
106
+ see the actual explanation.
107
+
108
+ The full parameter list, the measured API-validation matrix behind that split, and the warning
109
+ behaviour are in `D-greenfield-parameters` in `DECISIONS.md`.
110
+
111
+ ### Publishing a feature's contract as OpenAPI (optional)
112
+
113
+ ```bash
114
+ bskel contract export --feature 001-organization-management --out openapi/organization.json
115
+ ```
116
+
117
+ Renders an already-emitted, gate-passing contract as a standalone **OpenAPI 3.1** document — the
118
+ inverse of `contract emit --openapi-file`. Useful for a Swagger UI page scoped to one feature
119
+ instead of the whole repo, a client generator that can't follow `$ref` (an exported document has
120
+ none), or a mock server for one feature's operations.
121
+
122
+ It is a **deliberately lossy, narrow projection, and it says so**: the contract carries no query or
123
+ header parameters, no security requirements, no summaries or tags, and no per-status responses, so
124
+ none of those appear — every omission is disclosed both in prose (`info.description`) and
125
+ machine-readably (`info.x-bskel-omitted`). Nothing is invented to fill a gap: `security` is omitted
126
+ rather than emitted as `[]` (an empty array would positively claim no authentication is required),
127
+ and an operation whose body shape the contract doesn't know gets a JSON media-type entry with no
128
+ schema rather than a fabricated one.
129
+
130
+ Export refuses a zero-operation contract, refuses when the scan found a global path prefix the
131
+ contract's paths don't reflect (`--allow-unprefixed` overrides), and stamps every document with an
132
+ `x-bskel-generated` marker that `contract emit --openapi-file` then refuses to read back in —
133
+ reconciling a contract against its own export would make it confirm itself. See `D-openapi-export`
134
+ in `DECISIONS.md`.
135
+
136
+ ### Database schema (optional)
137
+
138
+ `bskel scan --db` additionally scans Flyway/Liquibase migration files (local only, no network).
139
+ Add `--database-url-env <NAME>` (naming an environment variable you've already exported, never
140
+ read from `.env`) for live, read-only Postgres introspection (`information_schema`/`pg_catalog`,
141
+ inside a `BEGIN TRANSACTION READ ONLY`) and a source-vs-live drift report. Both are informational
142
+ additions to the scan report — neither blocks any gate. See `D-db-schema-plane` in `DECISIONS.md`.
143
+
144
+ Every command is read-only until you explicitly run one of the mutating steps above — `bskel
145
+ status`/`bskel next` (no arguments needed) tell you which gate is next and print the exact
146
+ copy-pasteable command for it, without touching anything.
147
+
148
+ The full gated workflow, what each phase writes, and every flag is documented in `SKILL.md`
149
+ (present in this repository, not in the installed npm package — see "What ships in the package"
150
+ below).
151
+
152
+ ## Compatibility
153
+
154
+ | Requirement | Constraint | Why |
155
+ |---|---|---|
156
+ | Node.js | `>=18` | ES2022 (`Object.hasOwn`) + ESM top-level `await` — nothing newer is used anywhere in the runtime code (verified by grep across every recent-ES-addition pattern; see `D-npm-packaging` in `DECISIONS.md`) |
157
+ | git | required | every gate is git-state-derived |
158
+ | [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) | required for `scan`/`handles` | every scanner adapter shells out to it directly, and throws (not degrades) if it's missing |
159
+ | `gh` (GitHub CLI) | optional | only used for `preflight`'s 3-way default-branch cross-check; already soft-guarded, never a hard requirement |
160
+ | `python3` | optional | only needed to run this repository's own cross-language codec test — `bskel` itself never invokes `python3` |
161
+ | a build wrapper (`gradlew`/`pom.xml`+`mvnw`/`package.json`) | optional | only `bskel verify --build` needs one; `handles emit` never compiles anything itself |
162
+
163
+ Run `bskel doctor` in any target repo to see exactly which of these it found, with a remediation
164
+ string for anything missing.
165
+
166
+ **Supported scanner adapters** (auto-selected by specificity, never hardcoded — see
167
+ `D-adapter-registry` in `DECISIONS.md`):
168
+ - `java-spring` — Spring Boot (`build.gradle`/`pom.xml` + `src/main/java`). Full capability set:
169
+ operation extraction, request-body detection, and a real codegen provider for `handles emit`.
170
+ - `python-fastapi` — FastAPI + SQLModel. Real codegen provider for `handles emit`; contract-grade
171
+ operation extraction is not supported (FastAPI generates operation ids at runtime) — pass a real
172
+ OpenAPI document via `--openapi-file` for a trustworthy contract.
173
+ - `typescript-express` — TypeScript + Express + TypeORM. Real codegen provider for `handles emit`
174
+ (entities come from `@Entity`/`@PrimaryGeneratedColumn`); no operation extraction — plain Express
175
+ has no operationId concept, so pass `--openapi-file` for a contract. See
176
+ `D-typescript-express-provider` in `DECISIONS.md`.
177
+ - `javascript-express` — plain-JavaScript ESM Express with **no ORM** (raw `mysql2`/`mariadb`),
178
+ including `serverless-http`/Lambda deployments. **Scanner only** — routes and their real absolute
179
+ paths are resolved through a full mount-graph walk, but every capability is honestly `false`:
180
+ there is no codegen provider, because raw SQL string literals carry no trustworthy
181
+ table/primary-key/column-allow-list metadata. See `D-javascript-express-adapter` in `DECISIONS.md`
182
+ for the measured reasoning.
183
+ - `generic-grep` — unconditional last-resort fallback (Express/Flask/FastAPI-shaped route
184
+ detection). Reconnaissance only, never contract-grade — always `confidence: "low"`, requires
185
+ `--accept-low-confidence` to proceed past a feature-scoped scan.
186
+
187
+ ## Generated-file policy
188
+
189
+ `bskel handles emit` writes real Java/Python source into your repository. Two things are always
190
+ true about what it writes:
191
+
192
+ - **`fetch()` is wired to a real, existing, already-tested read-only service method** — never
193
+ hand-written business logic. It's generated only when a matching `<Entity>Service` method exists
194
+ and takes exactly the one resource UUID argument a resolver always passes (a mismatch there means
195
+ "no resolver generated", not "generate one and hope", since silently calling the wrong overload
196
+ can drop a required scoping argument — see `D-security-8` in `DECISIONS.md`).
197
+ - **`patchField()` is always a stub.** Real codebases mix at least three different partial-update
198
+ DTO conventions; guessing wrong would silently bypass real validation. A human finishes this by
199
+ hand, every time.
200
+
201
+ Reruns are safe by construction, not by convention (`D-handles-ownership` in `DECISIONS.md`):
202
+ safety is derived from the generated file's actual on-disk **content**, not from a manifest that
203
+ might be absent (a fresh checkout, CI, or a repo that doesn't commit `.sbf/`). A file that diverged
204
+ from what `bskel` generated (a hand-finished `patchField()`, someone else's edit) is never silently
205
+ overwritten — it reports a conflict, and the escape hatch (`--force --reason "..."`) is always
206
+ audited, never silent.
207
+
208
+ ## Security model
209
+
210
+ `bskel` generates code that runs in production, so it was put through an adversarial security
211
+ review (Codex, security-only lens, independent of the build process) — 8 findings, all fixed, each
212
+ with an inline `D-security-N` comment at its exact location in the code. Highlights (full record,
213
+ including 3 additional defensive-hardening items, in `DECISIONS.md`'s "Security hardening pass"
214
+ section):
215
+
216
+ - **Prototype-pollution guards** everywhere a user-controlled string indexes a plain object
217
+ (`operation_id` values like `"constructor"`/`"__proto__"` are rejected, not silently resolved via
218
+ the prototype chain).
219
+ - **Path-traversal containment** on every stack-catalog-driven file write (`--choice`, and every
220
+ catalog entry's own declared template/path fields).
221
+ - **No predictable temp files, no silent permission downgrades** in the generated bootstrap
222
+ scripts that touch `.env` (`mktemp` + an unconditional `chmod 600`).
223
+ - **Authority derivation is per-method, not per-file** — a controller's first `@PreAuthorize` match
224
+ no longer silently applies to every resolver generated from that file; an unsupported annotation
225
+ shape (`hasAnyRole`, SpEL) fails closed to a `TODO_ROLE` placeholder rather than guessing.
226
+ - **Handle recovery cross-checks type/kind/pointer against the registry row**, not just the raw
227
+ UUID — the most severe finding: an attacker who controls the handle's `type` field could
228
+ otherwise request a different, more sensitive resource's snapshot history that happens to share
229
+ the same UUID.
230
+
231
+ ## Troubleshooting
232
+
233
+ Start with `bskel doctor` — it names exactly which required tool is missing and why, or run `bskel
234
+ status`/`bskel next` to see which gate is currently blocking and the exact command to resolve it.
235
+
236
+ Every command shares one exit-code table (`lib/exit-codes.mjs`) and, with `--json`, an additive
237
+ diagnostic envelope on payload-less early exits — the number is the stable contract, `reason` in
238
+ the envelope is supplementary precision:
239
+
240
+ | Exit | Meaning |
241
+ |---|---|
242
+ | `0` | OK |
243
+ | `2` | a required gate hasn't passed yet, or a referenced resource/adapter/provider doesn't exist (`--json`'s `reason` field disambiguates which) |
244
+ | `3` | a gate is awaiting a disposition decision (`bskel scan disposition`/`bskel contract waive`) |
245
+ | `4` | a gate is stale — either an input actually changed, or (`preflight` only) the pass is simply too old |
246
+ | `10` | not inside a git repository |
247
+ | `11` | `preflight`: HEAD is behind the real default branch |
248
+ | `12` | `preflight`: the three independent sources for "what is the default branch" disagree, or none could be determined |
249
+ | `13` | `preflight`: uncommitted changes present (`--allow-dirty` to override) |
250
+ | `14` | bad arguments |
251
+ | `16` | a low-confidence scan was blocked (`--accept-low-confidence` to proceed anyway) |
252
+ | `17` | the selected adapter/provider doesn't support a capability the command needs |
253
+ | `18` | `preflight`: a `git fetch` was attempted and failed (`--offline` to accept a local-only verdict instead) |
254
+
255
+ Common cases:
256
+ - **`preflight` fails with `STALE_BASE`**: your branch really is behind — `git worktree add
257
+ <path> -b <branch> origin/<default-branch>` (or rebase in place), then re-run.
258
+ - **`scan` exits `16`**: the scanner fell back to `generic-grep` (low confidence, no real parser).
259
+ If this repo actually is Java/Spring or Python/FastAPI-shaped, run `bskel doctor` first — it
260
+ explains exactly why the real adapter didn't detect it, rather than reflexively passing
261
+ `--accept-low-confidence`.
262
+ - **`handles emit`/`contract emit` exits `17`**: the adapter that scanned this repo doesn't
263
+ declare the capability that command needs (e.g. `generic-grep` never declares `codegen.handles`
264
+ — there's no codegen provider for a route-pattern-only stack). `bskel doctor` lists every
265
+ installed adapter's declared capabilities.
266
+ - **A previously-passed `preflight` now reports stale with `ttl_expired`**: passes expire after 30
267
+ minutes by default (data-derived, see `D-preflight-freshness` in `DECISIONS.md`) — re-run
268
+ `bskel preflight`, or pass `--max-age-minutes 0` to disable the TTL for a deliberately
269
+ long-running or offline session.
270
+
271
+ The full exit-code/`reason` taxonomy, global flags (`--help`/`--version`/`--json`/`--quiet`), and
272
+ the complete gated-workflow reference live in `SKILL.md` and `DECISIONS.md` in this repository.
273
+
274
+ ## What ships in the package
275
+
276
+ `npm install` ships only what `bskel` reads at runtime — `bin/`, `lib/`, `contracts/`,
277
+ `scanners/`, `handles/` (including every codegen template), `stack/` (including the catalog and
278
+ bootstrap templates), `schemas/`, and `scripts/preflight-base-ref.sh`. This repository's own test
279
+ suite, `SKILL.md` (this project's Claude Code skill definition), `DECISIONS.md`, and `CATALOG.md`
280
+ are not part of the published package — clone this repository directly if you want those.
281
+
282
+ ## License
283
+
284
+ MIT — see `LICENSE`.