@danieljvdm/dev-kit 0.12.0 → 0.14.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/README.md CHANGED
@@ -179,6 +179,7 @@ tool versions. A project-local process lock also prevents concurrent applies.
179
179
  "include": [
180
180
  "dev-kit",
181
181
  "effect",
182
+ "open-pull-request",
182
183
  "workers-best-practices",
183
184
  "wrangler",
184
185
  "serve-sim",
@@ -198,9 +199,14 @@ tool versions. A project-local process lock also prevents concurrent applies.
198
199
  ```
199
200
 
200
201
  - `dev-kit` installs guidance for operating the toolkit itself.
202
+ - `open-pull-request` provides a conventional, context-complete PR workflow
203
+ with terse English descriptions and verified proof of work.
201
204
  - `effect` expands to the package-guidance `effect-ts` bootstrap, the
202
- opinionated `effect-architecture-audit`, and `build-effect-apis` for shared
203
- HTTP contracts, Effect Atom clients, TanStack Start, and Cloudflare Workers.
205
+ opinionated `effect-architecture-audit`, `build-effect-apis` for shared HTTP
206
+ contracts and clients, and `build-effect-clis` for typed command-line
207
+ applications, one-off scripts, and CI/deploy/build automation. The focused
208
+ references cover Effect Atom, TanStack Start, Cloudflare Workers, child
209
+ processes, runtime entrypoints, and script/CLI testing.
204
210
  - Prefer individual external skills such as `workers-best-practices` and
205
211
  `wrangler`, selected after scanning the project for relevant technologies.
206
212
  - `serve-sim` selects the approved Evan Bacon simulator skill directly.
@@ -305,7 +311,9 @@ export default defineConfig({
305
311
 
306
312
  Spread the returned top-level config before local options. When overriding a
307
313
  `fmt`, `lint`, `run`, or `staged` block, spread that returned block as well so
308
- its defaults remain composed.
314
+ its defaults remain composed. Merge nested collections too; for example, a
315
+ local lint rule block starts with `...recommended.lint.rules` before adding
316
+ repository-specific rules.
309
317
 
310
318
  The factory configures `vp staged`, matching Oxlint/Oxfmt ignores for Dev Kit's
311
319
  tool-owned paths, and separate `vp run check` and pure `vp run typecheck` tasks.
@@ -313,6 +321,13 @@ Project and framework-generated paths belong in `ignorePatterns` as shown;
313
321
  custom harness target paths belong there too. Dev Kit does not grow a global
314
322
  framework ignore list.
315
323
 
324
+ Vite+ 0.2.6 forwards JavaScript-plugin declarations into its effective lint
325
+ config but its bundled native Oxlint path does not register or execute those
326
+ rules. Native Oxlint rules and Oxfmt settings remain active; run standalone
327
+ Oxlint when enforcement of Dev Kit's `effect/*` or
328
+ `stylistic/padding-line-between-statements` rules is required. This limitation
329
+ can be removed once a supported Vite+ release executes configured JS plugins.
330
+
316
331
  ```jsonc
317
332
  {
318
333
  "include": ["dev-kit", "effect"],
@@ -439,7 +454,30 @@ Pin the compatible packages in the consuming project:
439
454
  {
440
455
  "$schema": "./node_modules/@effect/tsgo/schema.json",
441
456
  "compilerOptions": {
442
- "plugins": [{ "name": "@effect/language-service" }],
457
+ "plugins": [
458
+ {
459
+ "name": "@effect/language-service",
460
+ "diagnosticSeverity": {
461
+ "anyUnknownInErrorContext": "warning",
462
+ "instanceOfSchema": "suggestion",
463
+ "nestedEffectGenYield": "suggestion",
464
+ "newSchemaClass": "suggestion",
465
+ "preferSchemaTypeProperty": "suggestion",
466
+ "unsafeEffectTypeAssertion": "warning",
467
+ },
468
+ "overrides": [
469
+ {
470
+ "include": ["src/**/*.ts"],
471
+ "options": {
472
+ "diagnosticSeverity": {
473
+ "nodeBuiltinImport": "warning",
474
+ "preferSchemaOverJson": "suggestion",
475
+ },
476
+ },
477
+ },
478
+ ],
479
+ },
480
+ ],
443
481
  },
444
482
  }
445
483
  ```
@@ -449,7 +487,12 @@ native TypeScript compiler. It does not download dependencies and skips an
449
487
  installation that is already patched. Use `dev-kit tsgo patch --dry-run` when
450
488
  troubleshooting the task directly.
451
489
 
452
- Dependency and `tsconfig.json` edits remain explicit.
490
+ The same typed object is exported as `recommendedEffectTsgoPlugin` for
491
+ programmatic configuration tooling. Dependency and `tsconfig.json` edits remain
492
+ explicit. In a monorepo, put the plugin in the shared root config and ensure
493
+ every workspace extends it without redeclaring `compilerOptions.plugins`:
494
+ TypeScript replaces that array in child configs rather than merging it. Adjust
495
+ the `src/**/*.ts` override to the source layout seen from each config file.
453
496
 
454
497
  ## Installed package skills
455
498
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danieljvdm/dev-kit",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "private": false,
5
5
  "description": "Declarative project development toolkit with portable agent skills.",
6
6
  "license": "MIT",
@@ -64,6 +64,28 @@ from another version.
64
64
  runs on Cloudflare Workers or uses `effect-cf`, bindings, Durable Objects,
65
65
  Queues, WebSockets, streaming, or raw byte routes.
66
66
 
67
+ ## Choose typed Schema codecs by default
68
+
69
+ Match the codec to the static type at the call site. When decoding a value that
70
+ is already typed as the schema's `Encoded` type, use `Schema.decodeEffect` or
71
+ the typed `Schema.decodeSync`, `Schema.decodeExit`, `Schema.decodeOption`,
72
+ `Schema.decodeResult`, or `Schema.decodePromise` variant. When encoding a value
73
+ that is already typed as the schema's `Type`, use `Schema.encodeEffect` or the
74
+ corresponding typed `Schema.encodeSync`, `Schema.encodeExit`,
75
+ `Schema.encodeOption`, `Schema.encodeResult`, or `Schema.encodePromise` variant.
76
+
77
+ Reserve `Schema.decodeUnknown*` and `Schema.encodeUnknown*` for genuinely
78
+ untyped boundaries: values from `JSON.parse`, `Response.json`, external
79
+ messages, or persistence APIs whose declared result is actually `unknown`.
80
+ Never choose an unknown codec to bypass a `Schema.Class` or other static type
81
+ mismatch. Map the source value or construct the correct schema `Type` first,
82
+ then use the typed encoder; similarly, establish the correct `Encoded` value
83
+ before using a typed decoder.
84
+
85
+ This rule is toolchain-neutral. A repository may reinforce it with a lint
86
+ warning and a documented local suppression for a justified untyped boundary,
87
+ but the boundary and type reasoning remain the source of truth.
88
+
67
89
  ## Boundary rules
68
90
 
69
91
  - Let schemas own wire validation, encoding, status metadata, and branded IDs.
@@ -11,6 +11,20 @@ test integration and command authority.
11
11
  - Assert each expected error carries the intended HTTP status and body encoding.
12
12
  - Compare or smoke-test generated OpenAPI when the public contract changes.
13
13
 
14
+ ## Unknown codec audit
15
+
16
+ Inventory every `Schema.decodeUnknown*` and `Schema.encodeUnknown*` call in the
17
+ changed scope. Record a concrete untyped-boundary justification for each one,
18
+ such as `JSON.parse`, `Response.json`, an external message, or a persistence API
19
+ whose declared result is actually `unknown`. Replace any call whose input is
20
+ already the schema's `Encoded` or `Type` with the corresponding typed `Effect`,
21
+ `Sync`, `Exit`, `Option`, `Result`, or `Promise` codec.
22
+
23
+ An unknown codec is not a valid workaround for a `Schema.Class` or other static
24
+ type mismatch: map or construct the correct typed value instead. If the
25
+ repository enforces this policy with a lint warning, keep any necessary local
26
+ suppression documented with the same concrete boundary justification.
27
+
14
28
  ## Server tests
15
29
 
16
30
  - Build every changed group and fail the test if an endpoint handler is missing.
@@ -47,4 +61,5 @@ Account for every changed endpoint across these columns:
47
61
  | Params, query, headers, payload, success, errors | Scope, provided services, errors | Identifier, invariants, workflow | Typed call shape, identity, cache/invalidation | Round-trip and boundary behavior |
48
62
 
49
63
  Completion means every changed endpoint has an entry in every applicable
50
- column and the repository's formatter, linter, typechecker, and tests pass.
64
+ column, every unknown codec call has a concrete boundary justification, and the
65
+ repository's formatter, linter, typechecker, and tests pass.
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: build-effect-clis
3
+ description: Write and maintain every executable script and command-line application in an Effect repository as an Effect program. Use when creating or changing any script, one-off automation, CI check, deploy/release/build glue, package-script entrypoint, CLI command, arguments, flags, prompts, child process, filesystem or environment workflow, Node/Bun entrypoint, or integration test—including extending an existing plain-TypeScript script.
4
+ ---
5
+
6
+ # Build Effect Scripts and CLIs
7
+
8
+ ## Effect-first scope
9
+
10
+ Every executable script and CLI in an Effect repository is an Effect program.
11
+ This includes one-off scripts, CI checks, deploy/release/build glue, files under
12
+ `scripts/`, package-script targets, migrations, and application entrypoints—not
13
+ only polished command-line tools.
14
+
15
+ Apply this rule when modifying code as well as when creating it. When a task
16
+ touches an existing plain-TypeScript script, convert the whole script to Effect
17
+ in the same change; matching the surrounding file's style or minimizing the
18
+ diff is not a valid exception. Prefer the repository's established Effect
19
+ patterns, including those in sibling scripts, over legacy patterns in the file
20
+ being converted.
21
+
22
+ Leave a script outside Effect only for a good, concrete technical or user
23
+ constraint that makes Effect unsuitable. Explicitly state that reason before
24
+ proceeding and in the final handoff, and keep the exception as narrow as
25
+ possible. Convenience, one-off status, and existing plain-TypeScript style are
26
+ not sufficient reasons.
27
+
28
+ Use Effect platform services for filesystem, path, environment, terminal, and
29
+ child-process work. Raw `node:*` or Bun runtime imports, `process.env`, `fs`,
30
+ `path`, `child_process`, and synchronous helpers such as `execFileSync` do not
31
+ belong in script workflows. If the installed Effect platform has no required
32
+ capability, isolate the runtime call in an explicit boundary adapter whose API
33
+ returns an Effect with typed errors, and document why that adapter is required.
34
+
35
+ Treat each executable as an Effect application. For a CLI, the `Command` tree
36
+ owns the user-facing contract, handlers adapt decoded input into application
37
+ workflows, services own capabilities, and the executable entrypoint supplies
38
+ platform Layers and runs the program. A fixed automation script with no public
39
+ arguments may export an Effect workflow directly instead of inventing a
40
+ `Command` tree. Do not introduce a separate CLI framework for command work.
41
+
42
+ Effect CLI and process APIs are version-sensitive. Read the target repository's
43
+ `node_modules/effect/AGENTS.md` completely, follow its CLI and child-process
44
+ references, and confirm exact signatures from the installed declarations before
45
+ editing.
46
+
47
+ ## Build the executable boundary
48
+
49
+ 1. Inventory the existing executable entrypoints, package scripts, command
50
+ tree, shared flags, prompts, application services, platform Layers, output
51
+ modes, and subprocess helpers. Finish when every way to invoke and test the
52
+ affected scripts or CLI is known, including sibling Effect scripts whose
53
+ patterns should replace legacy plain-TypeScript style.
54
+ 2. When the executable accepts public arguments or flags, read
55
+ [command-design.md](references/command-design.md). Define arguments and flags
56
+ with `Argument` and `Flag`, compose commands with `Command`, and give every
57
+ public input useful help. Use `Effect.fn` handlers and yield the root command
58
+ when a subcommand needs shared parent input.
59
+ 3. Keep handlers thin. Decode user and file input at the boundary, enforce
60
+ cross-input invariants, then call an application service. Keep persistence,
61
+ network calls, orchestration, retries, and transactions in services.
62
+ 4. Keep expected operational failures typed with `Schema.TaggedError`; map
63
+ platform failures into application-owned errors near the adapter that knows
64
+ what the operation means. Let defects remain defects.
65
+ 5. Read [entrypoints-and-testing.md](references/entrypoints-and-testing.md).
66
+ Export the command tree or fixed script workflow without running it, wire
67
+ one Node or Bun entrypoint, and verify the applicable success, expected
68
+ failure, help, parsing, JSON, dry-run, and confirmation paths.
69
+
70
+ ## Optional branches
71
+
72
+ - Read [processes-and-platform.md](references/processes-and-platform.md) when a
73
+ script or command reads files, inspects the environment, starts child
74
+ processes, streams their output, or differs between Node and Bun.
75
+ - Use `Prompt` only for an intentionally interactive path. Keep required inputs
76
+ expressible as arguments or flags so automation never depends on a terminal.
77
+ - Add `--dry-run` for commands that mutate important state and `--yes` for
78
+ explicitly authorized non-interactive confirmation. Never prompt in JSON or
79
+ CI-oriented modes.
80
+
81
+ ## CLI ownership rules
82
+
83
+ - Let `Command`, `Argument`, and `Flag` own syntax, defaults, aliases, examples,
84
+ and help text.
85
+ - Let schemas own untrusted structured input and machine-readable output.
86
+ - Let handlers own CLI-to-application mapping and presentation selection.
87
+ - Let services own reusable behavior and external capabilities.
88
+ - Let Layers own implementations and runtime dependencies.
89
+ - Let the executable entrypoint own `Command.run`, platform provisioning,
90
+ scopes, signal handling, and `NodeRuntime.runMain` or `BunRuntime.runMain`.
91
+ - Keep stdout stable for primary or machine-readable output. Send diagnostics
92
+ and progress elsewhere; never mix prose into JSON output.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Build Effect Scripts and CLIs"
3
+ short_description: "Write every script and CLI with Effect"
4
+ default_prompt: "Use $build-effect-clis to write or modify this script or CLI as an Effect program with explicit platform boundaries and tests."
@@ -0,0 +1,93 @@
1
+ # Command design
2
+
3
+ Model the CLI's public surface as a typed contract. Prefer a small root command,
4
+ shared parent flags, focused subcommands, and Effect handlers.
5
+
6
+ ```ts
7
+ import { Effect } from "effect";
8
+ import { Argument, Command, Flag } from "effect/unstable/cli";
9
+
10
+ const workspace = Flag.string("workspace").pipe(
11
+ Flag.withAlias("w"),
12
+ Flag.withDescription("Workspace to operate on"),
13
+ Flag.withDefault("personal"),
14
+ );
15
+
16
+ const root = Command.make("acme").pipe(
17
+ Command.withSharedFlags({
18
+ workspace,
19
+ verbose: Flag.boolean("verbose").pipe(
20
+ Flag.withAlias("v"),
21
+ Flag.withDescription("Print diagnostic output"),
22
+ ),
23
+ }),
24
+ Command.withDescription("Operate Acme projects"),
25
+ );
26
+
27
+ const deploy = Command.make(
28
+ "deploy",
29
+ {
30
+ environment: Argument.string("environment").pipe(
31
+ Argument.withDescription("Target environment"),
32
+ ),
33
+ dryRun: Flag.boolean("dry-run").pipe(
34
+ Flag.withDescription("Show the deployment plan without applying it"),
35
+ ),
36
+ json: Flag.boolean("json").pipe(Flag.withDescription("Print machine-readable JSON")),
37
+ },
38
+ Effect.fn("deployCommand")(function* ({ dryRun, environment, json }) {
39
+ const shared = yield* root;
40
+ const deployments = yield* Deployments;
41
+ const plan = yield* deployments.plan({ environment, workspace: shared.workspace });
42
+
43
+ if (dryRun) return yield* renderPlan(plan, { json });
44
+
45
+ const result = yield* deployments.apply(plan);
46
+ return yield* renderDeployment(result, { json });
47
+ }),
48
+ ).pipe(
49
+ Command.withDescription("Deploy a workspace"),
50
+ Command.withExamples([
51
+ {
52
+ command: "acme --workspace team deploy production --dry-run",
53
+ description: "Preview a production deployment",
54
+ },
55
+ ]),
56
+ );
57
+
58
+ export const command = root.pipe(Command.withSubcommands([deploy]));
59
+ ```
60
+
61
+ ## Input and help
62
+
63
+ - Use arguments for essential positional identity and flags for optional
64
+ behavior. Prefer named flags when position would be ambiguous.
65
+ - Give public commands, arguments, and non-obvious flags descriptions. Add
66
+ examples for quoting, shared flags, or surprising combinations.
67
+ - Put cross-command inputs in `Command.withSharedFlags`; read them by yielding
68
+ the parent command instead of duplicating parsing or reading globals.
69
+ - Use `Flag.choice` for closed vocabularies and schemas for structured values
70
+ loaded from JSON, files, or environment boundaries.
71
+ - Keep aliases additive and unsurprising. Never give two concepts the same
72
+ short flag in one command path.
73
+
74
+ ## Interactive and automated use
75
+
76
+ - Make every required value available non-interactively. A fallback `Prompt`
77
+ may improve terminal use but must not be the only way to supply input.
78
+ - Prompt only after deterministic discovery cannot choose safely. Summarize the
79
+ detected state and the exact mutation before requesting confirmation.
80
+ - Pair destructive execution with a genuine plan/apply split. `--dry-run` must
81
+ run the real discovery and planning logic while skipping writes.
82
+ - Require explicit `--yes` or an equivalent authorization flag when a
83
+ non-interactive destructive path cannot prompt.
84
+
85
+ ## Output contract
86
+
87
+ - Treat human and machine output as separate renderers over the same result.
88
+ - In `--json` mode, emit one documented schema-encoded value to stdout. Send no
89
+ headings, spinners, progress, or warnings to stdout.
90
+ - Use stderr for diagnostics and failures. Keep normal human output concise and
91
+ stable enough for users to understand without reading source.
92
+ - Do not expose stack traces for expected failures. Preserve causes for logs and
93
+ tests, then render an actionable message at the executable boundary.
@@ -0,0 +1,64 @@
1
+ # Entrypoints and testing
2
+
3
+ Separate command definition from execution. Importing a command module in a test
4
+ or another program must not parse `process.argv`, start fibers, or terminate the
5
+ process.
6
+
7
+ For a fixed CI or automation script with no public command syntax, export its
8
+ Effect workflow and provide platform Layers only in a thin executable module;
9
+ it does not need an artificial `Command` tree. Use the same `runMain` boundary,
10
+ typed failures, platform services, and import-safety rules shown below.
11
+
12
+ ```ts
13
+ // src/cli/command.ts
14
+ export const command = root.pipe(Command.withSubcommands([deploy, status]));
15
+
16
+ // src/bin/acme.ts
17
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
18
+ import { Effect } from "effect";
19
+ import { Command } from "effect/unstable/cli";
20
+ import { command } from "../cli/command";
21
+
22
+ const program = Command.run(command, { version: VERSION }).pipe(
23
+ Effect.scoped,
24
+ Effect.provide(ApplicationLive),
25
+ Effect.provide(NodeServices.layer),
26
+ );
27
+
28
+ NodeRuntime.runMain(program, { disableErrorReporting: true });
29
+ ```
30
+
31
+ Use `BunRuntime` and `BunServices` together when Bun owns the executable. Let
32
+ `runMain` own signals, interruption, and process completion. Do not call
33
+ `Effect.runPromise`, `process.exit`, or runtime globals inside command handlers.
34
+ If expected errors need custom presentation, catch and render them immediately
35
+ before `runMain` while preserving CLI control-flow errors such as help output.
36
+
37
+ ## Test at three seams
38
+
39
+ 1. Test application services directly with deterministic Layers. Cover domain
40
+ success, expected failure, interruption, and plan/apply separation without
41
+ involving argument parsing.
42
+ 2. Test thin command handlers through their services when CLI input mapping or
43
+ output-mode selection contains meaningful logic.
44
+ 3. Spawn the real executable for boundary behavior. At minimum cover:
45
+ - root and changed-command `--help`;
46
+ - representative valid arguments and flags;
47
+ - missing or invalid input and a non-zero exit;
48
+ - one expected operational failure with an actionable message;
49
+ - exact JSON output with no prose contamination;
50
+ - dry-run proving writes did not occur;
51
+ - confirmation behavior in both interactive and non-interactive modes.
52
+
53
+ Run executable tests through Effect's child-process APIs with an explicit `cwd`,
54
+ captured stdout/stderr, and controlled environment. Use the repository's normal
55
+ runtime launcher and command authority; a test that bypasses the packaged or
56
+ declared entrypoint does not prove the CLI works for users.
57
+
58
+ ## Completion checks
59
+
60
+ - Ensure every executable TypeScript entrypoint belongs to a checked project.
61
+ - Run the repository's formatter, linter, typechecker, and tests.
62
+ - Execute `--help` through the real entrypoint.
63
+ - Exercise a harmless dry-run or read-only command outside the source module.
64
+ - Verify non-zero exits for parsing and expected operational failures.
@@ -0,0 +1,74 @@
1
+ # Processes and platform services
2
+
3
+ Use Effect platform services inside CLI workflows. Keep direct `node:*`, Bun
4
+ globals, `process`, filesystem calls, and shell execution inside explicit
5
+ boundary adapters. The executable boundary itself should use Effect platform
6
+ runtime and service APIs.
7
+
8
+ ## Own subprocess behavior in a service
9
+
10
+ ```ts
11
+ import { Context, Effect, Layer, Schema, String } from "effect";
12
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
13
+
14
+ export class ToolError extends Schema.TaggedError<ToolError>()("ToolError", {
15
+ command: Schema.String,
16
+ cause: Schema.Defect(),
17
+ }) {}
18
+
19
+ export class Tools extends Context.Service<
20
+ Tools,
21
+ {
22
+ readonly gitVersion: Effect.Effect<string, ToolError>;
23
+ changedFiles(baseRef: string): Effect.Effect<ReadonlyArray<string>, ToolError>;
24
+ }
25
+ >()("app/Tools") {
26
+ static readonly layer = Layer.effect(
27
+ Tools,
28
+ Effect.gen(function* () {
29
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
30
+ const gitVersion = spawner.string(ChildProcess.make("git", ["--version"])).pipe(
31
+ Effect.map(String.trim),
32
+ Effect.mapError((cause) => new ToolError({ command: "git --version", cause })),
33
+ );
34
+ const changedFiles = Effect.fn("Tools.changedFiles")(function* (baseRef: string) {
35
+ return yield* spawner
36
+ .lines(ChildProcess.make("git", ["diff", "--name-only", `${baseRef}...HEAD`]))
37
+ .pipe(
38
+ Effect.mapError(
39
+ (cause) =>
40
+ new ToolError({ command: `git diff --name-only ${baseRef}...HEAD`, cause }),
41
+ ),
42
+ );
43
+ });
44
+
45
+ return Tools.of({ changedFiles, gitVersion });
46
+ }),
47
+ );
48
+ }
49
+ ```
50
+
51
+ Pass the executable and arguments separately to `ChildProcess.make`; never
52
+ construct a shell command from user input. Set `cwd` explicitly when repository
53
+ identity matters. Provide `env` narrowly and choose `extendEnv` deliberately so
54
+ tests and CI do not inherit accidental machine state.
55
+
56
+ Use the smallest spawner operation that matches the contract:
57
+
58
+ - `string` for bounded complete output;
59
+ - `lines` for bounded line-oriented output;
60
+ - `spawn` plus a scoped process handle for streaming, interactive, or
61
+ exit-code-sensitive work;
62
+ - `ChildProcess.pipeTo` for a real pipeline without invoking a shell.
63
+
64
+ When using `spawn`, consume stdout/stderr without deadlocking, inspect
65
+ `handle.exitCode`, and wrap the whole process lifetime in `Effect.scoped`.
66
+ Map platform failures once into an operation-specific error; do not expose a
67
+ generic subprocess error throughout the application.
68
+
69
+ ## Keep the runtime choice at the edge
70
+
71
+ Application services may require `FileSystem`, `Path`, `Terminal`, or
72
+ `ChildProcessSpawner`, but they should not import `NodeServices` or `BunServices`.
73
+ Provide the matching platform Layer only in the executable entrypoint. This
74
+ keeps workflows reusable and lets tests provide deterministic substitutes.
@@ -155,7 +155,8 @@ matching Oxlint/Oxfmt ignores, staged checks, and separate `check` and pure
155
155
  manifest defaults. Workspace mode accepts explicit package directories with
156
156
  pure `typecheck` scripts and generates cached, dependency-ordered,
157
157
  bounded-concurrency filters. Spread the returned top-level config before local
158
- options; spread a returned nested block before overriding that block.
158
+ options; spread a returned nested block before overriding that block, and merge
159
+ nested collections such as `lint.rules` so the recommended rules remain active.
159
160
 
160
161
  Enable `setup.vitePlus.quality.workflow` to own only
161
162
  `.github/workflows/check.yml`. It requires direct Dev Kit, compatible Vite+,
@@ -254,8 +255,18 @@ Enable the setup task in the same manifest:
254
255
  Install the exact `@effect/tsgo` and native `typescript` versions required by
255
256
  the installed dev-kit. Point `tsconfig.json` at
256
257
  `./node_modules/@effect/tsgo/schema.json` and configure the
257
- `@effect/language-service` compiler plugin. `dev-kit plan` validates these local
258
- dependencies; `dev-kit apply` patches once and then converges.
258
+ `@effect/language-service` compiler plugin with Dev Kit's exported
259
+ `recommendedEffectTsgoPlugin` profile: warnings for
260
+ `anyUnknownInErrorContext` and `unsafeEffectTypeAssertion`; suggestions for
261
+ `instanceOfSchema`, `nestedEffectGenYield`, `newSchemaClass`, and
262
+ `preferSchemaTypeProperty`; plus a `src/**/*.ts` override that warns on
263
+ `nodeBuiltinImport` and suggests `preferSchemaOverJson`. Copy the exact JSON
264
+ profile from the README into JSON tsconfigs. In monorepos, child
265
+ `compilerOptions.plugins` arrays replace rather than merge the root array, so
266
+ workspace configs must inherit the root plugin without redeclaring it and the
267
+ source override must be relative to the config that contains it. `dev-kit plan`
268
+ validates the local dependencies; `dev-kit apply` patches once and then
269
+ converges.
259
270
 
260
271
  Use `dev-kit tsgo patch --dry-run` for focused diagnosis. Use `--force` only
261
272
  after the user accepts a potentially commit-incompatible TypeScript binary.
@@ -290,6 +301,12 @@ The Oxlint preset enables the fixable
290
301
  declarations grouped, requires a blank line before the next logical statement,
291
302
  and separates every `return` statement from the preceding statement.
292
303
 
304
+ Vite+ 0.2.6 forwards the preset's JavaScript-plugin declarations but its native
305
+ Oxlint path does not register or execute their rules. Treat native rules and
306
+ Oxfmt as active through `vp`, and use standalone Oxlint when the `effect/*` or
307
+ `stylistic/padding-line-between-statements` rules must be enforced. Re-enable a
308
+ Vite+ execution assertion when a supported release adds JS-plugin execution.
309
+
293
310
  The Oxlint preset registers Dev Kit's shared Effect plugin as `effect`, but
294
311
  does not enable its scope-sensitive rules globally. Effect projects should
295
312
  enable rules such as `effect/no-effect-run`, `effect/no-unsafe-promise`, and
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: open-pull-request
3
+ description: Open pull requests with conventional commits, terse context-complete English descriptions, and verified proof of work. Use whenever preparing or opening a pull request, including checking commit history, drafting the title or body, and attaching screenshots or other evidence.
4
+ ---
5
+
6
+ # Open a Pull Request
7
+
8
+ Produce a PR that a reviewer can understand and trust without access to the
9
+ task conversation.
10
+
11
+ ## Prepare the branch
12
+
13
+ 1. Read the repository's contribution instructions and PR template. Identify
14
+ the intended base branch, then inspect the full commit range, diff, and
15
+ working tree. Finish when the PR scope contains no accidental changes and
16
+ the description will cover the branch as it exists, not merely the latest
17
+ task.
18
+ 2. Use Conventional Commits for every commit you create and for the PR title:
19
+ `type(scope): imperative summary`. Follow repository-specific types and
20
+ scopes, and omit the scope when it adds no useful context. Otherwise use a
21
+ precise standard type such as `feat`, `fix`, `refactor`, `docs`, `test`,
22
+ `build`, `ci`, or `chore`. Keep each commit to one logical concern. Rewrite
23
+ only commits you created and know are unshared; get approval before
24
+ rewriting user-authored or published history.
25
+ 3. Run the repository's required validation on the final branch state. Record
26
+ the exact commands and results, then collect the strongest available proof
27
+ of the changed behavior. Finish when every claim in the PR can be traced to
28
+ the diff, a check result, or an artifact.
29
+
30
+ ## Write for the reviewer
31
+
32
+ Write terse, plain English for someone with little context. Lead with the
33
+ observable outcome and add only the minimum reason needed to understand it.
34
+ Prefer short bullets and concrete nouns. Expand uncommon acronyms. Describe
35
+ behavior and impact rather than narrating files, implementation steps, or the
36
+ task conversation.
37
+
38
+ Use the repository's required template when present. Otherwise use this small
39
+ shape and omit empty sections:
40
+
41
+ ```md
42
+ ## Summary
43
+
44
+ - <What changes for a user, operator, or developer>
45
+ - <Why it matters, only when the first bullet does not make that clear>
46
+
47
+ ## Proof
48
+
49
+ - `<validation command>` — passed
50
+ - <Screenshot, sample output, or other verified artifact>
51
+ ```
52
+
53
+ Keep the summary to one to three bullets. Make the title specific enough to
54
+ stand alone in release notes and conventional enough to become the squash
55
+ commit without editing.
56
+
57
+ ## Show proof of work
58
+
59
+ Proof is something the reviewer can inspect, not an assertion that the change
60
+ works.
61
+
62
+ - For a runnable UI or visual feature, capture and attach a screenshot or short
63
+ recording of the actual final state. Use a representative viewport, add a
64
+ short caption, and check the artifact for secrets or personal data.
65
+ - For CLI, API, or automation behavior, include concise terminal output, a
66
+ request/response example, generated artifact, or execution log when it proves
67
+ more than the validation command alone.
68
+ - For a bug fix or behavior change, prefer before/after evidence when it is
69
+ practical and materially clarifies the result.
70
+ - For internal-only changes, exact passing validation commands may be the most
71
+ useful proof.
72
+
73
+ Include only evidence that was actually produced and verified. When expected
74
+ visual proof cannot be produced, state the concrete reason briefly instead of
75
+ silently substituting a claim. Preserve terse descriptions by choosing the
76
+ smallest set of evidence that proves the outcome.
77
+
78
+ ## Open and verify
79
+
80
+ Open the PR against the intended base with the conventional title and prepared
81
+ body. Then read back the rendered PR and verify the base/head branches, title,
82
+ description, links, screenshots, and check results. Finish only when the PR is
83
+ reviewable as rendered and return its URL.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Open Pull Request"
3
+ short_description: "Open clear, evidence-backed pull requests"
4
+ default_prompt: "Use $open-pull-request to prepare and open a clear pull request with conventional commits and proof of work."
package/src/catalog.ts CHANGED
@@ -181,7 +181,7 @@ export const loadSkillCatalog = Effect.fn("loadSkillCatalog")(function* (
181
181
  });
182
182
  }
183
183
  const families: Readonly<Record<string, ReadonlyArray<string>>> = {
184
- effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis"],
184
+ effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis", "build-effect-clis"],
185
185
  ...Object.fromEntries(externalFamilies),
186
186
  };
187
187
 
@@ -9,6 +9,43 @@ export const EFFECT_TSGO_VERSION = "0.33.0";
9
9
  export const EFFECT_TSGO_TYPESCRIPT_VERSION = "7.0.2";
10
10
  export const EFFECT_TSGO_PLUGIN_NAME = "@effect/language-service";
11
11
 
12
+ export type EffectTsgoDiagnosticSeverity = "off" | "error" | "warning" | "message" | "suggestion";
13
+
14
+ export type EffectTsgoPluginConfig = {
15
+ readonly name: typeof EFFECT_TSGO_PLUGIN_NAME;
16
+ readonly diagnosticSeverity: Readonly<Record<string, EffectTsgoDiagnosticSeverity>>;
17
+ readonly overrides: ReadonlyArray<{
18
+ readonly include: ReadonlyArray<string>;
19
+ readonly options: {
20
+ readonly diagnosticSeverity: Readonly<Record<string, EffectTsgoDiagnosticSeverity>>;
21
+ };
22
+ }>;
23
+ };
24
+
25
+ /** Recommended diagnostics for projects using the Effect TypeScript-Go plugin. */
26
+ export const recommendedEffectTsgoPlugin = {
27
+ name: EFFECT_TSGO_PLUGIN_NAME,
28
+ diagnosticSeverity: {
29
+ anyUnknownInErrorContext: "warning",
30
+ instanceOfSchema: "suggestion",
31
+ nestedEffectGenYield: "suggestion",
32
+ newSchemaClass: "suggestion",
33
+ preferSchemaTypeProperty: "suggestion",
34
+ unsafeEffectTypeAssertion: "warning",
35
+ },
36
+ overrides: [
37
+ {
38
+ include: ["src/**/*.ts"],
39
+ options: {
40
+ diagnosticSeverity: {
41
+ nodeBuiltinImport: "warning",
42
+ preferSchemaOverJson: "suggestion",
43
+ },
44
+ },
45
+ },
46
+ ],
47
+ } as const satisfies EffectTsgoPluginConfig;
48
+
12
49
  export type EffectTsgoPatchOptions = {
13
50
  readonly dryRun?: boolean;
14
51
  readonly force?: boolean;
package/src/index.ts CHANGED
@@ -43,13 +43,16 @@ export {
43
43
  EFFECT_TSGO_PLUGIN_NAME,
44
44
  EFFECT_TSGO_TYPESCRIPT_VERSION,
45
45
  EFFECT_TSGO_VERSION,
46
+ type EffectTsgoDiagnosticSeverity,
46
47
  EffectTsgoDependencyError,
47
48
  InvalidEffectTsgoPackageNameError,
48
49
  type EffectTsgoPatchOptions,
49
50
  type EffectTsgoPatchPlan,
51
+ type EffectTsgoPluginConfig,
50
52
  EffectTsgoPatchCommandError,
51
53
  patchEffectTsgo,
52
54
  planEffectTsgoPatch,
55
+ recommendedEffectTsgoPlugin,
53
56
  } from "./effect-tsgo.ts";
54
57
  export {
55
58
  ExternalSkillSourceSchema,
@@ -143,9 +143,9 @@ const readManifest = Effect.fn("readManagedSkillManifest")(function* (
143
143
  if (errors.length > 0) {
144
144
  return yield* SkillManagerError.make({ message: `could not parse ${paths.manifestPath}` });
145
145
  }
146
- const manifest = yield* Schema.decodeUnknownEffect(DevKitManifestSchema)(parsed).pipe(
147
- Effect.mapError((error) => SkillManagerError.make({ message: error.message })),
148
- );
146
+ const manifest = yield* Schema.decodeUnknownEffect(DevKitManifestSchema, {
147
+ onExcessProperty: "error",
148
+ })(parsed).pipe(Effect.mapError((error) => SkillManagerError.make({ message: error.message })));
149
149
 
150
150
  return { ...paths, manifest, raw };
151
151
  });
package/src/sync.ts CHANGED
@@ -270,7 +270,7 @@ const encodePlanSnapshotJson = Schema.encodeSync(Schema.fromJsonString(Schema.Un
270
270
  const encodeAppliedStatePrettyJson = Schema.encodeSync(fromJsonString(AppliedStateSchema, 2));
271
271
 
272
272
  const SKILL_FAMILIES: SkillCatalog = {
273
- effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis"],
273
+ effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis", "build-effect-clis"],
274
274
  };
275
275
 
276
276
  export const DEFAULT_MANIFEST = "dev-kit.jsonc";
@@ -564,6 +564,7 @@ const parseStructuredFile = Effect.fn("parseStructuredFile")(function* <A>(
564
564
  filePath: string,
565
565
  raw: string,
566
566
  schema: Schema.ConstraintDecoder<A>,
567
+ options: { readonly rejectExcessProperties?: boolean } = {},
567
568
  ) {
568
569
  const errors: Array<ParseError> = [];
569
570
  const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
@@ -576,7 +577,10 @@ const parseStructuredFile = Effect.fn("parseStructuredFile")(function* <A>(
576
577
  });
577
578
  }
578
579
 
579
- return yield* Schema.decodeUnknownEffect(schema)(parsed).pipe(
580
+ return yield* Schema.decodeUnknownEffect(
581
+ schema,
582
+ options.rejectExcessProperties ? { onExcessProperty: "error" } : undefined,
583
+ )(parsed).pipe(
580
584
  Effect.mapError((cause) =>
581
585
  StructuredFileError.make({ path: filePath, message: cause.message }),
582
586
  ),
@@ -591,7 +595,9 @@ const readManifest = Effect.fn("readManifest")(function* (manifestPath: string)
591
595
  }
592
596
  const raw = yield* fs.readFileString(manifestPath);
593
597
 
594
- return yield* parseStructuredFile(manifestPath, raw, DevKitManifestSchema);
598
+ return yield* parseStructuredFile(manifestPath, raw, DevKitManifestSchema, {
599
+ rejectExcessProperties: true,
600
+ });
595
601
  });
596
602
 
597
603
  const readOptionalStructuredFile = Effect.fn("readOptionalStructuredFile")(function* <A>(
@@ -1752,7 +1758,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1752
1758
  });
1753
1759
  const stageDir = path.join(tempDir, "stage");
1754
1760
  const backupDir = path.join(tempDir, "backup");
1755
- const stagedByResource = new Map<string, string>();
1761
+ const stagedByAction = new Map<(typeof mutating)[number], string>();
1756
1762
  let stageIndex = 0;
1757
1763
 
1758
1764
  for (const action of mutating) {
@@ -1816,10 +1822,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1816
1822
  });
1817
1823
  }
1818
1824
  }
1819
- stagedByResource.set(
1820
- action.action === "remove" ? action.previous.resourceId : action.desired.resourceId,
1821
- staged,
1822
- );
1825
+ stagedByAction.set(action, staged);
1823
1826
  }
1824
1827
 
1825
1828
  yield* verifyPackageSkillSources(plan);
@@ -1841,10 +1844,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1841
1844
  let replacementIndex = 0;
1842
1845
 
1843
1846
  for (const action of mutating) {
1844
- const staged =
1845
- action.action === "remove"
1846
- ? stagedByResource.get(action.previous.resourceId)
1847
- : stagedByResource.get(action.desired.resourceId);
1847
+ const staged = stagedByAction.get(action);
1848
1848
 
1849
1849
  if (
1850
1850
  (action.action !== "remove" || action.stagedContent !== undefined) &&
package/src/vite-plus.js CHANGED
@@ -68,7 +68,7 @@ export const createRecommendedVitePlusConfig = (options = {}) => {
68
68
  ignorePatterns,
69
69
  },
70
70
  lint: {
71
- extends: [recommendedOxlintConfig],
71
+ ...recommendedOxlintConfig,
72
72
  ignorePatterns,
73
73
  },
74
74
  run: {
package/src/vite-plus.ts CHANGED
@@ -89,7 +89,7 @@ export const createRecommendedVitePlusConfig = (options: RecommendedVitePlusConf
89
89
  ignorePatterns,
90
90
  },
91
91
  lint: {
92
- extends: [recommendedOxlintConfig],
92
+ ...recommendedOxlintConfig,
93
93
  ignorePatterns,
94
94
  },
95
95
  run: {