devready-cli 0.1.0__tar.gz → 0.3.0__tar.gz

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 (64) hide show
  1. {devready_cli-0.1.0 → devready_cli-0.3.0}/.claude-plugin/plugin.json +1 -1
  2. {devready_cli-0.1.0 → devready_cli-0.3.0}/.github/workflows/ci.yml +1 -1
  3. {devready_cli-0.1.0 → devready_cli-0.3.0}/CHANGELOG.md +10 -0
  4. {devready_cli-0.1.0 → devready_cli-0.3.0}/CONTRIBUTING.md +7 -1
  5. {devready_cli-0.1.0 → devready_cli-0.3.0}/PKG-INFO +7 -3
  6. {devready_cli-0.1.0 → devready_cli-0.3.0}/README.md +6 -2
  7. {devready_cli-0.1.0 → devready_cli-0.3.0}/ROADMAP.md +14 -13
  8. {devready_cli-0.1.0 → devready_cli-0.3.0}/action.yml +5 -0
  9. devready_cli-0.3.0/docs/architecture.md +18 -0
  10. devready_cli-0.3.0/docs/checks.md +57 -0
  11. devready_cli-0.3.0/docs/security.md +11 -0
  12. devready_cli-0.3.0/docs/versioning.md +24 -0
  13. {devready_cli-0.1.0 → devready_cli-0.3.0}/pyproject.toml +1 -1
  14. devready_cli-0.3.0/src/devready/__init__.py +1 -0
  15. devready_cli-0.3.0/src/devready/baseline.py +43 -0
  16. devready_cli-0.3.0/src/devready/checks/config.py +102 -0
  17. devready_cli-0.3.0/src/devready/checks/platform_checks.py +108 -0
  18. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/checks/project.py +3 -2
  19. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/cli.py +29 -7
  20. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/explain.py +8 -0
  21. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/runner.py +13 -1
  22. devready_cli-0.3.0/tests/unit/test_baseline.py +49 -0
  23. devready_cli-0.3.0/tests/unit/test_cli.py +59 -0
  24. devready_cli-0.3.0/tests/unit/test_config.py +67 -0
  25. devready_cli-0.3.0/tests/unit/test_platform.py +72 -0
  26. devready_cli-0.3.0/tests/unit/test_stability.py +57 -0
  27. {devready_cli-0.1.0 → devready_cli-0.3.0}/tests/unit/test_versions_project.py +9 -0
  28. devready_cli-0.1.0/src/devready/__init__.py +0 -1
  29. devready_cli-0.1.0/tests/unit/test_cli.py +0 -35
  30. {devready_cli-0.1.0 → devready_cli-0.3.0}/.claude-plugin/marketplace.json +0 -0
  31. {devready_cli-0.1.0 → devready_cli-0.3.0}/.gitattributes +0 -0
  32. {devready_cli-0.1.0 → devready_cli-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  33. {devready_cli-0.1.0 → devready_cli-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  34. {devready_cli-0.1.0 → devready_cli-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  35. {devready_cli-0.1.0 → devready_cli-0.3.0}/.github/workflows/publish.yml +0 -0
  36. {devready_cli-0.1.0 → devready_cli-0.3.0}/.gitignore +0 -0
  37. {devready_cli-0.1.0 → devready_cli-0.3.0}/.mcp.json +0 -0
  38. {devready_cli-0.1.0 → devready_cli-0.3.0}/AGENTS.md +0 -0
  39. {devready_cli-0.1.0 → devready_cli-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  40. {devready_cli-0.1.0 → devready_cli-0.3.0}/LICENSE +0 -0
  41. {devready_cli-0.1.0 → devready_cli-0.3.0}/SECURITY.md +0 -0
  42. {devready_cli-0.1.0 → devready_cli-0.3.0}/commands/doctor.md +0 -0
  43. {devready_cli-0.1.0 → devready_cli-0.3.0}/docs/agents.md +0 -0
  44. {devready_cli-0.1.0 → devready_cli-0.3.0}/docs/research.md +0 -0
  45. {devready_cli-0.1.0 → devready_cli-0.3.0}/docs/schema.json +0 -0
  46. {devready_cli-0.1.0 → devready_cli-0.3.0}/docs/technical-plan.md +0 -0
  47. {devready_cli-0.1.0 → devready_cli-0.3.0}/skills/devready/SKILL.md +0 -0
  48. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/__main__.py +0 -0
  49. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/checks/__init__.py +0 -0
  50. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/checks/extras.py +0 -0
  51. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/checks/health.py +0 -0
  52. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/checks/python_check.py +0 -0
  53. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/checks/runtimes.py +0 -0
  54. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/checks/tools.py +0 -0
  55. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/formats.py +0 -0
  56. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/mcp_server.py +0 -0
  57. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/models.py +0 -0
  58. {devready_cli-0.1.0 → devready_cli-0.3.0}/src/devready/versions.py +0 -0
  59. {devready_cli-0.1.0 → devready_cli-0.3.0}/tests/unit/test_extras.py +0 -0
  60. {devready_cli-0.1.0 → devready_cli-0.3.0}/tests/unit/test_formats.py +0 -0
  61. {devready_cli-0.1.0 → devready_cli-0.3.0}/tests/unit/test_health.py +0 -0
  62. {devready_cli-0.1.0 → devready_cli-0.3.0}/tests/unit/test_mcp.py +0 -0
  63. {devready_cli-0.1.0 → devready_cli-0.3.0}/tests/unit/test_runtimes.py +0 -0
  64. {devready_cli-0.1.0 → devready_cli-0.3.0}/tests/unit/test_schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devready",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "homepage": "https://devready-ten.vercel.app/",
5
5
  "description": "Read-only environment preflight: compares a project's declared Node/Python versions with what is installed.",
6
6
  "author": { "name": "DevReady contributors" },
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest, windows-latest, macos-latest]
11
- python: ["3.10", "3.13"]
11
+ python: ["3.10", "3.11", "3.12", "3.13"]
12
12
  runs-on: ${{ matrix.os }}
13
13
  steps:
14
14
  - uses: actions/checkout@v4
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 - 2026-10-16
4
+ - Text output now shows the suggestion under each non-passing result (community PR #6, fixes #2).
5
+ - Fix: a pin file with short hostile text (e.g. `.nvmrc`, `rust-toolchain.toml`, `global.json`, `.tool-versions`) was echoed in the summary when the matching tool was not installed. Only specs the matcher fully parses are now shown. Affects 0.2.0 and earlier.
6
+ - Docs: check catalog, architecture, security model, versioning policy; CI matrix adds Python 3.11/3.12.
7
+ - Platform checks, shown only when relevant: Windows long paths, PowerShell execution policy, `core.autocrlf`; macOS Xcode CLT and Linux compiler/make for native builds.
8
+
9
+ ## 0.2.0 - 2026-10-16
10
+ - `check --baseline FILE` (and `baseline:` Action input): show only results that changed since a saved JSON report.
11
+ - Optional `.devready.json` (`skip`, `require`, `env`) and `devready init` (prints a starter).
12
+
3
13
  ## 0.1.0 - 2026-10-16
4
14
  - GitHub Action (`action.yml`) wrapping `devready check`.
5
15
  - `--format junit|sarif`, `--only`/`--skip` filters, and `devready fix --plan` (suggestions only).
@@ -23,7 +23,13 @@ ruff check . && ruff format --check . && pytest
23
23
  ## Adding a check
24
24
 
25
25
  Return a `Result` (`src/devready/models.py`). Run external commands with a fixed argument list and
26
- a timeout, and report only parsed values. Add the check to the README only once it works.
26
+ a timeout, and report only parsed values. Then:
27
+
28
+ 1. Pick a stable id matching `^[a-z_]+(\.[a-z_]+)+$` (no digits or hyphens). Ids are public API.
29
+ 2. Add an entry in `src/devready/explain.py` and a row in `docs/checks.md` (a test enforces both).
30
+ 3. Register it in `src/devready/runner.py`; make it silent when the project does not use the thing.
31
+ 4. Test with injected fakes, plus a hostile-content case if it reads project files.
32
+ 5. Mention it in `CHANGELOG.md`. See [docs/versioning.md](docs/versioning.md) for what may change.
27
33
 
28
34
  ## Conduct and security
29
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: devready-cli
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: A local-first, read-only CLI that checks a project's environment requirements against what is installed.
5
5
  Project-URL: Homepage, https://devready-ten.vercel.app/
6
6
  Project-URL: Repository, https://github.com/Evalogical-Inc/devReady
@@ -114,6 +114,9 @@ devready explain docker.daemon # what a check means and how to fix it
114
114
  devready check --format junit # or sarif, for CI
115
115
  devready check --only project --skip project.ports # filter by id or prefix
116
116
  devready fix --plan # suggested steps only; never runs them
117
+ devready check --format json > baseline.json # save a snapshot (commit it for a team baseline)
118
+ devready check --baseline baseline.json # show only what changed; fail only on new/worse failures
119
+ devready init > .devready.json # optional config: skip ids, require node/python, required env names
117
120
  devready mcp # run as an MCP server over stdio
118
121
  devready --version
119
122
  ```
@@ -190,7 +193,7 @@ args = ["--from", "devready-cli", "devready", "mcp"]
190
193
  - Environment variable values are never read into output. Nothing leaves your machine.
191
194
  - A passing report does **not** guarantee your project builds.
192
195
 
193
- See [SECURITY.md](SECURITY.md) to report a vulnerability.
196
+ See [docs/security.md](docs/security.md) for the model, [docs/versioning.md](docs/versioning.md) for what stays stable, [docs/checks.md](docs/checks.md) for every check, and [SECURITY.md](SECURITY.md) to report a vulnerability.
194
197
 
195
198
  ## Limitations (alpha)
196
199
 
@@ -202,7 +205,8 @@ See [SECURITY.md](SECURITY.md) to report a vulnerability.
202
205
 
203
206
  - [x] Docker CLI + daemon reachability, `.tool-versions`, `.env` key-name diff, disk space, git identity, `devready explain`
204
207
  - [x] Port-in-use checks, more ecosystems (Go, Rust, Java, Ruby, .NET, pnpm/yarn/bun), JUnit/SARIF output, `fix --plan`, GitHub Action
205
- - [ ] Config file, shared baseline (see [ROADMAP.md](ROADMAP.md))
208
+ - [x] `.devready.json` config, `--baseline` snapshots
209
+ - [ ] Platform-specific checks (see [ROADMAP.md](ROADMAP.md))
206
210
  - [ ] Standalone binaries
207
211
 
208
212
  Have a setup problem DevReady should catch? [Open an issue](https://github.com/Evalogical-Inc/devReady/issues/new/choose). Early feedback shapes the roadmap.
@@ -90,6 +90,9 @@ devready explain docker.daemon # what a check means and how to fix it
90
90
  devready check --format junit # or sarif, for CI
91
91
  devready check --only project --skip project.ports # filter by id or prefix
92
92
  devready fix --plan # suggested steps only; never runs them
93
+ devready check --format json > baseline.json # save a snapshot (commit it for a team baseline)
94
+ devready check --baseline baseline.json # show only what changed; fail only on new/worse failures
95
+ devready init > .devready.json # optional config: skip ids, require node/python, required env names
93
96
  devready mcp # run as an MCP server over stdio
94
97
  devready --version
95
98
  ```
@@ -166,7 +169,7 @@ args = ["--from", "devready-cli", "devready", "mcp"]
166
169
  - Environment variable values are never read into output. Nothing leaves your machine.
167
170
  - A passing report does **not** guarantee your project builds.
168
171
 
169
- See [SECURITY.md](SECURITY.md) to report a vulnerability.
172
+ See [docs/security.md](docs/security.md) for the model, [docs/versioning.md](docs/versioning.md) for what stays stable, [docs/checks.md](docs/checks.md) for every check, and [SECURITY.md](SECURITY.md) to report a vulnerability.
170
173
 
171
174
  ## Limitations (alpha)
172
175
 
@@ -178,7 +181,8 @@ See [SECURITY.md](SECURITY.md) to report a vulnerability.
178
181
 
179
182
  - [x] Docker CLI + daemon reachability, `.tool-versions`, `.env` key-name diff, disk space, git identity, `devready explain`
180
183
  - [x] Port-in-use checks, more ecosystems (Go, Rust, Java, Ruby, .NET, pnpm/yarn/bun), JUnit/SARIF output, `fix --plan`, GitHub Action
181
- - [ ] Config file, shared baseline (see [ROADMAP.md](ROADMAP.md))
184
+ - [x] `.devready.json` config, `--baseline` snapshots
185
+ - [ ] Platform-specific checks (see [ROADMAP.md](ROADMAP.md))
182
186
  - [ ] Standalone binaries
183
187
 
184
188
  Have a setup problem DevReady should catch? [Open an issue](https://github.com/Evalogical-Inc/devReady/issues/new/choose). Early feedback shapes the roadmap.
@@ -2,12 +2,12 @@
2
2
 
3
3
  Principles (unchanged): read-only, local-first, zero runtime dependencies, Python >= 3.10, never print env values or raw command output.
4
4
 
5
- ## Where we are (0.1.0)
5
+ ## Where we are (0.3.0)
6
6
 
7
7
  - Tools: Git, Node, npm, Python. Docker, Go, Ruby, Java, Rust, .NET, pnpm, Yarn, Bun only when the project declares them (details in the sections below).
8
8
  - Pins compared: `.nvmrc`, `.node-version`, `.python-version`, `package.json` engines.node, `pyproject.toml` requires-python, `.tool-versions` (node/python only).
9
9
  - Also: `.env.example` vs `.env` key names, git identity presence, free disk space.
10
- - Interfaces: `check` (text/JSON, exit 1 on `fail`), `explain`, `mcp`, Claude Code plugin, `AGENTS.md`.
10
+ - Interfaces: `check` (text/JSON/JUnit/SARIF, `--only`/`--skip`/`--baseline`, exit 1 on `fail`), `fix --plan`, `init`, `explain`, `mcp`, GitHub Action, Claude Code plugin, `AGENTS.md`.
11
11
  - CI and PyPI publish workflows exist.
12
12
 
13
13
  ## Decisions (from the docs review)
@@ -41,26 +41,27 @@ Principles (unchanged): read-only, local-first, zero runtime dependencies, Pytho
41
41
  - [x] `devready fix --plan`: prints each `fail`/`warn` with its suggestion. Never runs anything. (Suggestions are prose today, not exact commands.)
42
42
  - [x] `--format sarif` and `junit` (SARIF has no file locations yet, so GitHub code scanning may not show alerts).
43
43
  - [x] `--only` / `--skip` filters by check id or dotted prefix (repeatable).
44
- - [ ] Optional `.devready.toml` to declare requirements and silence checks (needs a 3.10-safe parser, or limit to JSON).
45
- - [ ] `devready init` generates that file from what it detects.
44
+ - [x] Optional `.devready.json` (JSON, so no parser dependency): `skip` ids, `require` node/python, required `env` names.
45
+ - [x] `devready init` prints a starter file (does not write it; DevReady stays read-only).
46
46
 
47
- ## 0.4 - Team and CI (in progress)
47
+ ## 0.4 - Team and CI
48
48
 
49
49
  - [x] GitHub Action (`action.yml`, composite). Untested on a real runner; needs a release with the new flags.
50
- - [ ] Shared baseline file (committed) to compare against.
51
- - [ ] `--watch`/diff mode: show only what changed since the last run.
50
+ - [x] `--baseline FILE`: compare with a saved `--format json` report (commit it for a team baseline). Shows new/changed results; exit 1 only for a new or worse `fail`. Results that disappeared are not reported.
51
+ - [x] Diff mode = `--baseline` against your own saved snapshot. DevReady never writes the snapshot (read-only), and there is no `--watch` loop.
52
52
 
53
- ## 0.5 - Platform specifics
53
+ ## 0.5 - Platform specifics (WSL left)
54
54
 
55
- - [ ] Windows: long paths enabled, execution policy, `core.autocrlf`.
55
+ - [x] Windows: long paths (registry), PowerShell execution policy (blocks npm.ps1), `core.autocrlf` without `.gitattributes`. Only when relevant (`package.json` / a git repo). Verified against real long-paths and policy values on one Windows machine; the warning paths are unit-tested only.
56
56
  - [ ] WSL version and Docker Desktop backend.
57
- - [ ] macOS: Xcode CLT; Linux: common missing build deps.
57
+ - [x] macOS Xcode CLT / Linux `cc`+`make`, only when `binding.gyp`, `setup.py`, `Gemfile` or `Cargo.toml` exists. Not run on macOS or Linux yet (CI runs the suite there, with fakes).
58
58
 
59
59
  ## 1.0 - Stabilize
60
60
 
61
- - [ ] Freeze JSON schema (`docs/schema.json`) with a versioning policy.
62
- - [ ] Test matrix on Windows/macOS/Linux x Python 3.10-3.13.
63
- - [ ] Docs pass: check catalog, plugin/MCP setup, contributing guide for adding a check.
61
+ - [x] Schema frozen at v1 with a policy: `docs/versioning.md`, enforced by `tests/unit/test_stability.py` (fields, statuses, id pattern, catalog in sync).
62
+ - [x] CI matrix: Ubuntu/Windows/macOS x Python 3.10, 3.11, 3.12, 3.13 (edited, not yet run on GitHub).
63
+ - [x] Docs: `docs/checks.md`, `architecture.md`, `security.md`, `versioning.md`, and an "Adding a check" guide in CONTRIBUTING. Plugin/MCP setup already in `docs/agents.md`.
64
+ - [ ] Bump to 1.0.0 once the patch release below is out and CI is green on the full matrix.
64
65
 
65
66
  ## Opt-in only (needs explicit design review)
66
67
 
@@ -16,6 +16,9 @@ inputs:
16
16
  skip:
17
17
  description: Space-separated check ids or prefixes to skip.
18
18
  default: ""
19
+ baseline:
20
+ description: Path to a committed `--format json` report; only changes are shown and only a new or worse failure fails the job.
21
+ default: ""
19
22
  version:
20
23
  description: devready-cli version to install (empty = latest).
21
24
  default: ""
@@ -36,10 +39,12 @@ runs:
36
39
  DR_ONLY: ${{ inputs.only }}
37
40
  DR_SKIP: ${{ inputs.skip }}
38
41
  DR_VERSION: ${{ inputs.version }}
42
+ DR_BASELINE: ${{ inputs.baseline }}
39
43
  run: |
40
44
  set -eu
41
45
  python -m pip install --quiet "devready-cli${DR_VERSION:+==$DR_VERSION}"
42
46
  args=(check --project "$DR_PROJECT" --format "$DR_FORMAT")
43
47
  for id in $DR_ONLY; do args+=(--only "$id"); done
44
48
  for id in $DR_SKIP; do args+=(--skip "$id"); done
49
+ if [ -n "$DR_BASELINE" ]; then args+=(--baseline "$DR_BASELINE"); fi
45
50
  python -m devready "${args[@]}"
@@ -0,0 +1,18 @@
1
+ # Architecture
2
+
3
+ ```
4
+ cli.py -> runner.run_checks(root) -> checks/* -> list[Result]
5
+ | |
6
+ .devready.json skip filters (--only/--skip), --baseline diff
7
+ |
8
+ text | json | junit | sarif | fix plan (formats.py)
9
+ ```
10
+
11
+ - `models.py`: `Result` and `Status`. Every check returns `Result`s; nothing else crosses a boundary.
12
+ - `runner.py`: runs the checks in order, applies the config `skip` list. `mcp_server.py` calls it too.
13
+ - `checks/`: `tools` (Git/Node/npm), `python_check`, `project` (pin files), `runtimes` (Go, Ruby, Java, Rust, .NET, pnpm, Yarn, Bun), `health` (.env ignored, node_modules, ports), `extras` (Docker, git identity, disk), `platform_checks`, `config` (`.devready.json`).
14
+ - `versions.py`: tiny range matcher; anything it cannot parse is `skip`, never a guess.
15
+ - `explain.py`: one text per check id; `docs/checks.md` mirrors it (enforced by a test).
16
+ - `baseline.py`: compares a run with a saved JSON report. DevReady never writes it.
17
+
18
+ Rules every check follows: fixed argv with a timeout, regular files only (no symlinks, 1 MB cap), report only parsed or validated values.
@@ -0,0 +1,57 @@
1
+ # Check catalog
2
+
3
+ Every check id DevReady can emit. Ids are a public API (see [versioning.md](versioning.md)).
4
+ `devready explain <id>` prints the same text. A test fails if an id is missing from this file.
5
+
6
+ | Check id | What it means |
7
+ |---|---|
8
+ | `bun.version` | Bun runtime and package manager; checked only when package.json packageManager names bun. |
9
+ | `disk.space` | Warns below 1 GB free where the project lives; installs and builds can fail when the disk is full. |
10
+ | `docker.cli` | Shown only when the project has a Dockerfile or compose file and the docker command is missing. |
11
+ | `docker.daemon` | The Docker CLI can exist while the daemon is stopped. Start Docker Desktop or the service. |
12
+ | `dotnet.version` | The .NET SDK; checked only when the project has a global.json or a dotnet pin. |
13
+ | `git.identity` | Commits need user.name and user.email. DevReady checks they exist, never what they are. |
14
+ | `git.version` | Git is needed to clone, branch and commit. Install: https://git-scm.com/downloads |
15
+ | `go.version` | Go toolchain; checked only when the project has a go.mod or a golang line in .tool-versions. |
16
+ | `java.version` | Java runtime; checked only when the project pins it (.java-version or .tool-versions). |
17
+ | `node.version` | Node.js runs JavaScript tooling. Many projects pin a version; see project.node.* checks. |
18
+ | `npm.version` | npm installs JavaScript dependencies and ships with Node.js. |
19
+ | `platform.autocrlf` | Windows only, in a git repo without .gitattributes. Unset core.autocrlf can flip line endings between Windows and Linux/macOS contributors. |
20
+ | `platform.build_tools` | macOS/Linux, when the project has binding.gyp, setup.py, Gemfile or Cargo.toml. Native dependencies need a compiler (Xcode CLT on macOS, cc/gcc/clang and make on Linux). |
21
+ | `platform.exec_policy` | Windows only, when package.json exists. A Restricted or AllSigned PowerShell policy blocks npm.ps1 ('running scripts is disabled'). Use cmd or relax the policy for your user. |
22
+ | `platform.long_paths` | Windows only, when package.json exists. Without LongPathsEnabled, deeply nested node_modules paths can exceed 260 characters and installs fail. |
23
+ | `pnpm.version` | pnpm package manager; checked only when package.json packageManager names pnpm. |
24
+ | `project.bun.pin` | package.json packageManager pins the Bun version. |
25
+ | `project.bun.tool_versions` | .tool-versions (asdf/mise) pins Bun for this project. |
26
+ | `project.config.file` | .devready.json is optional. With unknown keys or invalid values it is ignored entirely. Run `devready init` for a starter. |
27
+ | `project.dotnet.global` | global.json sdk.version pins the .NET SDK for this project. |
28
+ | `project.dotnet.tool_versions` | .tool-versions (asdf/mise) pins .NET for this project. |
29
+ | `project.env.ignored` | Warns when .env exists but git would commit it (not ignored, or already tracked). Values are never shown. |
30
+ | `project.env.keys` | Compares variable NAMES in .env.example with .env. Values are never read or shown. |
31
+ | `project.env.required` | Variable NAMES listed under env in .devready.json must be set in the environment. Values are never read. |
32
+ | `project.go.mod` | The go directive in go.mod is the minimum Go version. A lower toolchain fails the check. |
33
+ | `project.go.tool_versions` | .tool-versions (asdf/mise) pins Go for this project. |
34
+ | `project.java.pin` | .java-version pins Java for this project (jenv, mise). |
35
+ | `project.java.tool_versions` | .tool-versions (asdf/mise) pins Java for this project. |
36
+ | `project.node.config` | require.node in .devready.json is a hard requirement (fails when unmet). |
37
+ | `project.node.engines` | package.json engines.node is a hard requirement. A mismatch fails the check and npm may refuse to install. |
38
+ | `project.node.modules` | Checked when package.json lists dependencies: node_modules must exist and not be older than a lockfile. |
39
+ | `project.node.pin` | A .nvmrc/.node-version file says which Node the team uses. A mismatch is a warning; switch with nvm, fnm or mise. |
40
+ | `project.node.tool_versions` | .tool-versions (asdf/mise) pins Node for this project. |
41
+ | `project.pnpm.pin` | package.json packageManager pins the pnpm version (corepack enable can install it). |
42
+ | `project.pnpm.tool_versions` | .tool-versions (asdf/mise) pins pnpm for this project. |
43
+ | `project.ports.free` | Ports declared in compose files (host side) or *PORT= lines in .env.example, tested by connecting to localhost. A busy port may be this project already running. |
44
+ | `project.python.config` | require.python in .devready.json is a hard requirement (fails when unmet). |
45
+ | `project.python.pin` | .python-version (pyenv/uv) pins Python for this project. |
46
+ | `project.python.requires` | pyproject.toml requires-python is a hard requirement; pip refuses incompatible versions. |
47
+ | `project.python.tool_versions` | .tool-versions (asdf/mise) pins Python for this project. |
48
+ | `project.ruby.pin` | .ruby-version pins Ruby for this project (rbenv, rvm, mise). |
49
+ | `project.ruby.tool_versions` | .tool-versions (asdf/mise) pins Ruby for this project. |
50
+ | `project.rust.tool_versions` | .tool-versions (asdf/mise) pins Rust for this project. |
51
+ | `project.rust.toolchain` | rust-toolchain(.toml) pins the Rust channel. Named channels such as stable are reported as not evaluated. |
52
+ | `project.yarn.pin` | package.json packageManager pins the Yarn version (corepack enable can install it). |
53
+ | `project.yarn.tool_versions` | .tool-versions (asdf/mise) pins Yarn for this project. |
54
+ | `python.version` | The Python interpreter running DevReady; not necessarily your project's virtualenv. |
55
+ | `ruby.version` | Ruby interpreter; checked only when the project pins it (.ruby-version or .tool-versions). |
56
+ | `rust.version` | Rust compiler (rustc); checked only when the project has a rust-toolchain file or pin. |
57
+ | `yarn.version` | Yarn package manager; checked only when package.json packageManager names yarn. |
@@ -0,0 +1,11 @@
1
+ # Security model
2
+
3
+ DevReady is read-only and local. See [SECURITY.md](../SECURITY.md) to report a vulnerability.
4
+
5
+ - **Commands:** fixed argument lists, short timeouts, no shell. Never built from project content.
6
+ - **Files:** only named files, regular files only, symlinks skipped, 1 MB cap, never executed.
7
+ - **Output:** only parsed digits, validated version specs, validated names and fixed phrases. Raw file or command output is never echoed, so a hostile `.nvmrc` or `package.json` cannot inject instructions into an agent reading the JSON. Tests check this.
8
+ - **Secrets:** environment and `.env` values are never read into output; only variable names (validated) and presence.
9
+ - **Network:** none. The only socket use is connecting to `127.0.0.1` ports the project declares.
10
+ - **Writes:** none. `init`, `fix --plan` and `--baseline` print or read; saving files is up to you.
11
+ - **Known limit:** project-declared ids and names pass through validated character sets, not an allowlist of meanings.
@@ -0,0 +1,24 @@
1
+ # Stability and versioning
2
+
3
+ From 1.0, these are the public interface. Breaking any of them needs a major version.
4
+
5
+ ## JSON report (`--format json`, `docs/schema.json`)
6
+
7
+ - `schema_version` is `1`. Fields and enum values in the schema are frozen for the 1.x line.
8
+ - Allowed in a minor release: new check ids, new result summaries and suggestions (free text, never parse them).
9
+ - Needs `schema_version: 2` and a major release: adding, removing or renaming a result field, adding a status, changing a field's type.
10
+ - Check ids match `^[a-z_]+(\.[a-z_]+)+$`. Renaming or removing an id is breaking; adding one is not.
11
+ - Statuses: `pass`, `warn`, `fail`, `skip`, `error`. `fail` means a declared requirement is unmet; `skip`/`error` mean "not evaluated".
12
+
13
+ ## Exit codes (`check`)
14
+
15
+ `0` no blocking failure, `1` at least one `fail` (with `--baseline`: a new or worse `fail`), `2` usage error, `3` internal error.
16
+
17
+ ## Config and CLI
18
+
19
+ - `.devready.json` keys (`skip`, `require`, `env`) are frozen; unknown keys make the file invalid by design.
20
+ - Existing flags keep their meaning. New flags are minor releases.
21
+
22
+ ## Not covered
23
+
24
+ Human-readable text output, JUnit/SARIF layout details beyond their formats, summary wording, and the Python import API (`devready.*` modules are internal).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "devready-cli"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "A local-first, read-only CLI that checks a project's environment requirements against what is installed."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -0,0 +1,43 @@
1
+ """Compare a run with a saved report (`devready check --format json > baseline.json`).
2
+
3
+ DevReady only reads the baseline; saving or committing it is up to you. That covers both a
4
+ shared team baseline (committed file) and diffing against an earlier local run.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import re
11
+ from pathlib import Path
12
+
13
+ from devready.checks.project import _read
14
+ from devready.models import Result, Status
15
+
16
+ _ID = re.compile(r"^[a-z_]+(\.[a-z_]+)+$")
17
+ _RANK = {Status.PASS: 0, Status.SKIP: 0, Status.WARN: 1, Status.ERROR: 2, Status.FAIL: 3}
18
+
19
+
20
+ def load_baseline(path: Path) -> dict[str, Status] | None:
21
+ """check_id -> status, or None if the file is missing, too big, or not a v1 report."""
22
+ text = _read(path)
23
+ try:
24
+ data = json.loads(text) if text else None
25
+ if data["schema_version"] != 1:
26
+ return None
27
+ return {
28
+ r["check_id"]: Status(r["status"])
29
+ for r in data["results"]
30
+ if isinstance(r["check_id"], str) and _ID.match(r["check_id"])
31
+ }
32
+ except (ValueError, KeyError, TypeError):
33
+ return None
34
+
35
+
36
+ def diff(results: list[Result], base: dict[str, Status]) -> tuple[list[Result], bool]:
37
+ """(results that are new or changed status, whether any FAIL got worse)."""
38
+ changed = [r for r in results if base.get(r.check_id) is not r.status]
39
+ worse = any(
40
+ r.status is Status.FAIL and _RANK[r.status] > _RANK.get(base.get(r.check_id), 0)
41
+ for r in changed
42
+ )
43
+ return changed, worse
@@ -0,0 +1,102 @@
1
+ """Optional `.devready.json`: extra requirements, required env var names, checks to skip.
2
+
3
+ {"skip": ["project.ports"], "require": {"node": ">=20", "python": ">=3.11"}, "env": ["API_KEY"]}
4
+
5
+ Read-only, same file rules as project.py. Env vars are tested for presence only; values are
6
+ never read. Invalid files give one ERROR result and never echo file content.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ from collections.abc import Mapping
15
+ from pathlib import Path
16
+
17
+ from devready.checks.project import _compare, _read
18
+ from devready.models import Result, Status
19
+ from devready.versions import SAFE_SPEC, parse_version
20
+
21
+ CONFIG_FILE = ".devready.json"
22
+ _ID = re.compile(r"^[a-z_]+(\.[a-z_]+)*$")
23
+ _ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,63}$")
24
+ _RUNTIMES = {"node": "Node", "python": "Python"}
25
+
26
+
27
+ def _valid(cfg: object) -> bool:
28
+ if not isinstance(cfg, dict) or not set(cfg) <= {"skip", "require", "env"}:
29
+ return False
30
+ skip, req, env = cfg.get("skip", []), cfg.get("require", {}), cfg.get("env", [])
31
+ return (
32
+ isinstance(skip, list)
33
+ and all(isinstance(i, str) and _ID.match(i) for i in skip)
34
+ and isinstance(req, dict)
35
+ and all(
36
+ k in _RUNTIMES and isinstance(v, str) and SAFE_SPEC.match(v) for k, v in req.items()
37
+ )
38
+ and isinstance(env, list)
39
+ and all(isinstance(n, str) and _ENV_NAME.match(n) for n in env)
40
+ )
41
+
42
+
43
+ def load_config(root: Path) -> tuple[dict | None, Result | None]:
44
+ """(config, None) if absent/valid ({} when absent), (None, error result) if invalid."""
45
+ text = _read(root / CONFIG_FILE)
46
+ if text is None:
47
+ return {}, None
48
+ try:
49
+ cfg = json.loads(text)
50
+ except ValueError:
51
+ cfg = None
52
+ if _valid(cfg):
53
+ return cfg, None
54
+ return None, Result(
55
+ "project.config.file",
56
+ "Config",
57
+ Status.ERROR,
58
+ f"{CONFIG_FILE} is invalid; it was ignored",
59
+ 'Allowed keys: "skip" (check ids), "require" (node/python specs), "env" (variable names).',
60
+ )
61
+
62
+
63
+ def check_config(
64
+ cfg: dict, node: str | None, python: str | None, environ: Mapping[str, str] = os.environ
65
+ ) -> list[Result]:
66
+ out: list[Result] = []
67
+ versions = {
68
+ "node": parse_version(node) if node else None,
69
+ "python": parse_version(python or ""),
70
+ }
71
+ for key, spec in cfg.get("require", {}).items():
72
+ out.append(
73
+ _compare(
74
+ f"project.{key}.config",
75
+ f"{_RUNTIMES[key]} required",
76
+ CONFIG_FILE,
77
+ spec,
78
+ versions[key],
79
+ Status.FAIL,
80
+ )
81
+ )
82
+ wanted = cfg.get("env", [])
83
+ if wanted:
84
+ missing = [n for n in wanted if n not in environ]
85
+ cid, title = "project.env.required", "Required env"
86
+ if missing:
87
+ out.append(
88
+ Result(
89
+ cid,
90
+ title,
91
+ Status.FAIL,
92
+ f"{len(missing)} of {len(wanted)} not set: {', '.join(missing[:10])}",
93
+ "Set these variables in your shell or CI (values are never read).",
94
+ )
95
+ )
96
+ else:
97
+ out.append(Result(cid, title, Status.PASS, f"all {len(wanted)} variables are set"))
98
+ return out
99
+
100
+
101
+ # Empty on purpose: pre-filling "env" from .env.example would make optional variables fail.
102
+ TEMPLATE = json.dumps({"skip": [], "require": {}, "env": []}, indent=2)
@@ -0,0 +1,108 @@
1
+ """OS-specific pain points, each checked only when the project makes it relevant.
2
+
3
+ Windows: long paths, PowerShell execution policy (blocks npm.ps1), git line endings.
4
+ macOS/Linux: a compiler toolchain when the project looks like it builds native code.
5
+ Read-only; fixed argv only.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shutil
11
+ import sys
12
+ from collections.abc import Callable
13
+ from pathlib import Path
14
+
15
+ from devready.checks.extras import _run
16
+ from devready.models import Result, Status
17
+
18
+ NATIVE_HINTS = ("binding.gyp", "setup.py", "Gemfile", "Cargo.toml")
19
+ BLOCKING_POLICIES = {"Restricted", "AllSigned"}
20
+ KNOWN_POLICIES = BLOCKING_POLICIES | {"RemoteSigned", "Unrestricted", "Bypass", "Undefined"}
21
+
22
+
23
+ def _long_paths_enabled() -> bool | None:
24
+ try:
25
+ import winreg
26
+
27
+ with winreg.OpenKey(
28
+ winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\FileSystem"
29
+ ) as key:
30
+ return bool(winreg.QueryValueEx(key, "LongPathsEnabled")[0])
31
+ except (ImportError, OSError):
32
+ return None
33
+
34
+
35
+ def _windows(root: Path, run: Callable, which: Callable, long_paths: Callable) -> list[Result]:
36
+ out: list[Result] = []
37
+ if (root / "package.json").is_file():
38
+ if long_paths() is False:
39
+ out.append(
40
+ Result(
41
+ "platform.long_paths",
42
+ "Long paths",
43
+ Status.WARN,
44
+ "Windows long path support is off; deep node_modules trees can fail",
45
+ "Enable LongPathsEnabled (administrator) and git's core.longpaths.",
46
+ )
47
+ )
48
+ ps = which("powershell")
49
+ p = (
50
+ run([ps, "-NoProfile", "-NonInteractive", "-Command", "Get-ExecutionPolicy"])
51
+ if ps
52
+ else None
53
+ )
54
+ policy = p.stdout.strip() if p is not None and p.returncode == 0 else ""
55
+ if policy in BLOCKING_POLICIES:
56
+ out.append(
57
+ Result(
58
+ "platform.exec_policy",
59
+ "PowerShell policy",
60
+ Status.WARN,
61
+ f"PowerShell policy is {policy}; npm/pnpm .ps1 scripts will be blocked",
62
+ "Use cmd, or allow local scripts for your user: "
63
+ "Set-ExecutionPolicy -Scope CurrentUser RemoteSigned",
64
+ )
65
+ )
66
+ git = which("git")
67
+ if git and (root / ".git").exists() and not (root / ".gitattributes").is_file():
68
+ p = run([git, "-C", str(root), "config", "--get", "core.autocrlf"])
69
+ if p is not None and not p.stdout.strip():
70
+ out.append(
71
+ Result(
72
+ "platform.autocrlf",
73
+ "Line endings",
74
+ Status.WARN,
75
+ "core.autocrlf is unset and the repo has no .gitattributes",
76
+ "Add a .gitattributes with line-ending rules, or set core.autocrlf.",
77
+ )
78
+ )
79
+ return out
80
+
81
+
82
+ def _build_tools(root: Path, plat: str, run: Callable, which: Callable) -> list[Result]:
83
+ if not any((root / n).is_file() for n in NATIVE_HINTS):
84
+ return []
85
+ cid, title = "platform.build_tools", "Build tools"
86
+ if plat == "darwin":
87
+ xs = which("xcode-select")
88
+ p = run([xs, "-p"]) if xs else None
89
+ ok = p is not None and p.returncode == 0
90
+ fix = "Install the Xcode Command Line Tools: xcode-select --install"
91
+ else:
92
+ ok = any(which(c) for c in ("cc", "gcc", "clang")) and bool(which("make"))
93
+ fix = "Install a C compiler and make (for example your distro's build-essential)."
94
+ if ok:
95
+ return [Result(cid, title, Status.PASS, "compiler toolchain found")]
96
+ return [Result(cid, title, Status.WARN, "no compiler toolchain found for native builds", fix)]
97
+
98
+
99
+ def check_platform(
100
+ root: Path,
101
+ plat: str = sys.platform,
102
+ run: Callable = _run,
103
+ which: Callable = shutil.which,
104
+ long_paths: Callable = _long_paths_enabled,
105
+ ) -> list[Result]:
106
+ if plat == "win32":
107
+ return _windows(root, run, which, long_paths)
108
+ return _build_tools(root, plat, run, which)
@@ -11,7 +11,7 @@ import re
11
11
  from pathlib import Path
12
12
 
13
13
  from devready.models import Result, Status
14
- from devready.versions import SAFE_SPEC, Version, parse_version, satisfies
14
+ from devready.versions import Version, parse_version, satisfies
15
15
 
16
16
  MAX_BYTES = 1_000_000
17
17
 
@@ -25,7 +25,8 @@ def _read(path: Path) -> str | None:
25
25
  def _compare(
26
26
  check_id: str, title: str, source: str, spec: str, installed: Version | None, miss: Status
27
27
  ) -> Result:
28
- shown = spec if SAFE_SPEC.match(spec) else "(unrecognized)"
28
+ # Echo only specs the matcher fully parses: plain words can pass SAFE_SPEC but are not versions.
29
+ shown = spec if satisfies((0, 0, 0), spec) is not None else "(unrecognized)"
29
30
  if installed is None:
30
31
  return Result(check_id, title, Status.SKIP, f"{source} wants {shown}; tool not found")
31
32
  ok = satisfies(installed, spec)