@danieljvdm/dev-kit 0.5.0 → 0.7.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 +161 -68
- package/dev-kit.example.jsonc +8 -3
- package/package.json +19 -15
- package/schema/dev-kit.schema.json +52 -0
- package/skill-sources.jsonc +8 -12
- package/skill-sources.lock.json +3 -9
- package/skills/dev-kit/SKILL.md +74 -24
- package/skills/effect-atom-data-fetching/SKILL.md +40 -0
- package/skills/effect-atom-data-fetching/agents/openai.yaml +4 -0
- package/skills/effect-atom-data-fetching/references/cache-lifecycle.md +72 -0
- package/skills/effect-atom-data-fetching/references/http-and-invalidation.md +93 -0
- package/skills/effect-atom-data-fetching/references/tanstack-start.md +69 -0
- package/skills/effect-atom-data-fetching/references/testing.md +63 -0
- package/skills/effect-ts/agents/openai.yaml +0 -1
- package/skills/effect-ts/references/audit-services.md +11 -11
- package/skills/effect-ts/references/guide-effect.md +56 -69
- package/skills/effect-ts/references/guide-error-handling.md +64 -73
- package/skills/effect-ts/references/guide-layers.md +187 -215
- package/skills/effect-ts/references/guide-observability.md +91 -116
- package/skills/effect-ts/references/guide-retries.md +32 -44
- package/skills/effect-ts/references/guide-schedule.md +26 -40
- package/skills/effect-ts/references/guide-schema.md +50 -57
- package/skills/effect-ts/references/guide-sql.md +47 -50
- package/skills/effect-ts/references/guide-testing.md +96 -98
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +7 -7
- package/skills/effect-ts/references/version-and-source.md +0 -1
- package/src/bin/dev-kit.ts +61 -28
- package/src/catalog-manager.ts +86 -34
- package/src/catalog.ts +72 -34
- package/src/cli-ui.ts +20 -16
- package/src/effect-source.ts +49 -19
- package/src/effect-tsgo.ts +66 -35
- package/src/gitignore.ts +19 -6
- package/src/index.ts +12 -0
- package/src/manifest.ts +51 -3
- package/src/node-symbolic-link.ts +3 -0
- package/src/oxlint-plugin-effect.js +3 -0
- package/src/oxlint-plugin-style.d.ts +8 -0
- package/src/oxlint-plugin-style.js +8 -0
- package/src/oxlint.js +14 -0
- package/src/oxlint.ts +14 -0
- package/src/package-skill-source.ts +190 -75
- package/src/path-digest.ts +37 -10
- package/src/project-package.ts +59 -0
- package/src/project-process-lock.ts +19 -12
- package/src/project-state.ts +29 -2
- package/src/skill-manager.ts +134 -55
- package/src/skill-selector.ts +8 -2
- package/src/source-manifest.ts +2 -6
- package/src/sync.ts +491 -121
- package/src/vendor.ts +112 -42
- package/src/vite-plus-hooks.ts +174 -0
- package/src/vite-plus-quality.ts +49 -0
- package/templates/AGENTS.md +9 -0
- package/templates/vite-plus/github-actions-check.yml +44 -0
- package/templates/vite-plus/vite.config.ts +22 -0
package/src/path-digest.ts
CHANGED
|
@@ -2,9 +2,7 @@ import { Crypto, Effect, Encoding, FileSystem, Path, type PlatformError, Schema
|
|
|
2
2
|
|
|
3
3
|
import { observeSymbolicLink } from "./node-symbolic-link.ts";
|
|
4
4
|
|
|
5
|
-
export const DigestSchema = Schema.String.check(
|
|
6
|
-
Schema.isPattern(/^sha256:[0-9a-f]{64}$/),
|
|
7
|
-
);
|
|
5
|
+
export const DigestSchema = Schema.String.check(Schema.isPattern(/^sha256:[0-9a-f]{64}$/));
|
|
8
6
|
export type Digest = typeof DigestSchema.Type;
|
|
9
7
|
|
|
10
8
|
export type ObservedPath =
|
|
@@ -26,21 +24,29 @@ export class PathInspectionError extends Schema.TaggedErrorClass<PathInspectionE
|
|
|
26
24
|
|
|
27
25
|
const textEncoder = new TextEncoder();
|
|
28
26
|
|
|
27
|
+
// Git preserves only the executable distinction for regular files. Canonicalizing
|
|
28
|
+
// the remaining bits keeps digests stable across checkout and copy umasks.
|
|
29
|
+
const canonicalFileMode = (mode: number): number => ((mode & 0o111) === 0 ? 0o644 : 0o755);
|
|
30
|
+
|
|
29
31
|
const frame = (value: string | Uint8Array): Uint8Array => {
|
|
30
32
|
const bytes = typeof value === "string" ? textEncoder.encode(value) : value;
|
|
31
33
|
const framed = new Uint8Array(4 + bytes.length);
|
|
34
|
+
|
|
32
35
|
new DataView(framed.buffer).setUint32(0, bytes.length);
|
|
33
36
|
framed.set(bytes, 4);
|
|
37
|
+
|
|
34
38
|
return framed;
|
|
35
39
|
};
|
|
36
40
|
|
|
37
41
|
const concatenate = (chunks: ReadonlyArray<Uint8Array>): Uint8Array => {
|
|
38
42
|
const combined = new Uint8Array(chunks.reduce((length, chunk) => length + chunk.length, 0));
|
|
39
43
|
let offset = 0;
|
|
44
|
+
|
|
40
45
|
for (const chunk of chunks) {
|
|
41
46
|
combined.set(chunk, offset);
|
|
42
47
|
offset += chunk.length;
|
|
43
48
|
}
|
|
49
|
+
|
|
44
50
|
return combined;
|
|
45
51
|
};
|
|
46
52
|
|
|
@@ -48,10 +54,13 @@ const compareUtf8 = (left: string, right: string): number => {
|
|
|
48
54
|
const leftBytes = textEncoder.encode(left);
|
|
49
55
|
const rightBytes = textEncoder.encode(right);
|
|
50
56
|
const sharedLength = Math.min(leftBytes.length, rightBytes.length);
|
|
57
|
+
|
|
51
58
|
for (let index = 0; index < sharedLength; index += 1) {
|
|
52
59
|
const difference = (leftBytes[index] ?? 0) - (rightBytes[index] ?? 0);
|
|
60
|
+
|
|
53
61
|
if (difference !== 0) return difference;
|
|
54
62
|
}
|
|
63
|
+
|
|
55
64
|
return leftBytes.length - rightBytes.length;
|
|
56
65
|
};
|
|
57
66
|
|
|
@@ -60,12 +69,17 @@ const digestFrames = Effect.fn("digestPathFrames")(function* (
|
|
|
60
69
|
) {
|
|
61
70
|
const crypto = yield* Crypto.Crypto;
|
|
62
71
|
const digest = yield* crypto.digest("SHA-256", concatenate(values.map(frame)));
|
|
72
|
+
|
|
63
73
|
return `sha256:${Encoding.encodeHex(digest)}`;
|
|
64
74
|
});
|
|
65
75
|
|
|
66
76
|
const digestFileSystemPath = Effect.fn("digestFileSystemPath")(function* (
|
|
67
77
|
absolutePath: string,
|
|
68
|
-
): Effect.fn.Return<
|
|
78
|
+
): Effect.fn.Return<
|
|
79
|
+
ObservedPath,
|
|
80
|
+
PlatformError.PlatformError | PathInspectionError,
|
|
81
|
+
FileSystem.FileSystem | Path.Path | Crypto.Crypto
|
|
82
|
+
> {
|
|
69
83
|
const fs = yield* FileSystem.FileSystem;
|
|
70
84
|
const path = yield* Path.Path;
|
|
71
85
|
const symbolicLink = yield* observeSymbolicLink(absolutePath);
|
|
@@ -78,11 +92,14 @@ const digestFileSystemPath = Effect.fn("digestFileSystemPath")(function* (
|
|
|
78
92
|
};
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
const info = yield* fs
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
const info = yield* fs
|
|
96
|
+
.stat(absolutePath)
|
|
97
|
+
.pipe(
|
|
98
|
+
Effect.catch((error) =>
|
|
99
|
+
error.reason._tag === "NotFound" ? Effect.void : Effect.fail(error),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
86
103
|
if (info === undefined) return { kind: "missing" };
|
|
87
104
|
|
|
88
105
|
if (info.type === "File") {
|
|
@@ -90,7 +107,7 @@ const digestFileSystemPath = Effect.fn("digestFileSystemPath")(function* (
|
|
|
90
107
|
kind: "file",
|
|
91
108
|
digest: yield* digestFrames([
|
|
92
109
|
"file-v1",
|
|
93
|
-
String(info.mode
|
|
110
|
+
String(canonicalFileMode(info.mode)),
|
|
94
111
|
yield* fs.readFile(absolutePath),
|
|
95
112
|
]),
|
|
96
113
|
};
|
|
@@ -99,9 +116,11 @@ const digestFileSystemPath = Effect.fn("digestFileSystemPath")(function* (
|
|
|
99
116
|
if (info.type === "Directory") {
|
|
100
117
|
const entries = (yield* fs.readDirectory(absolutePath)).sort(compareUtf8);
|
|
101
118
|
const frames: Array<string | Uint8Array> = ["directory-v1"];
|
|
119
|
+
|
|
102
120
|
for (const entry of entries) {
|
|
103
121
|
const childPath = path.join(absolutePath, entry);
|
|
104
122
|
const child = yield* digestFileSystemPath(childPath);
|
|
123
|
+
|
|
105
124
|
if (child.kind === "missing") {
|
|
106
125
|
return yield* new PathInspectionError({
|
|
107
126
|
path: childPath,
|
|
@@ -111,6 +130,7 @@ const digestFileSystemPath = Effect.fn("digestFileSystemPath")(function* (
|
|
|
111
130
|
}
|
|
112
131
|
frames.push(entry, child.kind, child.digest);
|
|
113
132
|
}
|
|
133
|
+
|
|
114
134
|
return { kind: "directory", digest: yield* digestFrames(frames) };
|
|
115
135
|
}
|
|
116
136
|
|
|
@@ -135,6 +155,13 @@ export const digestText = Effect.fn("digestText")(function* (value: string) {
|
|
|
135
155
|
return yield* digestFrames(["text-v1", value]);
|
|
136
156
|
});
|
|
137
157
|
|
|
158
|
+
export const digestFileContent = Effect.fn("digestFileContent")(function* (
|
|
159
|
+
value: string,
|
|
160
|
+
mode = 0o644,
|
|
161
|
+
) {
|
|
162
|
+
return yield* digestFrames(["file-v1", String(canonicalFileMode(mode)), value]);
|
|
163
|
+
});
|
|
164
|
+
|
|
138
165
|
export const digestSymlinkTarget = Effect.fn("digestSymlinkTarget")(function* (target: string) {
|
|
139
166
|
return yield* digestFrames(["symlink-v1", target]);
|
|
140
167
|
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
export class ProjectPackageError extends Schema.TaggedErrorClass<ProjectPackageError>()(
|
|
4
|
+
"ProjectPackageError",
|
|
5
|
+
{ message: Schema.String },
|
|
6
|
+
) {}
|
|
7
|
+
|
|
8
|
+
const ProjectPackageSchema = Schema.fromJsonString(
|
|
9
|
+
Schema.Struct({
|
|
10
|
+
name: Schema.optional(Schema.String),
|
|
11
|
+
scripts: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
12
|
+
dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
13
|
+
devDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
14
|
+
optionalDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
15
|
+
peerDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const readProjectPackage = Effect.fn("readProjectPackage")(function* (projectDir: string) {
|
|
20
|
+
const fs = yield* FileSystem.FileSystem;
|
|
21
|
+
const path = yield* Path.Path;
|
|
22
|
+
const manifestPath = path.join(projectDir, "package.json");
|
|
23
|
+
|
|
24
|
+
if (!(yield* fs.exists(manifestPath))) {
|
|
25
|
+
return yield* new ProjectPackageError({ message: `package.json not found: ${manifestPath}` });
|
|
26
|
+
}
|
|
27
|
+
const manifest = yield* fs.readFileString(manifestPath).pipe(
|
|
28
|
+
Effect.flatMap(Schema.decodeUnknownEffect(ProjectPackageSchema)),
|
|
29
|
+
Effect.mapError(
|
|
30
|
+
() =>
|
|
31
|
+
new ProjectPackageError({
|
|
32
|
+
message: `invalid project package.json: ${manifestPath}`,
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return manifest;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const readDirectDependencyNames = Effect.fn("readDirectDependencyNames")(function* (
|
|
41
|
+
projectDir: string,
|
|
42
|
+
) {
|
|
43
|
+
const manifest = yield* readProjectPackage(projectDir).pipe(
|
|
44
|
+
Effect.catchTag("ProjectPackageError", (error) =>
|
|
45
|
+
error.message.startsWith("package.json not found:") ? Effect.void : Effect.fail(error),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (manifest === undefined) return [];
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
...new Set([
|
|
53
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
54
|
+
...Object.keys(manifest.devDependencies ?? {}),
|
|
55
|
+
...Object.keys(manifest.optionalDependencies ?? {}),
|
|
56
|
+
...Object.keys(manifest.peerDependencies ?? {}),
|
|
57
|
+
]),
|
|
58
|
+
].sort();
|
|
59
|
+
});
|
|
@@ -38,15 +38,18 @@ export const acquireProjectProcessLock = Effect.fn("acquireProjectProcessLock")(
|
|
|
38
38
|
return yield* Effect.acquireRelease(
|
|
39
39
|
Effect.gen(function* () {
|
|
40
40
|
yield* fs.makeDirectory(stateDir, { recursive: true });
|
|
41
|
+
|
|
41
42
|
return yield* Effect.uninterruptible(
|
|
42
43
|
Effect.gen(function* () {
|
|
43
|
-
yield* fs
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
yield* fs
|
|
45
|
+
.makeDirectory(lockDir)
|
|
46
|
+
.pipe(
|
|
47
|
+
Effect.mapError((error) =>
|
|
48
|
+
error.reason._tag === "AlreadyExists"
|
|
49
|
+
? new ProjectAlreadyLockedError({ path: lockDir })
|
|
50
|
+
: error,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
50
53
|
yield* fs.writeFileString(ownerPath, ownerContents).pipe(
|
|
51
54
|
Effect.catchCause((writeCause) =>
|
|
52
55
|
fs.remove(lockDir, { recursive: true, force: true }).pipe(
|
|
@@ -57,17 +60,21 @@ export const acquireProjectProcessLock = Effect.fn("acquireProjectProcessLock")(
|
|
|
57
60
|
),
|
|
58
61
|
),
|
|
59
62
|
);
|
|
63
|
+
|
|
60
64
|
return { lockDir, ownerContents };
|
|
61
65
|
}),
|
|
62
66
|
);
|
|
63
67
|
}),
|
|
64
68
|
({ lockDir: acquiredLockDir, ownerContents }) =>
|
|
65
69
|
Effect.gen(function* () {
|
|
66
|
-
const currentOwner = yield* fs
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const currentOwner = yield* fs
|
|
71
|
+
.readFileString(ownerPath)
|
|
72
|
+
.pipe(
|
|
73
|
+
Effect.catch((error) =>
|
|
74
|
+
error.reason._tag === "NotFound" ? Effect.void : Effect.fail(error),
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
|
|
71
78
|
if (currentOwner === ownerContents) {
|
|
72
79
|
yield* fs.remove(acquiredLockDir, { recursive: true, force: true });
|
|
73
80
|
}
|
package/src/project-state.ts
CHANGED
|
@@ -29,7 +29,17 @@ export const ManagedSkillOutputSchema = Schema.Struct({
|
|
|
29
29
|
});
|
|
30
30
|
export type ManagedSkillOutput = typeof ManagedSkillOutputSchema.Type;
|
|
31
31
|
|
|
32
|
-
export const
|
|
32
|
+
export const ManagedAgentInstructionsOutputSchema = Schema.Struct({
|
|
33
|
+
resourceId: Schema.Literal("setup:agent-instructions"),
|
|
34
|
+
path: Schema.String,
|
|
35
|
+
sourcePath: Schema.String,
|
|
36
|
+
mode: Schema.Literal("copy"),
|
|
37
|
+
kind: Schema.Literal("file"),
|
|
38
|
+
digest: DigestSchema,
|
|
39
|
+
});
|
|
40
|
+
export type ManagedAgentInstructionsOutput = typeof ManagedAgentInstructionsOutputSchema.Type;
|
|
41
|
+
|
|
42
|
+
export const ManagedClaudeInstructionsOutputSchema = Schema.Struct({
|
|
33
43
|
resourceId: Schema.Literal("setup:claude-instructions"),
|
|
34
44
|
path: Schema.String,
|
|
35
45
|
sourcePath: Schema.String,
|
|
@@ -37,11 +47,28 @@ export const ManagedInstructionOutputSchema = Schema.Struct({
|
|
|
37
47
|
kind: Schema.Literal("symlink"),
|
|
38
48
|
digest: DigestSchema,
|
|
39
49
|
});
|
|
50
|
+
export type ManagedClaudeInstructionsOutput = typeof ManagedClaudeInstructionsOutputSchema.Type;
|
|
51
|
+
|
|
52
|
+
export const ManagedGeneratedFileOutputSchema = Schema.Struct({
|
|
53
|
+
resourceId: Schema.Literals(["setup:vite-plus-config", "setup:vite-plus-github-actions"]),
|
|
54
|
+
path: Schema.String,
|
|
55
|
+
sourcePath: Schema.String,
|
|
56
|
+
mode: Schema.Literal("copy"),
|
|
57
|
+
kind: Schema.Literal("file"),
|
|
58
|
+
digest: DigestSchema,
|
|
59
|
+
});
|
|
60
|
+
export type ManagedGeneratedFileOutput = typeof ManagedGeneratedFileOutputSchema.Type;
|
|
61
|
+
|
|
62
|
+
export const ManagedInstructionOutputSchema = Schema.Union([
|
|
63
|
+
ManagedAgentInstructionsOutputSchema,
|
|
64
|
+
ManagedClaudeInstructionsOutputSchema,
|
|
65
|
+
]);
|
|
40
66
|
export type ManagedInstructionOutput = typeof ManagedInstructionOutputSchema.Type;
|
|
41
67
|
|
|
42
68
|
export const ManagedOutputSchema = Schema.Union([
|
|
43
69
|
ManagedSkillOutputSchema,
|
|
44
70
|
ManagedInstructionOutputSchema,
|
|
71
|
+
ManagedGeneratedFileOutputSchema,
|
|
45
72
|
]);
|
|
46
73
|
export type ManagedOutput = typeof ManagedOutputSchema.Type;
|
|
47
74
|
|
|
@@ -79,7 +106,7 @@ export const OwnershipReceiptSchema = Schema.Struct({
|
|
|
79
106
|
resourceId: Schema.String,
|
|
80
107
|
path: Schema.String,
|
|
81
108
|
mode: Schema.Literals(["copy", "symlink"]),
|
|
82
|
-
kind: Schema.Literals(["directory", "symlink"]),
|
|
109
|
+
kind: Schema.Literals(["file", "directory", "symlink"]),
|
|
83
110
|
digest: DigestSchema,
|
|
84
111
|
});
|
|
85
112
|
export type OwnershipReceipt = typeof OwnershipReceiptSchema.Type;
|