@savvy-web/silk-effects 5.4.0 → 5.5.1

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 CHANGED
@@ -13,7 +13,7 @@ Shared [Effect](https://effect.website/) library providing Silk Suite convention
13
13
  - Supply the Silk commitlint config, its prompt and formatter, and the `silk/body-no-markdown` rule
14
14
  - Run the lint-staged handlers and the `savvy lint fmt` formatters from a single implementation so the hook and the CLI cannot drift
15
15
  - Inspect a Turborepo read-only — diagnose per-package cache hits, derive the task graph and compute affected packages, all over `turbo --dry`
16
- - Manage the vendored reference repos declared in `.repos/config.json` — submodule status, sync, pinning and notes — and keep every vendored tree read-only between mutations
16
+ - Manage the vendored reference repos declared in `.repos/config.json` — submodule status, drift detection, sync, add, pin, note, remove, rename and restore — and keep every vendored tree read-only between mutations
17
17
  - Locate config files and keep Biome schema URLs in sync across workspaces
18
18
 
19
19
  ## Install
@@ -364,13 +364,13 @@ const diagnosis = await Effect.runPromise(
364
364
  // => CacheDiagnosis: per-package HIT/MISS breakdown for the task
365
365
  ```
366
366
 
367
- #### ReposManager and ReposLockdown
367
+ #### ReposManager, ReposDrift and ReposLockdown
368
368
 
369
- The `Repos` namespace drives vendored reference repos — upstream sources checked out as git submodules under `.repos/`, declared in a `.repos/config.json` manifest. `ReposConfigStore` reads and writes that manifest, and `ReposManager` does the git work: `status(root)` reports presence, the pinned commit, working-tree dirtiness and stale notes, `sync(root)` initializes missing submodules and applies each entry's sparse-checkout, `pin(root, name, ref)` moves an entry to a new ref, `add(root, options)` vendors a new one, and `note(root, name, op)` adds, removes or promotes an agent note.
369
+ The `Repos` namespace drives vendored reference repos — upstream sources checked out as git submodules under `.repos/`, declared in a `.repos/config.json` manifest. `ReposConfigStore` reads and writes that manifest, and `ReposManager` does the git work: `status(root)` reports presence, the pinned commit, working-tree dirtiness and stale notes, `sync(root)` initializes missing submodules and applies each entry's sparse-checkout, `pin(root, name, ref)` moves an entry to a new ref, `add(root, options)` vendors a new one, `note(root, name, op)` adds, removes or promotes an agent note, `remove(root, name)` unvendors an entry, `rename(root, oldName, newName)` renames one in place, and `restore(root, names?)` hard-resets one or more dirty checkouts back to their pinned commit. `ReposDrift.check(root)` is a read-only companion — it reconciles the manifest, `.gitmodules`, the worktree, and `git submodule status`, reporting any mismatch as a typed drift kind.
370
370
 
371
- `ReposLockdown` is the permissions boundary around all of that. `lock(root, name)` chmods a vendored worktree and its submodule git metadata to files `0444` and directories `0555`; `unlock` reverses it; `withUnlocked(root, name, effect)` brackets an effect between the two. `ReposManager`'s `sync`, `add` and `pin` run their git mutations inside that bracket and re-lock afterwards, so a vendored tree is read-only whenever the manager is not mid-write. Reads are unaffected — a locked tree needs no special handling to open a file.
371
+ `ReposLockdown` is the permissions boundary around all of that. `lock(root, name)` chmods a vendored worktree and its submodule git metadata to files `0444` and directories `0555` (an executable file locks at `0555` instead, preserving its executable bit); `unlock` reverses it, restoring `0644`/`0755` (`0755` for a file that was executable); `withUnlocked(root, name, effect)` brackets an effect between the two. `ReposManager`'s `sync`, `add`, `pin`, `remove` and `restore` run their git mutations inside that bracket and re-lock afterwards, and `rename` hand-rolls the same unlock/relock contract across its `oldName`→`newName` move, so a vendored tree is read-only whenever the manager is not mid-write. Reads are unaffected — a locked tree needs no special handling to open a file.
372
372
 
373
- Two consequences for callers: `ReposManager.layer` now requires `ReposLockdown` alongside `ReposConfigStore`, `Git`, `FileSystem` and `Path`, and `sync`, `add` and `pin` widen their error channel with `ReposLockdownError`, which carries the offending `path` and a `reason`.
373
+ Two consequences for callers: `ReposManager.layer` requires `ReposLockdown` alongside `ReposConfigStore`, `Git`, `FileSystem` and `Path`, and `sync`, `add`, `pin`, `remove`, `rename` and `restore` widen their error channel with `ReposLockdownError`, which carries the offending `path` and a `reason`.
374
374
 
375
375
  ```typescript
376
376
  import { Effect, Layer } from "effect";
@@ -95,10 +95,12 @@ function formatChangelogEntry(entry, options) {
95
95
  */
96
96
  function formatPRAndUserAttribution(pr, user, links) {
97
97
  let prReference = "";
98
- if (pr) if (links?.pull) {
99
- const pullUrl = extractUrlFromMarkdown(links.pull);
100
- prReference = ` [#${String(pr)}](${pullUrl})`;
101
- } else prReference = ` (#${String(pr)})`;
98
+ if (pr) {
99
+ if (links?.pull) {
100
+ const pullUrl = extractUrlFromMarkdown(links.pull);
101
+ prReference = ` [#${String(pr)}](${pullUrl})`;
102
+ } else prReference = ` (#${String(pr)})`;
103
+ }
102
104
  if (user) {
103
105
  if (links?.user) {
104
106
  const userUrl = extractUrlFromMarkdown(links.user);
@@ -69,18 +69,20 @@ function readSigningDiagnostic() {
69
69
  const isSsh = gpgFormat === "ssh";
70
70
  let keyResolves = false;
71
71
  let keyExpiry = null;
72
- if (signingKey) if (isSsh) keyResolves = yield* Effect.tryPromise(async () => {
73
- await stat(signingKey);
74
- return true;
75
- }).pipe(Effect.orElseSucceed(() => false));
76
- else {
77
- const stdout = yield* spawner.string(ChildProcess.make("gpg", [
78
- "--list-secret-keys",
79
- "--with-colons",
80
- signingKey
81
- ])).pipe(Effect.orElseSucceed(() => ""));
82
- keyResolves = stdout.trim().length > 0;
83
- keyExpiry = parseGpgKeyExpiry(stdout);
72
+ if (signingKey) {
73
+ if (isSsh) keyResolves = yield* Effect.tryPromise(async () => {
74
+ await stat(signingKey);
75
+ return true;
76
+ }).pipe(Effect.orElseSucceed(() => false));
77
+ else {
78
+ const stdout = yield* spawner.string(ChildProcess.make("gpg", [
79
+ "--list-secret-keys",
80
+ "--with-colons",
81
+ signingKey
82
+ ])).pipe(Effect.orElseSucceed(() => ""));
83
+ keyResolves = stdout.trim().length > 0;
84
+ keyExpiry = parseGpgKeyExpiry(stdout);
85
+ }
84
86
  }
85
87
  let agentResponsive = true;
86
88
  if (!isSsh) agentResponsive = yield* spawner.exitCode(ChildProcess.make("gpg-connect-agent", ["/bye"])).pipe(Effect.timeout("1 second"), Effect.map((code) => code === 0), Effect.orElseSucceed(() => false));
package/index.d.ts CHANGED
@@ -8296,6 +8296,46 @@ declare class ReposLockdownError extends ReposLockdownErrorBase<{
8296
8296
  get message(): string;
8297
8297
  }
8298
8298
  //#endregion
8299
+ //#region src/repos/schemas/drift.d.ts
8300
+ /**
8301
+ * The kinds of drift {@link ReposDrift} can detect between the four
8302
+ * authorities it reconciles: the manifest, `.gitmodules`, the worktree, and
8303
+ * `git submodule status`.
8304
+ * @public
8305
+ */
8306
+ declare const DriftKind: Schema.Literals<readonly ["urlMismatch", "pathMismatch", "unregisteredManifestEntry", "orphanGitmodulesEntry", "missingWorktree", "checkoutDiverged", "missingShallow", "gitmodulesUnparsable"]>;
8307
+ /** @public */
8308
+ type DriftKind = typeof DriftKind.Type;
8309
+ declare const RepoDrift_base: Schema.Class<RepoDrift, Schema.Struct<{
8310
+ readonly name: Schema.String;
8311
+ readonly kind: Schema.Literals<readonly ["urlMismatch", "pathMismatch", "unregisteredManifestEntry", "orphanGitmodulesEntry", "missingWorktree", "checkoutDiverged", "missingShallow", "gitmodulesUnparsable"]>;
8312
+ readonly detail: Schema.String;
8313
+ readonly manifestValue: Schema.optionalKey<Schema.String>;
8314
+ readonly observedValue: Schema.optionalKey<Schema.String>;
8315
+ }>, {}>;
8316
+ /**
8317
+ * One detected disagreement between two (or more) of the four authorities
8318
+ * for a single vendored repo.
8319
+ *
8320
+ * @remarks
8321
+ * `manifestValue`/`observedValue` carry the two disagreeing values when the
8322
+ * drift is a value mismatch (`urlMismatch`, `pathMismatch`); other kinds
8323
+ * populate whichever side has a value to show (or neither, for
8324
+ * `gitmodulesUnparsable`, which is not about one repo).
8325
+ * @public
8326
+ */
8327
+ declare class RepoDrift extends RepoDrift_base {}
8328
+ declare const ReposDriftReport_base: Schema.Class<ReposDriftReport, Schema.Struct<{
8329
+ readonly drifts: Schema.$Array<typeof RepoDrift>;
8330
+ readonly clean: Schema.Boolean;
8331
+ }>, {}>;
8332
+ /**
8333
+ * The result of reconciling the manifest, `.gitmodules`, the worktree, and
8334
+ * `git submodule status` for every vendored repo.
8335
+ * @public
8336
+ */
8337
+ declare class ReposDriftReport extends ReposDriftReport_base {}
8338
+ //#endregion
8299
8339
  //#region src/repos/schemas/manifest.d.ts
8300
8340
  /**
8301
8341
  * A vendored-repo manifest key: non-empty, contains no `/` or `\`, and is
@@ -8382,6 +8422,21 @@ type ReposManifestFile = typeof ReposManifestFile.Type;
8382
8422
  /**
8383
8423
  * Status of one vendored repo: gitlink presence and dirtiness, plus notes
8384
8424
  * that no longer match the pinned ref.
8425
+ *
8426
+ * @remarks
8427
+ * `commit` is an alias of `stagedCommit`, retained for one release so an
8428
+ * existing consumer reading `entry.commit` keeps working while it migrates to
8429
+ * the index-aware triple. It carries no independent release tag beyond
8430
+ * `@public` (this schema has no narrower audience to gate it behind).
8431
+ * Removal is scheduled by a tracked issue, not gated on a renderer migration
8432
+ * precondition here.
8433
+ *
8434
+ * Not fully behavior-preserving: `commit: stagedCommit ?? null` reads `null`
8435
+ * for a gitlink committed at `HEAD` but staged for REMOVAL, where the prior
8436
+ * single-`commit` field showed the committed oid. `stagedCommit` is `None`
8437
+ * in exactly that case (nothing is staged), so the alias reports "nothing
8438
+ * staged" rather than "here is what HEAD still has" — a real, if narrow,
8439
+ * difference from the pre-triple `commit` field's behavior.
8385
8440
  * @public
8386
8441
  */
8387
8442
  declare const RepoStatusEntry: Schema.Struct<{
@@ -8389,7 +8444,14 @@ declare const RepoStatusEntry: Schema.Struct<{
8389
8444
  readonly ref: Schema.String;
8390
8445
  readonly purpose: Schema.String;
8391
8446
  readonly present: Schema.Boolean;
8447
+ /** @deprecated alias of `stagedCommit`; retained for one release. */
8392
8448
  readonly commit: Schema.NullOr<Schema.String>;
8449
+ /** The gitlink oid staged in the index (`git ls-files --stage`) — sees a pin BEFORE it is committed. */
8450
+ readonly stagedCommit: Schema.optionalKey<Schema.String>;
8451
+ /** The gitlink oid committed at `HEAD` (`git ls-tree HEAD`). */
8452
+ readonly committedCommit: Schema.optionalKey<Schema.String>;
8453
+ /** The commit actually checked out in the submodule worktree (`git rev-parse HEAD` inside it); absent when there is no checkout. */
8454
+ readonly checkedOutCommit: Schema.optionalKey<Schema.String>;
8393
8455
  readonly dirty: Schema.Boolean;
8394
8456
  readonly staleNoteIds: Schema.$Array<Schema.String>;
8395
8457
  }>;
@@ -8405,7 +8467,14 @@ declare const ReposStatusReport: Schema.Struct<{
8405
8467
  readonly ref: Schema.String;
8406
8468
  readonly purpose: Schema.String;
8407
8469
  readonly present: Schema.Boolean;
8470
+ /** @deprecated alias of `stagedCommit`; retained for one release. */
8408
8471
  readonly commit: Schema.NullOr<Schema.String>;
8472
+ /** The gitlink oid staged in the index (`git ls-files --stage`) — sees a pin BEFORE it is committed. */
8473
+ readonly stagedCommit: Schema.optionalKey<Schema.String>;
8474
+ /** The gitlink oid committed at `HEAD` (`git ls-tree HEAD`). */
8475
+ readonly committedCommit: Schema.optionalKey<Schema.String>;
8476
+ /** The commit actually checked out in the submodule worktree (`git rev-parse HEAD` inside it); absent when there is no checkout. */
8477
+ readonly checkedOutCommit: Schema.optionalKey<Schema.String>;
8409
8478
  readonly dirty: Schema.Boolean;
8410
8479
  readonly staleNoteIds: Schema.$Array<Schema.String>;
8411
8480
  }>>;
@@ -8416,7 +8485,8 @@ type ReposStatusReport = typeof ReposStatusReport.Type;
8416
8485
  /**
8417
8486
  * Result of reconciling working-tree submodules with the manifest: missing
8418
8487
  * repos initialized, sparse-checkout patterns re-applied, already-present
8419
- * repos left alone, and stale locks cleared.
8488
+ * repos left alone, stale locks cleared, drifted submodule URLs reconciled,
8489
+ * and orphan manifest entries (no gitlink at all) registered.
8420
8490
  * @public
8421
8491
  */
8422
8492
  declare const ReposSyncReport: Schema.Struct<{
@@ -8424,6 +8494,8 @@ declare const ReposSyncReport: Schema.Struct<{
8424
8494
  readonly sparseApplied: Schema.$Array<Schema.String>;
8425
8495
  readonly upToDate: Schema.$Array<Schema.String>;
8426
8496
  readonly clearedLocks: Schema.$Array<Schema.String>;
8497
+ readonly urlSynced: Schema.$Array<Schema.String>;
8498
+ readonly registered: Schema.$Array<Schema.String>;
8427
8499
  }>;
8428
8500
  /** @public */
8429
8501
  type ReposSyncReport = typeof ReposSyncReport.Type;
@@ -8452,6 +8524,62 @@ declare const ReposAddResult: Schema.Struct<{
8452
8524
  }>;
8453
8525
  /** @public */
8454
8526
  type ReposAddResult = typeof ReposAddResult.Type;
8527
+ /**
8528
+ * Result of removing a vendored repo from the manifest: the gitlink, module
8529
+ * gitdir, and `.gitmodules` section are all gone, and the entry's notes are
8530
+ * surfaced so any durable ones can be promoted elsewhere before this result
8531
+ * is committed.
8532
+ * @public
8533
+ */
8534
+ declare const ReposRemoveResult: Schema.Struct<{
8535
+ readonly name: Schema.String;
8536
+ readonly path: Schema.String;
8537
+ readonly commitMessage: Schema.String;
8538
+ readonly removedNotes: Schema.$Array<Schema.Struct<{
8539
+ readonly id: Schema.String;
8540
+ readonly date: Schema.String;
8541
+ readonly ref: Schema.String;
8542
+ readonly note: Schema.String;
8543
+ }>>;
8544
+ }>;
8545
+ /** @public */
8546
+ type ReposRemoveResult = typeof ReposRemoveResult.Type;
8547
+ /**
8548
+ * Result of renaming a vendored repo's manifest key: the `.repos/<name>`
8549
+ * worktree moved, the module gitdir's `core.worktree` values re-pointed, the
8550
+ * `.gitmodules` section canonicalized to the new name, and the manifest key
8551
+ * renamed.
8552
+ * @public
8553
+ */
8554
+ declare const ReposRenameResult: Schema.Struct<{
8555
+ readonly oldName: Schema.String;
8556
+ readonly newName: Schema.String;
8557
+ readonly path: Schema.String;
8558
+ readonly commitMessage: Schema.String;
8559
+ }>;
8560
+ /** @public */
8561
+ type ReposRenameResult = typeof ReposRenameResult.Type;
8562
+ /**
8563
+ * Result of hard-resetting one or more vendored repos to their staged (or
8564
+ * committed) gitlink commit and re-applying sparse-checkout paths.
8565
+ *
8566
+ * @remarks
8567
+ * `restored` lists every repo actually reset, paired with the commit it was
8568
+ * reset to. `skippedClean` is populated ONLY by the names-omitted form of
8569
+ * {@link ReposManagerShape.restore} — repos left untouched because `status`
8570
+ * reported them clean; an explicit-names call never skips anything (an
8571
+ * explicit ask is always honored), so it always reports an empty array.
8572
+ * @public
8573
+ */
8574
+ declare const ReposRestoreResult: Schema.Struct<{
8575
+ readonly restored: Schema.$Array<Schema.Struct<{
8576
+ readonly name: Schema.String;
8577
+ readonly commit: Schema.String;
8578
+ }>>;
8579
+ readonly skippedClean: Schema.$Array<Schema.String>;
8580
+ }>;
8581
+ /** @public */
8582
+ type ReposRestoreResult = typeof ReposRestoreResult.Type;
8455
8583
  /**
8456
8584
  * Result of an agent-note mutation against a vendored repo.
8457
8585
  * @public
@@ -8474,6 +8602,14 @@ interface ReposConfigStoreShape {
8474
8602
  readonly exists: (root: string) => Effect.Effect<boolean>;
8475
8603
  readonly read: (root: string) => Effect.Effect<ReposManifestFile, ReposConfigError>;
8476
8604
  readonly write: (root: string, manifest: ReposManifestFile) => Effect.Effect<void, ReposConfigError>;
8605
+ /**
8606
+ * Serialized read-modify-write: acquires an exclusive-create lock file
8607
+ * beside the manifest, reads the current manifest (an absent manifest is
8608
+ * passed to `fn` as `{ repos: {} }` — `update` can initialize), runs `fn`,
8609
+ * writes the result, and always releases the lock. Concurrent callers
8610
+ * queue behind the lock rather than racing a lost update.
8611
+ */
8612
+ readonly update: (root: string, fn: (manifest: ReposManifestFile) => ReposManifestFile | Effect.Effect<ReposManifestFile, ReposConfigError>) => Effect.Effect<ReposManifestFile, ReposConfigError>;
8477
8613
  }
8478
8614
  declare const ReposConfigStore_base: Context.ServiceClass<ReposConfigStore, "@savvy-web/silk-effects/ReposConfigStore", ReposConfigStoreShape>;
8479
8615
  /**
@@ -8488,6 +8624,31 @@ declare class ReposConfigStore extends ReposConfigStore_base {
8488
8624
  static readonly layer: Layer.Layer<ReposConfigStore, never, FileSystem.FileSystem | Path.Path>;
8489
8625
  }
8490
8626
  //#endregion
8627
+ //#region src/repos/services/drift.d.ts
8628
+ /**
8629
+ * The {@link ReposDrift} service shape.
8630
+ * @public
8631
+ */
8632
+ interface ReposDriftShape {
8633
+ readonly check: (root: string) => Effect.Effect<ReposDriftReport, ReposConfigError | GitSubmoduleError>;
8634
+ }
8635
+ declare const ReposDrift_base: Context.ServiceClass<ReposDrift, "@savvy-web/silk-effects/ReposDrift", ReposDriftShape>;
8636
+ /**
8637
+ * Reconciles the four authorities a vendored repo's state is spread across —
8638
+ * the manifest, `.gitmodules`, the worktree, and `git submodule status` —
8639
+ * and reports every disagreement found. Read-only: no staging, no lockdown
8640
+ * interaction, so it runs unmodified against a locked (`ReposLockdown`)
8641
+ * tree.
8642
+ * @public
8643
+ */
8644
+ declare class ReposDrift extends ReposDrift_base {
8645
+ /**
8646
+ * Production implementation of {@link ReposDrift}.
8647
+ * @public
8648
+ */
8649
+ static readonly layer: Layer.Layer<ReposDrift, never, ReposConfigStore | Git | FileSystem.FileSystem | Path.Path>;
8650
+ }
8651
+ //#endregion
8491
8652
  //#region src/repos/services/lockdown.d.ts
8492
8653
  /**
8493
8654
  * The {@link ReposLockdown} service shape.
@@ -8578,13 +8739,36 @@ interface ReposManagerShape {
8578
8739
  readonly id: string;
8579
8740
  readonly into: "layout" | "startHere";
8580
8741
  }) => Effect.Effect<ReposNoteResult, ReposConfigError | RepoNotFoundError | NoteNotFoundError>;
8742
+ readonly remove: (root: string, name: string) => Effect.Effect<ReposRemoveResult, ReposConfigError | GitSubmoduleError | RepoNotFoundError | ReposLockdownError>;
8743
+ /**
8744
+ * Crash contract: unlike {@link add}, `rename` has no compensating
8745
+ * rollback. `Effect.uninterruptibleMask` guards fiber interruption, not a
8746
+ * hard process kill, so a `kill -9` between `git mv` and the manifest
8747
+ * write can leave the tree renamed in git, the manifest still holding
8748
+ * the old key, and the tree unlocked (the relock finalizer never runs).
8749
+ * This is a deliberate asymmetry with `add`, which DOES roll back —
8750
+ * `rename` does not, because unlike a fresh vendor there is no "nothing
8751
+ * happened yet" state to unwind back to.
8752
+ *
8753
+ * Recovery is NOT a guaranteed clean "just run it again": `git mv` is not
8754
+ * idempotent (a real-git probe confirms a second `git mv <old> <new>`
8755
+ * after the first already succeeded fails with "bad source"), so a crash
8756
+ * after `git mv` lands makes the next `rename` call fail at that same
8757
+ * step rather than resume past it. A crash in that window needs manual
8758
+ * inspection (`git status`, `.repos/config.json`, `.gitmodules`) before
8759
+ * retrying, not a blind re-invocation.
8760
+ */
8761
+ readonly rename: (root: string, oldName: string, newName: string) => Effect.Effect<ReposRenameResult, ReposConfigError | GitSubmoduleError | RepoNotFoundError | ReposLockdownError>;
8762
+ readonly restore: (root: string, names?: ReadonlyArray<string>) => Effect.Effect<ReposRestoreResult, ReposConfigError | GitSubmoduleError | RepoNotFoundError | ReposLockdownError>;
8581
8763
  }
8582
8764
  declare const ReposManager_base: Context.ServiceClass<ReposManager, "@savvy-web/silk-effects/ReposManager", ReposManagerShape>;
8583
8765
  /**
8584
8766
  * Drives the vendored `.repos/` submodules over git: reports status
8585
8767
  * (presence, dirtiness, stale notes), reconciles the working tree with the
8586
8768
  * manifest, vendors new entries (`add`), re-pins existing entries to a new
8587
- * ref (`pin`), and adds, removes, or promotes agent notes (`note`).
8769
+ * ref (`pin`), adds/removes/promotes agent notes (`note`), unvendors
8770
+ * (`remove`), renames (`rename`), and explicitly hard-resets dirty
8771
+ * checkouts back to their pinned commit (`restore`).
8588
8772
  * @public
8589
8773
  */
8590
8774
  declare class ReposManager extends ReposManager_base {
@@ -8602,7 +8786,7 @@ declare class ReposManager extends ReposManager_base {
8602
8786
  static readonly layer: Layer.Layer<ReposManager, never, ReposConfigStore | Git | FileSystem.FileSystem | Path.Path | ReposLockdown>;
8603
8787
  }
8604
8788
  declare namespace index_d_exports$3 {
8605
- export { GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NOTE_LIMIT, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposConfigStoreShape, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposLockdownShape, ReposManager, ReposManagerShape, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, resolveModuleDir };
8789
+ export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NOTE_LIMIT, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposConfigStoreShape, ReposDrift, ReposDriftReport, ReposDriftShape, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposLockdownShape, ReposManager, ReposManagerShape, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, resolveModuleDir };
8606
8790
  }
8607
8791
  //#endregion
8608
8792
  //#region src/schemas/BiomeConfig.d.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk-effects",
3
- "version": "5.4.0",
3
+ "version": "5.5.1",
4
4
  "private": false,
5
5
  "description": "Shared Effect library for Silk Suite conventions",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
@@ -33,15 +33,15 @@
33
33
  "@changesets/config": "^4.0.0-next.6",
34
34
  "@changesets/get-github-info": "^1.0.0-next.4",
35
35
  "@changesets/get-release-plan": "^5.0.0-next.9",
36
- "@effected/commands": "^0.3.1",
37
- "@effected/git": "^0.5.2",
38
- "@effected/glob": "^0.2.2",
39
- "@effected/jsonc": "^0.5.2",
40
- "@effected/package-json": "^0.7.3",
41
- "@effected/templates": "^0.1.1",
42
- "@effected/walker": "^0.3.4",
43
- "@effected/workspaces": "^0.10.0",
44
- "@effected/yaml": "^0.6.1",
36
+ "@effected/commands": "^0.4.0",
37
+ "@effected/git": "^0.7.0",
38
+ "@effected/glob": "^0.3.0",
39
+ "@effected/jsonc": "^0.6.0",
40
+ "@effected/package-json": "^0.8.0",
41
+ "@effected/templates": "^0.2.0",
42
+ "@effected/walker": "^0.4.0",
43
+ "@effected/workspaces": "^0.11.0",
44
+ "@effected/yaml": "^0.7.0",
45
45
  "@manypkg/get-packages": "^3.1.0",
46
46
  "mdast-util-heading-range": "^4.0.0",
47
47
  "mdast-util-to-string": "^4.0.0",
@@ -56,6 +56,6 @@
56
56
  "yaml-lint": "^1.7.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "effect": "4.0.0-beta.101"
59
+ "effect": "4.0.0-beta.107"
60
60
  }
61
61
  }
package/repos/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
2
  import { MANIFEST_PATH, REPOS_DIR } from "./constants.js";
3
3
  import { GitSubmoduleError, GitSubmoduleErrorBase, NoteNotFoundError, NoteNotFoundErrorBase, RepoNotFoundError, RepoNotFoundErrorBase, ReposConfigError, ReposConfigErrorBase, ReposLockdownError, ReposLockdownErrorBase } from "./errors.js";
4
+ import { DriftKind, RepoDrift, ReposDriftReport } from "./schemas/drift.js";
4
5
  import { RepoEntry, RepoName, RepoNote, RepoOrientation, ReposManifestFile } from "./schemas/manifest.js";
5
- import { RepoStatusEntry, ReposAddResult, ReposNoteResult, ReposPinResult, ReposStatusReport, ReposSyncReport } from "./schemas/reports.js";
6
+ import { RepoStatusEntry, ReposAddResult, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport } from "./schemas/reports.js";
6
7
  import { ReposConfigStore } from "./services/config-store.js";
8
+ import { ReposDrift } from "./services/drift.js";
7
9
  import { ReposLockdown, resolveModuleDir } from "./services/lockdown.js";
8
10
  import { ReposManager, STALE_LOCK_MAX_AGE_MS } from "./services/manager.js";
9
11
 
10
12
  //#region src/repos/index.ts
11
13
  var repos_exports = /* @__PURE__ */ __exportAll({
14
+ DriftKind: () => DriftKind,
12
15
  GitSubmoduleError: () => GitSubmoduleError,
13
16
  GitSubmoduleErrorBase: () => GitSubmoduleErrorBase,
14
17
  MANIFEST_PATH: () => MANIFEST_PATH,
@@ -16,6 +19,7 @@ var repos_exports = /* @__PURE__ */ __exportAll({
16
19
  NoteNotFoundError: () => NoteNotFoundError,
17
20
  NoteNotFoundErrorBase: () => NoteNotFoundErrorBase,
18
21
  REPOS_DIR: () => REPOS_DIR,
22
+ RepoDrift: () => RepoDrift,
19
23
  RepoEntry: () => RepoEntry,
20
24
  RepoName: () => RepoName,
21
25
  RepoNotFoundError: () => RepoNotFoundError,
@@ -27,6 +31,8 @@ var repos_exports = /* @__PURE__ */ __exportAll({
27
31
  ReposConfigError: () => ReposConfigError,
28
32
  ReposConfigErrorBase: () => ReposConfigErrorBase,
29
33
  ReposConfigStore: () => ReposConfigStore,
34
+ ReposDrift: () => ReposDrift,
35
+ ReposDriftReport: () => ReposDriftReport,
30
36
  ReposLockdown: () => ReposLockdown,
31
37
  ReposLockdownError: () => ReposLockdownError,
32
38
  ReposLockdownErrorBase: () => ReposLockdownErrorBase,
@@ -34,6 +40,9 @@ var repos_exports = /* @__PURE__ */ __exportAll({
34
40
  ReposManifestFile: () => ReposManifestFile,
35
41
  ReposNoteResult: () => ReposNoteResult,
36
42
  ReposPinResult: () => ReposPinResult,
43
+ ReposRemoveResult: () => ReposRemoveResult,
44
+ ReposRenameResult: () => ReposRenameResult,
45
+ ReposRestoreResult: () => ReposRestoreResult,
37
46
  ReposStatusReport: () => ReposStatusReport,
38
47
  ReposSyncReport: () => ReposSyncReport,
39
48
  STALE_LOCK_MAX_AGE_MS: () => STALE_LOCK_MAX_AGE_MS,
@@ -41,4 +50,4 @@ var repos_exports = /* @__PURE__ */ __exportAll({
41
50
  });
42
51
 
43
52
  //#endregion
44
- export { GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposManager, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repos_exports, resolveModuleDir };
53
+ export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposDrift, ReposDriftReport, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposManager, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repos_exports, resolveModuleDir };
@@ -0,0 +1,49 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/repos/schemas/drift.ts
4
+ /**
5
+ * The kinds of drift {@link ReposDrift} can detect between the four
6
+ * authorities it reconciles: the manifest, `.gitmodules`, the worktree, and
7
+ * `git submodule status`.
8
+ * @public
9
+ */
10
+ const DriftKind = Schema.Literals([
11
+ "urlMismatch",
12
+ "pathMismatch",
13
+ "unregisteredManifestEntry",
14
+ "orphanGitmodulesEntry",
15
+ "missingWorktree",
16
+ "checkoutDiverged",
17
+ "missingShallow",
18
+ "gitmodulesUnparsable"
19
+ ]);
20
+ /**
21
+ * One detected disagreement between two (or more) of the four authorities
22
+ * for a single vendored repo.
23
+ *
24
+ * @remarks
25
+ * `manifestValue`/`observedValue` carry the two disagreeing values when the
26
+ * drift is a value mismatch (`urlMismatch`, `pathMismatch`); other kinds
27
+ * populate whichever side has a value to show (or neither, for
28
+ * `gitmodulesUnparsable`, which is not about one repo).
29
+ * @public
30
+ */
31
+ var RepoDrift = class extends Schema.Class("RepoDrift")({
32
+ name: Schema.String,
33
+ kind: DriftKind,
34
+ detail: Schema.String,
35
+ manifestValue: Schema.optionalKey(Schema.String),
36
+ observedValue: Schema.optionalKey(Schema.String)
37
+ }) {};
38
+ /**
39
+ * The result of reconciling the manifest, `.gitmodules`, the worktree, and
40
+ * `git submodule status` for every vendored repo.
41
+ * @public
42
+ */
43
+ var ReposDriftReport = class extends Schema.Class("ReposDriftReport")({
44
+ drifts: Schema.Array(RepoDrift),
45
+ clean: Schema.Boolean
46
+ }) {};
47
+
48
+ //#endregion
49
+ export { DriftKind, RepoDrift, ReposDriftReport };
@@ -1,9 +1,25 @@
1
+ import { RepoNote } from "./manifest.js";
1
2
  import { Schema } from "effect";
2
3
 
3
4
  //#region src/repos/schemas/reports.ts
4
5
  /**
5
6
  * Status of one vendored repo: gitlink presence and dirtiness, plus notes
6
7
  * that no longer match the pinned ref.
8
+ *
9
+ * @remarks
10
+ * `commit` is an alias of `stagedCommit`, retained for one release so an
11
+ * existing consumer reading `entry.commit` keeps working while it migrates to
12
+ * the index-aware triple. It carries no independent release tag beyond
13
+ * `@public` (this schema has no narrower audience to gate it behind).
14
+ * Removal is scheduled by a tracked issue, not gated on a renderer migration
15
+ * precondition here.
16
+ *
17
+ * Not fully behavior-preserving: `commit: stagedCommit ?? null` reads `null`
18
+ * for a gitlink committed at `HEAD` but staged for REMOVAL, where the prior
19
+ * single-`commit` field showed the committed oid. `stagedCommit` is `None`
20
+ * in exactly that case (nothing is staged), so the alias reports "nothing
21
+ * staged" rather than "here is what HEAD still has" — a real, if narrow,
22
+ * difference from the pre-triple `commit` field's behavior.
7
23
  * @public
8
24
  */
9
25
  const RepoStatusEntry = Schema.Struct({
@@ -11,7 +27,14 @@ const RepoStatusEntry = Schema.Struct({
11
27
  ref: Schema.String,
12
28
  purpose: Schema.String,
13
29
  present: Schema.Boolean,
30
+ /** @deprecated alias of `stagedCommit`; retained for one release. */
14
31
  commit: Schema.NullOr(Schema.String),
32
+ /** The gitlink oid staged in the index (`git ls-files --stage`) — sees a pin BEFORE it is committed. */
33
+ stagedCommit: Schema.optionalKey(Schema.String),
34
+ /** The gitlink oid committed at `HEAD` (`git ls-tree HEAD`). */
35
+ committedCommit: Schema.optionalKey(Schema.String),
36
+ /** The commit actually checked out in the submodule worktree (`git rev-parse HEAD` inside it); absent when there is no checkout. */
37
+ checkedOutCommit: Schema.optionalKey(Schema.String),
15
38
  dirty: Schema.Boolean,
16
39
  staleNoteIds: Schema.Array(Schema.String)
17
40
  });
@@ -26,14 +49,17 @@ const ReposStatusReport = Schema.Struct({
26
49
  /**
27
50
  * Result of reconciling working-tree submodules with the manifest: missing
28
51
  * repos initialized, sparse-checkout patterns re-applied, already-present
29
- * repos left alone, and stale locks cleared.
52
+ * repos left alone, stale locks cleared, drifted submodule URLs reconciled,
53
+ * and orphan manifest entries (no gitlink at all) registered.
30
54
  * @public
31
55
  */
32
56
  const ReposSyncReport = Schema.Struct({
33
57
  initialized: Schema.Array(Schema.String),
34
58
  sparseApplied: Schema.Array(Schema.String),
35
59
  upToDate: Schema.Array(Schema.String),
36
- clearedLocks: Schema.Array(Schema.String)
60
+ clearedLocks: Schema.Array(Schema.String),
61
+ urlSynced: Schema.Array(Schema.String),
62
+ registered: Schema.Array(Schema.String)
37
63
  });
38
64
  /**
39
65
  * Result of re-pinning a vendored repo to a new ref.
@@ -57,6 +83,51 @@ const ReposAddResult = Schema.Struct({
57
83
  path: Schema.String
58
84
  });
59
85
  /**
86
+ * Result of removing a vendored repo from the manifest: the gitlink, module
87
+ * gitdir, and `.gitmodules` section are all gone, and the entry's notes are
88
+ * surfaced so any durable ones can be promoted elsewhere before this result
89
+ * is committed.
90
+ * @public
91
+ */
92
+ const ReposRemoveResult = Schema.Struct({
93
+ name: Schema.String,
94
+ path: Schema.String,
95
+ commitMessage: Schema.String,
96
+ removedNotes: Schema.Array(RepoNote)
97
+ });
98
+ /**
99
+ * Result of renaming a vendored repo's manifest key: the `.repos/<name>`
100
+ * worktree moved, the module gitdir's `core.worktree` values re-pointed, the
101
+ * `.gitmodules` section canonicalized to the new name, and the manifest key
102
+ * renamed.
103
+ * @public
104
+ */
105
+ const ReposRenameResult = Schema.Struct({
106
+ oldName: Schema.String,
107
+ newName: Schema.String,
108
+ path: Schema.String,
109
+ commitMessage: Schema.String
110
+ });
111
+ /**
112
+ * Result of hard-resetting one or more vendored repos to their staged (or
113
+ * committed) gitlink commit and re-applying sparse-checkout paths.
114
+ *
115
+ * @remarks
116
+ * `restored` lists every repo actually reset, paired with the commit it was
117
+ * reset to. `skippedClean` is populated ONLY by the names-omitted form of
118
+ * {@link ReposManagerShape.restore} — repos left untouched because `status`
119
+ * reported them clean; an explicit-names call never skips anything (an
120
+ * explicit ask is always honored), so it always reports an empty array.
121
+ * @public
122
+ */
123
+ const ReposRestoreResult = Schema.Struct({
124
+ restored: Schema.Array(Schema.Struct({
125
+ name: Schema.String,
126
+ commit: Schema.String
127
+ })),
128
+ skippedClean: Schema.Array(Schema.String)
129
+ });
130
+ /**
60
131
  * Result of an agent-note mutation against a vendored repo.
61
132
  * @public
62
133
  */
@@ -72,4 +143,4 @@ const ReposNoteResult = Schema.Struct({
72
143
  });
73
144
 
74
145
  //#endregion
75
- export { RepoStatusEntry, ReposAddResult, ReposNoteResult, ReposPinResult, ReposStatusReport, ReposSyncReport };
146
+ export { RepoStatusEntry, ReposAddResult, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport };