vibecodedshit 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +95 -0
- package/README.md +64 -2
- package/assets/fonts/NotoEmoji-OFL.txt +93 -0
- package/assets/fonts/NotoEmoji-Poop.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-Bold.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-OFL.txt +93 -0
- package/dist/bin.js +8 -0
- package/dist/card/previous.js +38 -0
- package/dist/card/render.js +71 -0
- package/dist/cli.js +121 -0
- package/dist/commands/card.js +69 -0
- package/dist/commands/common.js +1 -0
- package/dist/commands/diff.js +54 -0
- package/dist/commands/footer.js +25 -0
- package/dist/commands/report.js +49 -0
- package/dist/commands/route.js +62 -0
- package/dist/commands/submit.js +125 -0
- package/dist/commands/summary.js +77 -0
- package/dist/diff/compare.js +73 -0
- package/dist/diff/conditions.js +87 -0
- package/dist/diff/format.js +23 -0
- package/dist/exec/docker.js +158 -0
- package/dist/exec/findings.js +73 -0
- package/dist/exec/plan.js +50 -0
- package/dist/exec/reports.js +90 -0
- package/dist/exec/roots.js +49 -0
- package/dist/exec/run.js +182 -0
- package/dist/exec/steps/build.js +45 -0
- package/dist/exec/steps/coverage-install.js +12 -0
- package/dist/exec/steps/install.js +41 -0
- package/dist/exec/steps/knip.js +28 -0
- package/dist/exec/steps/lint.js +25 -0
- package/dist/exec/steps/lockfiles.js +25 -0
- package/dist/exec/steps/test.js +17 -0
- package/dist/exec/steps/typecheck.js +40 -0
- package/dist/exec/test-reports/coverage-py.js +23 -0
- package/dist/exec/test-reports/coverage-ranges.js +65 -0
- package/dist/exec/test-reports/coverage-summary.js +23 -0
- package/dist/exec/test-reports/jest-json.js +31 -0
- package/dist/exec/test-reports/junit.js +24 -0
- package/dist/exec/test-reports/mocha-json.js +18 -0
- package/dist/exec/test-reports/vitest-json.js +5 -0
- package/dist/exec/test-runners/detect.js +99 -0
- package/dist/exec/test-runners/index.js +6 -0
- package/dist/exec/test-runners/jest.js +27 -0
- package/dist/exec/test-runners/mocha.js +28 -0
- package/dist/exec/test-runners/node-test.js +24 -0
- package/dist/exec/test-runners/pytest.js +18 -0
- package/dist/exec/test-runners/script.js +48 -0
- package/dist/exec/test-runners/spec.js +1 -0
- package/dist/exec/test-runners/vitest.js +28 -0
- package/dist/exec/tests.js +120 -0
- package/dist/exec/types.js +1 -0
- package/dist/manifest/package-json.js +36 -0
- package/dist/manifest/python.js +106 -0
- package/dist/measure/d3/index.js +45 -0
- package/dist/measure/d3/presets.js +100 -0
- package/dist/measure/d3/py/adapter.js +67 -0
- package/dist/measure/d3/py/config.js +80 -0
- package/dist/measure/d3/py/contracts.js +57 -0
- package/dist/measure/d3/py/domain.js +57 -0
- package/dist/measure/d3/py/graph.js +214 -0
- package/dist/measure/d3/py/interpreter.js +31 -0
- package/dist/measure/d3/py/layers.js +120 -0
- package/dist/measure/d3/py/roots.js +58 -0
- package/dist/measure/d3/py/types.js +1 -0
- package/dist/measure/d3/scc.js +64 -0
- package/dist/measure/d3/ts/adapter.js +371 -0
- package/dist/measure/d3/ts/classify.js +107 -0
- package/dist/measure/d3/ts/config.js +318 -0
- package/dist/measure/d3/ts/domain.js +23 -0
- package/dist/measure/d3/ts/rules.js +65 -0
- package/dist/measure/d3/types.js +5 -0
- package/dist/measure/deps/cvss.js +66 -0
- package/dist/measure/deps/declared.js +195 -0
- package/dist/measure/deps/files.js +40 -0
- package/dist/measure/deps/lockfiles.js +308 -0
- package/dist/measure/deps/osv.js +122 -0
- package/dist/measure/deps/registry.js +142 -0
- package/dist/measure/deps/resolved.js +78 -0
- package/dist/measure/deps/units.js +5 -0
- package/dist/measure/eslint/health-plugin.js +19 -0
- package/dist/measure/ops/docs-commands.js +73 -0
- package/dist/measure/ops/docs-resolve.js +217 -0
- package/dist/measure/ops/entry-points.js +49 -0
- package/dist/measure/ops/env-example.js +20 -0
- package/dist/measure/ops/env-reads.js +46 -0
- package/dist/measure/ops/runtime.js +73 -0
- package/dist/measure/p1-1.js +8 -0
- package/dist/measure/p1-2.js +8 -0
- package/dist/measure/p1-3.js +8 -0
- package/dist/measure/p1-4.js +41 -0
- package/dist/measure/p1-5.js +59 -0
- package/dist/measure/p2-1.js +8 -0
- package/dist/measure/p2-2.js +44 -0
- package/dist/measure/p2-3.js +81 -0
- package/dist/measure/p2-4.js +288 -0
- package/dist/measure/p2-5.js +8 -0
- package/dist/measure/p3-1.js +16 -0
- package/dist/measure/p3-2.js +26 -0
- package/dist/measure/p3-3.js +37 -0
- package/dist/measure/p3-4.js +26 -0
- package/dist/measure/p3-5.js +26 -0
- package/dist/measure/p4-1.js +184 -0
- package/dist/measure/p4-2.js +27 -0
- package/dist/measure/p4-3.js +106 -0
- package/dist/measure/p4-4.js +309 -0
- package/dist/measure/p5-1.js +117 -0
- package/dist/measure/p5-2.js +80 -0
- package/dist/measure/p5-3.js +21 -0
- package/dist/measure/p5-4.js +161 -0
- package/dist/measure/p5-5.js +158 -0
- package/dist/measure/p6-1.js +20 -0
- package/dist/measure/p6-2.js +20 -0
- package/dist/measure/p6-3.js +40 -0
- package/dist/measure/p6-4.js +31 -0
- package/dist/measure/p6-5.js +60 -0
- package/dist/measure/p6-6.js +54 -0
- package/dist/measure/py/typecheck-config.js +95 -0
- package/dist/measure/registry.js +31 -0
- package/dist/measure/safety/py-tests.js +124 -0
- package/dist/measure/safety/ts-barrels.js +68 -0
- package/dist/measure/safety/ts-cases.js +111 -0
- package/dist/measure/safety/ts-credits.js +101 -0
- package/dist/measure/safety/ts-imports.js +63 -0
- package/dist/measure/safety/ts-resolve.js +43 -0
- package/dist/measure/ts/directives.js +37 -0
- package/dist/measure/ts/exports.js +60 -0
- package/dist/measure/ts/tsconfig.js +179 -0
- package/dist/measure/types.js +1 -0
- package/dist/measure/units.js +41 -0
- package/dist/methodology/load.js +159 -0
- package/dist/methodology/schema.js +105 -0
- package/dist/python/deadcode.js +27 -0
- package/dist/python/env.js +13 -0
- package/dist/python/helper.js +24 -0
- package/dist/python/imports.js +40 -0
- package/dist/python/nox.js +16 -0
- package/dist/python/scope.js +35 -0
- package/dist/python/tests.js +19 -0
- package/dist/python/typing.js +27 -0
- package/dist/report/escape.js +5 -0
- package/dist/report/format.js +75 -0
- package/dist/report/render.js +213 -0
- package/dist/report/roadmap.js +58 -0
- package/dist/report/scares.js +83 -0
- package/dist/report/style.js +27 -0
- package/dist/report-only/hotspots.js +48 -0
- package/dist/route/check.js +37 -0
- package/dist/route/files.js +74 -0
- package/dist/route/order.js +22 -0
- package/dist/route/select.js +114 -0
- package/dist/route/write.js +13 -0
- package/dist/run/read.js +89 -0
- package/dist/run/recompute.js +19 -0
- package/dist/run/scan.js +196 -0
- package/dist/run/schema.js +1 -0
- package/dist/run/score-run.js +42 -0
- package/dist/run/write.js +4 -0
- package/dist/scope/build.js +173 -0
- package/dist/scope/packages.js +87 -0
- package/dist/scope/rules.js +21 -0
- package/dist/scope/tracked.js +34 -0
- package/dist/scope/ts-lines.js +63 -0
- package/dist/scope/types.js +7 -0
- package/dist/scoring/aggregate.js +38 -0
- package/dist/scoring/pool.js +65 -0
- package/dist/scoring/score.js +81 -0
- package/dist/scoring/what-if.js +38 -0
- package/dist/scripts/checks.js +53 -0
- package/dist/scripts/ci.js +139 -0
- package/dist/scripts/entry-points/find.js +31 -0
- package/dist/scripts/entry-points/index.js +18 -0
- package/dist/scripts/entry-points/justfile.js +62 -0
- package/dist/scripts/entry-points/makefile.js +66 -0
- package/dist/scripts/entry-points/nox.js +29 -0
- package/dist/scripts/entry-points/package-json.js +21 -0
- package/dist/scripts/entry-points/pyproject.js +73 -0
- package/dist/scripts/entry-points/tox.js +130 -0
- package/dist/scripts/expand.js +300 -0
- package/dist/scripts/triggers.js +151 -0
- package/dist/scripts/types.js +1 -0
- package/dist/submit/constants.js +9 -0
- package/dist/submit/payload.js +42 -0
- package/dist/submit/repo-identity.js +46 -0
- package/dist/submit/send.js +29 -0
- package/dist/tools/catalog.js +46 -0
- package/dist/tools/image.js +180 -0
- package/dist/tools/kit.js +71 -0
- package/dist/tools/knip.js +169 -0
- package/dist/tools/outcome.js +21 -0
- package/dist/tools/resolve.js +66 -0
- package/dist/tools/workcopy.js +27 -0
- package/dist/util/ini.js +37 -0
- package/dist/util/pep440.js +163 -0
- package/dist/util/toml.js +27 -0
- package/docker/Dockerfile +142 -0
- package/docker/bin/health-capture +5 -0
- package/docker/bin/health-coverage-install +5 -0
- package/docker/bin/health-eslint +66 -0
- package/docker/bin/health-pytest +10 -0
- package/docker/bin/health-tsc +10 -0
- package/docker/bin/health-venv +11 -0
- package/docker/config/eslint.config.mjs +4 -0
- package/docker/config/eslint.rules.mjs +65 -0
- package/docker/config/eslint.untyped.config.mjs +4 -0
- package/docker/config/knip.json +1 -0
- package/docker/config/ruff.toml +7 -0
- package/methodology/LICENSE +396 -0
- package/methodology/v1.0/METHODOLOGY.md +1176 -0
- package/methodology/v1.0/dimensions/1-safety-net.yaml +240 -0
- package/methodology/v1.0/dimensions/2-type-static-rigor.yaml +289 -0
- package/methodology/v1.0/dimensions/3-architecture-boundaries.yaml +266 -0
- package/methodology/v1.0/dimensions/4-complexity-maintainability.yaml +192 -0
- package/methodology/v1.0/dimensions/5-dependency-security-hygiene.yaml +223 -0
- package/methodology/v1.0/dimensions/6-operability-feedback-loop.yaml +279 -0
- package/methodology/v1.0/methodology.yaml +242 -0
- package/methodology/v1.0/presets/django.yaml +43 -0
- package/methodology/v1.0/presets/express.yaml +47 -0
- package/methodology/v1.0/presets/fastapi.yaml +52 -0
- package/methodology/v1.0/presets/generic.yaml +41 -0
- package/methodology/v1.0/presets/nextjs.yaml +50 -0
- package/methodology/v1.0/scope.yaml +115 -0
- package/package.json +61 -4
- package/python/dead_code_helper.py +265 -0
- package/python/env_helper.py +71 -0
- package/python/graph_helper.py +277 -0
- package/python/imports_helper.py +132 -0
- package/python/nox_helper.py +79 -0
- package/python/scope_helper.py +96 -0
- package/python/tests_helper.py +171 -0
- package/python/typing_helper.py +235 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Copyright 2026 Peter Kisel
|
|
2
|
+
|
|
3
|
+
Elastic License 2.0
|
|
4
|
+
|
|
5
|
+
URL: https://www.elastic.co/licensing/elastic-license
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide,
|
|
14
|
+
non-sublicensable, non-transferable license to use, copy, distribute, make
|
|
15
|
+
available, and prepare derivative works of the software, in each case subject to
|
|
16
|
+
the limitations and conditions below.
|
|
17
|
+
|
|
18
|
+
## Limitations
|
|
19
|
+
|
|
20
|
+
You may not provide the software to third parties as a hosted or managed
|
|
21
|
+
service, where the service provides users with access to any substantial set of
|
|
22
|
+
the features or functionality of the software.
|
|
23
|
+
|
|
24
|
+
You may not move, change, disable, or circumvent the license key functionality
|
|
25
|
+
in the software, and you may not remove or obscure any functionality in the
|
|
26
|
+
software that is protected by the license key.
|
|
27
|
+
|
|
28
|
+
You may not alter, remove, or obscure any licensing, copyright, or other notices
|
|
29
|
+
of the licensor in the software. Any use of the licensor’s trademarks is subject
|
|
30
|
+
to applicable law.
|
|
31
|
+
|
|
32
|
+
## Patents
|
|
33
|
+
|
|
34
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
35
|
+
license, or becomes able to license, to make, have made, use, sell, offer for
|
|
36
|
+
sale, import and have imported the software, in each case subject to the
|
|
37
|
+
limitations and conditions in this license. This license does not cover any
|
|
38
|
+
patent claims that you cause to be infringed by modifications or additions to
|
|
39
|
+
the software. If you or your company make any written claim that the software
|
|
40
|
+
infringes or contributes to infringement of any patent, your patent license for
|
|
41
|
+
the software granted under these terms ends immediately. If your company makes
|
|
42
|
+
such a claim, your patent license ends immediately for work on behalf of your
|
|
43
|
+
company.
|
|
44
|
+
|
|
45
|
+
## Notices
|
|
46
|
+
|
|
47
|
+
You must ensure that anyone who gets a copy of any part of the software from you
|
|
48
|
+
also gets a copy of these terms.
|
|
49
|
+
|
|
50
|
+
If you modify the software, you must include in any modified copies of the
|
|
51
|
+
software prominent notices stating that you have modified the software.
|
|
52
|
+
|
|
53
|
+
## No Other Rights
|
|
54
|
+
|
|
55
|
+
These terms do not imply any licenses other than those expressly granted in
|
|
56
|
+
these terms.
|
|
57
|
+
|
|
58
|
+
## Termination
|
|
59
|
+
|
|
60
|
+
If you use the software in violation of these terms, such use is not licensed,
|
|
61
|
+
and your licenses will automatically terminate. If the licensor provides you
|
|
62
|
+
with a notice of your violation, and you cease all violation of this license no
|
|
63
|
+
later than 30 days after you receive that notice, your licenses will be
|
|
64
|
+
reinstated retroactively. However, if you violate these terms after such
|
|
65
|
+
reinstatement, any additional violation of these terms will cause your licenses
|
|
66
|
+
to terminate automatically and permanently.
|
|
67
|
+
|
|
68
|
+
## No Liability
|
|
69
|
+
|
|
70
|
+
*As far as the law allows, the software comes as is, without any warranty or
|
|
71
|
+
condition, and the licensor will not be liable to you for any damages arising
|
|
72
|
+
out of these terms or the use or nature of the software, under any kind of
|
|
73
|
+
legal claim.*
|
|
74
|
+
|
|
75
|
+
## Definitions
|
|
76
|
+
|
|
77
|
+
The **licensor** is the entity offering these terms, and the **software** is the
|
|
78
|
+
software the licensor makes available under these terms, including any portion
|
|
79
|
+
of it.
|
|
80
|
+
|
|
81
|
+
**you** refers to the individual or entity agreeing to these terms.
|
|
82
|
+
|
|
83
|
+
**your company** is any legal entity, sole proprietorship, or other kind of
|
|
84
|
+
organization that you work for, plus all organizations that have control over,
|
|
85
|
+
are under the control of, or are under common control with that
|
|
86
|
+
organization. **control** means ownership of substantially all the assets of an
|
|
87
|
+
entity, or the power to direct its management and policies by vote, contract, or
|
|
88
|
+
otherwise. Control can be direct or indirect.
|
|
89
|
+
|
|
90
|
+
**your licenses** are all the licenses granted to you for the software under
|
|
91
|
+
these terms.
|
|
92
|
+
|
|
93
|
+
**use** means anything you do with the software requiring one of your licenses.
|
|
94
|
+
|
|
95
|
+
**trademark** means trademarks, service marks, and similar rights.
|
package/README.md
CHANGED
|
@@ -1,3 +1,65 @@
|
|
|
1
|
-
#
|
|
1
|
+
# health
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Codebase Health benchmark. See `methodology/v1.0/METHODOLOGY.md` for how scores work.
|
|
4
|
+
|
|
5
|
+
## Run it with npx
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npx vibecodedshit scan .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Needs Node 22 or later and `git`. No clone, no plugin, no repository access for anyone. With Docker and the `health:1.0` image the scan is a full run. Without them it falls back to a preview scan, prints why and which parameters it skipped (they need Docker), and writes `run.json`. The scan runs on your machine. Your code is not uploaded; it only looks up your dependencies' names and versions on the public npm and PyPI registries (and OSV, if osv-scanner is installed). To get the full run, build the image with `docker build -t health:1.0 -f <package>/docker/Dockerfile <package>/docker` (the scan prints the exact command).
|
|
12
|
+
|
|
13
|
+
Submitting is opt-in: `npx vibecodedshit submit run.json --email <you@example.com>` prints the payload and sends it only with `--confirm`. Only full, comparable runs can be submitted.
|
|
14
|
+
|
|
15
|
+
## From a clone
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npm install
|
|
19
|
+
npm run health -- scan <path> --preview [--as-of YYYY-MM-DD] [--out run.json]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A preview scan runs the static layer only, measures every parameter that needs no execution (not 1.1 to 1.3, 2.1, 2.5, 6.1, 6.3) and writes a run.json marked "preview, not comparable". Without `--preview` the scan is a full run: install, build, typecheck, lint, the tests and every static analysis tool run in the pinned `health:1.0` image (`npm run image:build`, Docker needed, no other tool on the host), which adds 6.1, 2.1 and 2.5. A full run is not comparable until 1.1 to 1.3 and 6.3 are measured. Full runs keep the package managers' downloads in `~/.cache/health/packages` (or `$XDG_CACHE_HOME/health/packages`) so a second scan does not download them again. Set `HEALTH_PACKAGE_CACHE` to another directory, or to `off` to download everything on every scan.
|
|
23
|
+
|
|
24
|
+
Needs Node 22 or later, `git`, and `python3` for repositories with Python files.
|
|
25
|
+
|
|
26
|
+
## Claude Code plugin
|
|
27
|
+
|
|
28
|
+
The CLI ships as a Claude Code plugin with a `/codebase-health` skill. The plugin root is this directory; the marketplace file is `.claude-plugin/marketplace.json` at the repository root.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/plugin marketplace add Raavn/vibecodedshit.com
|
|
32
|
+
/plugin install codebase-health@vibecodedshit
|
|
33
|
+
/codebase-health [path]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The namespaced form `/codebase-health:codebase-health` also works. The skill runs a full scan when Docker and the `health:1.0` image are available. When Docker is running but the image is missing, it builds the image in the background while it runs a preview scan, then runs the full scan. When Docker is missing or not running, it asks whether to run a preview scan, which is not comparable, or wait while you install or start Docker. It then renders the HTML report, and from the second scan of a repository on (same mode) a PNG card with the previous and new score and their bands, and explains `run.json`: the health score and band, the weakest dimension and the three parameters most worth fixing. Claude only reads the scores. It never computes or adjusts them, and it sends something only after you have seen the exact payload and said yes.
|
|
37
|
+
|
|
38
|
+
## Codex plugin
|
|
39
|
+
|
|
40
|
+
The same package ships as a Codex plugin. The manifest is `.codex-plugin/plugin.json` in this directory, the skill is `codex/skills/codebase-health/SKILL.md`, and the marketplace file is `.agents/plugins/marketplace.json` at the repository root. It runs the same `dist/bin.js`, `health:1.0` image and methodology as the Claude Code plugin, and submits through the same API.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
codex plugin marketplace add Raavn/vibecodedshit.com
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then install codebase-health from the Plugins Directory and run `$codebase-health [path]`. Update with `codex plugin marketplace upgrade vibecodedshit`. Runs and checkouts are kept in `${CODEX_HOME:-~/.codex}/codebase-health/`.
|
|
47
|
+
|
|
48
|
+
## Submitting a result
|
|
49
|
+
|
|
50
|
+
Submission is opt-in. `health submit <run.json> --email <addr> [--confirm] [--newsletter] [--include-repo-name] [--summary-file <path>]` prints the full payload and sends it to app.vibecodedshit.com only with `--confirm`. The skill shows the payload and asks, with No as the default. Privacy page: https://app.vibecodedshit.com/privacy
|
|
51
|
+
|
|
52
|
+
- The repository is identified by the SHA-256 of its normalised git remote URL (the root commit when there is no remote). The name is sent only with `--include-repo-name`.
|
|
53
|
+
- The email is required. It is used for your rank, for methodology version notices, and for the newsletter only with `--newsletter`.
|
|
54
|
+
- The CLI prints your rank and the score needed for the top 10 on that methodology version. Nobody else's scores are published.
|
|
55
|
+
- Only a comparable full run can be submitted. Source code and file paths are never sent.
|
|
56
|
+
- The first email to submit a repository on a methodology version owns its entry; a different email gets a 409. The owner can re-submit to replace it, if the new run's as-of date is not earlier.
|
|
57
|
+
- Exit codes: 0 printed or sent, 1 usage error or refusal, 6 sending failed.
|
|
58
|
+
|
|
59
|
+
Installing by the GitHub slug works only for users whose git credentials can read the repository.
|
|
60
|
+
|
|
61
|
+
Development: `dist/` is committed because plugin installs don't run a build. Run `npm run build` and commit `dist/` with any change under `src/`. `npm run verify` fails if `dist/` is stale. When you bump `version` in `package.json`, bump it in `.claude-plugin/plugin.json` too.
|
|
62
|
+
|
|
63
|
+
## Publishing
|
|
64
|
+
|
|
65
|
+
The package is `vibecodedshit` (public, Elastic-2.0). To release: bump `version` in `package.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json` and `../version.json` together, then run `npm publish`. `prepublishOnly` builds `dist/` and runs `npm run verify`. Check the tarball first with `npm pack --dry-run`. Publishing needs an npm login with 2FA.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2013 Google LLC
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
https://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readRun, RunReadError } from '../run/read.js';
|
|
4
|
+
/**
|
|
5
|
+
* The newest `<historyDir>/<dir>/run.json` written before `currentFile` for the same repository path, methodology version
|
|
6
|
+
* and mode, valid and with a health score. Runs from other versions are never compared, and a preview next to a full run
|
|
7
|
+
* would compare different parameters. Unreadable files are skipped.
|
|
8
|
+
*/
|
|
9
|
+
export function findPreviousRun(currentFile, current, historyDir) {
|
|
10
|
+
const before = statSync(currentFile).mtimeMs;
|
|
11
|
+
let best = null;
|
|
12
|
+
for (const entry of readdirSync(historyDir, { withFileTypes: true })) {
|
|
13
|
+
if (!entry.isDirectory())
|
|
14
|
+
continue;
|
|
15
|
+
const file = path.join(historyDir, entry.name, 'run.json');
|
|
16
|
+
if (!existsSync(file))
|
|
17
|
+
continue;
|
|
18
|
+
// The current run is never older than itself, so this also skips it.
|
|
19
|
+
const mtime = statSync(file).mtimeMs;
|
|
20
|
+
if (mtime >= before || (best && mtime <= best.mtime))
|
|
21
|
+
continue;
|
|
22
|
+
let run;
|
|
23
|
+
try {
|
|
24
|
+
run = readRun(file);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (err instanceof RunReadError)
|
|
28
|
+
continue;
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
if (!run.valid || run.health === null)
|
|
32
|
+
continue;
|
|
33
|
+
if (run.repo.path !== current.repo.path || run.methodology.version !== current.methodology.version || run.mode !== current.mode)
|
|
34
|
+
continue;
|
|
35
|
+
best = { file, health: run.health, mtime };
|
|
36
|
+
}
|
|
37
|
+
return best && { file: best.file, health: best.health };
|
|
38
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { initWasm, Resvg } from '@resvg/resvg-wasm';
|
|
5
|
+
import { packageRoot } from '../methodology/load.js';
|
|
6
|
+
import { escapeHtml as e } from '../report/escape.js';
|
|
7
|
+
const WIDTH = 1200;
|
|
8
|
+
const HEIGHT = 630;
|
|
9
|
+
const BACKGROUND = '#CEA5C7';
|
|
10
|
+
const FOREGROUND = '#471044';
|
|
11
|
+
const FONT = 'Space Grotesk';
|
|
12
|
+
// The brand replaces every "i" with 💩; the Noto Emoji subset supplies that one glyph.
|
|
13
|
+
const BRAND = 'V\u{1F4A9}becodedsh\u{1F4A9}t.com';
|
|
14
|
+
const HEADER_SIZE = 44;
|
|
15
|
+
const HEADER_MAX_WIDTH = WIDTH - 2 * 60;
|
|
16
|
+
const SCORE_SIZE = 240;
|
|
17
|
+
const TIER_SIZE = 32;
|
|
18
|
+
const TIER_SPACING = 6;
|
|
19
|
+
const PILL_HEIGHT = 64;
|
|
20
|
+
const PILL_PADDING = 28;
|
|
21
|
+
function column(x, side, measure, filled) {
|
|
22
|
+
const tier = side.tier.toUpperCase();
|
|
23
|
+
const pill = Math.round(measure(tier, TIER_SIZE, TIER_SPACING)) + 2 * PILL_PADDING;
|
|
24
|
+
const outline = filled ? `fill="${FOREGROUND}"` : `fill="none" stroke="${FOREGROUND}" stroke-width="3"`;
|
|
25
|
+
return [
|
|
26
|
+
`<text x="${x}" y="400" font-size="${SCORE_SIZE}" text-anchor="middle" fill="${FOREGROUND}">${e(side.score)}</text>`,
|
|
27
|
+
`<rect x="${x - pill / 2}" y="452" width="${pill}" height="${PILL_HEIGHT}" rx="${PILL_HEIGHT / 2}" ${outline}/>`,
|
|
28
|
+
`<text x="${x}" y="495" font-size="${TIER_SIZE}" letter-spacing="${TIER_SPACING}" text-anchor="middle" fill="${filled ? BACKGROUND : FOREGROUND}">${e(tier)}</text>`,
|
|
29
|
+
].join('');
|
|
30
|
+
}
|
|
31
|
+
/** The card as SVG: the brand and repository, then the earlier score, an arrow and the new score, each with its tier below. */
|
|
32
|
+
export function cardSvg(card, measure) {
|
|
33
|
+
const header = `${BRAND} report for ${card.repo}`;
|
|
34
|
+
const width = measure(header, HEADER_SIZE, 0);
|
|
35
|
+
// A long repository name shrinks the header instead of running off the card.
|
|
36
|
+
const headerSize = width > HEADER_MAX_WIDTH ? Math.floor((HEADER_SIZE * HEADER_MAX_WIDTH) / width) : HEADER_SIZE;
|
|
37
|
+
return [
|
|
38
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}" font-family="${FONT}" font-weight="700">`,
|
|
39
|
+
`<rect width="${WIDTH}" height="${HEIGHT}" fill="${BACKGROUND}"/>`,
|
|
40
|
+
`<text x="${WIDTH / 2}" y="110" font-size="${headerSize}" text-anchor="middle" fill="${FOREGROUND}">${e(header)}</text>`,
|
|
41
|
+
column(300, card.before, measure, false),
|
|
42
|
+
`<text x="${WIDTH / 2}" y="346" font-size="120" text-anchor="middle" fill="${FOREGROUND}">→</text>`,
|
|
43
|
+
column(900, card.after, measure, true),
|
|
44
|
+
'</svg>',
|
|
45
|
+
].join('');
|
|
46
|
+
}
|
|
47
|
+
let wasm;
|
|
48
|
+
let fonts;
|
|
49
|
+
function options() {
|
|
50
|
+
fonts ??= ['SpaceGrotesk-Bold.ttf', 'NotoEmoji-Poop.ttf'].map((name) => readFileSync(path.join(packageRoot, 'assets', 'fonts', name)));
|
|
51
|
+
return { font: { fontBuffers: fonts, defaultFontFamily: FONT } };
|
|
52
|
+
}
|
|
53
|
+
function measure(text, fontSize, letterSpacing) {
|
|
54
|
+
const probe = new Resvg(`<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"><text font-family="${FONT}" font-weight="700" font-size="${fontSize}" letter-spacing="${letterSpacing}">${e(text)}</text></svg>`, options());
|
|
55
|
+
const box = probe.getBBox();
|
|
56
|
+
const width = box?.width ?? 0;
|
|
57
|
+
box?.free();
|
|
58
|
+
probe.free();
|
|
59
|
+
return width;
|
|
60
|
+
}
|
|
61
|
+
/** Render the card to a PNG with the bundled fonts only, so it looks the same on every machine. */
|
|
62
|
+
export async function renderCard(card) {
|
|
63
|
+
wasm ??= initWasm(readFileSync(createRequire(import.meta.url).resolve('@resvg/resvg-wasm/index_bg.wasm')));
|
|
64
|
+
await wasm;
|
|
65
|
+
const resvg = new Resvg(cardSvg(card, measure), options());
|
|
66
|
+
const image = resvg.render();
|
|
67
|
+
const png = image.asPng();
|
|
68
|
+
image.free();
|
|
69
|
+
resvg.free();
|
|
70
|
+
return png;
|
|
71
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { runCard } from './commands/card.js';
|
|
4
|
+
import { EXIT } from './commands/common.js';
|
|
5
|
+
import { runDiff } from './commands/diff.js';
|
|
6
|
+
import { footer, imageBuildCommand } from './commands/footer.js';
|
|
7
|
+
import { imageDigest } from './exec/docker.js';
|
|
8
|
+
import { runReport } from './commands/report.js';
|
|
9
|
+
import { runRoute } from './commands/route.js';
|
|
10
|
+
import { runSubmit } from './commands/submit.js';
|
|
11
|
+
import { summarize } from './commands/summary.js';
|
|
12
|
+
import { CURRENT_METHODOLOGY_VERSION, loadMethodology, MethodologyError, packageRoot } from './methodology/load.js';
|
|
13
|
+
import { invalidRun, NoLogicFilesError, runScan } from './run/scan.js';
|
|
14
|
+
import { writeRun } from './run/write.js';
|
|
15
|
+
import { NotAGitRepositoryError } from './scope/build.js';
|
|
16
|
+
import { ToolError } from './scope/types.js';
|
|
17
|
+
export { EXIT } from './commands/common.js';
|
|
18
|
+
export { summarize } from './commands/summary.js';
|
|
19
|
+
const USAGE = `Usage: vibecodedshit scan <path> [--preview] [--as-of YYYY-MM-DD] [--out <file>] [--methodology <version>]
|
|
20
|
+
vibecodedshit report <run.json> [--out <file>]
|
|
21
|
+
vibecodedshit diff <before.json> <after.json> [--json]
|
|
22
|
+
vibecodedshit route <run.json> --out <dir>
|
|
23
|
+
vibecodedshit route --check <dir> <run.json>
|
|
24
|
+
vibecodedshit card <run.json> --history <dir> [--out <file>]
|
|
25
|
+
vibecodedshit submit <run.json> --email <addr> [--confirm] [--newsletter] [--include-repo-name] [--summary-file <path>]
|
|
26
|
+
|
|
27
|
+
Exit codes: 0 ok, 1 usage or unreadable input, 2 methodology error, 3 no logic file, 4 tool error, 5 diff refused, 6 submit failed.
|
|
28
|
+
`;
|
|
29
|
+
export async function run(argv, io, deps = {}) {
|
|
30
|
+
if (argv[0] === 'report')
|
|
31
|
+
return runReport(argv.slice(1), io);
|
|
32
|
+
if (argv[0] === 'diff')
|
|
33
|
+
return runDiff(argv.slice(1), io);
|
|
34
|
+
if (argv[0] === 'route')
|
|
35
|
+
return runRoute(argv.slice(1), io);
|
|
36
|
+
if (argv[0] === 'card')
|
|
37
|
+
return runCard(argv.slice(1), io);
|
|
38
|
+
if (argv[0] === 'submit')
|
|
39
|
+
return runSubmit(argv.slice(1), io, { git: deps.git, fetch: deps.fetch });
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = parseArgs({
|
|
43
|
+
args: argv,
|
|
44
|
+
allowPositionals: true,
|
|
45
|
+
options: {
|
|
46
|
+
preview: { type: 'boolean' },
|
|
47
|
+
'as-of': { type: 'string' },
|
|
48
|
+
out: { type: 'string' },
|
|
49
|
+
methodology: { type: 'string' },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
io.stderr(`${err instanceof Error ? err.message : String(err)}\n${USAGE}`);
|
|
55
|
+
return EXIT.usage;
|
|
56
|
+
}
|
|
57
|
+
const [command, target] = parsed.positionals;
|
|
58
|
+
if (command !== 'scan' || !target || parsed.positionals.length > 2) {
|
|
59
|
+
io.stderr(USAGE);
|
|
60
|
+
return EXIT.usage;
|
|
61
|
+
}
|
|
62
|
+
const asOf = parsed.values['as-of'] ?? new Date().toISOString().slice(0, 10);
|
|
63
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(asOf) || Number.isNaN(Date.parse(asOf))) {
|
|
64
|
+
io.stderr(`--as-of must be a date like 2026-10-04, got "${asOf}"\n`);
|
|
65
|
+
return EXIT.usage;
|
|
66
|
+
}
|
|
67
|
+
const opts = {
|
|
68
|
+
repoPath: path.resolve(io.cwd, target),
|
|
69
|
+
methodologyVersion: parsed.values.methodology ?? CURRENT_METHODOLOGY_VERSION,
|
|
70
|
+
mode: parsed.values.preview ? 'preview' : 'full',
|
|
71
|
+
asOf,
|
|
72
|
+
...deps,
|
|
73
|
+
};
|
|
74
|
+
const outFile = path.resolve(io.cwd, parsed.values.out ?? 'run.json');
|
|
75
|
+
let methodology;
|
|
76
|
+
try {
|
|
77
|
+
methodology = loadMethodology(opts.methodologyVersion);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof MethodologyError) {
|
|
81
|
+
io.stderr(`Methodology error: ${err.message}\n`);
|
|
82
|
+
return EXIT.methodology;
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
if (opts.mode === 'full' && !deps.execRunner && deps.imageDigest === undefined) {
|
|
87
|
+
// No Docker, or no usable image: fall back to the static layer so `scan .` works from an npx install.
|
|
88
|
+
const tag = methodology.execution.runner.image;
|
|
89
|
+
try {
|
|
90
|
+
opts.imageDigest = await imageDigest(tag);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (!(err instanceof ToolError))
|
|
94
|
+
throw err;
|
|
95
|
+
opts.mode = 'preview';
|
|
96
|
+
io.stderr(`${err.message}\nRunning a preview scan (static layer only, not comparable). For a full run: ${imageBuildCommand(packageRoot, tag)}\n`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const result = await runScan(opts, methodology);
|
|
101
|
+
writeRun(outFile, result);
|
|
102
|
+
io.stdout(summarize(result, methodology) + footer(result, outFile));
|
|
103
|
+
return EXIT.ok;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err instanceof NoLogicFilesError) {
|
|
107
|
+
io.stderr(`${err.message}\n`);
|
|
108
|
+
return EXIT.noLogic;
|
|
109
|
+
}
|
|
110
|
+
if (err instanceof NotAGitRepositoryError) {
|
|
111
|
+
io.stderr(`${err.message}\n`);
|
|
112
|
+
return EXIT.usage;
|
|
113
|
+
}
|
|
114
|
+
if (err instanceof ToolError) {
|
|
115
|
+
writeRun(outFile, invalidRun(opts, err.message));
|
|
116
|
+
io.stderr(`Tool error: ${err.message}\n`);
|
|
117
|
+
return EXIT.tool;
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { findPreviousRun } from '../card/previous.js';
|
|
5
|
+
import { renderCard } from '../card/render.js';
|
|
6
|
+
import { loadMethodology, MethodologyError } from '../methodology/load.js';
|
|
7
|
+
import { readRun, RunReadError } from '../run/read.js';
|
|
8
|
+
import { EXIT } from './common.js';
|
|
9
|
+
const CARD_USAGE = 'Usage: vibecodedshit card <run.json> --history <dir> [--out <file>]\n';
|
|
10
|
+
function side(health, methodology) {
|
|
11
|
+
return { score: health.displayed, tier: methodology.bands.find((b) => b.key === health.band)?.name ?? health.band };
|
|
12
|
+
}
|
|
13
|
+
/** Writes a PNG card comparing a run with the previous run of the same repository in `--history`. No previous run, no card. */
|
|
14
|
+
export async function runCard(argv, io) {
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = parseArgs({ args: argv, allowPositionals: true, options: { out: { type: 'string' }, history: { type: 'string' } } });
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
io.stderr(`${err instanceof Error ? err.message : String(err)}\n${CARD_USAGE}`);
|
|
21
|
+
return EXIT.usage;
|
|
22
|
+
}
|
|
23
|
+
const [input] = parsed.positionals;
|
|
24
|
+
const history = parsed.values.history;
|
|
25
|
+
if (!input || !history || parsed.positionals.length > 1) {
|
|
26
|
+
io.stderr(CARD_USAGE);
|
|
27
|
+
return EXIT.usage;
|
|
28
|
+
}
|
|
29
|
+
const historyDir = path.resolve(io.cwd, history);
|
|
30
|
+
if (!existsSync(historyDir) || !statSync(historyDir).isDirectory()) {
|
|
31
|
+
io.stderr(`--history ${historyDir}: not a directory\n`);
|
|
32
|
+
return EXIT.usage;
|
|
33
|
+
}
|
|
34
|
+
// The default is the current directory, never the scanned repository, the same rule as run.json.
|
|
35
|
+
const outFile = path.resolve(io.cwd, parsed.values.out ?? 'health-card.png');
|
|
36
|
+
try {
|
|
37
|
+
const file = path.resolve(io.cwd, input);
|
|
38
|
+
const run = readRun(file);
|
|
39
|
+
if (!run.valid) {
|
|
40
|
+
io.stderr(`run.json is invalid: ${run.invalidReason ?? 'no reason given'}\n`);
|
|
41
|
+
return EXIT.usage;
|
|
42
|
+
}
|
|
43
|
+
if (run.health === null) {
|
|
44
|
+
io.stderr('No card: the run has no health score.\n');
|
|
45
|
+
return EXIT.ok;
|
|
46
|
+
}
|
|
47
|
+
const previous = findPreviousRun(file, run, historyDir);
|
|
48
|
+
if (!previous) {
|
|
49
|
+
io.stderr(`No card: no earlier ${run.mode} run of ${run.repo.path} for methodology ${run.methodology.version} in ${historyDir}.\n`);
|
|
50
|
+
return EXIT.ok;
|
|
51
|
+
}
|
|
52
|
+
const methodology = loadMethodology(run.methodology.version);
|
|
53
|
+
const card = { repo: path.basename(run.repo.path), before: side(previous.health, methodology), after: side(run.health, methodology) };
|
|
54
|
+
writeFileSync(outFile, await renderCard(card));
|
|
55
|
+
io.stdout(`${outFile}\n`);
|
|
56
|
+
return EXIT.ok;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err instanceof RunReadError) {
|
|
60
|
+
io.stderr(`${err.message}\n`);
|
|
61
|
+
return EXIT.usage;
|
|
62
|
+
}
|
|
63
|
+
if (err instanceof MethodologyError) {
|
|
64
|
+
io.stderr(`Methodology error: ${err.message}\n`);
|
|
65
|
+
return EXIT.methodology;
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
}
|