@danieljvdm/dev-kit 0.11.3 → 0.13.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 (79) hide show
  1. package/README.md +111 -78
  2. package/dev-kit.example.jsonc +0 -4
  3. package/package.json +10 -6
  4. package/schema/dev-kit.schema.json +1 -46
  5. package/skills/build-effect-apis/SKILL.md +77 -0
  6. package/skills/build-effect-apis/agents/openai.yaml +4 -0
  7. package/skills/build-effect-apis/references/cloudflare-workers.md +71 -0
  8. package/skills/build-effect-apis/references/effect-atom-client.md +161 -0
  9. package/skills/build-effect-apis/references/effect-atom-lifecycle.md +78 -0
  10. package/skills/build-effect-apis/references/effect-atom-testing.md +74 -0
  11. package/skills/build-effect-apis/references/runtime-assembly.md +56 -0
  12. package/skills/build-effect-apis/references/server-and-middleware.md +174 -0
  13. package/skills/build-effect-apis/references/shared-contracts.md +108 -0
  14. package/skills/build-effect-apis/references/tanstack-start.md +86 -0
  15. package/skills/build-effect-apis/references/verification.md +50 -0
  16. package/skills/build-effect-clis/SKILL.md +61 -0
  17. package/skills/build-effect-clis/agents/openai.yaml +4 -0
  18. package/skills/build-effect-clis/references/command-design.md +93 -0
  19. package/skills/build-effect-clis/references/entrypoints-and-testing.md +59 -0
  20. package/skills/build-effect-clis/references/processes-and-platform.md +73 -0
  21. package/skills/dev-kit/SKILL.md +77 -48
  22. package/skills/effect-architecture-audit/SKILL.md +26 -0
  23. package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
  24. package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
  25. package/skills/effect-ts/SKILL.md +21 -256
  26. package/skills/effect-ts/agents/openai.yaml +3 -3
  27. package/skills/testing/SKILL.md +5 -0
  28. package/src/catalog-manager.ts +16 -17
  29. package/src/catalog.ts +71 -16
  30. package/src/effect-source.ts +46 -24
  31. package/src/effect-tsgo.ts +86 -24
  32. package/src/gitignore.ts +5 -5
  33. package/src/index.ts +3 -6
  34. package/src/manifest.ts +0 -34
  35. package/src/node-symbolic-link.ts +2 -2
  36. package/src/oxfmt.js +5 -0
  37. package/src/oxfmt.ts +5 -0
  38. package/src/oxlint.js +5 -0
  39. package/src/oxlint.ts +5 -0
  40. package/src/package-skill-source.ts +51 -59
  41. package/src/path-digest.ts +7 -7
  42. package/src/project-package.ts +8 -7
  43. package/src/project-process-lock.ts +17 -12
  44. package/src/project-state.ts +1 -1
  45. package/src/skill-manager.ts +18 -16
  46. package/src/skill-selector.ts +12 -0
  47. package/src/sync.ts +181 -131
  48. package/src/tool-ignore-patterns.js +9 -0
  49. package/src/tool-ignore-patterns.ts +15 -0
  50. package/src/vendor.ts +67 -61
  51. package/src/vite-plus-dependency.ts +10 -11
  52. package/src/vite-plus-hooks.ts +24 -14
  53. package/src/vite-plus-quality.ts +21 -172
  54. package/src/vite-plus.js +81 -0
  55. package/src/vite-plus.ts +102 -0
  56. package/templates/AGENTS.md +1 -1
  57. package/skills/effect-ts/UPSTREAM.md +0 -28
  58. package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
  59. package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
  60. package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
  61. package/skills/effect-ts/references/atom-testing.md +0 -67
  62. package/skills/effect-ts/references/audit-services.md +0 -144
  63. package/skills/effect-ts/references/features.md +0 -525
  64. package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
  65. package/skills/effect-ts/references/guide-cli.md +0 -107
  66. package/skills/effect-ts/references/guide-datetime.md +0 -72
  67. package/skills/effect-ts/references/guide-effect.md +0 -440
  68. package/skills/effect-ts/references/guide-error-handling.md +0 -565
  69. package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
  70. package/skills/effect-ts/references/guide-layers.md +0 -989
  71. package/skills/effect-ts/references/guide-observability.md +0 -746
  72. package/skills/effect-ts/references/guide-retries.md +0 -434
  73. package/skills/effect-ts/references/guide-schedule.md +0 -343
  74. package/skills/effect-ts/references/guide-schema.md +0 -664
  75. package/skills/effect-ts/references/guide-sql.md +0 -536
  76. package/skills/effect-ts/references/guide-testing.md +0 -532
  77. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
  78. package/skills/effect-ts/references/version-and-source.md +0 -86
  79. package/templates/vite-plus/vite.config.ts +0 -22
package/src/sync.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Cause, Effect, FileSystem, Path, Schema, Stream } from "effect";
1
+ import { Cause, Effect, FileSystem, Path, Schema, SchemaGetter, Stream } from "effect";
2
2
  import { ChildProcess } from "effect/unstable/process";
3
3
  import { parse as parseJsonc, printParseErrorCode, type ParseError } from "jsonc-parser";
4
4
 
@@ -31,6 +31,9 @@ import { acquireProjectProcessLock, PROJECT_PROCESS_LOCK_PATH } from "./project-
31
31
  import {
32
32
  AppliedStateSchema,
33
33
  DevKitLockSchema,
34
+ EffectSourceLockSchema,
35
+ EffectTsgoLockSchema,
36
+ ManagedOutputSchema,
34
37
  type AppliedState,
35
38
  type DevKitLock,
36
39
  type ManagedAgentInstructionsOutput,
@@ -48,11 +51,8 @@ import {
48
51
  type VitePlusHooksPlan,
49
52
  } from "./vite-plus-hooks.ts";
50
53
  import {
51
- renderVitePlusConfigTemplate,
52
54
  renderVitePlusWorkflowTemplate,
53
55
  validateVitePlusQualitySupport,
54
- VITE_PLUS_CONFIG_PATH,
55
- VITE_PLUS_CONFIG_TEMPLATE,
56
56
  VITE_PLUS_GITHUB_ACTIONS_PATH,
57
57
  VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
58
58
  } from "./vite-plus-quality.ts";
@@ -149,7 +149,7 @@ export type SkillPlan = {
149
149
  readonly metadataChanged: boolean;
150
150
  };
151
151
 
152
- class ManifestNotFoundError extends Schema.TaggedErrorClass<ManifestNotFoundError>()(
152
+ class ManifestNotFoundError extends Schema.TaggedError<ManifestNotFoundError>()(
153
153
  "ManifestNotFoundError",
154
154
  { path: Schema.String },
155
155
  ) {
@@ -158,12 +158,12 @@ class ManifestNotFoundError extends Schema.TaggedErrorClass<ManifestNotFoundErro
158
158
  }
159
159
  }
160
160
 
161
- class StructuredFileError extends Schema.TaggedErrorClass<StructuredFileError>()(
162
- "StructuredFileError",
163
- { path: Schema.String, message: Schema.String },
164
- ) {}
161
+ class StructuredFileError extends Schema.TaggedError<StructuredFileError>()("StructuredFileError", {
162
+ path: Schema.String,
163
+ message: Schema.String,
164
+ }) {}
165
165
 
166
- class UnknownSkillOrFamilyError extends Schema.TaggedErrorClass<UnknownSkillOrFamilyError>()(
166
+ class UnknownSkillOrFamilyError extends Schema.TaggedError<UnknownSkillOrFamilyError>()(
167
167
  "UnknownSkillOrFamilyError",
168
168
  { name: Schema.String, known: Schema.Array(Schema.String) },
169
169
  ) {
@@ -172,12 +172,12 @@ class UnknownSkillOrFamilyError extends Schema.TaggedErrorClass<UnknownSkillOrFa
172
172
  }
173
173
  }
174
174
 
175
- class InvalidSkillCatalogError extends Schema.TaggedErrorClass<InvalidSkillCatalogError>()(
175
+ class InvalidSkillCatalogError extends Schema.TaggedError<InvalidSkillCatalogError>()(
176
176
  "InvalidSkillCatalogError",
177
177
  { family: Schema.String, message: Schema.String },
178
178
  ) {}
179
179
 
180
- class CommandError extends Schema.TaggedErrorClass<CommandError>()("CommandError", {
180
+ class CommandError extends Schema.TaggedError<CommandError>()("CommandError", {
181
181
  command: Schema.String,
182
182
  exitCode: Schema.Int,
183
183
  output: Schema.String,
@@ -189,7 +189,7 @@ class CommandError extends Schema.TaggedErrorClass<CommandError>()("CommandError
189
189
  }
190
190
  }
191
191
 
192
- class UnsafeManagedPathError extends Schema.TaggedErrorClass<UnsafeManagedPathError>()(
192
+ class UnsafeManagedPathError extends Schema.TaggedError<UnsafeManagedPathError>()(
193
193
  "UnsafeManagedPathError",
194
194
  { path: Schema.String, reason: Schema.String },
195
195
  ) {
@@ -198,17 +198,17 @@ class UnsafeManagedPathError extends Schema.TaggedErrorClass<UnsafeManagedPathEr
198
198
  }
199
199
  }
200
200
 
201
- class InvalidProjectStateError extends Schema.TaggedErrorClass<InvalidProjectStateError>()(
201
+ class InvalidProjectStateError extends Schema.TaggedError<InvalidProjectStateError>()(
202
202
  "InvalidProjectStateError",
203
203
  { message: Schema.String },
204
204
  ) {}
205
205
 
206
- class LockedPlanMismatchError extends Schema.TaggedErrorClass<LockedPlanMismatchError>()(
206
+ class LockedPlanMismatchError extends Schema.TaggedError<LockedPlanMismatchError>()(
207
207
  "LockedPlanMismatchError",
208
208
  { message: Schema.String },
209
209
  ) {}
210
210
 
211
- class PlanConflictError extends Schema.TaggedErrorClass<PlanConflictError>()("PlanConflictError", {
211
+ class PlanConflictError extends Schema.TaggedError<PlanConflictError>()("PlanConflictError", {
212
212
  conflicts: Schema.Array(Schema.String),
213
213
  }) {
214
214
  override get message() {
@@ -218,7 +218,7 @@ class PlanConflictError extends Schema.TaggedErrorClass<PlanConflictError>()("Pl
218
218
  }
219
219
  }
220
220
 
221
- class ApplyRaceError extends Schema.TaggedErrorClass<ApplyRaceError>()("ApplyRaceError", {
221
+ class ApplyRaceError extends Schema.TaggedError<ApplyRaceError>()("ApplyRaceError", {
222
222
  path: Schema.String,
223
223
  }) {
224
224
  override get message() {
@@ -226,9 +226,51 @@ class ApplyRaceError extends Schema.TaggedErrorClass<ApplyRaceError>()("ApplyRac
226
226
  }
227
227
  }
228
228
 
229
+ const fromJsonString = <S extends Schema.Constraint>(schema: S, space?: number) =>
230
+ space === undefined
231
+ ? Schema.fromJsonString(schema)
232
+ : Schema.String.pipe(
233
+ Schema.decodeTo(Schema.toCodecJson(schema), {
234
+ decode: SchemaGetter.parseJson(),
235
+ encode: SchemaGetter.stringifyJson({ space }),
236
+ }),
237
+ );
238
+
239
+ const DevKitSetupSchema = Schema.Struct({
240
+ effectSource: Schema.optional(EffectSourceLockSchema),
241
+ effectTsgo: Schema.optional(EffectTsgoLockSchema),
242
+ });
243
+ const OutputOwnershipIdentitySchema = Schema.Union([
244
+ Schema.Struct({
245
+ resourceId: Schema.String,
246
+ path: Schema.String,
247
+ mode: Schema.Literals(["copy", "symlink"]),
248
+ kind: Schema.Literals(["directory", "symlink"]),
249
+ skill: Schema.String,
250
+ target: Schema.Literals(["agents", "claude", "opencode"]),
251
+ }),
252
+ Schema.Struct({
253
+ resourceId: Schema.String,
254
+ path: Schema.String,
255
+ mode: Schema.Literals(["copy", "symlink"]),
256
+ kind: Schema.Literals(["file", "symlink"]),
257
+ sourcePath: Schema.String,
258
+ }),
259
+ ]);
260
+ const encodeAppliedStateJson = Schema.encodeSync(fromJsonString(AppliedStateSchema));
261
+ const encodeDevKitLockJson = Schema.encodeSync(fromJsonString(DevKitLockSchema));
262
+ const encodeDevKitLockPrettyJson = Schema.encodeSync(fromJsonString(DevKitLockSchema, 2));
263
+ const encodeDevKitSetupJson = Schema.encodeSync(fromJsonString(DevKitSetupSchema));
264
+ const encodeManifestJson = Schema.encodeSync(fromJsonString(DevKitManifestSchema));
265
+ const encodeManagedOutputJson = Schema.encodeSync(fromJsonString(ManagedOutputSchema));
266
+ const encodeOutputOwnershipIdentityJson = Schema.encodeSync(
267
+ fromJsonString(OutputOwnershipIdentitySchema),
268
+ );
269
+ const encodePlanSnapshotJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
270
+ const encodeAppliedStatePrettyJson = Schema.encodeSync(fromJsonString(AppliedStateSchema, 2));
271
+
229
272
  const SKILL_FAMILIES: SkillCatalog = {
230
- effect: ["effect-ts"],
231
- "effect-atom-data-fetching": ["effect-ts"],
273
+ effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis", "build-effect-clis"],
232
274
  };
233
275
 
234
276
  export const DEFAULT_MANIFEST = "dev-kit.jsonc";
@@ -236,6 +278,7 @@ const DEFAULT_LOCKFILE = "dev-kit.lock.json";
236
278
  const DEFAULT_STATE = ".dev-kit/state.json";
237
279
  const AGENT_INSTRUCTIONS_TEMPLATE = "templates/AGENTS.md";
238
280
  const DEV_KIT_SKILL_PATH_PLACEHOLDER = "{{DEV_KIT_SKILL_PATH}}";
281
+ const EFFECT_INSTRUCTIONS_PLACEHOLDER = "{{EFFECT_INSTRUCTIONS}}";
239
282
  const PROJECT_COMMAND_POLICY_PLACEHOLDER = "{{PROJECT_COMMAND_POLICY}}";
240
283
  const AGENT_INSTRUCTION_MARKERS = [
241
284
  { start: "<!-- DEV KIT START -->", end: "<!-- DEV KIT END -->" },
@@ -507,7 +550,7 @@ const runCommand = Effect.fn("runCommand")(function* (
507
550
  const trimmed = output.trim();
508
551
 
509
552
  if (exitCode !== 0) {
510
- return yield* new CommandError({ command: formatted, exitCode, output: trimmed });
553
+ return yield* CommandError.make({ command: formatted, exitCode, output: trimmed });
511
554
  }
512
555
 
513
556
  return trimmed;
@@ -521,20 +564,26 @@ const parseStructuredFile = Effect.fn("parseStructuredFile")(function* <A>(
521
564
  filePath: string,
522
565
  raw: string,
523
566
  schema: Schema.ConstraintDecoder<A>,
567
+ options: { readonly rejectExcessProperties?: boolean } = {},
524
568
  ) {
525
569
  const errors: Array<ParseError> = [];
526
570
  const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
527
571
  const first = errors[0];
528
572
 
529
573
  if (first !== undefined) {
530
- return yield* new StructuredFileError({
574
+ return yield* StructuredFileError.make({
531
575
  path: filePath,
532
576
  message: `${printParseErrorCode(first.error)} at offset ${first.offset}`,
533
577
  });
534
578
  }
535
579
 
536
- return yield* Schema.decodeUnknownEffect(schema)(parsed).pipe(
537
- Effect.mapError((cause) => new StructuredFileError({ path: filePath, message: cause.message })),
580
+ return yield* Schema.decodeUnknownEffect(
581
+ schema,
582
+ options.rejectExcessProperties ? { onExcessProperty: "error" } : undefined,
583
+ )(parsed).pipe(
584
+ Effect.mapError((cause) =>
585
+ StructuredFileError.make({ path: filePath, message: cause.message }),
586
+ ),
538
587
  );
539
588
  });
540
589
 
@@ -542,11 +591,13 @@ const readManifest = Effect.fn("readManifest")(function* (manifestPath: string)
542
591
  const fs = yield* FileSystem.FileSystem;
543
592
 
544
593
  if (!(yield* fs.exists(manifestPath))) {
545
- return yield* new ManifestNotFoundError({ path: manifestPath });
594
+ return yield* ManifestNotFoundError.make({ path: manifestPath });
546
595
  }
547
596
  const raw = yield* fs.readFileString(manifestPath);
548
597
 
549
- return yield* parseStructuredFile(manifestPath, raw, DevKitManifestSchema);
598
+ return yield* parseStructuredFile(manifestPath, raw, DevKitManifestSchema, {
599
+ rejectExcessProperties: true,
600
+ });
550
601
  });
551
602
 
552
603
  const readOptionalStructuredFile = Effect.fn("readOptionalStructuredFile")(function* <A>(
@@ -577,7 +628,7 @@ const expandSelection = (
577
628
  } else if (availableSkills.includes(name) || parseSkillSelector(name)?.type === "package") {
578
629
  selected.add(name);
579
630
  } else {
580
- return Effect.fail(new UnknownSkillOrFamilyError({ name, known }));
631
+ return Effect.fail(UnknownSkillOrFamilyError.make({ name, known }));
581
632
  }
582
633
  }
583
634
  for (const name of exclude) {
@@ -600,7 +651,7 @@ const resolveManagedPath = Effect.fn("resolveManagedPath")(function* (
600
651
  const path = yield* Path.Path;
601
652
 
602
653
  if (candidate.length === 0 || path.isAbsolute(candidate)) {
603
- return yield* new UnsafeManagedPathError({
654
+ return yield* UnsafeManagedPathError.make({
604
655
  path: candidate,
605
656
  reason: "must be a non-empty project-relative path",
606
657
  });
@@ -614,7 +665,7 @@ const resolveManagedPath = Effect.fn("resolveManagedPath")(function* (
614
665
  relative.startsWith(`..${path.sep}`) ||
615
666
  path.isAbsolute(relative)
616
667
  ) {
617
- return yield* new UnsafeManagedPathError({
668
+ return yield* UnsafeManagedPathError.make({
618
669
  path: candidate,
619
670
  reason: "resolves outside the project",
620
671
  });
@@ -628,7 +679,7 @@ const resolveManagedPath = Effect.fn("resolveManagedPath")(function* (
628
679
  const target = yield* observeSymbolicLink(ancestor);
629
680
 
630
681
  if (target.kind === "symlink") {
631
- return yield* new UnsafeManagedPathError({
682
+ return yield* UnsafeManagedPathError.make({
632
683
  path: candidate,
633
684
  reason: `ancestor is a symlink: ${portablePath(path, path.relative(projectDir, ancestor))}`,
634
685
  });
@@ -658,14 +709,14 @@ const validateReservedPaths = Effect.fn("validateReservedPaths")(function* (
658
709
  if (current === undefined) continue;
659
710
  for (const other of reserved.slice(index + 1)) {
660
711
  if (pathsOverlap(current.path, other.path)) {
661
- return yield* new InvalidProjectStateError({
712
+ return yield* InvalidProjectStateError.make({
662
713
  message: `${current.label} path ${current.path} overlaps ${other.label} path ${other.path}`,
663
714
  });
664
715
  }
665
716
  }
666
717
  for (const outputPath of outputPaths) {
667
718
  if (pathsOverlap(current.path, outputPath)) {
668
- return yield* new InvalidProjectStateError({
719
+ return yield* InvalidProjectStateError.make({
669
720
  message: `${current.label} path ${current.path} overlaps managed output ${outputPath}`,
670
721
  });
671
722
  }
@@ -673,28 +724,27 @@ const validateReservedPaths = Effect.fn("validateReservedPaths")(function* (
673
724
  }
674
725
  });
675
726
 
676
- const outputIdentity = (output: ManagedOutput) =>
677
- JSON.stringify({
678
- resourceId: output.resourceId,
679
- path: output.path,
680
- mode: output.mode,
681
- kind: output.kind,
682
- digest: output.digest,
683
- ...("skill" in output
684
- ? { skill: output.skill, target: output.target, catalog: output.catalog }
685
- : { sourcePath: output.sourcePath }),
686
- });
727
+ const outputIdentity = (output: ManagedOutput) => encodeManagedOutputJson(output);
687
728
 
688
729
  const outputOwnershipIdentity = (output: ManagedOutput) =>
689
- JSON.stringify({
690
- resourceId: output.resourceId,
691
- path: output.path,
692
- mode: output.mode,
693
- kind: output.kind,
694
- ...("skill" in output
695
- ? { skill: output.skill, target: output.target }
696
- : { sourcePath: output.sourcePath }),
697
- });
730
+ encodeOutputOwnershipIdentityJson(
731
+ "skill" in output
732
+ ? {
733
+ resourceId: output.resourceId,
734
+ path: output.path,
735
+ mode: output.mode,
736
+ kind: output.kind,
737
+ skill: output.skill,
738
+ target: output.target,
739
+ }
740
+ : {
741
+ resourceId: output.resourceId,
742
+ path: output.path,
743
+ mode: output.mode,
744
+ kind: output.kind,
745
+ sourcePath: output.sourcePath,
746
+ },
747
+ );
698
748
 
699
749
  const usesRawFileModeDigests = (toolVersion: string): boolean => {
700
750
  const match = /^(\d+)\.(\d+)\./.exec(toolVersion);
@@ -717,12 +767,12 @@ const validateInventory = Effect.fn("validateManagedInventory")(function* (
717
767
 
718
768
  for (const output of outputs) {
719
769
  if (ids.has(output.resourceId)) {
720
- return yield* new InvalidProjectStateError({
770
+ return yield* InvalidProjectStateError.make({
721
771
  message: `${label} contains duplicate resource id ${output.resourceId}`,
722
772
  });
723
773
  }
724
774
  if (paths.has(output.path)) {
725
- return yield* new InvalidProjectStateError({
775
+ return yield* InvalidProjectStateError.make({
726
776
  message: `${label} contains duplicate path ${output.path}`,
727
777
  });
728
778
  }
@@ -737,7 +787,7 @@ const validateInventory = Effect.fn("validateManagedInventory")(function* (
737
787
 
738
788
  if (previous === undefined || current === undefined) continue;
739
789
  if (current.startsWith(`${previous}/`)) {
740
- return yield* new InvalidProjectStateError({
790
+ return yield* InvalidProjectStateError.make({
741
791
  message: `${label} contains overlapping paths ${previous} and ${current}`,
742
792
  });
743
793
  }
@@ -761,7 +811,7 @@ const validateCrossInventoryPaths = Effect.fn("validateCrossInventoryPaths")(fun
761
811
 
762
812
  if (previous === undefined || current === undefined) continue;
763
813
  if (current.startsWith(`${previous}/`)) {
764
- return yield* new InvalidProjectStateError({
814
+ return yield* InvalidProjectStateError.make({
765
815
  message: `desired and previously owned paths overlap: ${previous} and ${current}`,
766
816
  });
767
817
  }
@@ -772,7 +822,7 @@ const renderAgentInstructions = Effect.fn("renderAgentInstructions")(function* (
772
822
  packageRoot: string,
773
823
  projectDir: string,
774
824
  sourceBySkill: ReadonlyMap<string, ResolvedSkillSource>,
775
- managesVitePlusQuality: boolean,
825
+ usesRecommendedVitePlusTasks: boolean,
776
826
  targets: ReturnType<typeof normalizeManifest>["targets"],
777
827
  ) {
778
828
  const fs = yield* FileSystem.FileSystem;
@@ -780,19 +830,24 @@ const renderAgentInstructions = Effect.fn("renderAgentInstructions")(function* (
780
830
  const templatePath = path.join(packageRoot, AGENT_INSTRUCTIONS_TEMPLATE);
781
831
 
782
832
  if ((yield* observePath(templatePath)).kind !== "file") {
783
- return yield* new InvalidProjectStateError({
833
+ return yield* InvalidProjectStateError.make({
784
834
  message: `dev-kit agent instructions template is not a regular file: ${AGENT_INSTRUCTIONS_TEMPLATE}`,
785
835
  });
786
836
  }
787
837
  const template = yield* fs.readFileString(templatePath);
788
838
 
789
839
  if (!template.includes(DEV_KIT_SKILL_PATH_PLACEHOLDER)) {
790
- return yield* new InvalidProjectStateError({
840
+ return yield* InvalidProjectStateError.make({
791
841
  message: `dev-kit agent instructions template is missing ${DEV_KIT_SKILL_PATH_PLACEHOLDER}`,
792
842
  });
793
843
  }
844
+ if (!template.includes(EFFECT_INSTRUCTIONS_PLACEHOLDER)) {
845
+ return yield* InvalidProjectStateError.make({
846
+ message: `dev-kit agent instructions template is missing ${EFFECT_INSTRUCTIONS_PLACEHOLDER}`,
847
+ });
848
+ }
794
849
  if (!template.includes(PROJECT_COMMAND_POLICY_PLACEHOLDER)) {
795
- return yield* new InvalidProjectStateError({
850
+ return yield* InvalidProjectStateError.make({
796
851
  message: `dev-kit agent instructions template is missing ${PROJECT_COMMAND_POLICY_PLACEHOLDER}`,
797
852
  });
798
853
  }
@@ -813,7 +868,24 @@ const renderAgentInstructions = Effect.fn("renderAgentInstructions")(function* (
813
868
  path.join(devKitSkill.linkPath ?? devKitSkill.path, "SKILL.md"),
814
869
  ),
815
870
  );
816
- const usesVitePlus = (yield* readDirectDependencyNames(projectDir)).includes("vite-plus");
871
+ const directDependencyNames = yield* readDirectDependencyNames(projectDir);
872
+ const usesVitePlus = directDependencyNames.includes("vite-plus");
873
+ const effectInstructions =
874
+ directDependencyNames.includes("effect") &&
875
+ (yield* observePath(path.join(projectDir, "node_modules", "effect", "AGENTS.md"))).kind ===
876
+ "file"
877
+ ? `# Learning more about the Effect
878
+
879
+ This repository uses the Effect Typescript library.
880
+
881
+ Before writing any Effect code, first read \`node_modules/effect/AGENTS.md\`
882
+ **completely**, and follow the links in the file when required.
883
+
884
+ If you need to learn more about particular Effect apis and concepts that the
885
+ guide doesn't cover, search through the source code in \`node_modules/effect/src\`.
886
+
887
+ `
888
+ : "";
817
889
  const projectPackage = yield* readProjectPackage(projectDir).pipe(
818
890
  Effect.catchTag("ProjectPackageError", (error) =>
819
891
  error.message.startsWith("package.json not found:") ? Effect.void : Effect.fail(error),
@@ -821,10 +893,11 @@ const renderAgentInstructions = Effect.fn("renderAgentInstructions")(function* (
821
893
  );
822
894
  const manager = yield* detectPackageManager(projectDir, projectPackage?.packageManager);
823
895
  const commandPolicy = usesVitePlus
824
- ? renderVitePlusCommandPolicy(projectPackage?.scripts ?? {}, managesVitePlusQuality)
896
+ ? renderVitePlusCommandPolicy(projectPackage?.scripts ?? {}, usesRecommendedVitePlusTasks)
825
897
  : renderPackageScriptCommandPolicy(manager, projectPackage?.scripts ?? {});
826
898
  const devKitInstructions = template
827
899
  .replaceAll(DEV_KIT_SKILL_PATH_PLACEHOLDER, devKitSkillPath)
900
+ .replaceAll(EFFECT_INSTRUCTIONS_PLACEHOLDER, effectInstructions)
828
901
  .replaceAll(PROJECT_COMMAND_POLICY_PLACEHOLDER, commandPolicy)
829
902
  .trimEnd();
830
903
 
@@ -840,7 +913,7 @@ const readGeneratedFileTemplate = Effect.fn("readGeneratedFileTemplate")(functio
840
913
  const templatePath = path.join(packageRoot, sourcePath);
841
914
 
842
915
  if ((yield* observePath(templatePath)).kind !== "file") {
843
- return yield* new InvalidProjectStateError({
916
+ return yield* InvalidProjectStateError.make({
844
917
  message: `dev-kit generated file template is not a regular file: ${sourcePath}`,
845
918
  });
846
919
  }
@@ -865,7 +938,9 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
865
938
  packageRoot,
866
939
  projectDir,
867
940
  sourceBySkill,
868
- setup.vitePlus.quality.config.enabled,
941
+ setup.vitePlus.quality.workflow.enabled &&
942
+ setup.vitePlus.quality.workflow.typecheck.length === 1 &&
943
+ setup.vitePlus.quality.workflow.typecheck[0] === "vp run typecheck",
869
944
  targets,
870
945
  );
871
946
 
@@ -887,7 +962,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
887
962
  : yield* observePath(source.absolute);
888
963
 
889
964
  if (!setup.agentInstructions.enabled && sourceObservation?.kind !== "file") {
890
- return yield* new InvalidProjectStateError({
965
+ return yield* InvalidProjectStateError.make({
891
966
  message: "Claude instructions source is not a regular file: AGENTS.md",
892
967
  });
893
968
  }
@@ -905,23 +980,6 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
905
980
  linkTarget,
906
981
  });
907
982
  }
908
- if (setup.vitePlus.quality.config.enabled) {
909
- const managed = yield* resolveManagedPath(projectDir, VITE_PLUS_CONFIG_PATH);
910
- const template = yield* readGeneratedFileTemplate(packageRoot, VITE_PLUS_CONFIG_TEMPLATE);
911
- const content = renderVitePlusConfigTemplate(template, setup.vitePlus.quality.config.typecheck);
912
-
913
- outputs.push({
914
- resourceId: "setup:vite-plus-config",
915
- path: managed.relative,
916
- sourcePath: VITE_PLUS_CONFIG_TEMPLATE,
917
- mode: "copy",
918
- kind: "file",
919
- digest: yield* digestFileContent(content),
920
- destination: managed.absolute,
921
- content,
922
- adoptIfExact: true,
923
- });
924
- }
925
983
  if (setup.vitePlus.quality.workflow.enabled) {
926
984
  const managed = yield* resolveManagedPath(projectDir, VITE_PLUS_GITHUB_ACTIONS_PATH);
927
985
  const template = yield* readGeneratedFileTemplate(
@@ -958,7 +1016,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
958
1016
  .filter((skill) => skill.name === duplicateOutput.name)
959
1017
  .map((skill) => skill.selector);
960
1018
 
961
- return yield* new InvalidProjectStateError({
1019
+ return yield* InvalidProjectStateError.make({
962
1020
  message: `selected skills would both install as ${duplicateOutput.name}: ${selectors.join(", ")}`,
963
1021
  });
964
1022
  }
@@ -966,7 +1024,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
966
1024
  const resolvedSource = sourceBySkill.get(skill.selector);
967
1025
 
968
1026
  if (resolvedSource === undefined) {
969
- return yield* new InvalidProjectStateError({
1027
+ return yield* InvalidProjectStateError.make({
970
1028
  message: `skill source is unavailable: ${skill.selector}`,
971
1029
  });
972
1030
  }
@@ -974,7 +1032,7 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
974
1032
  const sourceObservation = yield* observePath(source);
975
1033
 
976
1034
  if (sourceObservation.kind !== "directory") {
977
- return yield* new InvalidProjectStateError({
1035
+ return yield* InvalidProjectStateError.make({
978
1036
  message: `skill source is not a directory: ${source}`,
979
1037
  });
980
1038
  }
@@ -1027,8 +1085,8 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
1027
1085
  return outputs.sort((left, right) => left.path.localeCompare(right.path));
1028
1086
  });
1029
1087
 
1030
- const canonicalLock = (lock: DevKitLock): string => `${JSON.stringify(lock, null, 2)}\n`;
1031
- const canonicalState = (state: AppliedState): string => `${JSON.stringify(state, null, 2)}\n`;
1088
+ const canonicalLock = (lock: DevKitLock): string => `${encodeDevKitLockPrettyJson(lock)}\n`;
1089
+ const canonicalState = (state: AppliedState): string => `${encodeAppliedStatePrettyJson(state)}\n`;
1032
1090
 
1033
1091
  const planDesiredOutputs = Effect.fn("planDesiredSkillOutputs")(function* (
1034
1092
  projectDir: string,
@@ -1284,7 +1342,7 @@ const planDesiredOutputs = Effect.fn("planDesiredSkillOutputs")(function* (
1284
1342
  const lockedPlanMatches = (current: DevKitLock, next: DevKitLock): boolean =>
1285
1343
  current.toolVersion === next.toolVersion &&
1286
1344
  current.manifestDigest === next.manifestDigest &&
1287
- JSON.stringify(current.setup) === JSON.stringify(next.setup) &&
1345
+ encodeDevKitSetupJson(current.setup ?? {}) === encodeDevKitSetupJson(next.setup ?? {}) &&
1288
1346
  current.outputs.length === next.outputs.length &&
1289
1347
  current.outputs.every((output, index) => {
1290
1348
  const nextOutput = next.outputs[index];
@@ -1318,11 +1376,11 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1318
1376
  const manifest = normalizeManifest(yield* readManifest(manifestManaged.absolute));
1319
1377
 
1320
1378
  const vitePlusQuality = manifest.setup.vitePlus.quality;
1321
- const vitePlusQualityEnabled = vitePlusQuality.config.enabled || vitePlusQuality.workflow.enabled;
1379
+ const vitePlusQualityEnabled = vitePlusQuality.workflow.enabled;
1322
1380
 
1323
1381
  if (vitePlusQualityEnabled) {
1324
1382
  if (!manifest.setup.effectTsgo.enabled) {
1325
- return yield* new InvalidProjectStateError({
1383
+ return yield* InvalidProjectStateError.make({
1326
1384
  message:
1327
1385
  "setup.vitePlus.quality requires setup.effectTsgo.enabled so managed quality setup converges the Effect-patched compiler",
1328
1386
  });
@@ -1332,15 +1390,10 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1332
1390
  packageRoot,
1333
1391
  manifest.setup.effectTsgo.typescriptPackage,
1334
1392
  {
1335
- ...(vitePlusQuality.config.enabled ? { config: vitePlusQuality.config.typecheck } : {}),
1336
- ...(vitePlusQuality.workflow.enabled
1337
- ? {
1338
- workflow: {
1339
- beforeChecks: vitePlusQuality.workflow.beforeChecks,
1340
- typecheck: vitePlusQuality.workflow.typecheck,
1341
- },
1342
- }
1343
- : {}),
1393
+ workflow: {
1394
+ beforeChecks: vitePlusQuality.workflow.beforeChecks,
1395
+ typecheck: vitePlusQuality.workflow.typecheck,
1396
+ },
1344
1397
  },
1345
1398
  );
1346
1399
  }
@@ -1368,7 +1421,7 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1368
1421
 
1369
1422
  for (const [family, familySkills] of Object.entries(skillFamilies)) {
1370
1423
  if (availableSkills.includes(family)) {
1371
- return yield* new InvalidSkillCatalogError({
1424
+ return yield* InvalidSkillCatalogError.make({
1372
1425
  family,
1373
1426
  message: `family name conflicts with a skill name: ${family}`,
1374
1427
  });
@@ -1376,7 +1429,7 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1376
1429
  const missing = familySkills.filter((skill) => !availableSkills.includes(skill));
1377
1430
 
1378
1431
  if (missing.length > 0) {
1379
- return yield* new InvalidSkillCatalogError({
1432
+ return yield* InvalidSkillCatalogError.make({
1380
1433
  family,
1381
1434
  message: `family references missing skills: ${missing.join(", ")}`,
1382
1435
  });
@@ -1405,7 +1458,7 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1405
1458
  const catalogSkill = catalogBySelector.get(selector);
1406
1459
 
1407
1460
  if (catalogSkill === undefined) {
1408
- return yield* new InvalidProjectStateError({
1461
+ return yield* InvalidProjectStateError.make({
1409
1462
  message: `selected skill is unavailable: ${selector}`,
1410
1463
  });
1411
1464
  }
@@ -1422,7 +1475,7 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1422
1475
  const nextLock: DevKitLock = {
1423
1476
  version: 1,
1424
1477
  toolVersion: DEV_KIT_VERSION,
1425
- manifestDigest: yield* digestText(JSON.stringify(manifest)),
1478
+ manifestDigest: yield* digestText(encodeManifestJson(manifest)),
1426
1479
  setup: {
1427
1480
  ...(effectSource === undefined
1428
1481
  ? {}
@@ -1509,12 +1562,12 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1509
1562
  ]);
1510
1563
  if (options.locked) {
1511
1564
  if (!currentLock) {
1512
- return yield* new LockedPlanMismatchError({
1565
+ return yield* LockedPlanMismatchError.make({
1513
1566
  message: "dev-kit.lock.json is required with --locked",
1514
1567
  });
1515
1568
  }
1516
1569
  if (!lockedPlanMatches(currentLock, nextLock)) {
1517
- return yield* new LockedPlanMismatchError({
1570
+ return yield* LockedPlanMismatchError.make({
1518
1571
  message: "manifest or packaged skills differ from dev-kit.lock.json",
1519
1572
  });
1520
1573
  }
@@ -1534,7 +1587,7 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1534
1587
  );
1535
1588
 
1536
1589
  if (manifest.setup.claudeInstructions.enabled && removesClaudeInstructionsSource) {
1537
- return yield* new InvalidProjectStateError({
1590
+ return yield* InvalidProjectStateError.make({
1538
1591
  message:
1539
1592
  "cannot disable agentInstructions while claudeInstructions still links to an AGENTS.md that would be removed",
1540
1593
  });
@@ -1551,8 +1604,10 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
1551
1604
  nextLock,
1552
1605
  nextState: planned.nextState,
1553
1606
  metadataChanged:
1554
- JSON.stringify(currentLock) !== JSON.stringify(nextLock) ||
1555
- JSON.stringify(currentState) !== JSON.stringify(planned.nextState),
1607
+ currentLock === undefined ||
1608
+ encodeDevKitLockJson(currentLock) !== encodeDevKitLockJson(nextLock) ||
1609
+ currentState === undefined ||
1610
+ encodeAppliedStateJson(currentState) !== encodeAppliedStateJson(planned.nextState),
1556
1611
  } satisfies SkillPlan;
1557
1612
  });
1558
1613
 
@@ -1652,12 +1707,11 @@ const verifyPackageSkillSources = Effect.fn("verifyPackageSkillSources")(functio
1652
1707
  const observation = yield* observePath(resolved.path);
1653
1708
 
1654
1709
  if (
1655
- resolved.path !== action.desired.source ||
1656
1710
  resolved.version !== catalog.version ||
1657
1711
  observation.kind !== "directory" ||
1658
1712
  observation.digest !== catalog.digest
1659
1713
  ) {
1660
- return yield* new ApplyRaceError({ path: action.desired.source });
1714
+ return yield* ApplyRaceError.make({ path: action.desired.source });
1661
1715
  }
1662
1716
  verified.add(key);
1663
1717
  }
@@ -1667,7 +1721,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1667
1721
  const conflicts = plan.actions.filter((action) => action.action === "conflict");
1668
1722
 
1669
1723
  if (conflicts.length > 0) {
1670
- return yield* new PlanConflictError({
1724
+ return yield* PlanConflictError.make({
1671
1725
  conflicts: conflicts.map((action) =>
1672
1726
  action.action === "conflict" ? `${action.path}: ${action.reason}` : "",
1673
1727
  ),
@@ -1677,7 +1731,9 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1677
1731
  const fs = yield* FileSystem.FileSystem;
1678
1732
  const path = yield* Path.Path;
1679
1733
  const mutating = plan.actions.filter(
1680
- (action): action is Exclude<SkillPlanAction, { readonly action: "unchanged" | "conflict" }> =>
1734
+ (
1735
+ action,
1736
+ ): action is Extract<SkillPlanAction, { readonly action: "create" | "update" | "remove" }> =>
1681
1737
  action.action === "create" || action.action === "update" || action.action === "remove",
1682
1738
  );
1683
1739
 
@@ -1686,7 +1742,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1686
1742
  action.action === "remove" ? action.destination : action.desired.destination;
1687
1743
 
1688
1744
  if (!observationsEqual(yield* observePath(destination), action.observed)) {
1689
- return yield* new ApplyRaceError({
1745
+ return yield* ApplyRaceError.make({
1690
1746
  path: action.action === "remove" ? action.previous.path : action.desired.path,
1691
1747
  });
1692
1748
  }
@@ -1702,7 +1758,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1702
1758
  });
1703
1759
  const stageDir = path.join(tempDir, "stage");
1704
1760
  const backupDir = path.join(tempDir, "backup");
1705
- const stagedByResource = new Map<string, string>();
1761
+ const stagedByAction = new Map<(typeof mutating)[number], string>();
1706
1762
  let stageIndex = 0;
1707
1763
 
1708
1764
  for (const action of mutating) {
@@ -1718,7 +1774,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1718
1774
  yield* fs.writeFileString(staged, action.stagedContent);
1719
1775
  } else if (action.action === "remove") {
1720
1776
  if (action.stagedContent === undefined) {
1721
- return yield* new InvalidProjectStateError({
1777
+ return yield* InvalidProjectStateError.make({
1722
1778
  message: `missing staged content for ${action.previous.resourceId}`,
1723
1779
  });
1724
1780
  }
@@ -1733,7 +1789,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1733
1789
  const symbolicLink = yield* findNestedSymbolicLink(staged);
1734
1790
 
1735
1791
  if (symbolicLink !== undefined) {
1736
- return yield* new InvalidProjectStateError({
1792
+ return yield* InvalidProjectStateError.make({
1737
1793
  message: `staged skill contains a symlink: ${action.desired.path}`,
1738
1794
  });
1739
1795
  }
@@ -1753,7 +1809,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1753
1809
  : undefined;
1754
1810
 
1755
1811
  if (observation.kind !== "file" || digest !== action.desired.digest) {
1756
- return yield* new InvalidProjectStateError({
1812
+ return yield* InvalidProjectStateError.make({
1757
1813
  message: `staged output digest mismatch for ${action.desired.path}`,
1758
1814
  });
1759
1815
  }
@@ -1761,15 +1817,12 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1761
1817
  observation.kind !== action.desired.kind ||
1762
1818
  observation.digest !== action.desired.digest
1763
1819
  ) {
1764
- return yield* new InvalidProjectStateError({
1820
+ return yield* InvalidProjectStateError.make({
1765
1821
  message: `staged output digest mismatch for ${action.desired.path}`,
1766
1822
  });
1767
1823
  }
1768
1824
  }
1769
- stagedByResource.set(
1770
- action.action === "remove" ? action.previous.resourceId : action.desired.resourceId,
1771
- staged,
1772
- );
1825
+ stagedByAction.set(action, staged);
1773
1826
  }
1774
1827
 
1775
1828
  yield* verifyPackageSkillSources(plan);
@@ -1791,16 +1844,13 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1791
1844
  let replacementIndex = 0;
1792
1845
 
1793
1846
  for (const action of mutating) {
1794
- const staged =
1795
- action.action === "remove"
1796
- ? stagedByResource.get(action.previous.resourceId)
1797
- : stagedByResource.get(action.desired.resourceId);
1847
+ const staged = stagedByAction.get(action);
1798
1848
 
1799
1849
  if (
1800
1850
  (action.action !== "remove" || action.stagedContent !== undefined) &&
1801
1851
  staged === undefined
1802
1852
  ) {
1803
- return yield* new InvalidProjectStateError({
1853
+ return yield* InvalidProjectStateError.make({
1804
1854
  message: `missing staged output for ${
1805
1855
  action.action === "remove" ? action.previous.resourceId : action.desired.resourceId
1806
1856
  }`,
@@ -1849,7 +1899,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
1849
1899
  replacement.expected !== undefined &&
1850
1900
  !observationsEqual(observed, replacement.expected)
1851
1901
  ) {
1852
- return yield* new ApplyRaceError({ path: replacement.path });
1902
+ return yield* ApplyRaceError.make({ path: replacement.path });
1853
1903
  }
1854
1904
  if (observed.kind !== "missing") {
1855
1905
  yield* fs.makeDirectory(path.dirname(replacement.backup), { recursive: true });
@@ -1887,7 +1937,7 @@ export const runProjectSkillPlan = Effect.fn("runProjectSkillPlan")(function* (
1887
1937
  const conflicts = plan.actions.filter((action) => action.action === "conflict");
1888
1938
 
1889
1939
  if (conflicts.length > 0) {
1890
- return yield* new PlanConflictError({
1940
+ return yield* PlanConflictError.make({
1891
1941
  conflicts: conflicts.map((action) =>
1892
1942
  action.action === "conflict" ? `${action.path}: ${action.reason}` : "",
1893
1943
  ),
@@ -1897,7 +1947,7 @@ export const runProjectSkillPlan = Effect.fn("runProjectSkillPlan")(function* (
1897
1947
 
1898
1948
  yield* acquireProjectProcessLock(plan.projectDir);
1899
1949
  const replanned = yield* planProjectSkills(options);
1900
- const originalSignature = JSON.stringify({
1950
+ const originalSignature = encodePlanSnapshotJson({
1901
1951
  actions: plan.actions,
1902
1952
  effectSource: plan.effectSource,
1903
1953
  effectTsgo: plan.effectTsgo,
@@ -1905,7 +1955,7 @@ export const runProjectSkillPlan = Effect.fn("runProjectSkillPlan")(function* (
1905
1955
  nextLock: plan.nextLock,
1906
1956
  nextState: plan.nextState,
1907
1957
  });
1908
- const nextSignature = JSON.stringify({
1958
+ const nextSignature = encodePlanSnapshotJson({
1909
1959
  actions: replanned.actions,
1910
1960
  effectSource: replanned.effectSource,
1911
1961
  effectTsgo: replanned.effectTsgo,
@@ -1915,7 +1965,7 @@ export const runProjectSkillPlan = Effect.fn("runProjectSkillPlan")(function* (
1915
1965
  });
1916
1966
 
1917
1967
  if (originalSignature !== nextSignature) {
1918
- return yield* new ApplyRaceError({ path: "project state" });
1968
+ return yield* ApplyRaceError.make({ path: "project state" });
1919
1969
  }
1920
1970
  const changes = plannedChangeCount(replanned);
1921
1971