@danieljvdm/dev-kit 0.16.0 → 0.18.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 (43) hide show
  1. package/README.md +18 -7
  2. package/package.json +20 -9
  3. package/scripts/sync-anti-slop-runtime.mjs +19 -0
  4. package/skill-sources.jsonc +17 -0
  5. package/skill-sources.lock.json +48 -0
  6. package/skills/dev-kit/SKILL.md +4 -5
  7. package/src/bin/dev-kit.ts +11 -7
  8. package/src/catalog-manager.ts +23 -18
  9. package/src/catalog.ts +8 -5
  10. package/src/cli-ui.ts +2 -2
  11. package/src/effect-tsgo.ts +10 -6
  12. package/src/gitignore.ts +6 -3
  13. package/src/manifest.ts +10 -2
  14. package/src/oxlint-plugin-anti-slop/LICENSE +21 -0
  15. package/src/oxlint-plugin-anti-slop/index.ts +43 -0
  16. package/src/oxlint-plugin-anti-slop/rules/no-chained-type-assertions.ts +79 -0
  17. package/src/oxlint-plugin-anti-slop/rules/no-conditional-empty-object-spread.ts +51 -0
  18. package/src/oxlint-plugin-anti-slop/rules/no-known-value-widening.ts +267 -0
  19. package/src/oxlint-plugin-anti-slop/rules/no-module-mocking.ts +105 -0
  20. package/src/oxlint-plugin-anti-slop/rules/no-object-parameters.ts +141 -0
  21. package/src/oxlint-plugin-anti-slop/rules/no-reflect-apply.ts +28 -0
  22. package/src/oxlint-plugin-anti-slop/rules/no-reflect-get.ts +28 -0
  23. package/src/oxlint-plugin-anti-slop/rules/no-runtime-typeof.ts +25 -0
  24. package/src/oxlint-plugin-anti-slop/rules/no-shape-in-symbol-names.ts +38 -0
  25. package/src/oxlint-plugin-anti-slop/rules/no-unknown-parameters.ts +86 -0
  26. package/src/oxlint-plugin-anti-slop/rules/no-unknown-returns.ts +125 -0
  27. package/src/oxlint-plugin-anti-slop/rules/no-unknown-type-aliases.ts +73 -0
  28. package/src/oxlint-plugin-anti-slop/rules/no-unsafe-dictionary-type.ts +139 -0
  29. package/src/oxlint-plugin-anti-slop/rules/no-widen-then-assert.ts +396 -0
  30. package/src/oxlint-plugin-anti-slop/rules/require-safety-comment-for-type-assertion.ts +61 -0
  31. package/src/oxlint-plugin-anti-slop/runtime.d.ts +22 -0
  32. package/src/oxlint-plugin-anti-slop/runtime.js +1209 -0
  33. package/src/oxlint-plugin-anti-slop/shared/dictionary-types.ts +555 -0
  34. package/src/oxlint-plugin-anti-slop/shared/reflect-method.ts +40 -0
  35. package/src/oxlint-plugin-effect.d.ts +4 -2
  36. package/src/oxlint.js +19 -0
  37. package/src/oxlint.ts +22 -5
  38. package/src/package-skill-source.ts +17 -25
  39. package/src/path-digest.ts +2 -1
  40. package/src/project-package.ts +14 -12
  41. package/src/skill-manager.ts +25 -13
  42. package/src/sync.ts +65 -49
  43. package/src/vendor.ts +35 -16
@@ -1,4 +1,4 @@
1
- import { Effect, FileSystem, Path, Result, Schema } from "effect";
1
+ import { Effect, FileSystem, Option, Path, Result, Schema } from "effect";
2
2
 
3
3
  import { observeSymbolicLink } from "./node-symbolic-link.ts";
4
4
  import { readDirectDependencyNames } from "./project-package.ts";
@@ -34,33 +34,25 @@ const PackageMetadataSchema = Schema.fromJsonString(
34
34
  }),
35
35
  );
36
36
 
37
- const nonEmptyString = (value: unknown): value is string =>
38
- typeof value === "string" && value.trim().length > 0;
37
+ const DiscoveryLocationSchema = Schema.String.check(Schema.isPattern(/\S/));
38
+ const IntentDiscoveryMetadataSchema = Schema.Struct({
39
+ version: Schema.Literal(1),
40
+ repo: DiscoveryLocationSchema,
41
+ docs: DiscoveryLocationSchema,
42
+ });
43
+ const RepositoryDiscoveryMetadataSchema = Schema.Union([
44
+ DiscoveryLocationSchema,
45
+ Schema.Struct({ url: DiscoveryLocationSchema }),
46
+ ]);
47
+ const decodeIntentDiscoveryMetadata = Schema.decodeUnknownOption(IntentDiscoveryMetadataSchema);
48
+ const decodeRepositoryDiscoveryMetadata = Schema.decodeUnknownOption(
49
+ RepositoryDiscoveryMetadataSchema,
50
+ );
39
51
 
40
52
  const hasIntentDiscoveryMetadata = (metadata: typeof PackageMetadataSchema.Type): boolean => {
41
- const intent = metadata.intent;
53
+ if (Option.isSome(decodeIntentDiscoveryMetadata(metadata.intent))) return true;
42
54
 
43
- if (
44
- typeof intent === "object" &&
45
- intent !== null &&
46
- "version" in intent &&
47
- intent.version === 1 &&
48
- "repo" in intent &&
49
- nonEmptyString(intent.repo) &&
50
- "docs" in intent &&
51
- nonEmptyString(intent.docs)
52
- ) {
53
- return true;
54
- }
55
- const repository = metadata.repository;
56
-
57
- return (
58
- nonEmptyString(repository) ||
59
- (typeof repository === "object" &&
60
- repository !== null &&
61
- "url" in repository &&
62
- nonEmptyString(repository.url))
63
- );
55
+ return Option.isSome(decodeRepositoryDiscoveryMetadata(metadata.repository));
64
56
  };
65
57
 
66
58
  const isSafePackageVersion = (value: string): boolean =>
@@ -23,13 +23,14 @@ export class PathInspectionError extends Schema.TaggedError<PathInspectionError>
23
23
  }
24
24
 
25
25
  const textEncoder = new TextEncoder();
26
+ const isString = Schema.is(Schema.String);
26
27
 
27
28
  // Git preserves only the executable distinction for regular files. Canonicalizing
28
29
  // the remaining bits keeps digests stable across checkout and copy umasks.
29
30
  const canonicalFileMode = (mode: number): number => ((mode & 0o111) === 0 ? 0o644 : 0o755);
30
31
 
31
32
  const frame = (value: string | Uint8Array): Uint8Array => {
32
- const bytes = typeof value === "string" ? textEncoder.encode(value) : value;
33
+ const bytes = isString(value) ? textEncoder.encode(value) : value;
33
34
  const framed = new Uint8Array(4 + bytes.length);
34
35
 
35
36
  new DataView(framed.buffer).setUint32(0, bytes.length);
@@ -47,14 +47,16 @@ export const PACKAGE_MANAGER_COMMANDS = {
47
47
  yarn: { install: "yarn install", label: "Yarn" },
48
48
  } as const;
49
49
 
50
- export type PackageManagerName = keyof typeof PACKAGE_MANAGER_COMMANDS;
50
+ const PackageManagerNameSchema = Schema.Literals(["bun", "npm", "pnpm", "yarn"]);
51
+
52
+ export type PackageManagerName = typeof PackageManagerNameSchema.Type;
53
+
54
+ const decodePackageManagerName = Schema.decodeUnknownOption(PackageManagerNameSchema);
51
55
 
52
56
  const packageManagerName = (declaration: string | undefined): PackageManagerName | undefined => {
53
57
  const name = declaration?.split("@", 1)[0];
54
58
 
55
- return name !== undefined && name in PACKAGE_MANAGER_COMMANDS
56
- ? (name as PackageManagerName)
57
- : undefined;
59
+ return name === undefined ? undefined : Option.getOrUndefined(decodePackageManagerName(name));
58
60
  };
59
61
 
60
62
  export const detectPackageManager = Effect.fn("detectPackageManager")(function* (
@@ -129,13 +131,9 @@ const WorkspacePatternsSchema = Schema.Union([
129
131
  // this is a genuinely untyped boundary.
130
132
  const decodeWorkspacePatterns = Schema.decodeUnknownOption(WorkspacePatternsSchema);
131
133
 
132
- const workspacePatterns = (workspaces: unknown): ReadonlyArray<string> => {
133
- const decoded = decodeWorkspacePatterns(workspaces);
134
-
135
- if (Option.isNone(decoded)) return [];
136
-
137
- return "packages" in decoded.value ? decoded.value.packages : decoded.value;
138
- };
134
+ const workspacePatterns = (
135
+ workspaces: typeof WorkspacePatternsSchema.Type,
136
+ ): ReadonlyArray<string> => ("packages" in workspaces ? workspaces.packages : workspaces);
139
137
 
140
138
  /**
141
139
  * Direct dependency names of the project package plus every workspace member
@@ -149,8 +147,12 @@ export const readWorkspaceDependencyNames = Effect.fn("readWorkspaceDependencyNa
149
147
  const path = yield* Path.Path;
150
148
  const manifest = yield* readOptionalProjectPackage(projectDir);
151
149
  const names = new Set(manifestDependencyNames(manifest));
150
+ const decodedWorkspaces = decodeWorkspacePatterns(manifest?.workspaces);
151
+ const patterns = Option.isSome(decodedWorkspaces)
152
+ ? workspacePatterns(decodedWorkspaces.value)
153
+ : [];
152
154
 
153
- for (const pattern of workspacePatterns(manifest?.workspaces)) {
155
+ for (const pattern of patterns) {
154
156
  if (pattern.startsWith("!")) continue;
155
157
  const star = pattern.indexOf("*");
156
158
  let memberDirs: ReadonlyArray<string> = [];
@@ -19,6 +19,11 @@ type ManagerOptions = {
19
19
  readonly apply?: boolean;
20
20
  };
21
21
 
22
+ type ManagerSyncOptions = {
23
+ projectDir?: string;
24
+ manifestPath?: string;
25
+ };
26
+
22
27
  const packageRoot = Effect.fn("skillManagerPackageRoot")(function* () {
23
28
  const path = yield* Path.Path;
24
29
 
@@ -150,6 +155,11 @@ const readManifest = Effect.fn("readManagedSkillManifest")(function* (
150
155
  return { ...paths, manifest, raw };
151
156
  });
152
157
 
158
+ const ManifestSelectionSchema = Schema.Struct({
159
+ include: Schema.optional(Schema.Array(Schema.String)),
160
+ exclude: Schema.optional(Schema.Array(Schema.String)),
161
+ });
162
+
153
163
  const writeArray = Effect.fn("writeManifestArray")(function* (
154
164
  manifestPath: string,
155
165
  raw: string,
@@ -157,12 +167,10 @@ const writeArray = Effect.fn("writeManifestArray")(function* (
157
167
  values: ReadonlyArray<string>,
158
168
  ) {
159
169
  const fs = yield* FileSystem.FileSystem;
160
- const parsed = parseJsonc(raw) as Record<string, unknown>;
161
- const current = Array.isArray(parsed[property])
162
- ? (parsed[property] as Array<unknown>).filter(
163
- (value): value is string => typeof value === "string",
164
- )
165
- : undefined;
170
+ const parsed = yield* Schema.decodeUnknownEffect(ManifestSelectionSchema)(parseJsonc(raw)).pipe(
171
+ Effect.mapError((error) => SkillManagerError.make({ message: error.message })),
172
+ );
173
+ const current = parsed[property];
166
174
 
167
175
  if (current === undefined) {
168
176
  if (values.length === 0) return;
@@ -239,13 +247,17 @@ const summary = (description: string, defaultDescription: string): string => {
239
247
  return firstSentence.length > 96 ? `${firstSentence.slice(0, 93).trimEnd()}…` : firstSentence;
240
248
  };
241
249
 
242
- const applyIfRequested = (options: ManagerOptions) =>
243
- options.apply === false
244
- ? printStatus("success", "Manifest updated", "run dev-kit sync to apply")
245
- : runProjectSkillPlan({
246
- ...(options.projectDir === undefined ? {} : { projectDir: options.projectDir }),
247
- ...(options.manifestPath === undefined ? {} : { manifestPath: options.manifestPath }),
248
- });
250
+ const applyIfRequested = (options: ManagerOptions) => {
251
+ if (options.apply === false) {
252
+ return printStatus("success", "Manifest updated", "run dev-kit sync to apply");
253
+ }
254
+ const syncOptions: ManagerSyncOptions = {};
255
+
256
+ if (options.projectDir !== undefined) syncOptions.projectDir = options.projectDir;
257
+ if (options.manifestPath !== undefined) syncOptions.manifestPath = options.manifestPath;
258
+
259
+ return runProjectSkillPlan(syncOptions);
260
+ };
249
261
 
250
262
  export const initProject = Effect.fn("initDevKitProject")(function* (options: ManagerOptions) {
251
263
  const fs = yield* FileSystem.FileSystem;
package/src/sync.ts CHANGED
@@ -267,7 +267,7 @@ const encodeOutputOwnershipIdentityJson = Schema.encodeSync(
267
267
  const encodePlanSnapshotJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
268
268
  const encodeAppliedStatePrettyJson = Schema.encodeSync(fromJsonString(AppliedStateSchema, 2));
269
269
 
270
- const SKILL_FAMILIES: SkillCatalog = {
270
+ const SKILL_FAMILIES = {
271
271
  effect: [
272
272
  "effect-ts",
273
273
  "effect-architecture-audit",
@@ -275,7 +275,7 @@ const SKILL_FAMILIES: SkillCatalog = {
275
275
  "effect-atom-state",
276
276
  "build-effect-clis",
277
277
  ],
278
- };
278
+ } satisfies SkillCatalog;
279
279
 
280
280
  export const DEFAULT_MANIFEST = "dev-kit.jsonc";
281
281
  const DEFAULT_LOCKFILE = "dev-kit.lock.json";
@@ -356,12 +356,12 @@ const inspectManagedInstructionSections = (content: string): ManagedInstructionI
356
356
  }
357
357
  }
358
358
 
359
+ if (ranges.length === 0) return { kind: "valid", ranges };
360
+
359
361
  return {
360
362
  kind: "valid",
361
363
  ranges,
362
- ...(ranges.length === 0
363
- ? {}
364
- : { content: `${ranges.map((range) => range.content.trim()).join("\n\n")}\n` }),
364
+ content: `${ranges.map((range) => range.content.trim()).join("\n\n")}\n`,
365
365
  };
366
366
  };
367
367
 
@@ -460,6 +460,8 @@ const renderPackageScriptCommandPolicy = (
460
460
  const additionalCommands = Object.keys(scripts)
461
461
  .filter(
462
462
  (script) =>
463
+ // SAFETY: `script` is only asserted to the set's string-literal union for a
464
+ // runtime membership check; the assertion does not narrow any returned value.
463
465
  !knownScripts.has(script as (typeof entries)[number][0]) &&
464
466
  /^(?:check|validate|fmt|format|lint|test|type-?check)(?::|$)/.test(script),
465
467
  )
@@ -955,7 +957,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
955
957
  const managed = yield* resolveManagedPath(projectDir, path.join(target.path, skill.name));
956
958
 
957
959
  if (target.mode === "copy") {
958
- outputs.push({
960
+ const output: DesiredSkillOutput = {
959
961
  resourceId: `skill:${skill.selector}@${targetName}`,
960
962
  path: managed.relative,
961
963
  skill: skill.name,
@@ -963,10 +965,12 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
963
965
  mode: "copy",
964
966
  kind: "directory",
965
967
  digest: sourceObservation.digest,
966
- ...(resolvedSource.catalog ? { catalog: resolvedSource.catalog } : {}),
967
968
  source,
968
969
  destination: managed.absolute,
969
- });
970
+ };
971
+
972
+ if (resolvedSource.catalog) Object.assign(output, { catalog: resolvedSource.catalog });
973
+ outputs.push(output);
970
974
  continue;
971
975
  }
972
976
  const linkSource =
@@ -977,7 +981,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
977
981
  const linkTarget = path.relative(path.dirname(managed.absolute), linkSource);
978
982
  const linkDigest = yield* digestSymlinkTarget(linkTarget);
979
983
 
980
- outputs.push({
984
+ const output: DesiredSkillOutput = {
981
985
  resourceId: `skill:${skill.selector}@${targetName}`,
982
986
  path: managed.relative,
983
987
  skill: skill.name,
@@ -985,11 +989,13 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
985
989
  mode: "symlink",
986
990
  kind: "symlink",
987
991
  digest: linkDigest,
988
- ...(resolvedSource.catalog ? { catalog: resolvedSource.catalog } : {}),
989
992
  source,
990
993
  destination: managed.absolute,
991
994
  linkTarget,
992
- });
995
+ };
996
+
997
+ if (resolvedSource.catalog) Object.assign(output, { catalog: resolvedSource.catalog });
998
+ outputs.push(output);
993
999
  }
994
1000
  }
995
1001
  yield* validateInventory(projectDir, outputs, "desired outputs");
@@ -1175,14 +1181,15 @@ const planDesiredOutputs = Effect.fn("planDesiredSkillOutputs")(function* (
1175
1181
 
1176
1182
  if (managedDigest === receipt.digest || observed.digest === receipt.digest) {
1177
1183
  const remaining = removeManagedInstructionSections(existingContent, inspection.ranges);
1178
-
1179
- actions.push({
1184
+ const action: SkillPlanAction = {
1180
1185
  action: "remove",
1181
1186
  previous: receipt,
1182
1187
  destination: managed.absolute,
1183
1188
  observed,
1184
- ...(remaining.trim().length === 0 ? {} : { stagedContent: remaining }),
1185
- });
1189
+ };
1190
+
1191
+ if (remaining.trim().length > 0) Object.assign(action, { stagedContent: remaining });
1192
+ actions.push(action);
1186
1193
  } else {
1187
1194
  actions.push({
1188
1195
  action: "conflict",
@@ -1363,35 +1370,36 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1363
1370
  manifest.setup,
1364
1371
  manifest.targets,
1365
1372
  );
1373
+ const nextSetup: NonNullable<DevKitLock["setup"]> = {};
1374
+
1375
+ if (effectSource !== undefined) {
1376
+ Object.assign(nextSetup, {
1377
+ effectSource: {
1378
+ packageName: effectSource.packageName,
1379
+ packageVersion: effectSource.packageVersion,
1380
+ path: effectSource.path,
1381
+ repository: effectSource.repository,
1382
+ tag: effectSource.tag,
1383
+ },
1384
+ });
1385
+ }
1386
+ if (effectTsgo !== undefined) {
1387
+ Object.assign(nextSetup, {
1388
+ effectTsgo: {
1389
+ effectTsgoVersion: effectTsgo.effectTsgoVersion,
1390
+ typescriptPackage: effectTsgo.typescriptPackage,
1391
+ typescriptVersion: effectTsgo.typescriptVersion,
1392
+ },
1393
+ });
1394
+ }
1366
1395
  const nextLock: DevKitLock = {
1367
1396
  version: 1,
1368
1397
  toolVersion: DEV_KIT_VERSION,
1369
1398
  manifestDigest: yield* digestText(encodeManifestJson(manifest)),
1370
- setup: {
1371
- ...(effectSource === undefined
1372
- ? {}
1373
- : {
1374
- effectSource: {
1375
- packageName: effectSource.packageName,
1376
- packageVersion: effectSource.packageVersion,
1377
- path: effectSource.path,
1378
- repository: effectSource.repository,
1379
- tag: effectSource.tag,
1380
- },
1381
- }),
1382
- ...(effectTsgo === undefined
1383
- ? {}
1384
- : {
1385
- effectTsgo: {
1386
- effectTsgoVersion: effectTsgo.effectTsgoVersion,
1387
- typescriptPackage: effectTsgo.typescriptPackage,
1388
- typescriptVersion: effectTsgo.typescriptVersion,
1389
- },
1390
- }),
1391
- },
1399
+ setup: nextSetup,
1392
1400
  outputs: desired.map((output): ManagedOutput => {
1393
1401
  if ("skill" in output) {
1394
- return {
1402
+ const managedOutput: ManagedSkillOutput = {
1395
1403
  resourceId: output.resourceId,
1396
1404
  path: output.path,
1397
1405
  skill: output.skill,
@@ -1399,8 +1407,11 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1399
1407
  mode: output.mode,
1400
1408
  kind: output.kind,
1401
1409
  digest: output.digest,
1402
- ...(output.catalog ? { catalog: output.catalog } : {}),
1403
1410
  };
1411
+
1412
+ if (output.catalog) Object.assign(managedOutput, { catalog: output.catalog });
1413
+
1414
+ return managedOutput;
1404
1415
  }
1405
1416
  if (output.resourceId === "setup:agent-instructions") {
1406
1417
  return {
@@ -1488,16 +1499,11 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1488
1499
  });
1489
1500
  }
1490
1501
 
1491
- return {
1502
+ const plan: SkillPlan = {
1492
1503
  projectDir,
1493
1504
  lockfilePath: lockManaged.absolute,
1494
1505
  statePath: stateManaged.absolute,
1495
1506
  actions: planned.actions,
1496
- ...(effectSource === undefined ? {} : { effectSource }),
1497
- ...(effectTsgo === undefined ? {} : { effectTsgo }),
1498
- ...(vitePlusHooks === undefined ? {} : { vitePlusHooks }),
1499
- ...(vitePlusWorkflow === undefined ? {} : { vitePlusWorkflow }),
1500
- ...(worktrunkConfig === undefined ? {} : { worktrunkConfig }),
1501
1507
  nextLock,
1502
1508
  nextState: planned.nextState,
1503
1509
  metadataChanged:
@@ -1505,7 +1511,15 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1505
1511
  encodeDevKitLockJson(currentLock) !== encodeDevKitLockJson(nextLock) ||
1506
1512
  currentState === undefined ||
1507
1513
  encodeAppliedStateJson(currentState) !== encodeAppliedStateJson(planned.nextState),
1508
- } satisfies SkillPlan;
1514
+ };
1515
+
1516
+ if (effectSource !== undefined) Object.assign(plan, { effectSource });
1517
+ if (effectTsgo !== undefined) Object.assign(plan, { effectTsgo });
1518
+ if (vitePlusHooks !== undefined) Object.assign(plan, { vitePlusHooks });
1519
+ if (vitePlusWorkflow !== undefined) Object.assign(plan, { vitePlusWorkflow });
1520
+ if (worktrunkConfig !== undefined) Object.assign(plan, { worktrunkConfig });
1521
+
1522
+ return plan;
1509
1523
  });
1510
1524
 
1511
1525
  const formatAction = (action: SkillPlanAction): string => {
@@ -1761,13 +1775,15 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1761
1775
  }`,
1762
1776
  });
1763
1777
  }
1764
- replacements.push({
1778
+ const replacement: Replacement = {
1765
1779
  destination: action.action === "remove" ? action.destination : action.desired.destination,
1766
1780
  backup: path.join(backupDir, String(replacementIndex++)),
1767
1781
  expected: action.observed,
1768
1782
  path: action.action === "remove" ? action.previous.path : action.desired.path,
1769
- ...(staged === undefined ? {} : { staged }),
1770
- });
1783
+ };
1784
+
1785
+ if (staged !== undefined) Object.assign(replacement, { staged });
1786
+ replacements.push(replacement);
1771
1787
  }
1772
1788
  replacements.push(
1773
1789
  {
package/src/vendor.ts CHANGED
@@ -158,11 +158,13 @@ const normalizeRepositoryLocator = (
158
158
  const normalized = `https://github.com/${owner}/${name}.git`;
159
159
 
160
160
  if (segments[2] === "tree" && segments[3]) {
161
- return Effect.succeed({
162
- repository: normalized,
163
- ref: segments[3],
164
- ...(segments.length > 4 ? { skillsPath: segments.slice(4).join("/") } : {}),
165
- });
161
+ const locator = { repository: normalized, ref: segments[3] };
162
+
163
+ if (segments.length > 4) {
164
+ Object.assign(locator, { skillsPath: segments.slice(4).join("/") });
165
+ }
166
+
167
+ return Effect.succeed(locator);
166
168
  }
167
169
 
168
170
  return Effect.succeed({ repository: normalized });
@@ -493,13 +495,16 @@ const prepareSource = Effect.fn("prepareSkillSource")(function* (
493
495
  }
494
496
  }
495
497
 
496
- return {
498
+ const prepared = {
497
499
  checkoutDir,
498
500
  resolved,
499
501
  skills,
500
502
  source,
501
- ...(licenseSource ? { licenseSource } : {}),
502
- } satisfies PreparedSource;
503
+ };
504
+
505
+ if (licenseSource) Object.assign(prepared, { licenseSource });
506
+
507
+ return prepared satisfies PreparedSource;
503
508
  });
504
509
 
505
510
  const readCurrentLock = Effect.fn("readCurrentSkillSourcesLock")(function* (lockfilePath: string) {
@@ -713,20 +718,31 @@ const buildLock = Effect.fn("buildSkillCatalogLock")(function* (
713
718
  }
714
719
  digests[skill] = observation.digest;
715
720
  }
716
- sources.push({
721
+ const lockedSource = {
717
722
  id: source.id,
718
723
  repository: source.repository,
719
724
  ref: source.ref,
720
725
  resolved,
721
726
  skillsPath: source.skillsPath,
722
727
  include: source.include,
723
- ...(source.exclude ? { exclude: source.exclude } : {}),
728
+ };
729
+
730
+ if (source.exclude) Object.assign(lockedSource, { exclude: source.exclude });
731
+
732
+ const completeLockedSource = Object.assign(lockedSource, {
724
733
  skills,
725
734
  descriptions,
726
735
  digests,
727
- ...(source.licensePath ? { licensePath: source.licensePath } : {}),
728
- ...(source.stripFrontmatter ? { stripFrontmatter: source.stripFrontmatter } : {}),
729
- });
736
+ }) satisfies LockedSkillSource;
737
+
738
+ if (source.licensePath) {
739
+ Object.assign(completeLockedSource, { licensePath: source.licensePath });
740
+ }
741
+ if (source.stripFrontmatter) {
742
+ Object.assign(completeLockedSource, { stripFrontmatter: source.stripFrontmatter });
743
+ }
744
+
745
+ sources.push(completeLockedSource);
730
746
  }
731
747
 
732
748
  return { version: 1, sources } satisfies SkillSourcesLock;
@@ -807,15 +823,18 @@ export const inspectCatalogRepository = Effect.fn("inspectCatalogRepository")(fu
807
823
  }
808
824
  }
809
825
 
810
- return {
826
+ const inspection: CatalogInspection = {
811
827
  id,
812
828
  repository,
813
829
  ref,
814
830
  resolved: prepared.resolved,
815
831
  skillsPath: source.skillsPath,
816
832
  skills,
817
- ...(licensePath ? { licensePath } : {}),
818
- } satisfies CatalogInspection;
833
+ };
834
+
835
+ if (licensePath) Object.assign(inspection, { licensePath });
836
+
837
+ return inspection;
819
838
  });
820
839
 
821
840
  export const refreshSkillCatalog = Effect.fn("refreshSkillCatalog")(function* (