@danieljvdm/dev-kit 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -15
- package/bin/dev-kit.mjs +16 -2
- package/package.json +5 -5
- package/schema/dev-kit.schema.json +1 -1
- package/skills/dev-kit/SKILL.md +36 -11
- package/src/bin/dev-kit.ts +3 -3
- package/src/project-package.ts +1 -0
- package/src/sync.ts +487 -48
- package/src/vite-plus-quality.ts +2 -1
- package/templates/AGENTS.md +2 -0
- package/templates/vite-plus/github-actions-check.yml +5 -1
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@ Install the published Dev Kit package:
|
|
|
20
20
|
bun add -d @danieljvdm/dev-kit
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
Dev Kit requires Bun 1.3 or newer. Its published executable runs TypeScript
|
|
24
|
+
natively with Bun and rejects direct Node.js execution.
|
|
25
|
+
|
|
23
26
|
Initialize the project, browse available built-in, approved Git, and installed
|
|
24
27
|
package skills, then add the ones you want:
|
|
25
28
|
|
|
@@ -94,12 +97,14 @@ before running locked mode:
|
|
|
94
97
|
|
|
95
98
|
```bash
|
|
96
99
|
bun install --ignore-scripts
|
|
97
|
-
bun
|
|
100
|
+
bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked
|
|
98
101
|
```
|
|
99
102
|
|
|
100
103
|
Or allow the normal postinstall and fail CI when it leaves tracked changes.
|
|
101
104
|
Do not run an unlocked apply before a locked verification because that would
|
|
102
105
|
regenerate the drift being checked.
|
|
106
|
+
The package-qualified path cannot be shadowed by a consumer script named
|
|
107
|
+
`dev-kit`.
|
|
103
108
|
|
|
104
109
|
This repository dogfoods the same flow with its committed `dev-kit.jsonc` and
|
|
105
110
|
`dev-kit.lock.json`. From this source checkout, invoke the local CLI with:
|
|
@@ -211,7 +216,7 @@ The patch is idempotent, preserves existing lines, and refuses symlinked
|
|
|
211
216
|
|
|
212
217
|
## Agent instructions
|
|
213
218
|
|
|
214
|
-
Enable
|
|
219
|
+
Enable managed project-root instruction sections and a portable Claude Code
|
|
215
220
|
bridge in the manifest:
|
|
216
221
|
|
|
217
222
|
```jsonc
|
|
@@ -224,18 +229,37 @@ bridge in the manifest:
|
|
|
224
229
|
}
|
|
225
230
|
```
|
|
226
231
|
|
|
227
|
-
`setup.agentInstructions` manages
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
`setup.agentInstructions` manages marked sections in the project-root
|
|
233
|
+
`AGENTS.md`, preserving handwritten project guidance around them. The Dev Kit
|
|
234
|
+
section contains a short description and a pointer to the installed `dev-kit`
|
|
235
|
+
skill. When the root `package.json` declares `vite-plus` directly, Dev Kit
|
|
236
|
+
renders its own repository-specific Vite+ guidance, including the unified
|
|
237
|
+
toolchain overview, help and documentation entry points, and `vp env doctor`
|
|
238
|
+
troubleshooting. It does not import Vite+'s generic `AGENTS.md`, which can
|
|
239
|
+
conflict with the repository's exact commands; transitive installations do not
|
|
240
|
+
opt a project in. Previously managed Vite+ sections are removed during a safe
|
|
241
|
+
owned update. Ambiguous or malformed managed markers fail closed.
|
|
242
|
+
|
|
243
|
+
The Dev Kit section also renders an opinionated project command policy. A
|
|
244
|
+
direct Vite+ dependency makes `vp` the only supported front door: built-in
|
|
245
|
+
format, lint, and test commands use `vp`, while repository tasks and package
|
|
246
|
+
scripts use `vp run`. When Dev Kit manages the quality config, the canonical
|
|
247
|
+
full validation and typecheck commands are `vp run check` and
|
|
248
|
+
`vp run typecheck`; `vp check` alone is only the Vite+ static-check command.
|
|
249
|
+
Without Vite+, Bun is the required package-script runner and Dev Kit lists only
|
|
250
|
+
quality scripts the root package actually declares. The package manager named
|
|
251
|
+
by `package.json#packageManager`, or inferred from a single recognized root
|
|
252
|
+
lockfile, is used only for dependency-install guidance. The policy forbids
|
|
253
|
+
switching script runners or bypassing project entry points with raw `tsc`,
|
|
254
|
+
test-runner, linter, or formatter commands.
|
|
232
255
|
|
|
233
256
|
`setup.claudeInstructions` manages `CLAUDE.md` as the relative symlink
|
|
234
|
-
`CLAUDE.md → AGENTS.md`. It can link to the
|
|
257
|
+
`CLAUDE.md → AGENTS.md`. It can link to the section-managed file in the same apply,
|
|
235
258
|
or retain the older behavior of linking to an existing regular `AGENTS.md` when
|
|
236
|
-
the
|
|
237
|
-
lockfile and local ownership state.
|
|
238
|
-
and
|
|
259
|
+
the section task is disabled. Both outputs are recorded independently in the
|
|
260
|
+
lockfile and local ownership state. Disabling agent instructions removes only
|
|
261
|
+
unchanged managed sections and deletes `AGENTS.md` only when no handwritten
|
|
262
|
+
content remains.
|
|
239
263
|
|
|
240
264
|
## Vite+ Git hooks
|
|
241
265
|
|
|
@@ -351,10 +375,11 @@ scripts, or TypeScript topology. It performs one frozen, script-suppressed
|
|
|
351
375
|
install, runs `dev-kit apply --locked`, and only then runs custom preparation,
|
|
352
376
|
formatting, linting, tests, and typechecking. Its default typecheck command is
|
|
353
377
|
`vp run typecheck`; `workflow.typecheck` replaces it. Vite+ maps install flags
|
|
354
|
-
to the detected package manager. The template
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
378
|
+
to the detected package manager. The template installs the consumer's declared
|
|
379
|
+
Bun version and pins exact `setup-bun` and `setup-vp` releases; the latter's
|
|
380
|
+
`v1` tag is frozen. Keep both current with Renovate or Dependabot. Existing
|
|
381
|
+
workflows remain user-owned until their rendered content matches exactly—Dev
|
|
382
|
+
Kit never merges YAML. See the primary
|
|
358
383
|
[`setup-vp` versioning guidance](https://github.com/voidzero-dev/setup-vp#versioning),
|
|
359
384
|
[Vite+ install guide](https://viteplus.dev/guide/install), and
|
|
360
385
|
[Vite Task run guide](https://viteplus.dev/guide/run) when maintaining the
|
package/bin/dev-kit.mjs
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import "
|
|
3
|
+
import semver from "semver";
|
|
4
|
+
|
|
5
|
+
const MINIMUM_BUN_VERSION = "1.3.0";
|
|
6
|
+
const bunVersion = globalThis.Bun?.version;
|
|
7
|
+
|
|
8
|
+
if (bunVersion === undefined) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`Dev Kit requires the Bun runtime, version ${MINIMUM_BUN_VERSION} or newer. Install Bun and run \`dev-kit\` again.`,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
if (!semver.satisfies(bunVersion, `>=${MINIMUM_BUN_VERSION}`)) {
|
|
14
|
+
throw new Error(`Dev Kit requires Bun ${MINIMUM_BUN_VERSION} or newer; found ${bunVersion}.`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await import("../src/bin/dev-kit.ts");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danieljvdm/dev-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Declarative project development toolkit with portable agent skills.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,15 +65,15 @@
|
|
|
65
65
|
"catalog:check": "./bin/dev-kit.mjs catalog verify"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@effect/platform-
|
|
68
|
+
"@effect/platform-bun": "4.0.0-beta.102",
|
|
69
69
|
"@stylistic/eslint-plugin": "5.10.0",
|
|
70
70
|
"effect": "4.0.0-beta.102",
|
|
71
71
|
"jsonc-parser": "3.3.1",
|
|
72
|
-
"semver": "7.8.5"
|
|
73
|
-
"tsx": "4.22.4"
|
|
72
|
+
"semver": "7.8.5"
|
|
74
73
|
},
|
|
75
74
|
"devDependencies": {
|
|
76
75
|
"@changesets/cli": "3.0.0-next.10",
|
|
76
|
+
"@effect/platform-node": "4.0.0-beta.102",
|
|
77
77
|
"@effect/tsgo": "0.24.3",
|
|
78
78
|
"@effect/vitest": "4.0.0-beta.102",
|
|
79
79
|
"@types/node": "25.9.1",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
103
|
"engines": {
|
|
104
|
-
"
|
|
104
|
+
"bun": ">=1.3.0"
|
|
105
105
|
},
|
|
106
106
|
"packageManager": "bun@1.3.14"
|
|
107
107
|
}
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"required": ["include"],
|
|
69
69
|
"$defs": {
|
|
70
70
|
"agentInstructionsSetup": {
|
|
71
|
-
"description": "Manage
|
|
71
|
+
"description": "Manage marked sections in project-root AGENTS.md with dev-kit guidance and conditional tool instructions while preserving handwritten content.",
|
|
72
72
|
"type": "object",
|
|
73
73
|
"additionalProperties": false,
|
|
74
74
|
"properties": {
|
package/skills/dev-kit/SKILL.md
CHANGED
|
@@ -95,14 +95,30 @@ use symlinks for additional harness discovery paths. Keep every target path
|
|
|
95
95
|
project-relative and separate from the manifest, lock, state, and process-lock
|
|
96
96
|
paths.
|
|
97
97
|
|
|
98
|
-
Enable `setup.agentInstructions` to manage a project-root
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
Enable `setup.agentInstructions` to manage marked sections in a project-root
|
|
99
|
+
`AGENTS.md` while preserving handwritten project guidance. The Dev Kit section
|
|
100
|
+
points agents back to this skill. When `vite-plus` is a declared direct
|
|
101
|
+
dependency, dev-kit synthesizes repository-specific Vite+ guidance inside its
|
|
102
|
+
own section instead of importing Vite+'s generic `AGENTS.md`. Preserve the
|
|
103
|
+
useful unified-toolchain overview, help and documentation entry points, and
|
|
104
|
+
`vp env doctor` troubleshooting without duplicating generic commands that can
|
|
105
|
+
contradict the repository policy. Treat duplicate, overlapping, reversed, or
|
|
106
|
+
unmatched managed markers as a conflict rather than guessing which content Dev
|
|
107
|
+
Kit owns; remove a legacy owned Vite+ section during migration.
|
|
108
|
+
The managed section also publishes the repository's command authority. Direct
|
|
109
|
+
Vite+ projects must use `vp` built-ins and `vp run <task>`; managed quality
|
|
110
|
+
projects use `vp run check` for the complete format/lint/test/typecheck suite
|
|
111
|
+
and `vp run typecheck` for the Effect-patched compiler. Non-Vite+ projects run
|
|
112
|
+
existing root quality scripts through `bun run`; package-manager metadata and
|
|
113
|
+
lockfiles affect dependency-install guidance only. Never substitute another
|
|
114
|
+
script runner or call raw `tsc`, test, lint, or format binaries when a project
|
|
115
|
+
command exists.
|
|
101
116
|
Enable `setup.claudeInstructions` when Claude Code should consume the same
|
|
102
117
|
project-root instructions; it manages `CLAUDE.md` as a relative symlink to the
|
|
103
|
-
|
|
104
|
-
when disabled, dev-kit removes only unchanged
|
|
105
|
-
ownership state
|
|
118
|
+
section-managed file or to an existing regular `AGENTS.md`. Preserve
|
|
119
|
+
conflicting paths; when disabled, dev-kit removes only unchanged marked
|
|
120
|
+
sections recorded in local ownership state and leaves handwritten content in
|
|
121
|
+
place.
|
|
106
122
|
|
|
107
123
|
Enable `setup.vitePlus.hooks` when an installed direct `vite-plus` dependency
|
|
108
124
|
should manage Git hooks. Each apply checks the local `.vite-hooks/_` dispatcher,
|
|
@@ -129,9 +145,11 @@ builds custom. Workflow-only consumers may configure `workflow.beforeChecks`
|
|
|
129
145
|
and `workflow.typecheck`; treat these commands as trusted manifest input.
|
|
130
146
|
|
|
131
147
|
The workflow must use one frozen, script-suppressed install, then locked Dev Kit
|
|
132
|
-
convergence before preparation or checks.
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
convergence before preparation or checks. Set up Bun from the consumer's
|
|
149
|
+
`packageManager` or `engines.bun` declaration before Vite+ setup. Keep both
|
|
150
|
+
`setup-bun` and `setup-vp` pinned to reviewed release commits—the `setup-vp`
|
|
151
|
+
`v1` tag is frozen—and let Vite+ resolve the consumer's compatible locked
|
|
152
|
+
version.
|
|
135
153
|
|
|
136
154
|
## Ownership and conflicts
|
|
137
155
|
|
|
@@ -157,6 +175,10 @@ Run `dev-kit gitignore` to add `.repos/` and `.dev-kit/` additively. Preview wit
|
|
|
157
175
|
`dev-kit gitignore --dry-run`. Treat `.repos/<source-id>` as the reserved source
|
|
158
176
|
checkout root.
|
|
159
177
|
|
|
178
|
+
Require Bun 1.3 or newer wherever Dev Kit runs. The published `dev-kit`
|
|
179
|
+
executable uses Bun directly and rejects Node execution; keep Bun available to
|
|
180
|
+
package lifecycle scripts and locked CI verification.
|
|
181
|
+
|
|
160
182
|
For one lifecycle entry point, configure:
|
|
161
183
|
|
|
162
184
|
```jsonc
|
|
@@ -171,7 +193,10 @@ This intentionally refreshes the committed lock and owned outputs when the
|
|
|
171
193
|
package manager installs a new Dev Kit or selected package-skill version.
|
|
172
194
|
Review and commit those changes with the dependency update. Keep
|
|
173
195
|
`dev-kit apply --locked` as a verification command, not the normal local
|
|
174
|
-
lifecycle; in CI, run it only before any unlocked apply.
|
|
196
|
+
lifecycle; in CI, run it only before any unlocked apply. Invoke locked consumer
|
|
197
|
+
verification as
|
|
198
|
+
`bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked` so a
|
|
199
|
+
package script named `dev-kit` cannot shadow the executable.
|
|
175
200
|
|
|
176
201
|
## Effect source checkout
|
|
177
202
|
|
|
@@ -263,7 +288,7 @@ in the consuming project.
|
|
|
263
288
|
|
|
264
289
|
## Current boundary
|
|
265
290
|
|
|
266
|
-
Manage skill outputs, the `setup.agentInstructions`
|
|
291
|
+
Manage skill outputs, the `setup.agentInstructions` marked sections, the
|
|
267
292
|
`setup.claudeInstructions` link, the `setup.vitePlus.hooks` dispatcher, the
|
|
268
293
|
independently opt-in `setup.vitePlus.quality` config and GitHub workflow, the
|
|
269
294
|
`setup.effectSource` checkout, and the explicit `setup.effectTsgo` task.
|
package/src/bin/dev-kit.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BunRuntime, BunServices } from "@effect/platform-bun";
|
|
2
2
|
import { Effect, Result } from "effect";
|
|
3
3
|
import { Argument, CliError, Command as CliCommand, Flag } from "effect/unstable/cli";
|
|
4
4
|
|
|
@@ -399,7 +399,7 @@ const program = CliCommand.run(command, { version: DEV_KIT_VERSION }).pipe(
|
|
|
399
399
|
),
|
|
400
400
|
),
|
|
401
401
|
Effect.scoped,
|
|
402
|
-
Effect.provide(
|
|
402
|
+
Effect.provide(BunServices.layer),
|
|
403
403
|
);
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
BunRuntime.runMain(program, { disableErrorReporting: true });
|
package/src/project-package.ts
CHANGED
|
@@ -8,6 +8,7 @@ export class ProjectPackageError extends Schema.TaggedErrorClass<ProjectPackageE
|
|
|
8
8
|
const ProjectPackageSchema = Schema.fromJsonString(
|
|
9
9
|
Schema.Struct({
|
|
10
10
|
name: Schema.optional(Schema.String),
|
|
11
|
+
packageManager: Schema.optional(Schema.String),
|
|
11
12
|
scripts: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
12
13
|
dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
13
14
|
devDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
package/src/sync.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
observePathWithRawModes,
|
|
27
27
|
type ObservedPath,
|
|
28
28
|
} from "./path-digest.ts";
|
|
29
|
-
import { readDirectDependencyNames } from "./project-package.ts";
|
|
29
|
+
import { readDirectDependencyNames, readProjectPackage } from "./project-package.ts";
|
|
30
30
|
import { acquireProjectProcessLock, PROJECT_PROCESS_LOCK_PATH } from "./project-process-lock.ts";
|
|
31
31
|
import {
|
|
32
32
|
AppliedStateSchema,
|
|
@@ -115,12 +115,14 @@ type SkillPlanAction =
|
|
|
115
115
|
readonly action: "create" | "update";
|
|
116
116
|
readonly desired: DesiredOutput;
|
|
117
117
|
readonly observed: ObservedPath;
|
|
118
|
+
readonly stagedContent?: string;
|
|
118
119
|
}
|
|
119
120
|
| {
|
|
120
121
|
readonly action: "remove";
|
|
121
122
|
readonly previous: OwnershipReceipt;
|
|
122
123
|
readonly destination: string;
|
|
123
124
|
readonly observed: ObservedPath;
|
|
125
|
+
readonly stagedContent?: string;
|
|
124
126
|
}
|
|
125
127
|
| {
|
|
126
128
|
readonly action: "unchanged";
|
|
@@ -234,6 +236,255 @@ const DEFAULT_LOCKFILE = "dev-kit.lock.json";
|
|
|
234
236
|
const DEFAULT_STATE = ".dev-kit/state.json";
|
|
235
237
|
const AGENT_INSTRUCTIONS_TEMPLATE = "templates/AGENTS.md";
|
|
236
238
|
const DEV_KIT_SKILL_PATH_PLACEHOLDER = "{{DEV_KIT_SKILL_PATH}}";
|
|
239
|
+
const PROJECT_COMMAND_POLICY_PLACEHOLDER = "{{PROJECT_COMMAND_POLICY}}";
|
|
240
|
+
const AGENT_INSTRUCTION_MARKERS = [
|
|
241
|
+
{ start: "<!-- DEV KIT START -->", end: "<!-- DEV KIT END -->" },
|
|
242
|
+
// Legacy Dev Kit releases copied this upstream section into AGENTS.md. Keep
|
|
243
|
+
// recognizing it so an owned section can be removed during migration.
|
|
244
|
+
{ start: "<!--VITE PLUS START-->", end: "<!--VITE PLUS END-->" },
|
|
245
|
+
] as const;
|
|
246
|
+
|
|
247
|
+
type ManagedInstructionRange = {
|
|
248
|
+
readonly start: number;
|
|
249
|
+
readonly end: number;
|
|
250
|
+
readonly content: string;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
type ManagedInstructionInspection =
|
|
254
|
+
| {
|
|
255
|
+
readonly kind: "valid";
|
|
256
|
+
readonly ranges: ReadonlyArray<ManagedInstructionRange>;
|
|
257
|
+
readonly content?: string;
|
|
258
|
+
}
|
|
259
|
+
| { readonly kind: "invalid"; readonly reason: string };
|
|
260
|
+
|
|
261
|
+
const findOccurrences = (content: string, marker: string): ReadonlyArray<number> => {
|
|
262
|
+
const positions: Array<number> = [];
|
|
263
|
+
let offset = 0;
|
|
264
|
+
|
|
265
|
+
while (offset < content.length) {
|
|
266
|
+
const position = content.indexOf(marker, offset);
|
|
267
|
+
|
|
268
|
+
if (position === -1) break;
|
|
269
|
+
positions.push(position);
|
|
270
|
+
offset = position + marker.length;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return positions;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const inspectManagedInstructionSections = (content: string): ManagedInstructionInspection => {
|
|
277
|
+
const ranges: Array<ManagedInstructionRange> = [];
|
|
278
|
+
|
|
279
|
+
for (const markers of AGENT_INSTRUCTION_MARKERS) {
|
|
280
|
+
const starts = findOccurrences(content, markers.start);
|
|
281
|
+
const ends = findOccurrences(content, markers.end);
|
|
282
|
+
|
|
283
|
+
if (starts.length === 0 && ends.length === 0) continue;
|
|
284
|
+
if (
|
|
285
|
+
starts.length !== 1 ||
|
|
286
|
+
ends.length !== 1 ||
|
|
287
|
+
starts[0] === undefined ||
|
|
288
|
+
ends[0] === undefined
|
|
289
|
+
) {
|
|
290
|
+
return {
|
|
291
|
+
kind: "invalid",
|
|
292
|
+
reason: `expected exactly one ${markers.start}/${markers.end} marker pair`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (starts[0] >= ends[0]) {
|
|
296
|
+
return { kind: "invalid", reason: `${markers.end} appears before ${markers.start}` };
|
|
297
|
+
}
|
|
298
|
+
const end = ends[0] + markers.end.length;
|
|
299
|
+
|
|
300
|
+
ranges.push({ start: starts[0], end, content: content.slice(starts[0], end) });
|
|
301
|
+
}
|
|
302
|
+
ranges.sort((left, right) => left.start - right.start);
|
|
303
|
+
for (let index = 1; index < ranges.length; index += 1) {
|
|
304
|
+
const previous = ranges[index - 1];
|
|
305
|
+
const current = ranges[index];
|
|
306
|
+
|
|
307
|
+
if (previous !== undefined && current !== undefined && current.start < previous.end) {
|
|
308
|
+
return { kind: "invalid", reason: "managed instruction marker pairs overlap" };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
kind: "valid",
|
|
314
|
+
ranges,
|
|
315
|
+
...(ranges.length === 0
|
|
316
|
+
? {}
|
|
317
|
+
: { content: `${ranges.map((range) => range.content.trim()).join("\n\n")}\n` }),
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const removeManagedInstructionSections = (
|
|
322
|
+
content: string,
|
|
323
|
+
ranges: ReadonlyArray<ManagedInstructionRange>,
|
|
324
|
+
): string => {
|
|
325
|
+
const first = ranges[0];
|
|
326
|
+
const last = ranges.at(-1);
|
|
327
|
+
const hasOnlyManagedSeparators = ranges.every((range, index) => {
|
|
328
|
+
const next = ranges[index + 1];
|
|
329
|
+
|
|
330
|
+
return next === undefined || /^\s*$/.test(content.slice(range.end, next.start));
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
if (first?.start === 0 && last !== undefined && hasOnlyManagedSeparators) {
|
|
334
|
+
let end = last.end;
|
|
335
|
+
|
|
336
|
+
if (content.startsWith("\r\n", end)) end += 2;
|
|
337
|
+
else if (content.startsWith("\n", end)) end += 1;
|
|
338
|
+
|
|
339
|
+
return content.slice(end);
|
|
340
|
+
}
|
|
341
|
+
let result = content;
|
|
342
|
+
|
|
343
|
+
for (const range of [...ranges].reverse()) {
|
|
344
|
+
result = result.slice(0, range.start) + result.slice(range.end);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return result;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const prependManagedInstructionSections = (content: string, managed: string): string => {
|
|
351
|
+
if (content.trim().length === 0) return managed;
|
|
352
|
+
|
|
353
|
+
return `${managed}${content}`;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const reconcileManagedInstructionSections = (
|
|
357
|
+
content: string,
|
|
358
|
+
inspection: Extract<ManagedInstructionInspection, { readonly kind: "valid" }>,
|
|
359
|
+
managed: string,
|
|
360
|
+
): string =>
|
|
361
|
+
prependManagedInstructionSections(
|
|
362
|
+
removeManagedInstructionSections(content, inspection.ranges),
|
|
363
|
+
managed,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const renderVitePlusCommandPolicy = (
|
|
367
|
+
scripts: Readonly<Record<string, string>>,
|
|
368
|
+
managesQualityConfig: boolean,
|
|
369
|
+
): string => {
|
|
370
|
+
const hasCheck = managesQualityConfig || scripts.check !== undefined;
|
|
371
|
+
const hasTypecheck = managesQualityConfig || scripts.typecheck !== undefined;
|
|
372
|
+
|
|
373
|
+
return [
|
|
374
|
+
"## Project command policy",
|
|
375
|
+
"",
|
|
376
|
+
"Vite+ is the unified toolchain and command authority for this repository. It wraps Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task behind the `vp` CLI; Vite+ is distinct from Vite.",
|
|
377
|
+
"",
|
|
378
|
+
"Run `vp help` for available commands and `vp <command> --help` for command-specific options. Documentation is available locally in `node_modules/vite-plus/docs` and online at https://viteplus.dev/guide/.",
|
|
379
|
+
"",
|
|
380
|
+
"Use these repository commands:",
|
|
381
|
+
"",
|
|
382
|
+
"- Install dependencies: `vp install`.",
|
|
383
|
+
...(hasCheck ? ["- Full validation: `vp run check`."] : []),
|
|
384
|
+
"- Static checks: `vp check`.",
|
|
385
|
+
"- Format check: `vp fmt --check`; format fixes: `vp fmt`.",
|
|
386
|
+
"- Lint only: `vp lint`; lint fixes: `vp lint --fix`.",
|
|
387
|
+
"- Tests only: `vp test`.",
|
|
388
|
+
...(hasTypecheck ? ["- Typecheck only: `vp run typecheck`."] : []),
|
|
389
|
+
"- Other repository tasks and package scripts: `vp run <task>`.",
|
|
390
|
+
"- Toolchain or runtime troubleshooting: run `vp env doctor` and include its output when asking for help.",
|
|
391
|
+
"",
|
|
392
|
+
"Do not use `bun run`, `npm run`, `pnpm run`, or `yarn run` in this repository. Do not invoke underlying tools such as `tsc`, `vitest`, `oxlint`, or `oxfmt` directly; use the Vite+ entry points above.",
|
|
393
|
+
].join("\n");
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const PACKAGE_MANAGER_COMMANDS = {
|
|
397
|
+
bun: { install: "bun install", label: "Bun" },
|
|
398
|
+
npm: { install: "npm install", label: "npm" },
|
|
399
|
+
pnpm: { install: "pnpm install", label: "pnpm" },
|
|
400
|
+
yarn: { install: "yarn install", label: "Yarn" },
|
|
401
|
+
} as const;
|
|
402
|
+
|
|
403
|
+
type PackageManagerName = keyof typeof PACKAGE_MANAGER_COMMANDS;
|
|
404
|
+
|
|
405
|
+
const packageManagerName = (declaration: string | undefined): PackageManagerName | undefined => {
|
|
406
|
+
const name = declaration?.split("@", 1)[0];
|
|
407
|
+
|
|
408
|
+
return name !== undefined && name in PACKAGE_MANAGER_COMMANDS
|
|
409
|
+
? (name as PackageManagerName)
|
|
410
|
+
: undefined;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const detectPackageManager = Effect.fn("detectPackageManager")(function* (
|
|
414
|
+
projectDir: string,
|
|
415
|
+
declaration: string | undefined,
|
|
416
|
+
) {
|
|
417
|
+
const declared = packageManagerName(declaration);
|
|
418
|
+
|
|
419
|
+
if (declared !== undefined || declaration !== undefined) return declared;
|
|
420
|
+
const fs = yield* FileSystem.FileSystem;
|
|
421
|
+
const path = yield* Path.Path;
|
|
422
|
+
const lockfiles: ReadonlyArray<readonly [PackageManagerName, ReadonlyArray<string>]> = [
|
|
423
|
+
["bun", ["bun.lock", "bun.lockb"]],
|
|
424
|
+
["npm", ["package-lock.json", "npm-shrinkwrap.json"]],
|
|
425
|
+
["pnpm", ["pnpm-lock.yaml"]],
|
|
426
|
+
["yarn", ["yarn.lock"]],
|
|
427
|
+
];
|
|
428
|
+
const detected: Array<PackageManagerName> = [];
|
|
429
|
+
|
|
430
|
+
for (const [manager, files] of lockfiles) {
|
|
431
|
+
let found = false;
|
|
432
|
+
|
|
433
|
+
for (const file of files) {
|
|
434
|
+
if (yield* fs.exists(path.join(projectDir, file))) {
|
|
435
|
+
found = true;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (found) {
|
|
440
|
+
detected.push(manager);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return detected.length === 1 ? detected[0] : undefined;
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const renderPackageScriptCommandPolicy = (
|
|
448
|
+
manager: PackageManagerName | undefined,
|
|
449
|
+
scripts: Readonly<Record<string, string>>,
|
|
450
|
+
): string => {
|
|
451
|
+
const installer = manager === undefined ? undefined : PACKAGE_MANAGER_COMMANDS[manager];
|
|
452
|
+
const entries = [
|
|
453
|
+
["check", "Full validation"],
|
|
454
|
+
["format:check", "Format check"],
|
|
455
|
+
["format", "Format"],
|
|
456
|
+
["lint", "Lint"],
|
|
457
|
+
["test", "Tests"],
|
|
458
|
+
["typecheck", "Typecheck"],
|
|
459
|
+
] as const;
|
|
460
|
+
const commands = entries.flatMap(([script, label]) =>
|
|
461
|
+
scripts[script] === undefined ? [] : [`- ${label}: \`bun run ${script}\`.`],
|
|
462
|
+
);
|
|
463
|
+
const knownScripts = new Set(entries.map(([script]) => script));
|
|
464
|
+
const additionalCommands = Object.keys(scripts)
|
|
465
|
+
.filter(
|
|
466
|
+
(script) =>
|
|
467
|
+
!knownScripts.has(script as (typeof entries)[number][0]) &&
|
|
468
|
+
/^(?:check|validate|fmt|format|lint|test|type-?check)(?::|$)/.test(script),
|
|
469
|
+
)
|
|
470
|
+
.sort()
|
|
471
|
+
.map((script) => `- Script \`${script}\`: \`bun run ${script}\`.`);
|
|
472
|
+
const qualityCommands = [...commands, ...additionalCommands];
|
|
473
|
+
|
|
474
|
+
return [
|
|
475
|
+
"## Project command policy",
|
|
476
|
+
"",
|
|
477
|
+
"Bun is the package-script runner for this repository:",
|
|
478
|
+
"",
|
|
479
|
+
...(installer === undefined
|
|
480
|
+
? []
|
|
481
|
+
: [`- Install dependencies with ${installer.label}: \`${installer.install}\`.`]),
|
|
482
|
+
...qualityCommands,
|
|
483
|
+
...(qualityCommands.length === 0 ? ["- No root quality scripts are currently declared."] : []),
|
|
484
|
+
"",
|
|
485
|
+
"Run only declared scripts through `bun run <script>`. Do not use `npm run`, `pnpm run`, or `yarn run`, invent missing scripts, or invoke underlying tools such as `tsc`, `vitest`, `eslint`, or `prettier` directly. The Bun script-runner requirement does not choose the package manager used to install dependencies.",
|
|
486
|
+
].join("\n");
|
|
487
|
+
};
|
|
237
488
|
|
|
238
489
|
const resolvePackageRoot = Effect.fn("resolvePackageRoot")(function* () {
|
|
239
490
|
const path = yield* Path.Path;
|
|
@@ -521,6 +772,8 @@ const renderAgentInstructions = Effect.fn("renderAgentInstructions")(function* (
|
|
|
521
772
|
packageRoot: string,
|
|
522
773
|
projectDir: string,
|
|
523
774
|
sourceBySkill: ReadonlyMap<string, ResolvedSkillSource>,
|
|
775
|
+
managesVitePlusQuality: boolean,
|
|
776
|
+
targets: ReturnType<typeof normalizeManifest>["targets"],
|
|
524
777
|
) {
|
|
525
778
|
const fs = yield* FileSystem.FileSystem;
|
|
526
779
|
const path = yield* Path.Path;
|
|
@@ -538,33 +791,44 @@ const renderAgentInstructions = Effect.fn("renderAgentInstructions")(function* (
|
|
|
538
791
|
message: `dev-kit agent instructions template is missing ${DEV_KIT_SKILL_PATH_PLACEHOLDER}`,
|
|
539
792
|
});
|
|
540
793
|
}
|
|
794
|
+
if (!template.includes(PROJECT_COMMAND_POLICY_PLACEHOLDER)) {
|
|
795
|
+
return yield* new InvalidProjectStateError({
|
|
796
|
+
message: `dev-kit agent instructions template is missing ${PROJECT_COMMAND_POLICY_PLACEHOLDER}`,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
541
799
|
|
|
542
800
|
const devKitSkill = sourceBySkill.get("dev-kit");
|
|
801
|
+
const devKitTarget = (["agents", "claude", "opencode"] as const)
|
|
802
|
+
.map((name) => targets[name])
|
|
803
|
+
.find((target) => target.enabled);
|
|
543
804
|
const devKitSkillPath =
|
|
544
805
|
devKitSkill === undefined
|
|
545
806
|
? "node_modules/@danieljvdm/dev-kit/skills/dev-kit/SKILL.md"
|
|
546
|
-
:
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
path.
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
807
|
+
: devKitTarget !== undefined
|
|
808
|
+
? portablePath(path, path.join(devKitTarget.path, "dev-kit", "SKILL.md"))
|
|
809
|
+
: portablePath(
|
|
810
|
+
path,
|
|
811
|
+
path.relative(
|
|
812
|
+
projectDir,
|
|
813
|
+
path.join(devKitSkill.linkPath ?? devKitSkill.path, "SKILL.md"),
|
|
814
|
+
),
|
|
815
|
+
);
|
|
816
|
+
const usesVitePlus = (yield* readDirectDependencyNames(projectDir)).includes("vite-plus");
|
|
817
|
+
const projectPackage = yield* readProjectPackage(projectDir).pipe(
|
|
818
|
+
Effect.catchTag("ProjectPackageError", (error) =>
|
|
819
|
+
error.message.startsWith("package.json not found:") ? Effect.void : Effect.fail(error),
|
|
820
|
+
),
|
|
821
|
+
);
|
|
822
|
+
const manager = yield* detectPackageManager(projectDir, projectPackage?.packageManager);
|
|
823
|
+
const commandPolicy = usesVitePlus
|
|
824
|
+
? renderVitePlusCommandPolicy(projectPackage?.scripts ?? {}, managesVitePlusQuality)
|
|
825
|
+
: renderPackageScriptCommandPolicy(manager, projectPackage?.scripts ?? {});
|
|
826
|
+
const devKitInstructions = template
|
|
827
|
+
.replaceAll(DEV_KIT_SKILL_PATH_PLACEHOLDER, devKitSkillPath)
|
|
828
|
+
.replaceAll(PROJECT_COMMAND_POLICY_PLACEHOLDER, commandPolicy)
|
|
829
|
+
.trimEnd();
|
|
830
|
+
|
|
831
|
+
return `${devKitInstructions}\n`;
|
|
568
832
|
});
|
|
569
833
|
|
|
570
834
|
const readGeneratedFileTemplate = Effect.fn("readGeneratedFileTemplate")(function* (
|
|
@@ -597,7 +861,13 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
|
|
|
597
861
|
|
|
598
862
|
if (setup.agentInstructions.enabled) {
|
|
599
863
|
const managed = yield* resolveManagedPath(projectDir, "AGENTS.md");
|
|
600
|
-
const content = yield* renderAgentInstructions(
|
|
864
|
+
const content = yield* renderAgentInstructions(
|
|
865
|
+
packageRoot,
|
|
866
|
+
projectDir,
|
|
867
|
+
sourceBySkill,
|
|
868
|
+
setup.vitePlus.quality.config.enabled,
|
|
869
|
+
targets,
|
|
870
|
+
);
|
|
601
871
|
|
|
602
872
|
outputs.push({
|
|
603
873
|
resourceId: "setup:agent-instructions",
|
|
@@ -662,7 +932,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
|
|
|
662
932
|
devKitCommand:
|
|
663
933
|
projectDir === packageRoot
|
|
664
934
|
? "./bin/dev-kit.mjs apply --locked"
|
|
665
|
-
: "
|
|
935
|
+
: "bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked",
|
|
666
936
|
workflow: setup.vitePlus.quality.workflow,
|
|
667
937
|
});
|
|
668
938
|
|
|
@@ -803,6 +1073,89 @@ const planDesiredOutputs = Effect.fn("planDesiredSkillOutputs")(function* (
|
|
|
803
1073
|
(rawModeObservation?.kind === matchingLockedOutput.kind &&
|
|
804
1074
|
rawModeObservation.digest === matchingLockedOutput.digest));
|
|
805
1075
|
|
|
1076
|
+
if (output.resourceId === "setup:agent-instructions" && "content" in output) {
|
|
1077
|
+
if (observed.kind === "missing") {
|
|
1078
|
+
actions.push({
|
|
1079
|
+
action: "create",
|
|
1080
|
+
desired: output,
|
|
1081
|
+
observed,
|
|
1082
|
+
stagedContent: output.content,
|
|
1083
|
+
});
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
if (observed.kind !== "file") {
|
|
1087
|
+
actions.push({
|
|
1088
|
+
action: "conflict",
|
|
1089
|
+
path: output.path,
|
|
1090
|
+
reason: "destination is not a regular file",
|
|
1091
|
+
});
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
const existingContent = yield* FileSystem.FileSystem.pipe(
|
|
1095
|
+
Effect.flatMap((fs) => fs.readFileString(output.destination)),
|
|
1096
|
+
);
|
|
1097
|
+
const inspection = inspectManagedInstructionSections(existingContent);
|
|
1098
|
+
|
|
1099
|
+
if (inspection.kind === "invalid") {
|
|
1100
|
+
actions.push({ action: "conflict", path: output.path, reason: inspection.reason });
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
const managedDigest =
|
|
1104
|
+
inspection.content === undefined ? undefined : yield* digestFileContent(inspection.content);
|
|
1105
|
+
const receiptOwnsManaged =
|
|
1106
|
+
sameReceipt !== undefined &&
|
|
1107
|
+
(managedDigest === sameReceipt.digest || observed.digest === sameReceipt.digest);
|
|
1108
|
+
const lockOwnsManaged =
|
|
1109
|
+
matchingLockedOutput !== undefined &&
|
|
1110
|
+
(managedDigest === matchingLockedOutput.digest ||
|
|
1111
|
+
observed.digest === matchingLockedOutput.digest);
|
|
1112
|
+
const legacyOwnsWholeFile =
|
|
1113
|
+
(sameReceipt !== undefined &&
|
|
1114
|
+
observed.digest === sameReceipt.digest &&
|
|
1115
|
+
managedDigest !== sameReceipt.digest) ||
|
|
1116
|
+
(matchingLockedOutput !== undefined &&
|
|
1117
|
+
lockedOwnsObserved &&
|
|
1118
|
+
managedDigest !== matchingLockedOutput.digest);
|
|
1119
|
+
|
|
1120
|
+
if (managedDigest === output.digest && !legacyOwnsWholeFile) {
|
|
1121
|
+
if (sameReceipt !== undefined || lockOwnsManaged || lockedOwnsObserved) {
|
|
1122
|
+
actions.push({
|
|
1123
|
+
action: "unchanged",
|
|
1124
|
+
desired: output,
|
|
1125
|
+
observed,
|
|
1126
|
+
adopted: sameReceipt === undefined,
|
|
1127
|
+
});
|
|
1128
|
+
} else {
|
|
1129
|
+
actions.push({
|
|
1130
|
+
action: "conflict",
|
|
1131
|
+
path: output.path,
|
|
1132
|
+
reason: "managed instruction sections exist but are not owned",
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
} else if (
|
|
1136
|
+
managedDigest === undefined ||
|
|
1137
|
+
receiptOwnsManaged ||
|
|
1138
|
+
lockOwnsManaged ||
|
|
1139
|
+
lockedOwnsObserved
|
|
1140
|
+
) {
|
|
1141
|
+
actions.push({
|
|
1142
|
+
action: "update",
|
|
1143
|
+
desired: output,
|
|
1144
|
+
observed,
|
|
1145
|
+
stagedContent: legacyOwnsWholeFile
|
|
1146
|
+
? output.content
|
|
1147
|
+
: reconcileManagedInstructionSections(existingContent, inspection, output.content),
|
|
1148
|
+
});
|
|
1149
|
+
} else {
|
|
1150
|
+
actions.push({
|
|
1151
|
+
action: "conflict",
|
|
1152
|
+
path: output.path,
|
|
1153
|
+
reason: "managed instruction sections exist but are not owned",
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
806
1159
|
if (observed.kind === "missing") {
|
|
807
1160
|
actions.push({ action: "create", desired: output, observed });
|
|
808
1161
|
} else if (observed.kind === output.kind && observed.digest === output.digest) {
|
|
@@ -839,6 +1192,46 @@ const planDesiredOutputs = Effect.fn("planDesiredSkillOutputs")(function* (
|
|
|
839
1192
|
const observed = yield* observePath(managed.absolute);
|
|
840
1193
|
|
|
841
1194
|
if (observed.kind === "missing") continue;
|
|
1195
|
+
if (receipt.resourceId === "setup:agent-instructions") {
|
|
1196
|
+
if (observed.kind !== "file") {
|
|
1197
|
+
actions.push({
|
|
1198
|
+
action: "conflict",
|
|
1199
|
+
path: receipt.path,
|
|
1200
|
+
reason: "stale owned destination is not a regular file",
|
|
1201
|
+
});
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
const existingContent = yield* FileSystem.FileSystem.pipe(
|
|
1205
|
+
Effect.flatMap((fs) => fs.readFileString(managed.absolute)),
|
|
1206
|
+
);
|
|
1207
|
+
const inspection = inspectManagedInstructionSections(existingContent);
|
|
1208
|
+
|
|
1209
|
+
if (inspection.kind === "invalid") {
|
|
1210
|
+
actions.push({ action: "conflict", path: receipt.path, reason: inspection.reason });
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
if (inspection.content === undefined) continue;
|
|
1214
|
+
const managedDigest = yield* digestFileContent(inspection.content);
|
|
1215
|
+
|
|
1216
|
+
if (managedDigest === receipt.digest || observed.digest === receipt.digest) {
|
|
1217
|
+
const remaining = removeManagedInstructionSections(existingContent, inspection.ranges);
|
|
1218
|
+
|
|
1219
|
+
actions.push({
|
|
1220
|
+
action: "remove",
|
|
1221
|
+
previous: receipt,
|
|
1222
|
+
destination: managed.absolute,
|
|
1223
|
+
observed,
|
|
1224
|
+
...(remaining.trim().length === 0 ? {} : { stagedContent: remaining }),
|
|
1225
|
+
});
|
|
1226
|
+
} else {
|
|
1227
|
+
actions.push({
|
|
1228
|
+
action: "conflict",
|
|
1229
|
+
path: receipt.path,
|
|
1230
|
+
reason: "stale owned managed instruction sections were modified",
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
842
1235
|
if (observed.kind === receipt.kind && observed.digest === receipt.digest) {
|
|
843
1236
|
actions.push({
|
|
844
1237
|
action: "remove",
|
|
@@ -1110,16 +1503,6 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
1110
1503
|
const currentLock = yield* readOptionalStructuredFile(lockManaged.absolute, DevKitLockSchema);
|
|
1111
1504
|
const currentState = yield* readOptionalStructuredFile(stateManaged.absolute, AppliedStateSchema);
|
|
1112
1505
|
|
|
1113
|
-
if (
|
|
1114
|
-
manifest.setup.claudeInstructions.enabled &&
|
|
1115
|
-
!manifest.setup.agentInstructions.enabled &&
|
|
1116
|
-
currentState?.outputs.some((output) => output.resourceId === "setup:agent-instructions")
|
|
1117
|
-
) {
|
|
1118
|
-
return yield* new InvalidProjectStateError({
|
|
1119
|
-
message:
|
|
1120
|
-
"cannot disable agentInstructions while claudeInstructions still links to its AGENTS.md wrapper",
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1123
1506
|
yield* validateReservedPaths(projectDir, reservedPaths, [
|
|
1124
1507
|
...(currentLock?.outputs ?? []),
|
|
1125
1508
|
...(currentState?.outputs ?? []),
|
|
@@ -1143,6 +1526,19 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
1143
1526
|
currentState,
|
|
1144
1527
|
nextLock,
|
|
1145
1528
|
);
|
|
1529
|
+
const removesClaudeInstructionsSource = planned.actions.some(
|
|
1530
|
+
(action) =>
|
|
1531
|
+
action.action === "remove" &&
|
|
1532
|
+
action.previous.resourceId === "setup:agent-instructions" &&
|
|
1533
|
+
action.stagedContent === undefined,
|
|
1534
|
+
);
|
|
1535
|
+
|
|
1536
|
+
if (manifest.setup.claudeInstructions.enabled && removesClaudeInstructionsSource) {
|
|
1537
|
+
return yield* new InvalidProjectStateError({
|
|
1538
|
+
message:
|
|
1539
|
+
"cannot disable agentInstructions while claudeInstructions still links to an AGENTS.md that would be removed",
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1146
1542
|
|
|
1147
1543
|
return {
|
|
1148
1544
|
projectDir,
|
|
@@ -1310,13 +1706,28 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
1310
1706
|
let stageIndex = 0;
|
|
1311
1707
|
|
|
1312
1708
|
for (const action of mutating) {
|
|
1313
|
-
if (action.action === "remove") continue;
|
|
1709
|
+
if (action.action === "remove" && action.stagedContent === undefined) continue;
|
|
1314
1710
|
const staged = path.join(stageDir, String(stageIndex++));
|
|
1315
1711
|
|
|
1316
1712
|
yield* fs.makeDirectory(path.dirname(staged), { recursive: true });
|
|
1317
|
-
if (action.
|
|
1713
|
+
if (action.stagedContent !== undefined && action.observed.kind === "file") {
|
|
1714
|
+
const destination =
|
|
1715
|
+
action.action === "remove" ? action.destination : action.desired.destination;
|
|
1716
|
+
|
|
1717
|
+
yield* fs.copy(destination, staged, { overwrite: true });
|
|
1718
|
+
yield* fs.writeFileString(staged, action.stagedContent);
|
|
1719
|
+
} else if (action.action === "remove") {
|
|
1720
|
+
if (action.stagedContent === undefined) {
|
|
1721
|
+
return yield* new InvalidProjectStateError({
|
|
1722
|
+
message: `missing staged content for ${action.previous.resourceId}`,
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
yield* fs.writeFileString(staged, action.stagedContent, { mode: 0o644 });
|
|
1726
|
+
} else if (action.desired.mode === "copy") {
|
|
1318
1727
|
if (action.desired.kind === "file") {
|
|
1319
|
-
yield* fs.writeFileString(staged, action.desired.content, {
|
|
1728
|
+
yield* fs.writeFileString(staged, action.stagedContent ?? action.desired.content, {
|
|
1729
|
+
mode: 0o644,
|
|
1730
|
+
});
|
|
1320
1731
|
} else {
|
|
1321
1732
|
yield* fs.copy(action.desired.source, staged, { overwrite: true });
|
|
1322
1733
|
const symbolicLink = yield* findNestedSymbolicLink(staged);
|
|
@@ -1330,14 +1741,35 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
1330
1741
|
} else {
|
|
1331
1742
|
yield* fs.symlink(action.desired.linkTarget, staged);
|
|
1332
1743
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1744
|
+
if (action.action !== "remove") {
|
|
1745
|
+
const observation = yield* observePath(staged);
|
|
1746
|
+
|
|
1747
|
+
if (action.desired.resourceId === "setup:agent-instructions") {
|
|
1748
|
+
const content = yield* fs.readFileString(staged);
|
|
1749
|
+
const inspection = inspectManagedInstructionSections(content);
|
|
1750
|
+
const digest =
|
|
1751
|
+
inspection.kind === "valid" && inspection.content !== undefined
|
|
1752
|
+
? yield* digestFileContent(inspection.content)
|
|
1753
|
+
: undefined;
|
|
1754
|
+
|
|
1755
|
+
if (observation.kind !== "file" || digest !== action.desired.digest) {
|
|
1756
|
+
return yield* new InvalidProjectStateError({
|
|
1757
|
+
message: `staged output digest mismatch for ${action.desired.path}`,
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
} else if (
|
|
1761
|
+
observation.kind !== action.desired.kind ||
|
|
1762
|
+
observation.digest !== action.desired.digest
|
|
1763
|
+
) {
|
|
1764
|
+
return yield* new InvalidProjectStateError({
|
|
1765
|
+
message: `staged output digest mismatch for ${action.desired.path}`,
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1339
1768
|
}
|
|
1340
|
-
stagedByResource.set(
|
|
1769
|
+
stagedByResource.set(
|
|
1770
|
+
action.action === "remove" ? action.previous.resourceId : action.desired.resourceId,
|
|
1771
|
+
staged,
|
|
1772
|
+
);
|
|
1341
1773
|
}
|
|
1342
1774
|
|
|
1343
1775
|
yield* verifyPackageSkillSources(plan);
|
|
@@ -1360,11 +1792,18 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
1360
1792
|
|
|
1361
1793
|
for (const action of mutating) {
|
|
1362
1794
|
const staged =
|
|
1363
|
-
action.action === "remove"
|
|
1795
|
+
action.action === "remove"
|
|
1796
|
+
? stagedByResource.get(action.previous.resourceId)
|
|
1797
|
+
: stagedByResource.get(action.desired.resourceId);
|
|
1364
1798
|
|
|
1365
|
-
if (
|
|
1799
|
+
if (
|
|
1800
|
+
(action.action !== "remove" || action.stagedContent !== undefined) &&
|
|
1801
|
+
staged === undefined
|
|
1802
|
+
) {
|
|
1366
1803
|
return yield* new InvalidProjectStateError({
|
|
1367
|
-
message: `missing staged output for ${
|
|
1804
|
+
message: `missing staged output for ${
|
|
1805
|
+
action.action === "remove" ? action.previous.resourceId : action.desired.resourceId
|
|
1806
|
+
}`,
|
|
1368
1807
|
});
|
|
1369
1808
|
}
|
|
1370
1809
|
replacements.push({
|
package/src/vite-plus-quality.ts
CHANGED
|
@@ -32,7 +32,8 @@ export type VitePlusQualitySelection = {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
const SINGLE_PROJECT_TYPECHECK_TASK = ' typecheck: "tsc --noEmit",';
|
|
35
|
-
const LOCKED_DEV_KIT_COMMAND =
|
|
35
|
+
const LOCKED_DEV_KIT_COMMAND =
|
|
36
|
+
"bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked";
|
|
36
37
|
const BEFORE_CHECKS_MARKER =
|
|
37
38
|
" # Dev Kit inserts configured quality.workflow.beforeChecks steps here.\n\n";
|
|
38
39
|
const DEFAULT_WORKFLOW_TYPECHECK = ` - name: Type check with Effect TypeScript-Go
|
package/templates/AGENTS.md
CHANGED
|
@@ -22,6 +22,10 @@ jobs:
|
|
|
22
22
|
with:
|
|
23
23
|
persist-credentials: false
|
|
24
24
|
|
|
25
|
+
# setup-bun resolves the consumer's packageManager or engines.bun version.
|
|
26
|
+
- name: Set up Bun
|
|
27
|
+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # setup-bun action v2.2.0
|
|
28
|
+
|
|
25
29
|
# setup-vp's v1 tag is frozen. Keep this v1.16.1 commit current with
|
|
26
30
|
# Renovate or Dependabot so fixes arrive through reviewed pull requests.
|
|
27
31
|
- name: Set up Vite+ and install dependencies
|
|
@@ -34,7 +38,7 @@ jobs:
|
|
|
34
38
|
args: ["--frozen-lockfile", "--ignore-scripts"]
|
|
35
39
|
|
|
36
40
|
- name: Verify locked Dev Kit setup
|
|
37
|
-
run:
|
|
41
|
+
run: bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked
|
|
38
42
|
|
|
39
43
|
# Dev Kit inserts configured quality.workflow.beforeChecks steps here.
|
|
40
44
|
|