@danieljvdm/dev-kit 0.14.0 → 0.15.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 +83 -93
- package/dev-kit.example.jsonc +4 -3
- package/package.json +1 -1
- package/schema/dev-kit.schema.json +19 -42
- package/skills/dev-kit/SKILL.md +115 -222
- package/skills/open-pull-request/SKILL.md +62 -23
- package/src/index.ts +6 -6
- package/src/manifest.ts +28 -29
- package/src/path-digest.ts +0 -13
- package/src/project-package.ts +51 -0
- package/src/project-state.ts +3 -0
- package/src/scaffold.ts +79 -0
- package/src/sync.ts +64 -171
- package/src/vite-plus-workflow.ts +82 -0
- package/src/worktrunk-config.ts +88 -0
- package/templates/vite-plus/github-actions-check.yml +0 -2
- package/templates/worktrunk/wt.toml +27 -0
- package/src/vite-plus-quality.ts +0 -148
package/src/manifest.ts
CHANGED
|
@@ -53,31 +53,28 @@ export const VitePlusHooksSetupSchema = Schema.Struct({
|
|
|
53
53
|
enabled: Schema.optional(Schema.Boolean),
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
export const
|
|
57
|
-
name: Schema.String,
|
|
58
|
-
run: Schema.Array(Schema.String),
|
|
59
|
-
});
|
|
60
|
-
export type VitePlusQualityWorkflowStep = typeof VitePlusQualityWorkflowStepSchema.Type;
|
|
61
|
-
|
|
62
|
-
export const VitePlusQualityWorkflowSetupSchema = Schema.Struct({
|
|
56
|
+
export const VitePlusWorkflowSetupSchema = Schema.Struct({
|
|
63
57
|
enabled: Schema.optional(Schema.Boolean),
|
|
64
|
-
beforeChecks: Schema.optional(Schema.Array(VitePlusQualityWorkflowStepSchema)),
|
|
65
|
-
typecheck: Schema.optional(Schema.Array(Schema.String)),
|
|
66
|
-
});
|
|
67
|
-
export type VitePlusQualityWorkflowSetup = typeof VitePlusQualityWorkflowSetupSchema.Type;
|
|
68
|
-
|
|
69
|
-
export const VitePlusQualitySetupSchema = Schema.Struct({
|
|
70
|
-
workflow: Schema.optional(VitePlusQualityWorkflowSetupSchema),
|
|
71
58
|
});
|
|
72
|
-
export type
|
|
59
|
+
export type VitePlusWorkflowSetup = typeof VitePlusWorkflowSetupSchema.Type;
|
|
73
60
|
|
|
74
61
|
export const VitePlusSetupSchema = Schema.Struct({
|
|
75
62
|
hooks: Schema.optional(VitePlusHooksSetupSchema),
|
|
76
|
-
|
|
63
|
+
workflow: Schema.optional(VitePlusWorkflowSetupSchema),
|
|
77
64
|
});
|
|
78
65
|
|
|
79
66
|
export type VitePlusSetup = typeof VitePlusSetupSchema.Type;
|
|
80
67
|
|
|
68
|
+
export const WorktrunkConfigSetupSchema = Schema.Struct({
|
|
69
|
+
enabled: Schema.optional(Schema.Boolean),
|
|
70
|
+
});
|
|
71
|
+
export type WorktrunkConfigSetup = typeof WorktrunkConfigSetupSchema.Type;
|
|
72
|
+
|
|
73
|
+
export const WorktrunkSetupSchema = Schema.Struct({
|
|
74
|
+
config: Schema.optional(WorktrunkConfigSetupSchema),
|
|
75
|
+
});
|
|
76
|
+
export type WorktrunkSetup = typeof WorktrunkSetupSchema.Type;
|
|
77
|
+
|
|
81
78
|
export const DevKitManifestSchema = Schema.Struct({
|
|
82
79
|
$schema: Schema.optional(Schema.String),
|
|
83
80
|
include: Schema.Array(Schema.String.check(Schema.isPattern(SKILL_SELECTOR_PATTERN))),
|
|
@@ -91,6 +88,7 @@ export const DevKitManifestSchema = Schema.Struct({
|
|
|
91
88
|
effectSource: Schema.optional(EffectSourceSetupSchema),
|
|
92
89
|
effectTsgo: Schema.optional(EffectTsgoSetupSchema),
|
|
93
90
|
vitePlus: Schema.optional(VitePlusSetupSchema),
|
|
91
|
+
worktrunk: Schema.optional(WorktrunkSetupSchema),
|
|
94
92
|
}),
|
|
95
93
|
),
|
|
96
94
|
targets: Schema.optional(
|
|
@@ -135,12 +133,13 @@ export type NormalizedManifest = {
|
|
|
135
133
|
readonly hooks: {
|
|
136
134
|
readonly enabled: boolean;
|
|
137
135
|
};
|
|
138
|
-
readonly
|
|
139
|
-
readonly
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
readonly workflow: {
|
|
137
|
+
readonly enabled: boolean;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
readonly worktrunk: {
|
|
141
|
+
readonly config: {
|
|
142
|
+
readonly enabled: boolean;
|
|
144
143
|
};
|
|
145
144
|
};
|
|
146
145
|
};
|
|
@@ -175,7 +174,6 @@ export const normalizeManifest = (manifest: DevKitManifest): NormalizedManifest
|
|
|
175
174
|
};
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
|
-
const quality = manifest.setup?.vitePlus?.quality;
|
|
179
177
|
|
|
180
178
|
return {
|
|
181
179
|
exclude: manifest.exclude ?? [],
|
|
@@ -203,12 +201,13 @@ export const normalizeManifest = (manifest: DevKitManifest): NormalizedManifest
|
|
|
203
201
|
hooks: {
|
|
204
202
|
enabled: manifest.setup?.vitePlus?.hooks?.enabled ?? false,
|
|
205
203
|
},
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
204
|
+
workflow: {
|
|
205
|
+
enabled: manifest.setup?.vitePlus?.workflow?.enabled ?? false,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
worktrunk: {
|
|
209
|
+
config: {
|
|
210
|
+
enabled: manifest.setup?.worktrunk?.config?.enabled ?? false,
|
|
212
211
|
},
|
|
213
212
|
},
|
|
214
213
|
},
|
package/src/path-digest.ts
CHANGED
|
@@ -27,7 +27,6 @@ const textEncoder = new TextEncoder();
|
|
|
27
27
|
// Git preserves only the executable distinction for regular files. Canonicalizing
|
|
28
28
|
// the remaining bits keeps digests stable across checkout and copy umasks.
|
|
29
29
|
const canonicalFileMode = (mode: number): number => ((mode & 0o111) === 0 ? 0o644 : 0o755);
|
|
30
|
-
const rawFileMode = (mode: number): number => mode & 0o777;
|
|
31
30
|
|
|
32
31
|
const frame = (value: string | Uint8Array): Uint8Array => {
|
|
33
32
|
const bytes = typeof value === "string" ? textEncoder.encode(value) : value;
|
|
@@ -153,18 +152,6 @@ export const observePath = Effect.fn("observeManagedPath")(function* (absolutePa
|
|
|
153
152
|
);
|
|
154
153
|
});
|
|
155
154
|
|
|
156
|
-
export const observePathWithRawModes = Effect.fn("observeManagedPathWithRawModes")(function* (
|
|
157
|
-
absolutePath: string,
|
|
158
|
-
) {
|
|
159
|
-
return yield* digestFileSystemPath(absolutePath, rawFileMode).pipe(
|
|
160
|
-
Effect.mapError((cause) =>
|
|
161
|
-
Schema.is(PathInspectionError)(cause)
|
|
162
|
-
? cause
|
|
163
|
-
: PathInspectionError.make({ path: absolutePath, operation: "inspect", cause }),
|
|
164
|
-
),
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
155
|
export const digestText = Effect.fn("digestText")(function* (value: string) {
|
|
169
156
|
return yield* digestFrames(["text-v1", value]);
|
|
170
157
|
});
|
package/src/project-package.ts
CHANGED
|
@@ -40,6 +40,57 @@ export const readProjectPackage = Effect.fn("readProjectPackage")(function* (pro
|
|
|
40
40
|
return manifest;
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
export const PACKAGE_MANAGER_COMMANDS = {
|
|
44
|
+
bun: { install: "bun install", label: "Bun" },
|
|
45
|
+
npm: { install: "npm install", label: "npm" },
|
|
46
|
+
pnpm: { install: "pnpm install", label: "pnpm" },
|
|
47
|
+
yarn: { install: "yarn install", label: "Yarn" },
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
export type PackageManagerName = keyof typeof PACKAGE_MANAGER_COMMANDS;
|
|
51
|
+
|
|
52
|
+
const packageManagerName = (declaration: string | undefined): PackageManagerName | undefined => {
|
|
53
|
+
const name = declaration?.split("@", 1)[0];
|
|
54
|
+
|
|
55
|
+
return name !== undefined && name in PACKAGE_MANAGER_COMMANDS
|
|
56
|
+
? (name as PackageManagerName)
|
|
57
|
+
: undefined;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const detectPackageManager = Effect.fn("detectPackageManager")(function* (
|
|
61
|
+
projectDir: string,
|
|
62
|
+
declaration: string | undefined,
|
|
63
|
+
) {
|
|
64
|
+
const declared = packageManagerName(declaration);
|
|
65
|
+
|
|
66
|
+
if (declared !== undefined || declaration !== undefined) return declared;
|
|
67
|
+
const fs = yield* FileSystem.FileSystem;
|
|
68
|
+
const path = yield* Path.Path;
|
|
69
|
+
const lockfiles: ReadonlyArray<readonly [PackageManagerName, ReadonlyArray<string>]> = [
|
|
70
|
+
["bun", ["bun.lock", "bun.lockb"]],
|
|
71
|
+
["npm", ["package-lock.json", "npm-shrinkwrap.json"]],
|
|
72
|
+
["pnpm", ["pnpm-lock.yaml"]],
|
|
73
|
+
["yarn", ["yarn.lock"]],
|
|
74
|
+
];
|
|
75
|
+
const detected: Array<PackageManagerName> = [];
|
|
76
|
+
|
|
77
|
+
for (const [manager, files] of lockfiles) {
|
|
78
|
+
let found = false;
|
|
79
|
+
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
if (yield* fs.exists(path.join(projectDir, file))) {
|
|
82
|
+
found = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (found) {
|
|
87
|
+
detected.push(manager);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return detected.length === 1 ? detected[0] : undefined;
|
|
92
|
+
});
|
|
93
|
+
|
|
43
94
|
export const readDirectDependencyNames = Effect.fn("readDirectDependencyNames")(function* (
|
|
44
95
|
projectDir: string,
|
|
45
96
|
) {
|
package/src/project-state.ts
CHANGED
|
@@ -49,6 +49,9 @@ export const ManagedClaudeInstructionsOutputSchema = Schema.Struct({
|
|
|
49
49
|
});
|
|
50
50
|
export type ManagedClaudeInstructionsOutput = typeof ManagedClaudeInstructionsOutputSchema.Type;
|
|
51
51
|
|
|
52
|
+
// Legacy (dev-kit ≤0.14) lock entry for the previously managed check workflow.
|
|
53
|
+
// Kept only so old locks and receipts still decode; planning discards these
|
|
54
|
+
// entries, releasing the file to the repository. Never produced anymore.
|
|
52
55
|
export const ManagedGeneratedFileOutputSchema = Schema.Struct({
|
|
53
56
|
resourceId: Schema.Literal("setup:vite-plus-github-actions"),
|
|
54
57
|
path: Schema.String,
|
package/src/scaffold.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { observeSymbolicLink } from "./node-symbolic-link.ts";
|
|
4
|
+
|
|
5
|
+
export class ScaffoldTemplateError extends Schema.TaggedError<ScaffoldTemplateError>()(
|
|
6
|
+
"ScaffoldTemplateError",
|
|
7
|
+
{ message: Schema.String },
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
export type ScaffoldPlan = {
|
|
11
|
+
readonly action: "scaffold" | "unchanged";
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly destination: string;
|
|
14
|
+
readonly content?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const replaceUniqueTemplateMarker = (
|
|
18
|
+
template: string,
|
|
19
|
+
marker: string,
|
|
20
|
+
replacement: string,
|
|
21
|
+
): string => {
|
|
22
|
+
const parts = template.split(marker);
|
|
23
|
+
|
|
24
|
+
if (parts.length !== 2) {
|
|
25
|
+
throw new Error(`expected exactly one generated template marker: ${marker}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `${parts[0]}${replacement}${parts[1]}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const readScaffoldTemplate = Effect.fn("readScaffoldTemplate")(function* (
|
|
32
|
+
packageRoot: string,
|
|
33
|
+
templatePath: string,
|
|
34
|
+
) {
|
|
35
|
+
const fs = yield* FileSystem.FileSystem;
|
|
36
|
+
const path = yield* Path.Path;
|
|
37
|
+
const absolute = path.join(packageRoot, templatePath);
|
|
38
|
+
|
|
39
|
+
if (!(yield* fs.exists(absolute))) {
|
|
40
|
+
return yield* ScaffoldTemplateError.make({
|
|
41
|
+
message: `dev-kit scaffold template is missing: ${templatePath}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return yield* fs.readFileString(absolute);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Scaffolds are created once and repository-owned afterwards: an existing
|
|
49
|
+
// destination is never read, compared, updated, or removed, and `content` is
|
|
50
|
+
// only computed (and validated) when the file is actually being created.
|
|
51
|
+
export const planScaffold = Effect.fn("planScaffold")(function* <E, R>(options: {
|
|
52
|
+
readonly projectDir: string;
|
|
53
|
+
readonly path: string;
|
|
54
|
+
readonly content: Effect.Effect<string, E, R>;
|
|
55
|
+
}) {
|
|
56
|
+
const path = yield* Path.Path;
|
|
57
|
+
const destination = path.join(options.projectDir, ...options.path.split("/"));
|
|
58
|
+
const observed = yield* observeSymbolicLink(destination);
|
|
59
|
+
|
|
60
|
+
if (observed.kind !== "missing") {
|
|
61
|
+
return { action: "unchanged", path: options.path, destination } satisfies ScaffoldPlan;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
action: "scaffold",
|
|
66
|
+
path: options.path,
|
|
67
|
+
destination,
|
|
68
|
+
content: yield* options.content,
|
|
69
|
+
} satisfies ScaffoldPlan;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const applyScaffoldPlan = Effect.fn("applyScaffoldPlan")(function* (plan: ScaffoldPlan) {
|
|
73
|
+
if (plan.action !== "scaffold" || plan.content === undefined) return;
|
|
74
|
+
const fs = yield* FileSystem.FileSystem;
|
|
75
|
+
const path = yield* Path.Path;
|
|
76
|
+
|
|
77
|
+
yield* fs.makeDirectory(path.dirname(plan.destination), { recursive: true });
|
|
78
|
+
yield* fs.writeFileString(plan.destination, plan.content);
|
|
79
|
+
});
|
package/src/sync.ts
CHANGED
|
@@ -23,10 +23,15 @@ import {
|
|
|
23
23
|
digestSymlinkTarget,
|
|
24
24
|
digestText,
|
|
25
25
|
observePath,
|
|
26
|
-
observePathWithRawModes,
|
|
27
26
|
type ObservedPath,
|
|
28
27
|
} from "./path-digest.ts";
|
|
29
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
detectPackageManager,
|
|
30
|
+
PACKAGE_MANAGER_COMMANDS,
|
|
31
|
+
readDirectDependencyNames,
|
|
32
|
+
readProjectPackage,
|
|
33
|
+
type PackageManagerName,
|
|
34
|
+
} from "./project-package.ts";
|
|
30
35
|
import { acquireProjectProcessLock, PROJECT_PROCESS_LOCK_PATH } from "./project-process-lock.ts";
|
|
31
36
|
import {
|
|
32
37
|
AppliedStateSchema,
|
|
@@ -38,11 +43,11 @@ import {
|
|
|
38
43
|
type DevKitLock,
|
|
39
44
|
type ManagedAgentInstructionsOutput,
|
|
40
45
|
type ManagedClaudeInstructionsOutput,
|
|
41
|
-
type ManagedGeneratedFileOutput,
|
|
42
46
|
type ManagedOutput,
|
|
43
47
|
type ManagedSkillOutput,
|
|
44
48
|
type OwnershipReceipt,
|
|
45
49
|
} from "./project-state.ts";
|
|
50
|
+
import { applyScaffoldPlan, type ScaffoldPlan } from "./scaffold.ts";
|
|
46
51
|
import { parseSkillSelector } from "./skill-selector.ts";
|
|
47
52
|
import { DEV_KIT_VERSION } from "./tool-metadata.ts";
|
|
48
53
|
import {
|
|
@@ -50,12 +55,8 @@ import {
|
|
|
50
55
|
planVitePlusHooks,
|
|
51
56
|
type VitePlusHooksPlan,
|
|
52
57
|
} from "./vite-plus-hooks.ts";
|
|
53
|
-
import {
|
|
54
|
-
|
|
55
|
-
validateVitePlusQualitySupport,
|
|
56
|
-
VITE_PLUS_GITHUB_ACTIONS_PATH,
|
|
57
|
-
VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
|
|
58
|
-
} from "./vite-plus-quality.ts";
|
|
58
|
+
import { planVitePlusWorkflow } from "./vite-plus-workflow.ts";
|
|
59
|
+
import { planWorktrunkConfig } from "./worktrunk-config.ts";
|
|
59
60
|
|
|
60
61
|
export type SyncOptions = {
|
|
61
62
|
readonly manifestPath?: string;
|
|
@@ -98,17 +99,10 @@ type DesiredClaudeInstructionsOutput = ManagedClaudeInstructionsOutput & {
|
|
|
98
99
|
readonly linkTarget: string;
|
|
99
100
|
};
|
|
100
101
|
|
|
101
|
-
type DesiredGeneratedFileOutput = ManagedGeneratedFileOutput & {
|
|
102
|
-
readonly adoptIfExact: true;
|
|
103
|
-
readonly content: string;
|
|
104
|
-
readonly destination: string;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
102
|
type DesiredOutput =
|
|
108
103
|
| DesiredSkillOutput
|
|
109
104
|
| DesiredAgentInstructionsOutput
|
|
110
|
-
| DesiredClaudeInstructionsOutput
|
|
111
|
-
| DesiredGeneratedFileOutput;
|
|
105
|
+
| DesiredClaudeInstructionsOutput;
|
|
112
106
|
|
|
113
107
|
type SkillPlanAction =
|
|
114
108
|
| {
|
|
@@ -144,6 +138,8 @@ export type SkillPlan = {
|
|
|
144
138
|
readonly effectSource?: EffectSourcePlan;
|
|
145
139
|
readonly effectTsgo?: EffectTsgoPatchPlan;
|
|
146
140
|
readonly vitePlusHooks?: VitePlusHooksPlan;
|
|
141
|
+
readonly vitePlusWorkflow?: ScaffoldPlan;
|
|
142
|
+
readonly worktrunkConfig?: ScaffoldPlan;
|
|
147
143
|
readonly nextLock: DevKitLock;
|
|
148
144
|
readonly nextState: AppliedState;
|
|
149
145
|
readonly metadataChanged: boolean;
|
|
@@ -436,57 +432,6 @@ const renderVitePlusCommandPolicy = (
|
|
|
436
432
|
].join("\n");
|
|
437
433
|
};
|
|
438
434
|
|
|
439
|
-
const PACKAGE_MANAGER_COMMANDS = {
|
|
440
|
-
bun: { install: "bun install", label: "Bun" },
|
|
441
|
-
npm: { install: "npm install", label: "npm" },
|
|
442
|
-
pnpm: { install: "pnpm install", label: "pnpm" },
|
|
443
|
-
yarn: { install: "yarn install", label: "Yarn" },
|
|
444
|
-
} as const;
|
|
445
|
-
|
|
446
|
-
type PackageManagerName = keyof typeof PACKAGE_MANAGER_COMMANDS;
|
|
447
|
-
|
|
448
|
-
const packageManagerName = (declaration: string | undefined): PackageManagerName | undefined => {
|
|
449
|
-
const name = declaration?.split("@", 1)[0];
|
|
450
|
-
|
|
451
|
-
return name !== undefined && name in PACKAGE_MANAGER_COMMANDS
|
|
452
|
-
? (name as PackageManagerName)
|
|
453
|
-
: undefined;
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
const detectPackageManager = Effect.fn("detectPackageManager")(function* (
|
|
457
|
-
projectDir: string,
|
|
458
|
-
declaration: string | undefined,
|
|
459
|
-
) {
|
|
460
|
-
const declared = packageManagerName(declaration);
|
|
461
|
-
|
|
462
|
-
if (declared !== undefined || declaration !== undefined) return declared;
|
|
463
|
-
const fs = yield* FileSystem.FileSystem;
|
|
464
|
-
const path = yield* Path.Path;
|
|
465
|
-
const lockfiles: ReadonlyArray<readonly [PackageManagerName, ReadonlyArray<string>]> = [
|
|
466
|
-
["bun", ["bun.lock", "bun.lockb"]],
|
|
467
|
-
["npm", ["package-lock.json", "npm-shrinkwrap.json"]],
|
|
468
|
-
["pnpm", ["pnpm-lock.yaml"]],
|
|
469
|
-
["yarn", ["yarn.lock"]],
|
|
470
|
-
];
|
|
471
|
-
const detected: Array<PackageManagerName> = [];
|
|
472
|
-
|
|
473
|
-
for (const [manager, files] of lockfiles) {
|
|
474
|
-
let found = false;
|
|
475
|
-
|
|
476
|
-
for (const file of files) {
|
|
477
|
-
if (yield* fs.exists(path.join(projectDir, file))) {
|
|
478
|
-
found = true;
|
|
479
|
-
break;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (found) {
|
|
483
|
-
detected.push(manager);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return detected.length === 1 ? detected[0] : undefined;
|
|
488
|
-
});
|
|
489
|
-
|
|
490
435
|
const renderPackageScriptCommandPolicy = (
|
|
491
436
|
manager: PackageManagerName | undefined,
|
|
492
437
|
scripts: Readonly<Record<string, string>>,
|
|
@@ -746,16 +691,6 @@ const outputOwnershipIdentity = (output: ManagedOutput) =>
|
|
|
746
691
|
},
|
|
747
692
|
);
|
|
748
693
|
|
|
749
|
-
const usesRawFileModeDigests = (toolVersion: string): boolean => {
|
|
750
|
-
const match = /^(\d+)\.(\d+)\./.exec(toolVersion);
|
|
751
|
-
|
|
752
|
-
if (match === null) return false;
|
|
753
|
-
const major = Number(match[1]);
|
|
754
|
-
const minor = Number(match[2]);
|
|
755
|
-
|
|
756
|
-
return major === 0 && minor <= 6;
|
|
757
|
-
};
|
|
758
|
-
|
|
759
694
|
const validateInventory = Effect.fn("validateManagedInventory")(function* (
|
|
760
695
|
projectDir: string,
|
|
761
696
|
outputs: ReadonlyArray<ManagedOutput | OwnershipReceipt>,
|
|
@@ -904,23 +839,6 @@ guide doesn't cover, search through the source code in \`node_modules/effect/src
|
|
|
904
839
|
return `${devKitInstructions}\n`;
|
|
905
840
|
});
|
|
906
841
|
|
|
907
|
-
const readGeneratedFileTemplate = Effect.fn("readGeneratedFileTemplate")(function* (
|
|
908
|
-
packageRoot: string,
|
|
909
|
-
sourcePath: string,
|
|
910
|
-
) {
|
|
911
|
-
const fs = yield* FileSystem.FileSystem;
|
|
912
|
-
const path = yield* Path.Path;
|
|
913
|
-
const templatePath = path.join(packageRoot, sourcePath);
|
|
914
|
-
|
|
915
|
-
if ((yield* observePath(templatePath)).kind !== "file") {
|
|
916
|
-
return yield* InvalidProjectStateError.make({
|
|
917
|
-
message: `dev-kit generated file template is not a regular file: ${sourcePath}`,
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
return yield* fs.readFileString(templatePath);
|
|
922
|
-
});
|
|
923
|
-
|
|
924
842
|
const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
|
|
925
843
|
packageRoot: string,
|
|
926
844
|
projectDir: string,
|
|
@@ -938,9 +856,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
|
|
|
938
856
|
packageRoot,
|
|
939
857
|
projectDir,
|
|
940
858
|
sourceBySkill,
|
|
941
|
-
setup.vitePlus.
|
|
942
|
-
setup.vitePlus.quality.workflow.typecheck.length === 1 &&
|
|
943
|
-
setup.vitePlus.quality.workflow.typecheck[0] === "vp run typecheck",
|
|
859
|
+
setup.vitePlus.workflow.enabled,
|
|
944
860
|
targets,
|
|
945
861
|
);
|
|
946
862
|
|
|
@@ -980,32 +896,6 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
|
|
|
980
896
|
linkTarget,
|
|
981
897
|
});
|
|
982
898
|
}
|
|
983
|
-
if (setup.vitePlus.quality.workflow.enabled) {
|
|
984
|
-
const managed = yield* resolveManagedPath(projectDir, VITE_PLUS_GITHUB_ACTIONS_PATH);
|
|
985
|
-
const template = yield* readGeneratedFileTemplate(
|
|
986
|
-
packageRoot,
|
|
987
|
-
VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
|
|
988
|
-
);
|
|
989
|
-
const content = renderVitePlusWorkflowTemplate(template, {
|
|
990
|
-
devKitCommand:
|
|
991
|
-
projectDir === packageRoot
|
|
992
|
-
? "./bin/dev-kit.mjs apply --locked"
|
|
993
|
-
: "bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked",
|
|
994
|
-
workflow: setup.vitePlus.quality.workflow,
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
outputs.push({
|
|
998
|
-
resourceId: "setup:vite-plus-github-actions",
|
|
999
|
-
path: managed.relative,
|
|
1000
|
-
sourcePath: VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
|
|
1001
|
-
mode: "copy",
|
|
1002
|
-
kind: "file",
|
|
1003
|
-
digest: yield* digestFileContent(content),
|
|
1004
|
-
destination: managed.absolute,
|
|
1005
|
-
content,
|
|
1006
|
-
adoptIfExact: true,
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
899
|
const agentsTarget = targets.agents;
|
|
1010
900
|
const duplicateOutput = skills.find(
|
|
1011
901
|
(skill, index) => skills.findIndex((candidate) => candidate.name === skill.name) !== index,
|
|
@@ -1116,20 +1006,10 @@ const planDesiredOutputs = Effect.fn("planDesiredSkillOutputs")(function* (
|
|
|
1116
1006
|
observed.kind === locked.kind
|
|
1117
1007
|
? locked
|
|
1118
1008
|
: undefined;
|
|
1119
|
-
const rawModeObservation =
|
|
1120
|
-
matchingLockedOutput !== undefined &&
|
|
1121
|
-
observed.kind !== "missing" &&
|
|
1122
|
-
observed.digest !== matchingLockedOutput.digest &&
|
|
1123
|
-
currentLock !== undefined &&
|
|
1124
|
-
usesRawFileModeDigests(currentLock.toolVersion)
|
|
1125
|
-
? yield* observePathWithRawModes(output.destination)
|
|
1126
|
-
: undefined;
|
|
1127
1009
|
const lockedOwnsObserved =
|
|
1128
1010
|
matchingLockedOutput !== undefined &&
|
|
1129
1011
|
observed.kind !== "missing" &&
|
|
1130
|
-
|
|
1131
|
-
(rawModeObservation?.kind === matchingLockedOutput.kind &&
|
|
1132
|
-
rawModeObservation.digest === matchingLockedOutput.digest));
|
|
1012
|
+
observed.digest === matchingLockedOutput.digest;
|
|
1133
1013
|
|
|
1134
1014
|
if (output.resourceId === "setup:agent-instructions" && "content" in output) {
|
|
1135
1015
|
if (observed.kind === "missing") {
|
|
@@ -1217,7 +1097,7 @@ const planDesiredOutputs = Effect.fn("planDesiredSkillOutputs")(function* (
|
|
|
1217
1097
|
if (observed.kind === "missing") {
|
|
1218
1098
|
actions.push({ action: "create", desired: output, observed });
|
|
1219
1099
|
} else if (observed.kind === output.kind && observed.digest === output.digest) {
|
|
1220
|
-
if (sameReceipt || lockedOwnsObserved
|
|
1100
|
+
if (sameReceipt || lockedOwnsObserved) {
|
|
1221
1101
|
actions.push({ action: "unchanged", desired: output, observed, adopted: !sameReceipt });
|
|
1222
1102
|
} else {
|
|
1223
1103
|
actions.push({
|
|
@@ -1375,28 +1255,6 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
1375
1255
|
const packageRoot = yield* resolvePackageRoot();
|
|
1376
1256
|
const manifest = normalizeManifest(yield* readManifest(manifestManaged.absolute));
|
|
1377
1257
|
|
|
1378
|
-
const vitePlusQuality = manifest.setup.vitePlus.quality;
|
|
1379
|
-
const vitePlusQualityEnabled = vitePlusQuality.workflow.enabled;
|
|
1380
|
-
|
|
1381
|
-
if (vitePlusQualityEnabled) {
|
|
1382
|
-
if (!manifest.setup.effectTsgo.enabled) {
|
|
1383
|
-
return yield* InvalidProjectStateError.make({
|
|
1384
|
-
message:
|
|
1385
|
-
"setup.vitePlus.quality requires setup.effectTsgo.enabled so managed quality setup converges the Effect-patched compiler",
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
yield* validateVitePlusQualitySupport(
|
|
1389
|
-
projectDir,
|
|
1390
|
-
packageRoot,
|
|
1391
|
-
manifest.setup.effectTsgo.typescriptPackage,
|
|
1392
|
-
{
|
|
1393
|
-
workflow: {
|
|
1394
|
-
beforeChecks: vitePlusQuality.workflow.beforeChecks,
|
|
1395
|
-
typecheck: vitePlusQuality.workflow.typecheck,
|
|
1396
|
-
},
|
|
1397
|
-
},
|
|
1398
|
-
);
|
|
1399
|
-
}
|
|
1400
1258
|
const effectSource = manifest.setup.effectSource.enabled
|
|
1401
1259
|
? yield* planEffectSource({
|
|
1402
1260
|
packageName: manifest.setup.effectSource.packageName,
|
|
@@ -1415,6 +1273,17 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
1415
1273
|
const vitePlusHooks = manifest.setup.vitePlus.hooks.enabled
|
|
1416
1274
|
? yield* planVitePlusHooks(projectDir)
|
|
1417
1275
|
: undefined;
|
|
1276
|
+
const vitePlusWorkflow = manifest.setup.vitePlus.workflow.enabled
|
|
1277
|
+
? yield* planVitePlusWorkflow({
|
|
1278
|
+
packageRoot,
|
|
1279
|
+
projectDir,
|
|
1280
|
+
effectTsgoEnabled: manifest.setup.effectTsgo.enabled,
|
|
1281
|
+
typescriptPackage: manifest.setup.effectTsgo.typescriptPackage,
|
|
1282
|
+
})
|
|
1283
|
+
: undefined;
|
|
1284
|
+
const worktrunkConfig = manifest.setup.worktrunk.config.enabled
|
|
1285
|
+
? yield* planWorktrunkConfig(packageRoot, projectDir)
|
|
1286
|
+
: undefined;
|
|
1418
1287
|
const catalog = yield* loadSkillCatalog(packageRoot, projectDir);
|
|
1419
1288
|
const availableSkills = catalog.skills.map((skill) => skill.selector);
|
|
1420
1289
|
const skillFamilies = { ...SKILL_FAMILIES, ...catalog.families };
|
|
@@ -1521,16 +1390,6 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
1521
1390
|
digest: output.digest,
|
|
1522
1391
|
};
|
|
1523
1392
|
}
|
|
1524
|
-
if (output.resourceId === "setup:claude-instructions") {
|
|
1525
|
-
return {
|
|
1526
|
-
resourceId: output.resourceId,
|
|
1527
|
-
path: output.path,
|
|
1528
|
-
sourcePath: output.sourcePath,
|
|
1529
|
-
mode: output.mode,
|
|
1530
|
-
kind: output.kind,
|
|
1531
|
-
digest: output.digest,
|
|
1532
|
-
};
|
|
1533
|
-
}
|
|
1534
1393
|
|
|
1535
1394
|
return {
|
|
1536
1395
|
resourceId: output.resourceId,
|
|
@@ -1553,8 +1412,22 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
1553
1412
|
];
|
|
1554
1413
|
|
|
1555
1414
|
yield* validateReservedPaths(projectDir, reservedPaths, desired);
|
|
1556
|
-
const
|
|
1557
|
-
const
|
|
1415
|
+
const rawLock = yield* readOptionalStructuredFile(lockManaged.absolute, DevKitLockSchema);
|
|
1416
|
+
const rawState = yield* readOptionalStructuredFile(stateManaged.absolute, AppliedStateSchema);
|
|
1417
|
+
// Migration: dev-kit ≤0.14 owned the check workflow as a managed output. It
|
|
1418
|
+
// is a scaffold now, so stale lock entries and receipts are dropped on read —
|
|
1419
|
+
// releasing ownership to the repository instead of planning a removal.
|
|
1420
|
+
const dropRetiredOutputs = <O extends { readonly resourceId: string }>(
|
|
1421
|
+
outputs: ReadonlyArray<O>,
|
|
1422
|
+
) => outputs.filter((output) => output.resourceId !== "setup:vite-plus-github-actions");
|
|
1423
|
+
const currentLock =
|
|
1424
|
+
rawLock === undefined
|
|
1425
|
+
? undefined
|
|
1426
|
+
: { ...rawLock, outputs: dropRetiredOutputs(rawLock.outputs) };
|
|
1427
|
+
const currentState =
|
|
1428
|
+
rawState === undefined
|
|
1429
|
+
? undefined
|
|
1430
|
+
: { ...rawState, outputs: dropRetiredOutputs(rawState.outputs) };
|
|
1558
1431
|
|
|
1559
1432
|
yield* validateReservedPaths(projectDir, reservedPaths, [
|
|
1560
1433
|
...(currentLock?.outputs ?? []),
|
|
@@ -1601,6 +1474,8 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
1601
1474
|
...(effectSource === undefined ? {} : { effectSource }),
|
|
1602
1475
|
...(effectTsgo === undefined ? {} : { effectTsgo }),
|
|
1603
1476
|
...(vitePlusHooks === undefined ? {} : { vitePlusHooks }),
|
|
1477
|
+
...(vitePlusWorkflow === undefined ? {} : { vitePlusWorkflow }),
|
|
1478
|
+
...(worktrunkConfig === undefined ? {} : { worktrunkConfig }),
|
|
1604
1479
|
nextLock,
|
|
1605
1480
|
nextState: planned.nextState,
|
|
1606
1481
|
metadataChanged:
|
|
@@ -1627,7 +1502,9 @@ const operationalChangeCount = (plan: SkillPlan): number =>
|
|
|
1627
1502
|
plan.actions.filter((action) => action.action !== "unchanged").length +
|
|
1628
1503
|
(plan.effectSource?.action === "sync" ? 1 : 0) +
|
|
1629
1504
|
(plan.effectTsgo !== undefined && !plan.effectTsgo.alreadyPatched ? 1 : 0) +
|
|
1630
|
-
(plan.vitePlusHooks?.action === "configure" ? 1 : 0)
|
|
1505
|
+
(plan.vitePlusHooks?.action === "configure" ? 1 : 0) +
|
|
1506
|
+
(plan.vitePlusWorkflow?.action === "scaffold" ? 1 : 0) +
|
|
1507
|
+
(plan.worktrunkConfig?.action === "scaffold" ? 1 : 0);
|
|
1631
1508
|
|
|
1632
1509
|
const plannedChangeCount = (plan: SkillPlan): number => {
|
|
1633
1510
|
const operational = operationalChangeCount(plan);
|
|
@@ -1658,6 +1535,12 @@ export const printSkillPlan = Effect.fn("printSkillPlan")(function* (plan: Skill
|
|
|
1658
1535
|
if (plan.vitePlusHooks?.action === "configure") {
|
|
1659
1536
|
yield* printDetail(`+ Vite+ hooks → ${plan.vitePlusHooks.hooksPath}`);
|
|
1660
1537
|
}
|
|
1538
|
+
if (plan.vitePlusWorkflow?.action === "scaffold") {
|
|
1539
|
+
yield* printDetail(`+ scaffold check workflow → ${plan.vitePlusWorkflow.path}`);
|
|
1540
|
+
}
|
|
1541
|
+
if (plan.worktrunkConfig?.action === "scaffold") {
|
|
1542
|
+
yield* printDetail(`+ scaffold Worktrunk config → ${plan.worktrunkConfig.path}`);
|
|
1543
|
+
}
|
|
1661
1544
|
if (operationalChangeCount(plan) === 0 && plan.metadataChanged) {
|
|
1662
1545
|
yield* printDetail("+ Dev kit metadata");
|
|
1663
1546
|
}
|
|
@@ -1952,6 +1835,8 @@ export const runProjectSkillPlan = Effect.fn("runProjectSkillPlan")(function* (
|
|
|
1952
1835
|
effectSource: plan.effectSource,
|
|
1953
1836
|
effectTsgo: plan.effectTsgo,
|
|
1954
1837
|
vitePlusHooks: plan.vitePlusHooks,
|
|
1838
|
+
vitePlusWorkflow: plan.vitePlusWorkflow,
|
|
1839
|
+
worktrunkConfig: plan.worktrunkConfig,
|
|
1955
1840
|
nextLock: plan.nextLock,
|
|
1956
1841
|
nextState: plan.nextState,
|
|
1957
1842
|
});
|
|
@@ -1960,6 +1845,8 @@ export const runProjectSkillPlan = Effect.fn("runProjectSkillPlan")(function* (
|
|
|
1960
1845
|
effectSource: replanned.effectSource,
|
|
1961
1846
|
effectTsgo: replanned.effectTsgo,
|
|
1962
1847
|
vitePlusHooks: replanned.vitePlusHooks,
|
|
1848
|
+
vitePlusWorkflow: replanned.vitePlusWorkflow,
|
|
1849
|
+
worktrunkConfig: replanned.worktrunkConfig,
|
|
1963
1850
|
nextLock: replanned.nextLock,
|
|
1964
1851
|
nextState: replanned.nextState,
|
|
1965
1852
|
});
|
|
@@ -1981,6 +1868,12 @@ export const runProjectSkillPlan = Effect.fn("runProjectSkillPlan")(function* (
|
|
|
1981
1868
|
if (replanned.vitePlusHooks !== undefined) {
|
|
1982
1869
|
yield* applyVitePlusHooksPlan(replanned.vitePlusHooks);
|
|
1983
1870
|
}
|
|
1871
|
+
if (replanned.vitePlusWorkflow !== undefined) {
|
|
1872
|
+
yield* applyScaffoldPlan(replanned.vitePlusWorkflow);
|
|
1873
|
+
}
|
|
1874
|
+
if (replanned.worktrunkConfig !== undefined) {
|
|
1875
|
+
yield* applyScaffoldPlan(replanned.worktrunkConfig);
|
|
1876
|
+
}
|
|
1984
1877
|
yield* applyPlannedSkillChanges(replanned);
|
|
1985
1878
|
}),
|
|
1986
1879
|
);
|