@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.
Files changed (56) hide show
  1. package/README.md +161 -68
  2. package/dev-kit.example.jsonc +8 -3
  3. package/package.json +19 -15
  4. package/schema/dev-kit.schema.json +52 -0
  5. package/skill-sources.jsonc +8 -12
  6. package/skill-sources.lock.json +3 -9
  7. package/skills/dev-kit/SKILL.md +74 -24
  8. package/skills/effect-atom-data-fetching/SKILL.md +40 -0
  9. package/skills/effect-atom-data-fetching/agents/openai.yaml +4 -0
  10. package/skills/effect-atom-data-fetching/references/cache-lifecycle.md +72 -0
  11. package/skills/effect-atom-data-fetching/references/http-and-invalidation.md +93 -0
  12. package/skills/effect-atom-data-fetching/references/tanstack-start.md +69 -0
  13. package/skills/effect-atom-data-fetching/references/testing.md +63 -0
  14. package/skills/effect-ts/agents/openai.yaml +0 -1
  15. package/skills/effect-ts/references/audit-services.md +11 -11
  16. package/skills/effect-ts/references/guide-effect.md +56 -69
  17. package/skills/effect-ts/references/guide-error-handling.md +64 -73
  18. package/skills/effect-ts/references/guide-layers.md +187 -215
  19. package/skills/effect-ts/references/guide-observability.md +91 -116
  20. package/skills/effect-ts/references/guide-retries.md +32 -44
  21. package/skills/effect-ts/references/guide-schedule.md +26 -40
  22. package/skills/effect-ts/references/guide-schema.md +50 -57
  23. package/skills/effect-ts/references/guide-sql.md +47 -50
  24. package/skills/effect-ts/references/guide-testing.md +96 -98
  25. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +7 -7
  26. package/skills/effect-ts/references/version-and-source.md +0 -1
  27. package/src/bin/dev-kit.ts +61 -28
  28. package/src/catalog-manager.ts +86 -34
  29. package/src/catalog.ts +72 -34
  30. package/src/cli-ui.ts +20 -16
  31. package/src/effect-source.ts +49 -19
  32. package/src/effect-tsgo.ts +66 -35
  33. package/src/gitignore.ts +19 -6
  34. package/src/index.ts +12 -0
  35. package/src/manifest.ts +51 -3
  36. package/src/node-symbolic-link.ts +3 -0
  37. package/src/oxlint-plugin-effect.js +3 -0
  38. package/src/oxlint-plugin-style.d.ts +8 -0
  39. package/src/oxlint-plugin-style.js +8 -0
  40. package/src/oxlint.js +14 -0
  41. package/src/oxlint.ts +14 -0
  42. package/src/package-skill-source.ts +190 -75
  43. package/src/path-digest.ts +37 -10
  44. package/src/project-package.ts +59 -0
  45. package/src/project-process-lock.ts +19 -12
  46. package/src/project-state.ts +29 -2
  47. package/src/skill-manager.ts +134 -55
  48. package/src/skill-selector.ts +8 -2
  49. package/src/source-manifest.ts +2 -6
  50. package/src/sync.ts +491 -121
  51. package/src/vendor.ts +112 -42
  52. package/src/vite-plus-hooks.ts +174 -0
  53. package/src/vite-plus-quality.ts +49 -0
  54. package/templates/AGENTS.md +9 -0
  55. package/templates/vite-plus/github-actions-check.yml +44 -0
  56. package/templates/vite-plus/vite.config.ts +22 -0
@@ -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<ObservedPath, PlatformError.PlatformError | PathInspectionError, FileSystem.FileSystem | Path.Path | Crypto.Crypto> {
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.stat(absolutePath).pipe(
82
- Effect.catch((error) =>
83
- error.reason._tag === "NotFound" ? Effect.void : Effect.fail(error),
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 & 0o777),
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.makeDirectory(lockDir).pipe(
44
- Effect.mapError((error) =>
45
- error.reason._tag === "AlreadyExists"
46
- ? new ProjectAlreadyLockedError({ path: lockDir })
47
- : error,
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.readFileString(ownerPath).pipe(
67
- Effect.catch((error) =>
68
- error.reason._tag === "NotFound" ? Effect.void : Effect.fail(error),
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
  }
@@ -29,7 +29,17 @@ export const ManagedSkillOutputSchema = Schema.Struct({
29
29
  });
30
30
  export type ManagedSkillOutput = typeof ManagedSkillOutputSchema.Type;
31
31
 
32
- export const ManagedInstructionOutputSchema = Schema.Struct({
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;