@drkmttr/adhere 0.18.1 → 0.21.2
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/README.md +1099 -1088
- package/package.json +15 -15
- package/presets/README.md +132 -0
- package/skills/adhere/SKILL.md +4 -3
- package/skills/adhere-setup/SKILL.md +11 -10
- package/src/cli.ts +121 -14
- package/src/config.ts +147 -139
- package/src/create/agents.ts +149 -0
- package/src/create/ask.ts +147 -0
- package/src/create/look.ts +140 -0
- package/src/create/wizard.ts +432 -0
- package/src/index.ts +2 -0
- package/src/init.ts +2 -1
- package/src/install.ts +1 -1
- package/src/instructions.ts +76 -0
- package/src/markdown.ts +3 -9
- package/src/rules.ts +42 -45
- package/src/services/AdhereConfig.ts +31 -18
- package/src/services/Credentials.ts +14 -8
- package/src/services/Jev.reads.test.ts +22 -0
- package/src/services/Jev.ts +35 -13
- package/src/wording.ts +1 -1
- package/src/workflows/audit.ts +82 -14
- package/src/workflows/format.ts +97 -0
- package/src/workspaces.test.ts +37 -1
- package/src/workspaces.ts +121 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drkmttr/adhere",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Lints a repository against the rules in .adhere/, judged by Jev.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,32 +38,32 @@
|
|
|
38
38
|
"prepare": "vp config"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"effect": "4.0.
|
|
41
|
+
"effect": "4.0.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@effect/platform-bun": "4.0.
|
|
44
|
+
"@effect/platform-bun": "4.0.3",
|
|
45
45
|
"@types/bun": "^1.4.2",
|
|
46
|
-
"@types/node": "^
|
|
46
|
+
"@types/node": "^26.0.0",
|
|
47
47
|
"release-it": "^21.1.0",
|
|
48
|
-
"typescript": "7.1.0-dev.
|
|
48
|
+
"typescript": "7.1.0-dev.20261012.1",
|
|
49
49
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
|
|
50
50
|
"vite-plus": "1.1.0",
|
|
51
|
-
"vitest": "5.0.
|
|
51
|
+
"vitest": "5.0.5"
|
|
52
52
|
},
|
|
53
53
|
"overrides": {
|
|
54
|
-
"@effect/platform-node-shared": "4.0.
|
|
54
|
+
"@effect/platform-node-shared": "4.0.3",
|
|
55
55
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
|
|
56
|
-
"vitest": "5.0.
|
|
56
|
+
"vitest": "5.0.5"
|
|
57
57
|
},
|
|
58
58
|
"devEngines": {
|
|
59
59
|
"packageManager": {
|
|
60
60
|
"name": "bun",
|
|
61
|
-
"version": "1.4.
|
|
61
|
+
"version": "1.4.3",
|
|
62
62
|
"onFail": "download"
|
|
63
63
|
},
|
|
64
64
|
"runtime": {
|
|
65
65
|
"name": "node",
|
|
66
|
-
"version": "
|
|
66
|
+
"version": "26.11.1",
|
|
67
67
|
"onFail": "download"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
"--force"
|
|
91
91
|
],
|
|
92
92
|
"optionalDependencies": {
|
|
93
|
-
"@drkmttr/adhere-darwin-arm64": "0.
|
|
94
|
-
"@drkmttr/adhere-darwin-x64": "0.
|
|
95
|
-
"@drkmttr/adhere-linux-arm64": "0.
|
|
96
|
-
"@drkmttr/adhere-linux-x64": "0.
|
|
97
|
-
"@drkmttr/adhere-win32-x64": "0.
|
|
93
|
+
"@drkmttr/adhere-darwin-arm64": "0.21.2",
|
|
94
|
+
"@drkmttr/adhere-darwin-x64": "0.21.2",
|
|
95
|
+
"@drkmttr/adhere-linux-arm64": "0.21.2",
|
|
96
|
+
"@drkmttr/adhere-linux-x64": "0.21.2",
|
|
97
|
+
"@drkmttr/adhere-win32-x64": "0.21.2"
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Presets
|
|
2
|
+
|
|
3
|
+
Every rule in adhere's built-in presets is listed below, with a one-line
|
|
4
|
+
summary. Each id links to the full rule, and is the id that reports,
|
|
5
|
+
`overrides`, and `adhere-ignore` comments use. [Presets](../README.md#presets)
|
|
6
|
+
says how to name a preset or one of its topics.
|
|
7
|
+
|
|
8
|
+
## typescript
|
|
9
|
+
|
|
10
|
+
- **Source.** The TypeScript Handbook and the Google TypeScript Style Guide.
|
|
11
|
+
- **Checked by a linter instead.** The preset has no rules for these, since
|
|
12
|
+
typescript-eslint and ESLint check them exactly: unhandled promises
|
|
13
|
+
(`no-floating-promises`), throwing non-errors (`only-throw-error`), `any`
|
|
14
|
+
(`no-explicit-any`), exhaustive switches (`switch-exhaustiveness-check`), a
|
|
15
|
+
lost `cause` (`preserve-caught-error`), and empty catch blocks (`no-empty`).
|
|
16
|
+
|
|
17
|
+
- [`typescript/async/concurrent-independent-work`](./typescript/async/concurrent-independent-work/RULE.md): Independent awaited calls run concurrently, not one after another.
|
|
18
|
+
- [`typescript/async/network-calls-have-timeouts`](./typescript/async/network-calls-have-timeouts/RULE.md): HTTP requests carry a timeout or an abort signal.
|
|
19
|
+
- [`typescript/design/no-argument-mutation`](./typescript/design/no-argument-mutation/RULE.md): Functions return changed copies instead of mutating arguments.
|
|
20
|
+
- [`typescript/errors/no-swallowed-errors`](./typescript/errors/no-swallowed-errors/RULE.md): Caught errors are handled, rethrown, or returned as failures.
|
|
21
|
+
- [`typescript/errors/release-on-every-path`](./typescript/errors/release-on-every-path/RULE.md): Acquired resources are released on every path, including errors.
|
|
22
|
+
- [`typescript/testing/assert-outcomes`](./typescript/testing/assert-outcomes/RULE.md): Tests assert results and effects, not only that code ran.
|
|
23
|
+
- [`typescript/testing/independent-tests`](./typescript/testing/independent-tests/RULE.md): Each test sets up its own state and relies on no other test.
|
|
24
|
+
- [`typescript/types/assertions-only-when-proven`](./typescript/types/assertions-only-when-proven/RULE.md): Type assertions claim only what surrounding code has proven.
|
|
25
|
+
- [`typescript/types/derive-dont-restate`](./typescript/types/derive-dont-restate/RULE.md): Types are derived from their source instead of restated by hand.
|
|
26
|
+
- [`typescript/types/unrepresentable-invalid-states`](./typescript/types/unrepresentable-invalid-states/RULE.md): Types make invalid field combinations unrepresentable.
|
|
27
|
+
- [`typescript/types/validate-external-data`](./typescript/types/validate-external-data/RULE.md): Data from outside is checked at runtime before use.
|
|
28
|
+
|
|
29
|
+
## react
|
|
30
|
+
|
|
31
|
+
- **Source.** react.dev, mostly
|
|
32
|
+
[You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
|
|
33
|
+
and
|
|
34
|
+
[Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure).
|
|
35
|
+
- **Checked by a linter instead.** The preset has no rules for these, since
|
|
36
|
+
the React Compiler's lint rules check them, in
|
|
37
|
+
[eslint-plugin-react-hooks](https://react.dev/reference/eslint-plugin-react-hooks)'s
|
|
38
|
+
recommended set and in Oxlint: pure render (`purity`), props and state
|
|
39
|
+
never mutated (`immutability`), refs not read during render (`refs`), no
|
|
40
|
+
`setState` in render or synchronously in an effect (`set-state-in-render`,
|
|
41
|
+
`set-state-in-effect`), which covers state derived in an effect, and no
|
|
42
|
+
component defined inside another (`static-components`).
|
|
43
|
+
|
|
44
|
+
- [`react/effects/clean-up-what-they-start`](./react/effects/clean-up-what-they-start/RULE.md): Effects return cleanup that undoes what they started.
|
|
45
|
+
- [`react/effects/event-logic-in-handlers`](./react/effects/event-logic-in-handlers/RULE.md): Code responding to a user action runs in its event handler.
|
|
46
|
+
- [`react/effects/external-stores-use-sync-external-store`](./react/effects/external-stores-use-sync-external-store/RULE.md): External stores are read with `useSyncExternalStore`.
|
|
47
|
+
- [`react/effects/fetches-ignore-stale-responses`](./react/effects/fetches-ignore-stale-responses/RULE.md): Effects that fetch ignore or abort stale responses.
|
|
48
|
+
- [`react/effects/notify-parents-in-handlers`](./react/effects/notify-parents-in-handlers/RULE.md): Parents are notified in the handler that makes the change.
|
|
49
|
+
- [`react/server/no-private-data-to-client`](./react/server/no-private-data-to-client/RULE.md): Client Components receive only the fields they show.
|
|
50
|
+
- [`react/server/server-functions-authorize-and-validate`](./react/server/server-functions-authorize-and-validate/RULE.md): Server Functions authorize callers and validate arguments.
|
|
51
|
+
- [`react/state/no-contradictory-state`](./react/state/no-contradictory-state/RULE.md): One status value replaces booleans that can contradict.
|
|
52
|
+
- [`react/state/no-props-copied-into-state`](./react/state/no-props-copied-into-state/RULE.md): Props are read during render, not copied into state.
|
|
53
|
+
- [`react/state/store-ids-not-copies`](./react/state/store-ids-not-copies/RULE.md): State holds a selected item's id, not a copy of the item.
|
|
54
|
+
|
|
55
|
+
## security
|
|
56
|
+
|
|
57
|
+
- **Source.** OWASP's cheat sheets.
|
|
58
|
+
|
|
59
|
+
- [`security/constant-time-secret-compare`](./security/constant-time-secret-compare/RULE.md): Received secrets are compared in constant time.
|
|
60
|
+
- [`security/no-secrets-in-output`](./security/no-secrets-in-output/RULE.md): Secrets stay out of logs, errors, and HTTP responses.
|
|
61
|
+
- [`security/no-untrusted-input-in-commands-or-paths`](./security/no-untrusted-input-in-commands-or-paths/RULE.md): Untrusted input stays out of command strings and unchecked paths.
|
|
62
|
+
- [`security/parameterized-queries`](./security/parameterized-queries/RULE.md): SQL values from outside are passed as bound parameters.
|
|
63
|
+
|
|
64
|
+
## effect
|
|
65
|
+
|
|
66
|
+
- **Source.** The
|
|
67
|
+
[effect-solutions](https://github.com/kitlangton/effect-solutions) docs and
|
|
68
|
+
the [effect/platform](https://effect.website/docs/platform/introduction/)
|
|
69
|
+
docs.
|
|
70
|
+
|
|
71
|
+
- [`effect/basics/gen-for-sequencing`](./effect/basics/gen-for-sequencing/RULE.md): Sequential effectful steps use `Effect.gen` and `yield*`.
|
|
72
|
+
- [`effect/basics/instrument-with-pipe`](./effect/basics/instrument-with-pipe/RULE.md): Timeouts, retries, logging, and spans attach with `.pipe`.
|
|
73
|
+
- [`effect/config/business-logic-depends-on-config-service`](./effect/config/business-logic-depends-on-config-service/RULE.md): Business logic depends on a config service, not Config reads.
|
|
74
|
+
- [`effect/config/secrets-are-redacted`](./effect/config/secrets-are-redacted/RULE.md): Tokens, passwords, and keys are read with `Config.Redacted`.
|
|
75
|
+
- [`effect/config/tests-provide-values-directly`](./effect/config/tests-provide-values-directly/RULE.md): Tests supply config with `Layer.succeed` on the config service.
|
|
76
|
+
- [`effect/config/validate-with-schema`](./effect/config/validate-with-schema/RULE.md): Constrained config values are validated as they are read.
|
|
77
|
+
- [`effect/data/brand-meaningful-primitives`](./effect/data/brand-meaningful-primitives/RULE.md): Meaningful primitives in domain types are branded schemas.
|
|
78
|
+
- [`effect/data/variants-are-tagged-unions`](./effect/data/variants-are-tagged-unions/RULE.md): Domain variants are tagged schemas in a union.
|
|
79
|
+
- [`effect/errors/catch-defects-at-boundaries-only`](./effect/errors/catch-defects-at-boundaries-only/RULE.md): Defects are caught only at system boundaries.
|
|
80
|
+
- [`effect/errors/defects-for-bugs`](./effect/errors/defects-for-bugs/RULE.md): Bugs and invariant violations are defects, not typed errors.
|
|
81
|
+
- [`effect/errors/wrap-external-errors`](./effect/errors/wrap-external-errors/RULE.md): External errors are wrapped in tagged errors.
|
|
82
|
+
- [`effect/services/dependencies-through-layers`](./effect/services/dependencies-through-layers/RULE.md): Service dependencies are yielded inside the service's Layer.
|
|
83
|
+
- [`effect/services/fresh-layer-per-test`](./effect/services/fresh-layer-per-test/RULE.md): Each `it.effect` test provides its own layer.
|
|
84
|
+
- [`effect/services/memoize-parameterized-layers`](./effect/services/memoize-parameterized-layers/RULE.md): Parameterized layers are built once and stored in a constant.
|
|
85
|
+
- [`effect/services/no-mutable-state`](./effect/services/no-mutable-state/RULE.md): Service interfaces expose only readonly members.
|
|
86
|
+
- [`effect/testing/test-clock-for-time`](./effect/testing/test-clock-for-time/RULE.md): Tests that depend on time use `TestClock`.
|
|
87
|
+
|
|
88
|
+
## alchemy
|
|
89
|
+
|
|
90
|
+
- **Source.** alchemy's docs and blog.
|
|
91
|
+
|
|
92
|
+
- [`alchemy/apis/match-remote-errors-by-tag`](./alchemy/apis/match-remote-errors-by-tag/RULE.md): Remote errors are matched by tag and data, not `instanceof`.
|
|
93
|
+
- [`alchemy/apis/schemas-at-trust-boundaries`](./alchemy/apis/schemas-at-trust-boundaries/RULE.md): Outside input is schema-validated at trust boundaries.
|
|
94
|
+
- [`alchemy/data/commit-migrations-then-deploy`](./alchemy/data/commit-migrations-then-deploy/RULE.md): Migrations are committed ahead of time and applied as committed.
|
|
95
|
+
- [`alchemy/data/hyperdrive-direct-origin`](./alchemy/data/hyperdrive-direct-origin/RULE.md): Hyperdrive points at the database's direct endpoint.
|
|
96
|
+
- [`alchemy/data/migrations-use-direct-url`](./alchemy/data/migrations-use-direct-url/RULE.md): Migration runners connect with the database's direct URL.
|
|
97
|
+
- [`alchemy/data/never-disable-tls-verification`](./alchemy/data/never-disable-tls-verification/RULE.md): Database connections verify the server's TLS certificate.
|
|
98
|
+
- [`alchemy/data/private-endpoint-at-runtime`](./alchemy/data/private-endpoint-at-runtime/RULE.md): Service code reaches its database through the private binding.
|
|
99
|
+
- [`alchemy/data/single-migration-owner`](./alchemy/data/single-migration-owner/RULE.md): Each database has exactly one migration owner.
|
|
100
|
+
- [`alchemy/durable/idempotent-event-handlers`](./alchemy/durable/idempotent-event-handlers/RULE.md): Handlers for at-least-once events are idempotent.
|
|
101
|
+
- [`alchemy/durable/io-inside-steps`](./alchemy/durable/io-inside-steps/RULE.md): Workflow I/O runs inside steps; replayed code stays deterministic.
|
|
102
|
+
- [`alchemy/durable/no-self-triggering-bucket-writes`](./alchemy/durable/no-self-triggering-bucket-writes/RULE.md): Bucket handlers filter out events from their own writes.
|
|
103
|
+
- [`alchemy/durable/stable-step-names`](./alchemy/durable/stable-step-names/RULE.md): Workflow step names stay stable across replays.
|
|
104
|
+
- [`alchemy/durable/webhook-state-is-truth`](./alchemy/durable/webhook-state-is-truth/RULE.md): Paid access depends on webhook-recorded state, not success URLs.
|
|
105
|
+
- [`alchemy/providers/diff-guards-unresolved-props`](./alchemy/providers/diff-guards-unresolved-props/RULE.md): Provider diffs guard unresolved props with `isResolved`.
|
|
106
|
+
- [`alchemy/providers/idempotent-delete`](./alchemy/providers/idempotent-delete/RULE.md): Provider delete treats an already-gone resource as success.
|
|
107
|
+
- [`alchemy/providers/lazy-layer-construction`](./alchemy/providers/lazy-layer-construction/RULE.md): Provider layers defer credentials and connections until use.
|
|
108
|
+
- [`alchemy/providers/reconcile-from-observed-state`](./alchemy/providers/reconcile-from-observed-state/RULE.md): Provider reconcile converges from the resource's live state.
|
|
109
|
+
- [`alchemy/resources/anchor-paths-with-import-meta`](./alchemy/resources/anchor-paths-with-import-meta/RULE.md): Path props of resources declared outside the run directory use `import.meta`.
|
|
110
|
+
- [`alchemy/resources/idempotent-actions-and-seeds`](./alchemy/resources/idempotent-actions-and-seeds/RULE.md): Actions and seed files are safe to run again.
|
|
111
|
+
- [`alchemy/resources/previews-reference-shared-resources`](./alchemy/resources/previews-reference-shared-resources/RULE.md): Preview stages reference shared resources and add cheap pieces.
|
|
112
|
+
- [`alchemy/resources/protect-irreplaceable-data`](./alchemy/resources/protect-irreplaceable-data/RULE.md): Buckets and databases with irreplaceable data are retained.
|
|
113
|
+
- [`alchemy/resources/remote-state-for-ci`](./alchemy/resources/remote-state-for-ci/RULE.md): CI and multi-machine stacks use a shared state store.
|
|
114
|
+
- [`alchemy/resources/stable-logical-ids`](./alchemy/resources/stable-logical-ids/RULE.md): Resource logical ids stay the same across deploys.
|
|
115
|
+
- [`alchemy/resources/stage-unique-physical-names`](./alchemy/resources/stage-unique-physical-names/RULE.md): Global physical names are derived per stage.
|
|
116
|
+
- [`alchemy/runtime/bind-tag-not-implementation`](./alchemy/runtime/bind-tag-not-implementation/RULE.md): Other runtimes bind the Tag; only the Stack provides the Layer.
|
|
117
|
+
- [`alchemy/runtime/deterministic-config-defaults`](./alchemy/runtime/deterministic-config-defaults/RULE.md): Config defaults and fallbacks are deterministic.
|
|
118
|
+
- [`alchemy/runtime/disposables-in-request-scope`](./alchemy/runtime/disposables-in-request-scope/RULE.md): Disposables are acquired inside handlers, within request scope.
|
|
119
|
+
- [`alchemy/runtime/lambda-no-post-response-work`](./alchemy/runtime/lambda-no-post-response-work/RULE.md): Lambda handlers write durable work before responding.
|
|
120
|
+
- [`alchemy/runtime/no-per-request-work-at-construction`](./alchemy/runtime/no-per-request-work-at-construction/RULE.md): Construction builds resources and handlers, not per-request work.
|
|
121
|
+
- [`alchemy/runtime/resolve-config-and-bindings-at-construction`](./alchemy/runtime/resolve-config-and-bindings-at-construction/RULE.md): Config and bindings are resolved at construction, not in handlers.
|
|
122
|
+
- [`alchemy/secrets/let-alchemy-mint-infra-secrets`](./alchemy/secrets/let-alchemy-mint-infra-secrets/RULE.md): Infrastructure tokens are minted once by alchemy, not hardcoded.
|
|
123
|
+
- [`alchemy/secrets/none-in-browser-env`](./alchemy/secrets/none-in-browser-env/RULE.md): Browser-inlined env values hold only public configuration.
|
|
124
|
+
- [`alchemy/secrets/redacted-until-use`](./alchemy/secrets/redacted-until-use/RULE.md): Secrets stay `Redacted` until the call that needs the raw string.
|
|
125
|
+
- [`alchemy/secrets/unwrap-before-compare-or-send`](./alchemy/secrets/unwrap-before-compare-or-send/RULE.md): `Redacted` values are unwrapped before compare, send, or serialize.
|
|
126
|
+
- [`alchemy/security/authorize-public-endpoints`](./alchemy/security/authorize-public-endpoints/RULE.md): Public handlers authenticate and authorize every caller.
|
|
127
|
+
- [`alchemy/security/github-secret-not-variable`](./alchemy/security/github-secret-not-variable/RULE.md): Sensitive CI values use `GitHub.Secret`, not `GitHub.Variable`.
|
|
128
|
+
- [`alchemy/security/identity-from-verified-session`](./alchemy/security/identity-from-verified-session/RULE.md): The acting user comes from the verified session or token.
|
|
129
|
+
- [`alchemy/security/keep-auth-protections-on`](./alchemy/security/keep-auth-protections-on/RULE.md): Auth checks and protections stay enabled.
|
|
130
|
+
- [`alchemy/security/least-privilege-bindings`](./alchemy/security/least-privilege-bindings/RULE.md): Runtimes bind the narrowest capability their calls need.
|
|
131
|
+
- [`alchemy/security/least-privilege-ci-credentials`](./alchemy/security/least-privilege-ci-credentials/RULE.md): CI credentials are scoped to what the stack deploys.
|
|
132
|
+
- [`alchemy/security/lookup-failure-is-not-anonymous`](./alchemy/security/lookup-failure-is-not-anonymous/RULE.md): A failed session lookup is an error, not an anonymous user.
|
package/skills/adhere/SKILL.md
CHANGED
|
@@ -23,6 +23,7 @@ adhere init [--force] # .adhere/config.ts, two example rules, and the
|
|
|
23
23
|
adhere init --shared org/repo # a repo of rules other repos install, with CI and an alchemy stack
|
|
24
24
|
adhere login # save a TypeSafe AI API key; TYPESAFE_API_KEY takes precedence
|
|
25
25
|
adhere validate # rule wording, rules a linter could check, contradictions
|
|
26
|
+
adhere rules create [idea] # wizard: claude, codex, or omp drafts a RULE.md or RULE.ts from excerpts you judge; org rules pushed for a PR
|
|
26
27
|
adhere lint # judge the working directory; exit 1 on an error finding
|
|
27
28
|
adhere lint --limit 0 # compiler-free counts/cache plan; no budgeting
|
|
28
29
|
adhere lint --limit 100 # first 100 files needing requests; all pending rules per file
|
|
@@ -173,9 +174,9 @@ Opting in with `typeAware: true` or `--type-aware` enables both `symbols` and
|
|
|
173
174
|
`references`, regardless of `reads`. Legacy native-reader entries in `reads`
|
|
174
175
|
alone do not enable the compiler; leaving them out does not disable it when
|
|
175
176
|
type awareness is on. Code alone requires `reads: []` and type awareness off.
|
|
176
|
-
Presets do not own or override this selection.
|
|
177
|
-
front matter, `defineRule`, or inline
|
|
178
|
-
|
|
177
|
+
Presets do not own or override this selection. A rule-level `reads`, in
|
|
178
|
+
Markdown front matter, `defineRule`, or an inline rule, has no effect. The
|
|
179
|
+
context maps are:
|
|
179
180
|
|
|
180
181
|
- `workspacePackages`: only packages matched by this file's literal imports,
|
|
181
182
|
as `{ canonicalPackageName: directory }`. Subpaths match the owning package;
|
|
@@ -25,10 +25,10 @@ the `packageManager` field in `package.json` names it; with neither, **Ask**.
|
|
|
25
25
|
|
|
26
26
|
| Manager | Add adhere | Run adhere |
|
|
27
27
|
| ------- | -------------------------------- | ------------------ |
|
|
28
|
-
| bun | `bun add -D @drkmttr/adhere` | `
|
|
28
|
+
| bun | `bun add -D @drkmttr/adhere` | `bun run adhere` |
|
|
29
29
|
| pnpm | `pnpm add -D @drkmttr/adhere` | `pnpm exec adhere` |
|
|
30
30
|
| yarn | `yarn add -D @drkmttr/adhere` | `yarn adhere` |
|
|
31
|
-
| npm | `npm install -D @drkmttr/adhere` | `
|
|
31
|
+
| npm | `npm install -D @drkmttr/adhere` | `npm exec adhere` |
|
|
32
32
|
|
|
33
33
|
In a repo with a `package.json`, add adhere as a dev dependency with its
|
|
34
34
|
manager, so the repo pins the version the team and CI run. From then on,
|
|
@@ -56,18 +56,19 @@ and validate do not start the compiler.
|
|
|
56
56
|
In a repo without a `package.json`, inspect any `adhere` on PATH with
|
|
57
57
|
`adhere --version`, then **Ask** whether to use the npm/Bun package launcher
|
|
58
58
|
or a raw executable for the same default compiler-free audit. The raw release
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
needs no Node or package manager and runs with workspace context without
|
|
60
|
+
special configuration. It cannot run a type-aware audit: use the package
|
|
61
|
+
launcher or a checkout if the user opts in. Do not silently turn off an
|
|
62
|
+
explicitly chosen `typeAware` setting. Only fetch the raw release after the
|
|
63
|
+
user chooses that alternative. On macOS and Linux:
|
|
64
64
|
|
|
65
65
|
```sh
|
|
66
|
-
curl -fsSL
|
|
67
|
-
https://github.com/darkmatter/adhere/releases/latest/download/adhere-$(uname -s)-$(uname -m)
|
|
68
|
-
chmod +x ~/.local/bin/adhere
|
|
66
|
+
curl -fsSL https://github.com/darkmatter/adhere/raw/main/scripts/install.sh | sh
|
|
69
67
|
```
|
|
70
68
|
|
|
69
|
+
On Windows, download `adhere-Windows-x86_64.exe` from
|
|
70
|
+
https://github.com/darkmatter/adhere/releases/latest.
|
|
71
|
+
|
|
71
72
|
Validate and lint's paid requests need a TypeSafe AI API key: `TYPESAFE_API_KEY`,
|
|
72
73
|
or one saved by `adhere login`, which the user runs; never ask for the key in
|
|
73
74
|
chat or print it. Compiler-free planning with `--limit 0` needs no key.
|
package/src/cli.ts
CHANGED
|
@@ -24,12 +24,18 @@ import {
|
|
|
24
24
|
type Progress,
|
|
25
25
|
progressAfter,
|
|
26
26
|
progressLine,
|
|
27
|
+
renderJson,
|
|
28
|
+
renderMarkdown,
|
|
29
|
+
renderRuleReports,
|
|
27
30
|
sendQuestion,
|
|
28
31
|
} from "#workflows/format.ts";
|
|
29
32
|
import type { Flags } from "#config.ts";
|
|
30
33
|
import { findContradictions, formatContradictions } from "#contradictions.ts";
|
|
34
|
+
import { createRule } from "#create/wizard.ts";
|
|
35
|
+
import { AGENTS } from "#create/agents.ts";
|
|
31
36
|
import { type Install, initProject } from "#init.ts";
|
|
32
37
|
import { installRules, listRules, parseSource } from "#install.ts";
|
|
38
|
+
import { renderInstructions } from "#instructions.ts";
|
|
33
39
|
import { formatLinterCheck, tallyProjectRules } from "#mechanical.ts";
|
|
34
40
|
import { type PresetName, presetNames } from "#presets.ts";
|
|
35
41
|
import type { SavedKey } from "#providers/provider.ts";
|
|
@@ -39,6 +45,7 @@ import {
|
|
|
39
45
|
Console,
|
|
40
46
|
Effect,
|
|
41
47
|
Exit,
|
|
48
|
+
FileSystem,
|
|
42
49
|
Layer,
|
|
43
50
|
LogLevel,
|
|
44
51
|
Option,
|
|
@@ -49,8 +56,9 @@ import {
|
|
|
49
56
|
Schema,
|
|
50
57
|
Stdio,
|
|
51
58
|
Stream,
|
|
59
|
+
Terminal,
|
|
52
60
|
} from "effect";
|
|
53
|
-
import { Argument, Command, Flag, Prompt } from "effect/cli";
|
|
61
|
+
import { Argument, CliError, Command, Flag, Prompt } from "effect/cli";
|
|
54
62
|
// A Bun text import (https://bun.sh/docs/bundler/loaders#text): the file's
|
|
55
63
|
// contents become a string at bundle time, so the compiled binary carries the
|
|
56
64
|
// skill without an --asset flag. `skills/` sits beside `src/`, outside the `#`
|
|
@@ -246,9 +254,43 @@ export const auditLayer = (flags: Flags, filter: ReadonlyArray<string> = []) =>
|
|
|
246
254
|
/** `adhere lint`: the audit. */
|
|
247
255
|
export const lintCommand = Command.make(
|
|
248
256
|
"lint",
|
|
249
|
-
{
|
|
257
|
+
{
|
|
258
|
+
preset,
|
|
259
|
+
threshold,
|
|
260
|
+
sufficiencyThreshold,
|
|
261
|
+
typeAware,
|
|
262
|
+
yes,
|
|
263
|
+
limit,
|
|
264
|
+
rpm,
|
|
265
|
+
filter,
|
|
266
|
+
denyWarnings,
|
|
267
|
+
json: Flag.Boolean("json").pipe(
|
|
268
|
+
Flag.withDefault(false),
|
|
269
|
+
Flag.withDescription(
|
|
270
|
+
"Write one JSON file per violated rule to .adhere/reports/. Use --stdout for one combined report instead.",
|
|
271
|
+
),
|
|
272
|
+
),
|
|
273
|
+
md: Flag.Boolean("md").pipe(
|
|
274
|
+
Flag.withDefault(false),
|
|
275
|
+
Flag.withDescription(
|
|
276
|
+
"Write one Markdown file per violated rule to .adhere/reports/. Use --stdout for one combined report instead.",
|
|
277
|
+
),
|
|
278
|
+
),
|
|
279
|
+
stdout: Flag.Boolean("stdout").pipe(
|
|
280
|
+
Flag.withDefault(false),
|
|
281
|
+
Flag.withDescription(
|
|
282
|
+
"Print all findings together instead of writing files. Select either --json or --md for a formatted report.",
|
|
283
|
+
),
|
|
284
|
+
),
|
|
285
|
+
},
|
|
250
286
|
(input) =>
|
|
251
287
|
Effect.gen(function* () {
|
|
288
|
+
if (input.stdout && input.json && input.md) {
|
|
289
|
+
return yield* CliError.UserError.make({
|
|
290
|
+
cause:
|
|
291
|
+
"Choose either --json or --md with --stdout; both formats can be written to files together.",
|
|
292
|
+
});
|
|
293
|
+
}
|
|
252
294
|
const stdio = yield* Stdio.Stdio;
|
|
253
295
|
const plan = yield* planAudit({ limit: Option.getOrUndefined(input.limit) });
|
|
254
296
|
const limits = { filter: input.filter, rpm: Option.getOrUndefined(input.rpm) };
|
|
@@ -258,8 +300,7 @@ export const lintCommand = Command.make(
|
|
|
258
300
|
if (input.filter.length === 0) yield* pruneCache(plan);
|
|
259
301
|
return;
|
|
260
302
|
}
|
|
261
|
-
//
|
|
262
|
-
// the report is redirected, or in CI.
|
|
303
|
+
// Keep prompting limited to interactive runs, including when JSON is requested.
|
|
263
304
|
const interactive = (yield* stdio.stdinIsTerminal) && (yield* stdio.stdoutIsTerminal);
|
|
264
305
|
if (
|
|
265
306
|
(plan.requests > 0 || (plan.cachedLocateRequests ?? 0) > 0) &&
|
|
@@ -268,6 +309,11 @@ export const lintCommand = Command.make(
|
|
|
268
309
|
) {
|
|
269
310
|
const send = yield* Prompt.run(
|
|
270
311
|
Prompt.Confirm({ message: sendQuestion(plan), initial: true }),
|
|
312
|
+
).pipe(
|
|
313
|
+
Effect.updateService(Terminal.Terminal, (terminal) => ({
|
|
314
|
+
...terminal,
|
|
315
|
+
display: toStderr,
|
|
316
|
+
})),
|
|
271
317
|
);
|
|
272
318
|
if (!send) {
|
|
273
319
|
yield* toStderr("Nothing sent.\n");
|
|
@@ -283,16 +329,33 @@ export const lintCommand = Command.make(
|
|
|
283
329
|
if (input.filter.length === 0) yield* pruneCache(plan);
|
|
284
330
|
const color = yield* stdio.stdoutIsTerminal;
|
|
285
331
|
const path = yield* Path.Path;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
332
|
+
const options = {
|
|
333
|
+
color,
|
|
334
|
+
root: path.resolve(),
|
|
335
|
+
threshold: plan.threshold,
|
|
336
|
+
sufficiencyThreshold: plan.sufficiencyThreshold,
|
|
337
|
+
...(plan.provider === undefined ? {} : { provider: plan.provider }),
|
|
338
|
+
};
|
|
339
|
+
if (!input.stdout && (input.json || input.md)) {
|
|
340
|
+
const fs = yield* FileSystem.FileSystem;
|
|
341
|
+
const directory = path.join(options.root, ".adhere", "reports");
|
|
342
|
+
const reports = renderRuleReports(result, input, options);
|
|
343
|
+
yield* fs.remove(directory, { recursive: true, force: true });
|
|
344
|
+
yield* fs.makeDirectory(directory, { recursive: true });
|
|
345
|
+
for (const [name, report] of reports) {
|
|
346
|
+
yield* fs.writeFileString(path.join(directory, name), report);
|
|
347
|
+
}
|
|
348
|
+
yield* toStderr(`Wrote ${reports.size} report files to .adhere/reports/.\n`);
|
|
349
|
+
} else {
|
|
350
|
+
// One write: separate Console.log calls have interleaved out of order here.
|
|
351
|
+
yield* Console.log(
|
|
352
|
+
input.json
|
|
353
|
+
? renderJson(result, options)
|
|
354
|
+
: input.md
|
|
355
|
+
? renderMarkdown(result, options)
|
|
356
|
+
: render(result, options).join("\n"),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
296
359
|
// Only errors fail the run, unless --deny-warnings: a warning is for a nit or a noisy rule.
|
|
297
360
|
const failed = failing(result.findings, input.denyWarnings);
|
|
298
361
|
if (failed.length > 0) {
|
|
@@ -556,6 +619,48 @@ export const skillCommand = Command.make("skill", { name: skillName }, ({ name }
|
|
|
556
619
|
),
|
|
557
620
|
);
|
|
558
621
|
|
|
622
|
+
const agent = Flag.Literals("agent", AGENTS).pipe(
|
|
623
|
+
Flag.optional,
|
|
624
|
+
Flag.withDescription(
|
|
625
|
+
"The coding agent to ask this time: claude, codex, or omp, in place of the one setup saved.",
|
|
626
|
+
),
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
/** `adhere rules create [idea]`: a wizard that writes one rule, with a coding agent's help. */
|
|
630
|
+
const rulesCreateCommand = Command.make(
|
|
631
|
+
"create",
|
|
632
|
+
{
|
|
633
|
+
idea: Argument.String("idea").pipe(
|
|
634
|
+
Argument.optional,
|
|
635
|
+
Argument.withDescription("What the rule should enforce. Asked for when left out."),
|
|
636
|
+
),
|
|
637
|
+
agent,
|
|
638
|
+
setup: Flag.Boolean("setup").pipe(
|
|
639
|
+
Flag.withDefault(false),
|
|
640
|
+
Flag.withDescription(
|
|
641
|
+
"Choose again which agent to ask and which GitHub repo, as org/repo, holds the organization's shared rules. The first run asks without it.",
|
|
642
|
+
),
|
|
643
|
+
),
|
|
644
|
+
},
|
|
645
|
+
(input) => createRule(input.idea, input.agent, input.setup).pipe(Effect.provide(statusLayer)),
|
|
646
|
+
).pipe(
|
|
647
|
+
Command.withDescription(
|
|
648
|
+
"Write a rule with a coding agent's help: it suggests whether the rule belongs to this project or the organization's shared rules repo, names it, chooses a RULE.md or, when Jev needs state the file cannot show, a RULE.ts, and says which config context (typeAware, reads) it needs. It finds code in the repo for you to judge, with the lines around it, and drafts the rule from your answers; the draft is checked as adhere validate checks wording. A project rule is written to .adhere/rules/; an organization rule is pushed to the shared repo on a branch for a pull request.",
|
|
649
|
+
),
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
export const rulesCommand = Command.make("rules").pipe(
|
|
653
|
+
Command.withDescription("Work on this repo's rules."),
|
|
654
|
+
Command.withSubcommands([rulesCreateCommand]),
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
export const postinstallCommand = Command.make("postinstall", {}, () =>
|
|
658
|
+
Effect.gen(function* () {
|
|
659
|
+
const path = yield* Path.Path;
|
|
660
|
+
yield* Console.log(yield* renderInstructions(path.resolve()));
|
|
661
|
+
}),
|
|
662
|
+
).pipe(Command.withDescription("Render instruction files into a managed block in AGENTS.md."));
|
|
663
|
+
|
|
559
664
|
/** The whole CLI. Bare `adhere` prints its help. */
|
|
560
665
|
export const cli = Command.make("adhere").pipe(
|
|
561
666
|
Command.withDescription("Lint a repository against the rules in .adhere/rules/, judged by Jev."),
|
|
@@ -580,10 +685,12 @@ export const cli = Command.make("adhere").pipe(
|
|
|
580
685
|
lintCommand,
|
|
581
686
|
validateCommand,
|
|
582
687
|
initCommand,
|
|
688
|
+
postinstallCommand,
|
|
583
689
|
listCommand,
|
|
584
690
|
installCommand,
|
|
585
691
|
loginCommand,
|
|
586
692
|
logoutCommand,
|
|
587
693
|
skillCommand,
|
|
694
|
+
rulesCommand,
|
|
588
695
|
]),
|
|
589
696
|
);
|