@danieljvdm/dev-kit 0.6.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 +123 -56
- package/dev-kit.example.jsonc +7 -3
- package/package.json +19 -16
- package/schema/dev-kit.schema.json +38 -0
- package/skill-sources.jsonc +8 -12
- package/skill-sources.lock.json +3 -9
- package/skills/dev-kit/SKILL.md +52 -17
- 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 +71 -33
- 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 +6 -0
- package/src/manifest.ts +38 -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 +189 -52
- package/src/path-digest.ts +31 -11
- package/src/project-package.ts +44 -19
- package/src/project-process-lock.ts +19 -12
- package/src/project-state.ts +11 -0
- 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 +371 -103
- package/src/vendor.ts +112 -42
- package/src/vite-plus-hooks.ts +174 -0
- package/src/vite-plus-quality.ts +49 -0
- package/templates/vite-plus/github-actions-check.yml +44 -0
- package/templates/vite-plus/vite.config.ts +22 -0
package/src/project-package.ts
CHANGED
|
@@ -5,30 +5,55 @@ export class ProjectPackageError extends Schema.TaggedErrorClass<ProjectPackageE
|
|
|
5
5
|
{ message: Schema.String },
|
|
6
6
|
) {}
|
|
7
7
|
|
|
8
|
-
const ProjectPackageSchema = Schema.fromJsonString(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
);
|
|
14
18
|
|
|
15
|
-
export const
|
|
16
|
-
projectDir: string,
|
|
17
|
-
) {
|
|
19
|
+
export const readProjectPackage = Effect.fn("readProjectPackage")(function* (projectDir: string) {
|
|
18
20
|
const fs = yield* FileSystem.FileSystem;
|
|
19
21
|
const path = yield* Path.Path;
|
|
20
22
|
const manifestPath = path.join(projectDir, "package.json");
|
|
21
|
-
|
|
23
|
+
|
|
24
|
+
if (!(yield* fs.exists(manifestPath))) {
|
|
25
|
+
return yield* new ProjectPackageError({ message: `package.json not found: ${manifestPath}` });
|
|
26
|
+
}
|
|
22
27
|
const manifest = yield* fs.readFileString(manifestPath).pipe(
|
|
23
28
|
Effect.flatMap(Schema.decodeUnknownEffect(ProjectPackageSchema)),
|
|
24
|
-
Effect.mapError(
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
Effect.mapError(
|
|
30
|
+
() =>
|
|
31
|
+
new ProjectPackageError({
|
|
32
|
+
message: `invalid project package.json: ${manifestPath}`,
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
27
35
|
);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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();
|
|
34
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
|
@@ -49,6 +49,16 @@ export const ManagedClaudeInstructionsOutputSchema = Schema.Struct({
|
|
|
49
49
|
});
|
|
50
50
|
export type ManagedClaudeInstructionsOutput = typeof ManagedClaudeInstructionsOutputSchema.Type;
|
|
51
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
|
+
|
|
52
62
|
export const ManagedInstructionOutputSchema = Schema.Union([
|
|
53
63
|
ManagedAgentInstructionsOutputSchema,
|
|
54
64
|
ManagedClaudeInstructionsOutputSchema,
|
|
@@ -58,6 +68,7 @@ export type ManagedInstructionOutput = typeof ManagedInstructionOutputSchema.Typ
|
|
|
58
68
|
export const ManagedOutputSchema = Schema.Union([
|
|
59
69
|
ManagedSkillOutputSchema,
|
|
60
70
|
ManagedInstructionOutputSchema,
|
|
71
|
+
ManagedGeneratedFileOutputSchema,
|
|
61
72
|
]);
|
|
62
73
|
export type ManagedOutput = typeof ManagedOutputSchema.Type;
|
|
63
74
|
|
package/src/skill-manager.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { applyEdits, modify, parse as parseJsonc, type ParseError } from "jsonc-parser";
|
|
2
1
|
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
3
2
|
import { Prompt } from "effect/unstable/cli";
|
|
3
|
+
import { applyEdits, modify, parse as parseJsonc, type ParseError } from "jsonc-parser";
|
|
4
4
|
|
|
5
5
|
import { loadSkillCatalog } from "./catalog.ts";
|
|
6
6
|
import { isInteractiveTerminal, printDetail, printLine, printStatus } from "./cli-ui.ts";
|
|
7
|
+
import { patchProjectGitignore } from "./gitignore.ts";
|
|
7
8
|
import { DevKitManifestSchema } from "./manifest.ts";
|
|
8
9
|
import { observeSymbolicLink } from "./node-symbolic-link.ts";
|
|
9
10
|
import { runProjectSkillPlan } from "./sync.ts";
|
|
10
|
-
import { patchProjectGitignore } from "./gitignore.ts";
|
|
11
11
|
|
|
12
|
-
class SkillManagerError extends Schema.TaggedErrorClass<SkillManagerError>()(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
) {}
|
|
12
|
+
class SkillManagerError extends Schema.TaggedErrorClass<SkillManagerError>()("SkillManagerError", {
|
|
13
|
+
message: Schema.String,
|
|
14
|
+
}) {}
|
|
16
15
|
|
|
17
16
|
type ManagerOptions = {
|
|
18
17
|
readonly projectDir?: string;
|
|
@@ -22,16 +21,16 @@ type ManagerOptions = {
|
|
|
22
21
|
|
|
23
22
|
const packageRoot = Effect.fn("skillManagerPackageRoot")(function* () {
|
|
24
23
|
const path = yield* Path.Path;
|
|
24
|
+
|
|
25
25
|
return path.resolve(path.dirname(yield* path.fromFileUrl(new URL(import.meta.url))), "..");
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (
|
|
29
|
-
options: ManagerOptions,
|
|
30
|
-
) {
|
|
28
|
+
const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (options: ManagerOptions) {
|
|
31
29
|
const fs = yield* FileSystem.FileSystem;
|
|
32
30
|
const path = yield* Path.Path;
|
|
33
31
|
const projectDir = path.resolve(options.projectDir ?? ".");
|
|
34
32
|
const candidate = options.manifestPath ?? "dev-kit.jsonc";
|
|
33
|
+
|
|
35
34
|
if (candidate.length === 0 || path.isAbsolute(candidate)) {
|
|
36
35
|
return yield* new SkillManagerError({
|
|
37
36
|
message: "--manifest must be a non-empty project-relative path",
|
|
@@ -39,16 +38,14 @@ const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (
|
|
|
39
38
|
}
|
|
40
39
|
const manifestPath = path.resolve(projectDir, candidate);
|
|
41
40
|
const relative = path.relative(projectDir, manifestPath);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
relative.startsWith(`..${path.sep}`) ||
|
|
45
|
-
path.isAbsolute(relative)
|
|
46
|
-
) {
|
|
41
|
+
|
|
42
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
47
43
|
return yield* new SkillManagerError({
|
|
48
44
|
message: "--manifest must resolve inside the project",
|
|
49
45
|
});
|
|
50
46
|
}
|
|
51
47
|
let ancestor = projectDir;
|
|
48
|
+
|
|
52
49
|
for (const segment of relative.split(path.sep).slice(0, -1)) {
|
|
53
50
|
ancestor = path.join(ancestor, segment);
|
|
54
51
|
if ((yield* observeSymbolicLink(ancestor)).kind === "symlink") {
|
|
@@ -58,12 +55,14 @@ const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (
|
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
57
|
const destination = yield* observeSymbolicLink(manifestPath);
|
|
58
|
+
|
|
61
59
|
if (destination.kind === "symlink") {
|
|
62
60
|
return yield* new SkillManagerError({ message: `manifest is a symlink: ${relative}` });
|
|
63
61
|
}
|
|
64
62
|
if (destination.kind === "not-symlink" && (yield* fs.stat(manifestPath)).type !== "File") {
|
|
65
63
|
return yield* new SkillManagerError({ message: `manifest is not a regular file: ${relative}` });
|
|
66
64
|
}
|
|
65
|
+
|
|
67
66
|
return {
|
|
68
67
|
projectDir,
|
|
69
68
|
manifestPath,
|
|
@@ -73,31 +72,45 @@ const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (
|
|
|
73
72
|
const renderDefaultManifest = (projectDir: string, manifestPath: string, path: Path.Path) => {
|
|
74
73
|
const rawSchemaPath = path.relative(
|
|
75
74
|
path.dirname(manifestPath),
|
|
76
|
-
path.join(
|
|
75
|
+
path.join(
|
|
76
|
+
projectDir,
|
|
77
|
+
"node_modules",
|
|
78
|
+
"@danieljvdm",
|
|
79
|
+
"dev-kit",
|
|
80
|
+
"schema",
|
|
81
|
+
"dev-kit.schema.json",
|
|
82
|
+
),
|
|
77
83
|
);
|
|
78
|
-
const portableSchemaPath =
|
|
79
|
-
? rawSchemaPath
|
|
80
|
-
: rawSchemaPath.split(path.sep).join("/");
|
|
84
|
+
const portableSchemaPath =
|
|
85
|
+
path.sep === "/" ? rawSchemaPath : rawSchemaPath.split(path.sep).join("/");
|
|
81
86
|
const schemaPath = portableSchemaPath.startsWith(".")
|
|
82
87
|
? portableSchemaPath
|
|
83
88
|
: `./${portableSchemaPath}`;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
|
|
90
|
+
return `${JSON.stringify(
|
|
91
|
+
{
|
|
92
|
+
$schema: schemaPath,
|
|
93
|
+
include: [],
|
|
94
|
+
targets: { agents: { enabled: true, mode: "copy" } },
|
|
95
|
+
},
|
|
96
|
+
null,
|
|
97
|
+
2,
|
|
98
|
+
)}\n`;
|
|
89
99
|
};
|
|
90
100
|
|
|
91
|
-
const createDefaultManifest = Effect.fn("createDefaultSkillManifest")(function* (
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
const createDefaultManifest = Effect.fn("createDefaultSkillManifest")(function* (paths: {
|
|
102
|
+
readonly projectDir: string;
|
|
103
|
+
readonly manifestPath: string;
|
|
104
|
+
}) {
|
|
94
105
|
const fs = yield* FileSystem.FileSystem;
|
|
95
106
|
const path = yield* Path.Path;
|
|
107
|
+
|
|
96
108
|
yield* fs.makeDirectory(path.dirname(paths.manifestPath), { recursive: true });
|
|
97
109
|
const staged = yield* fs.makeTempFileScoped({
|
|
98
110
|
directory: path.dirname(paths.manifestPath),
|
|
99
111
|
prefix: ".dev-kit-init-",
|
|
100
112
|
});
|
|
113
|
+
|
|
101
114
|
yield* fs.writeFileString(
|
|
102
115
|
staged,
|
|
103
116
|
renderDefaultManifest(paths.projectDir, paths.manifestPath, path),
|
|
@@ -112,6 +125,7 @@ const readManifest = Effect.fn("readManagedSkillManifest")(function* (
|
|
|
112
125
|
) {
|
|
113
126
|
const fs = yield* FileSystem.FileSystem;
|
|
114
127
|
const paths = yield* resolvePaths(options);
|
|
128
|
+
|
|
115
129
|
if (!(yield* fs.exists(paths.manifestPath))) {
|
|
116
130
|
if (!create) {
|
|
117
131
|
return yield* new SkillManagerError({
|
|
@@ -123,12 +137,14 @@ const readManifest = Effect.fn("readManagedSkillManifest")(function* (
|
|
|
123
137
|
const raw = yield* fs.readFileString(paths.manifestPath);
|
|
124
138
|
const errors: Array<ParseError> = [];
|
|
125
139
|
const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
|
|
140
|
+
|
|
126
141
|
if (errors.length > 0) {
|
|
127
142
|
return yield* new SkillManagerError({ message: `could not parse ${paths.manifestPath}` });
|
|
128
143
|
}
|
|
129
144
|
const manifest = yield* Schema.decodeUnknownEffect(DevKitManifestSchema)(parsed).pipe(
|
|
130
145
|
Effect.mapError((error) => new SkillManagerError({ message: error.message })),
|
|
131
146
|
);
|
|
147
|
+
|
|
132
148
|
return { ...paths, manifest, raw };
|
|
133
149
|
});
|
|
134
150
|
|
|
@@ -141,33 +157,46 @@ const writeArray = Effect.fn("writeManifestArray")(function* (
|
|
|
141
157
|
const fs = yield* FileSystem.FileSystem;
|
|
142
158
|
const parsed = parseJsonc(raw) as Record<string, unknown>;
|
|
143
159
|
const current = Array.isArray(parsed[property])
|
|
144
|
-
? (parsed[property] as Array<unknown>).filter(
|
|
160
|
+
? (parsed[property] as Array<unknown>).filter(
|
|
161
|
+
(value): value is string => typeof value === "string",
|
|
162
|
+
)
|
|
145
163
|
: undefined;
|
|
164
|
+
|
|
146
165
|
if (current === undefined) {
|
|
147
166
|
if (values.length === 0) return;
|
|
148
167
|
const edits = modify(raw, [property], [...values], {
|
|
149
168
|
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
150
169
|
});
|
|
170
|
+
|
|
151
171
|
yield* fs.writeFileString(manifestPath, applyEdits(raw, edits));
|
|
172
|
+
|
|
152
173
|
return;
|
|
153
174
|
}
|
|
154
175
|
let next = raw;
|
|
155
176
|
const retained = [...current];
|
|
177
|
+
|
|
156
178
|
for (let index = current.length - 1; index >= 0; index -= 1) {
|
|
157
179
|
const currentValue = current[index];
|
|
180
|
+
|
|
158
181
|
if (currentValue !== undefined && !values.includes(currentValue)) {
|
|
159
|
-
next = applyEdits(
|
|
160
|
-
|
|
161
|
-
|
|
182
|
+
next = applyEdits(
|
|
183
|
+
next,
|
|
184
|
+
modify(next, [property, index], undefined, {
|
|
185
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
162
188
|
retained.splice(index, 1);
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
191
|
for (const value of values) {
|
|
166
192
|
if (retained.includes(value)) continue;
|
|
167
|
-
next = applyEdits(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
193
|
+
next = applyEdits(
|
|
194
|
+
next,
|
|
195
|
+
modify(next, [property, retained.length], value, {
|
|
196
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
197
|
+
isArrayInsertion: true,
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
171
200
|
retained.push(value);
|
|
172
201
|
}
|
|
173
202
|
if (next !== raw) yield* fs.writeFileString(manifestPath, next);
|
|
@@ -179,24 +208,32 @@ const selectedNames = (
|
|
|
179
208
|
families: Readonly<Record<string, ReadonlyArray<string>>>,
|
|
180
209
|
) => {
|
|
181
210
|
const selected = new Set<string>();
|
|
211
|
+
|
|
182
212
|
for (const name of include) {
|
|
183
213
|
for (const skill of families[name] ?? [name]) selected.add(skill);
|
|
184
214
|
}
|
|
185
215
|
for (const name of exclude) {
|
|
186
216
|
for (const skill of families[name] ?? [name]) selected.delete(skill);
|
|
187
217
|
}
|
|
218
|
+
|
|
188
219
|
return selected;
|
|
189
220
|
};
|
|
190
221
|
|
|
191
222
|
const displayValue = (value: string): string =>
|
|
192
|
-
[...value]
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
223
|
+
[...value]
|
|
224
|
+
.map((character) => {
|
|
225
|
+
const code = character.charCodeAt(0);
|
|
226
|
+
|
|
227
|
+
return code <= 31 || (code >= 127 && code <= 159) ? " " : character;
|
|
228
|
+
})
|
|
229
|
+
.join("")
|
|
230
|
+
.replace(/\s+/g, " ")
|
|
231
|
+
.trim();
|
|
196
232
|
|
|
197
233
|
const summary = (description: string, defaultDescription: string): string => {
|
|
198
234
|
const text = displayValue(description || defaultDescription);
|
|
199
235
|
const firstSentence = text.match(/^.*?[.!?](?:\s|$)/)?.[0]?.trim() ?? text;
|
|
236
|
+
|
|
200
237
|
return firstSentence.length > 96 ? `${firstSentence.slice(0, 93).trimEnd()}…` : firstSentence;
|
|
201
238
|
};
|
|
202
239
|
|
|
@@ -211,8 +248,10 @@ const applyIfRequested = (options: ManagerOptions) =>
|
|
|
211
248
|
export const initProject = Effect.fn("initDevKitProject")(function* (options: ManagerOptions) {
|
|
212
249
|
const fs = yield* FileSystem.FileSystem;
|
|
213
250
|
const paths = yield* resolvePaths(options);
|
|
251
|
+
|
|
214
252
|
if (yield* fs.exists(paths.manifestPath)) {
|
|
215
253
|
yield* printStatus("info", "Already initialized", paths.manifestPath);
|
|
254
|
+
|
|
216
255
|
return;
|
|
217
256
|
}
|
|
218
257
|
yield* createDefaultManifest(paths);
|
|
@@ -226,14 +265,19 @@ export const addSkills = Effect.fn("addManagedSkills")(function* (
|
|
|
226
265
|
) {
|
|
227
266
|
const current = yield* readManifest(options, true);
|
|
228
267
|
const catalog = yield* loadSkillCatalog(yield* packageRoot(), current.projectDir);
|
|
229
|
-
const known = new Set([
|
|
268
|
+
const known = new Set([
|
|
269
|
+
...catalog.skills.map((skill) => skill.selector),
|
|
270
|
+
...Object.keys(catalog.families),
|
|
271
|
+
]);
|
|
230
272
|
const unknown = names.filter((name) => !known.has(name));
|
|
273
|
+
|
|
231
274
|
if (unknown.length > 0) {
|
|
232
275
|
return yield* new SkillManagerError({
|
|
233
276
|
message: `unknown skill${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}. Try \`dev-kit search ${unknown[0]}\`.`,
|
|
234
277
|
});
|
|
235
278
|
}
|
|
236
279
|
const sourceFamilies = catalog.lock?.sources ?? [];
|
|
280
|
+
|
|
237
281
|
for (const source of sourceFamilies) {
|
|
238
282
|
if (!names.includes(source.id)) continue;
|
|
239
283
|
yield* printStatus(
|
|
@@ -246,8 +290,10 @@ export const addSkills = Effect.fn("addManagedSkills")(function* (
|
|
|
246
290
|
}
|
|
247
291
|
const include = [...new Set([...current.manifest.include, ...names])];
|
|
248
292
|
const exclude = (current.manifest.exclude ?? []).filter((name) => !names.includes(name));
|
|
293
|
+
|
|
249
294
|
yield* writeArray(current.manifestPath, current.raw, "include", include);
|
|
250
295
|
const reread = yield* FileSystem.FileSystem;
|
|
296
|
+
|
|
251
297
|
yield* writeArray(
|
|
252
298
|
current.manifestPath,
|
|
253
299
|
yield* reread.readFileString(current.manifestPath),
|
|
@@ -268,18 +314,23 @@ export const removeSkills = Effect.fn("removeManagedSkills")(function* (
|
|
|
268
314
|
current.manifest.exclude ?? [],
|
|
269
315
|
catalog.families,
|
|
270
316
|
);
|
|
271
|
-
const absent = names.filter(
|
|
317
|
+
const absent = names.filter(
|
|
318
|
+
(name) => !before.has(name) && !current.manifest.include.includes(name),
|
|
319
|
+
);
|
|
320
|
+
|
|
272
321
|
if (absent.length > 0) {
|
|
273
322
|
return yield* new SkillManagerError({ message: `not selected: ${absent.join(", ")}` });
|
|
274
323
|
}
|
|
275
324
|
const include = current.manifest.include.filter((name) => !names.includes(name));
|
|
276
325
|
const excluded = new Set(current.manifest.exclude ?? []);
|
|
326
|
+
|
|
277
327
|
for (const name of names) {
|
|
278
328
|
if (before.has(name) && !current.manifest.include.includes(name)) excluded.add(name);
|
|
279
329
|
else excluded.delete(name);
|
|
280
330
|
}
|
|
281
331
|
yield* writeArray(current.manifestPath, current.raw, "include", include);
|
|
282
332
|
const fs = yield* FileSystem.FileSystem;
|
|
333
|
+
|
|
283
334
|
yield* writeArray(
|
|
284
335
|
current.manifestPath,
|
|
285
336
|
yield* fs.readFileString(current.manifestPath),
|
|
@@ -300,17 +351,22 @@ export const listSkills = Effect.fn("listManagedSkills")(function* (
|
|
|
300
351
|
: { include: [], exclude: [] };
|
|
301
352
|
const selected = selectedNames(manifest.include, manifest.exclude ?? [], catalog.families);
|
|
302
353
|
const query = options.query?.toLowerCase();
|
|
303
|
-
const visible = catalog.skills.filter(
|
|
304
|
-
(
|
|
305
|
-
|
|
354
|
+
const visible = catalog.skills.filter(
|
|
355
|
+
(skill) =>
|
|
356
|
+
(options.all || selected.has(skill.selector)) &&
|
|
357
|
+
(!query ||
|
|
358
|
+
`${skill.selector} ${skill.description} ${skill.source}`.toLowerCase().includes(query)),
|
|
306
359
|
);
|
|
307
360
|
const catalogSelectors = new Set(catalog.skills.map((skill) => skill.selector));
|
|
308
|
-
const unavailable = [...selected].filter(
|
|
309
|
-
|
|
361
|
+
const unavailable = [...selected].filter(
|
|
362
|
+
(selector) =>
|
|
363
|
+
!catalogSelectors.has(selector) && (!query || selector.toLowerCase().includes(query)),
|
|
310
364
|
);
|
|
365
|
+
|
|
311
366
|
if (visible.length === 0 && unavailable.length === 0) {
|
|
312
367
|
yield* printStatus("info", query ? "No matching skills" : "No skills selected");
|
|
313
368
|
if (!query && !options.all) yield* printDetail("Browse with: dev-kit list --all");
|
|
369
|
+
|
|
314
370
|
return;
|
|
315
371
|
}
|
|
316
372
|
for (const skill of visible) {
|
|
@@ -318,11 +374,18 @@ export const listSkills = Effect.fn("listManagedSkills")(function* (
|
|
|
318
374
|
const origin = skill.bundled ? "built in" : skill.source;
|
|
319
375
|
const provenance = skill.package
|
|
320
376
|
? ` [installed ${displayValue(skill.package.version)}]`
|
|
321
|
-
: skill.bundled
|
|
322
|
-
|
|
377
|
+
: skill.bundled
|
|
378
|
+
? ""
|
|
379
|
+
: ` [${skill.source}]`;
|
|
380
|
+
|
|
381
|
+
yield* printLine(
|
|
382
|
+
`${marker} ${skill.selector}${provenance} ${summary(skill.description, origin)}`,
|
|
383
|
+
);
|
|
323
384
|
}
|
|
324
385
|
for (const selector of unavailable) {
|
|
325
|
-
yield* printLine(
|
|
386
|
+
yield* printLine(
|
|
387
|
+
`! ${selector} [unavailable] install or repair the selected direct dependency`,
|
|
388
|
+
);
|
|
326
389
|
}
|
|
327
390
|
yield* printLine();
|
|
328
391
|
yield* printLine(`${selected.size} selected · ${catalog.skills.length} available`);
|
|
@@ -335,6 +398,7 @@ export const showSkill = Effect.fn("showCatalogSkill")(function* (
|
|
|
335
398
|
const paths = yield* resolvePaths(options);
|
|
336
399
|
const catalog = yield* loadSkillCatalog(yield* packageRoot(), paths.projectDir);
|
|
337
400
|
const skill = catalog.skills.find((candidate) => candidate.selector === name);
|
|
401
|
+
|
|
338
402
|
if (!skill) return yield* new SkillManagerError({ message: `unknown skill: ${name}` });
|
|
339
403
|
yield* printLine(skill.selector);
|
|
340
404
|
if (skill.description) yield* printLine(displayValue(skill.description));
|
|
@@ -342,14 +406,17 @@ export const showSkill = Effect.fn("showCatalogSkill")(function* (
|
|
|
342
406
|
yield* printLine(`Source: installed package`);
|
|
343
407
|
yield* printLine(`Package: ${skill.package.name}`);
|
|
344
408
|
yield* printLine(`Version: ${displayValue(skill.package.version)}`);
|
|
409
|
+
|
|
345
410
|
return;
|
|
346
411
|
}
|
|
347
412
|
yield* printLine(`Source: ${skill.bundled ? "dev-kit (built in)" : skill.source}`);
|
|
348
413
|
if (!skill.bundled) {
|
|
349
414
|
const source = catalog.lock?.sources.find((candidate) => candidate.id === skill.source);
|
|
415
|
+
|
|
350
416
|
if (source) {
|
|
351
417
|
yield* printLine(`Repository: ${source.repository}`);
|
|
352
418
|
yield* printLine(`Approved commit: ${source.resolved}`);
|
|
419
|
+
|
|
353
420
|
return;
|
|
354
421
|
}
|
|
355
422
|
}
|
|
@@ -369,7 +436,9 @@ export const chooseSkillsToAdd = Effect.fn("chooseSkillsToAdd")(function* (
|
|
|
369
436
|
options: ManagerOptions,
|
|
370
437
|
) {
|
|
371
438
|
if (!(yield* isInteractiveTerminal)) {
|
|
372
|
-
return yield* new SkillManagerError({
|
|
439
|
+
return yield* new SkillManagerError({
|
|
440
|
+
message: "pass one or more skill names, or run this command in a terminal",
|
|
441
|
+
});
|
|
373
442
|
}
|
|
374
443
|
const current = yield* readManifest(options, true);
|
|
375
444
|
const catalog = yield* loadSkillCatalog(yield* packageRoot(), current.projectDir);
|
|
@@ -379,8 +448,10 @@ export const chooseSkillsToAdd = Effect.fn("chooseSkillsToAdd")(function* (
|
|
|
379
448
|
catalog.families,
|
|
380
449
|
);
|
|
381
450
|
const available = catalog.skills.filter((skill) => !selected.has(skill.selector));
|
|
451
|
+
|
|
382
452
|
if (available.length === 0) {
|
|
383
453
|
yield* printStatus("success", "All available skills are selected");
|
|
454
|
+
|
|
384
455
|
return;
|
|
385
456
|
}
|
|
386
457
|
const names = yield* Prompt.multiSelect({
|
|
@@ -392,6 +463,7 @@ export const chooseSkillsToAdd = Effect.fn("chooseSkillsToAdd")(function* (
|
|
|
392
463
|
})),
|
|
393
464
|
min: 1,
|
|
394
465
|
});
|
|
466
|
+
|
|
395
467
|
yield* addSkills(names, options);
|
|
396
468
|
});
|
|
397
469
|
|
|
@@ -399,7 +471,9 @@ export const chooseSkillsToRemove = Effect.fn("chooseSkillsToRemove")(function*
|
|
|
399
471
|
options: ManagerOptions,
|
|
400
472
|
) {
|
|
401
473
|
if (!(yield* isInteractiveTerminal)) {
|
|
402
|
-
return yield* new SkillManagerError({
|
|
474
|
+
return yield* new SkillManagerError({
|
|
475
|
+
message: "pass one or more skill names, or run this command in a terminal",
|
|
476
|
+
});
|
|
403
477
|
}
|
|
404
478
|
const current = yield* readManifest(options);
|
|
405
479
|
const catalog = yield* loadSkillCatalog(yield* packageRoot(), current.projectDir);
|
|
@@ -408,18 +482,22 @@ export const chooseSkillsToRemove = Effect.fn("chooseSkillsToRemove")(function*
|
|
|
408
482
|
current.manifest.exclude ?? [],
|
|
409
483
|
catalog.families,
|
|
410
484
|
);
|
|
485
|
+
|
|
411
486
|
if (selected.size === 0) {
|
|
412
487
|
yield* printStatus("info", "No skills selected");
|
|
488
|
+
|
|
413
489
|
return;
|
|
414
490
|
}
|
|
415
491
|
const names = yield* Prompt.multiSelect({
|
|
416
492
|
message: "Choose skills to remove",
|
|
417
493
|
choices: [
|
|
418
|
-
...catalog.skills
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
494
|
+
...catalog.skills
|
|
495
|
+
.filter((skill) => selected.has(skill.selector))
|
|
496
|
+
.map((skill) => ({
|
|
497
|
+
title: skill.selector,
|
|
498
|
+
value: skill.selector,
|
|
499
|
+
description: summary(skill.description, skill.source),
|
|
500
|
+
})),
|
|
423
501
|
...[...selected]
|
|
424
502
|
.filter((selector) => !catalog.skills.some((skill) => skill.selector === selector))
|
|
425
503
|
.map((selector) => ({
|
|
@@ -430,5 +508,6 @@ export const chooseSkillsToRemove = Effect.fn("chooseSkillsToRemove")(function*
|
|
|
430
508
|
],
|
|
431
509
|
min: 1,
|
|
432
510
|
});
|
|
511
|
+
|
|
433
512
|
yield* removeSkills(names, options);
|
|
434
513
|
});
|
package/src/skill-selector.ts
CHANGED
|
@@ -35,9 +35,15 @@ export const parseSkillSelector = (value: string): SkillSelector | undefined =>
|
|
|
35
35
|
const match = PACKAGE_SKILL_SELECTOR_PATTERN.exec(value);
|
|
36
36
|
const packageName = match?.groups?.package;
|
|
37
37
|
const skill = match?.groups?.skill;
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
packageName === undefined ||
|
|
41
|
+
skill === undefined ||
|
|
42
|
+
!isTypeScriptPackageName(packageName) ||
|
|
43
|
+
!isSkillName(skill)
|
|
44
|
+
) {
|
|
40
45
|
return undefined;
|
|
41
46
|
}
|
|
47
|
+
|
|
42
48
|
return { type: "package", package: packageName, skill };
|
|
43
49
|
};
|
package/src/source-manifest.ts
CHANGED
|
@@ -31,12 +31,8 @@ export const LockedSkillSourceSchema = Schema.Struct({
|
|
|
31
31
|
include: Schema.Array(Schema.String),
|
|
32
32
|
exclude: Schema.optional(Schema.Array(Schema.String)),
|
|
33
33
|
skills: Schema.Array(Schema.String),
|
|
34
|
-
descriptions: Schema.optional(
|
|
35
|
-
|
|
36
|
-
),
|
|
37
|
-
digests: Schema.optional(
|
|
38
|
-
Schema.Record(Schema.String, DigestSchema),
|
|
39
|
-
),
|
|
34
|
+
descriptions: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
35
|
+
digests: Schema.optional(Schema.Record(Schema.String, DigestSchema)),
|
|
40
36
|
licensePath: Schema.optional(Schema.String),
|
|
41
37
|
stripFrontmatter: Schema.optional(Schema.Array(Schema.String)),
|
|
42
38
|
});
|