dsh-plugin-shop 0.8.6 → 0.8.7-beta.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/lib/index.js CHANGED
@@ -21,7 +21,7 @@ import { isMap, isSeq, parseDocument } from "yaml";
21
21
  * both the source tree (tests) and the bundled `lib/index.js` sit exactly
22
22
  * one level below the package root, so the same relative URL resolves in
23
23
  * both. */
24
- function ownManifest() {
24
+ function ownManifest$1() {
25
25
  return JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
26
26
  }
27
27
  /**
@@ -48,7 +48,7 @@ function versionAtLoad(read) {
48
48
  }
49
49
  /** The version of the code in this process, read when the module was first
50
50
  * evaluated — at boot, before any self-update could rewrite the file. */
51
- const runningVersion = versionAtLoad(() => ownManifest().version);
51
+ const runningVersion = versionAtLoad(() => ownManifest$1().version);
52
52
  /**
53
53
  * Which run of the code this is: drawn when the module is first evaluated,
54
54
  * fixed for the life of the process, and different in the next one.
@@ -70,7 +70,7 @@ const bootId = randomUUID();
70
70
  * compare against is worth a verdict.
71
71
  */
72
72
  function ownPeerRanges() {
73
- const declared = ownManifest().peerDependencies;
73
+ const declared = ownManifest$1().peerDependencies;
74
74
  if (declared === null || typeof declared !== "object") return {};
75
75
  const ranges = {};
76
76
  for (const [spec, range] of Object.entries(declared)) if (typeof range === "string") ranges[spec] = range;
@@ -649,9 +649,11 @@ const entrySchema = z.object({
649
649
  profiles: z.array(z.string()).optional()
650
650
  }).optional(),
651
651
  dshPeers: z.record(z.string(), z.string()).optional(),
652
+ manifestVersion: z.string().optional(),
652
653
  unpackedSize: z.number().int().nonnegative().optional(),
653
654
  installSize: z.number().int().nonnegative().optional(),
654
- archived: z.boolean().optional()
655
+ archived: z.boolean().optional(),
656
+ forked: z.boolean().optional()
655
657
  }).superRefine((entry, ctx) => {
656
658
  if (entry.source === "npm") {
657
659
  if (!NPM_NAME.test(entry.name)) ctx.addIssue({
@@ -1249,20 +1251,35 @@ function unmetProfile(declared, running, templates) {
1249
1251
  * could not tell".
1250
1252
  *
1251
1253
  * The rule is dsh's own (`check`, from the running app-boot), handed the
1252
- * manifest dsh's installer reads before it installs a registry spec — `pnpm
1253
- * view <spec> name version peerDependencies`, measured in dsh-plugin-manager
1254
- * 0.1.7-rc.2 — with the catalog's `dshPeers` for the peers. Those are the
1255
- * only peers the rule reads, verbatim. What this cannot see errs toward
1256
- * silence, never toward a refusal dsh would not make: a malformed peer range
1257
- * elsewhere in the manifest, which dsh rejects only after installing, and
1258
- * the plugins a bundle's patch rows name, which it checks then too. Those
1259
- * reach the reader as the install's own failure (`installFailureDetail`).
1260
- *
1261
- * npm entries only. dsh keys an exemption by the INSTALLED manifest's
1262
- * `name@version`, and a github entry's catalog version is a commit: its
1263
- * exemption status cannot be read, and a refusal named here could never be
1264
- * cleared from the shop. dsh refuses such an install itself, and says how
1265
- * to exempt it.
1254
+ * manifest dsh's installer judges (`ownManifest`): the name and version dsh
1255
+ * keys an exemption by, with the catalog's `dshPeers` for the peers. Those
1256
+ * are the only peers the rule reads, verbatim. Both channels are judged, each
1257
+ * by the manifest dsh reads for it:
1258
+ *
1259
+ * - An npm entry by its catalog `version`. dsh reads that manifest before it
1260
+ * installs a registry spec: `pnpm view <spec> name version
1261
+ * peerDependencies`, measured in dsh-plugin-manager 0.1.7-rc.2.
1262
+ * - A github entry by `manifestVersion`, the version of the manifest dsh
1263
+ * installs: at the pinned commit (a subpackage's own), or in the release
1264
+ * asset a rescued entry installs from. dsh skips a git or tarball spec
1265
+ * before pnpm and judges the installed manifest after it, rolling the
1266
+ * install back on a refusal (the same dsh-plugin-manager). The entry's
1267
+ * catalog `version` is a commit or a tag, so without `manifestVersion` it
1268
+ * is not judged at all: judged by its commit instead, it would miss an
1269
+ * exemption the profile holds under its real `name@version`, disabling an
1270
+ * install dsh accepts, and could offer no command, since `allow-version`
1271
+ * records no commit (design 2026-09-28-bundle-components-and-github-peers,
1272
+ * sections 6.1 and 7.3).
1273
+ *
1274
+ * dsh's key names no channel: an exemption recorded for one `name@version`
1275
+ * clears every entry whose manifest carries it, npm or github, here as in
1276
+ * dsh.
1277
+ *
1278
+ * What this cannot see errs toward silence, never toward a refusal dsh would
1279
+ * not make: a malformed peer range elsewhere in the manifest, which dsh
1280
+ * rejects only after installing, and the plugins a bundle's patch rows name,
1281
+ * which it checks then too. Those reach the reader as the install's own
1282
+ * failure (`installFailureDetail`).
1266
1283
  *
1267
1284
  * A throw from the check forms no verdict for that entry, as a resolver's
1268
1285
  * throw does in `incompatibilityMap`.
@@ -1270,14 +1287,11 @@ function unmetProfile(declared, running, templates) {
1270
1287
  function peerVerdictsOf(entries, check, exemptions, profile) {
1271
1288
  const out = {};
1272
1289
  for (const entry of entries) {
1273
- if (entry.source !== "npm" || entry.dshPeers === void 0) continue;
1290
+ const manifest = ownManifest(entry);
1291
+ if (manifest === null) continue;
1274
1292
  let issue;
1275
1293
  try {
1276
- issue = check.evaluate({
1277
- name: entry.name,
1278
- version: entry.version,
1279
- peerDependencies: entry.dshPeers
1280
- }, exemptions);
1294
+ issue = check.evaluate(manifest, exemptions);
1281
1295
  } catch {
1282
1296
  continue;
1283
1297
  }
@@ -1290,6 +1304,28 @@ function peerVerdictsOf(entries, check, exemptions, profile) {
1290
1304
  }
1291
1305
  return out;
1292
1306
  }
1307
+ /**
1308
+ * The manifest dsh judges an entry's install by, as `peerVerdictsOf` hands it
1309
+ * to the rule, or null when none can be formed: the entry declares no
1310
+ * harness peers, or it is a github entry with no `manifestVersion`. An npm
1311
+ * entry's catalog `version` is its manifest's; a github entry's is a commit
1312
+ * or a tag, so its manifest's own version is the one recorded beside its
1313
+ * peers, and the key is read on github entries only.
1314
+ */
1315
+ function ownManifest(entry) {
1316
+ if (entry.dshPeers === void 0) return null;
1317
+ if (entry.source === "npm") return {
1318
+ name: entry.name,
1319
+ version: entry.version,
1320
+ peerDependencies: entry.dshPeers
1321
+ };
1322
+ if (entry.manifestVersion === void 0) return null;
1323
+ return {
1324
+ name: entry.name,
1325
+ version: entry.manifestVersion,
1326
+ peerDependencies: entry.dshPeers
1327
+ };
1328
+ }
1293
1329
  /** npm's package-name grammar as dsh's exemption records accept it, verbatim
1294
1330
  * from app-boot 0.1.7-rc.2 (`PACKAGE_NAME` in profile-compatibility). */
1295
1331
  const EXEMPTION_PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
@@ -1671,10 +1707,14 @@ const REFUSAL_WARNING_TAIL = " Running it may cause crashes or data loss.";
1671
1707
  /**
1672
1708
  * dsh's own refusal of an install (0.1.7 on) as one detail, or null when the
1673
1709
  * log holds none. The install was dsh's to refuse, not pnpm's to fail: it
1674
- * refuses before pnpm runs, when it can read the manifest — a registry spec,
1675
- * which the catalog's peer verdict already predicts — and after pnpm
1676
- * succeeded otherwise, restoring the profile. Reading that as a pnpm failure
1677
- * sent the reader to `dsh plugin install`, which repairs nothing.
1710
+ * refuses before pnpm runs, when it can read the manifest — a registry spec —
1711
+ * and after pnpm succeeded otherwise, restoring the profile. The catalog's
1712
+ * peer verdict predicts the first, and the second only for a github entry's
1713
+ * own peers, judged by the manifest version the catalog records
1714
+ * (`peerVerdictsOf`). It predicts no refusal of a bundle's components, and
1715
+ * none of a github entry that carries no usable manifest version: dsh makes
1716
+ * those after pnpm all the same. Reading any refusal as a pnpm failure sent
1717
+ * the reader to `dsh plugin install`, which repairs nothing.
1678
1718
  *
1679
1719
  * The detail is what dsh refused, what it restored, and the command that
1680
1720
  * records its exact-version exemption, rebuilt from what dsh printed through
@@ -50,9 +50,11 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
50
50
  'profiles': z.union([z.undefined(), z.array(z.string())]).optional(),
51
51
  })]).optional(),
52
52
  'dshPeers': z.union([z.undefined(), z.record(z.string(), z.string())]).optional(),
53
+ 'manifestVersion': z.union([z.undefined(), z.string()]).optional(),
53
54
  'unpackedSize': z.union([z.undefined(), z.number()]).optional(),
54
55
  'installSize': z.union([z.undefined(), z.number()]).optional(),
55
56
  'archived': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
57
+ 'forked': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
56
58
  })),
57
59
  'denied': z.array(z.object({
58
60
  'name': z.string(),
@@ -498,7 +500,7 @@ export const TYPERT = {
498
500
  },
499
501
  {
500
502
  "name": "CatalogEntry",
501
- "declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion?: string; reviewedCommit?: string; reviewedSha256?: string; repo?: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n source: 'npm' | 'github';\n publisher?: string;\n repo?: string;\n subdir?: string;\n tarball?: { url: string; sha256: string; };\n added?: string;\n peers?: string[];\n compatibility?: { dsh?: string; profiles?: string[]; };\n dshPeers?: Record<string, string>;\n unpackedSize?: number;\n installSize?: number;\n archived?: boolean;\n}"
503
+ "declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion?: string; reviewedCommit?: string; reviewedSha256?: string; repo?: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n source: 'npm' | 'github';\n publisher?: string;\n repo?: string;\n subdir?: string;\n tarball?: { url: string; sha256: string; };\n added?: string;\n peers?: string[];\n compatibility?: { dsh?: string; profiles?: string[]; };\n dshPeers?: Record<string, string>;\n manifestVersion?: string;\n unpackedSize?: number;\n installSize?: number;\n archived?: boolean;\n forked?: boolean;\n}"
502
504
  },
503
505
  {
504
506
  "name": "CatalogSection",
@@ -50,9 +50,11 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
50
50
  'profiles': z.union([z.undefined(), z.array(z.string())]).optional(),
51
51
  })]).optional(),
52
52
  'dshPeers': z.union([z.undefined(), z.record(z.string(), z.string())]).optional(),
53
+ 'manifestVersion': z.union([z.undefined(), z.string()]).optional(),
53
54
  'unpackedSize': z.union([z.undefined(), z.number()]).optional(),
54
55
  'installSize': z.union([z.undefined(), z.number()]).optional(),
55
56
  'archived': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
57
+ 'forked': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
56
58
  })),
57
59
  'denied': z.array(z.object({
58
60
  'name': z.string(),
@@ -19,6 +19,14 @@ export declare const zh: {
19
19
  all: string;
20
20
  catalog: string;
21
21
  catalogStats: string;
22
+ /** The shelf's order control, and the date each card carries under the two
23
+ * date orders (design 2026-10-12-shelf-orders §4). */
24
+ sortLabel: string;
25
+ sortStars: string;
26
+ sortAdded: string;
27
+ sortUpdated: string;
28
+ listedOn: string;
29
+ updatedOn: string;
22
30
  stars: string;
23
31
  categoryTool: string;
24
32
  categoryProvider: string;
@@ -117,6 +125,13 @@ export declare const zh: {
117
125
  nameTakenDetail: string;
118
126
  npmSource: string;
119
127
  githubSource: string;
128
+ /** The accessible name and the tooltip of a card's version, which say which
129
+ * of three things the printed string is (market borrowings §11): the string
130
+ * alone cannot tell a release tag from a version number, and a short commit
131
+ * printed with a `v` read as one. */
132
+ npmVersionLabel: string;
133
+ commitVersionLabel: string;
134
+ releaseVersionLabel: string;
120
135
  /** The badge and the explanation for an installed github entry no catalog
121
136
  * row claims (C10 MVP). The mechanism synthesizes a row for ANY github spec
122
137
  * with no catalog row — shadowed by a same-named npm package, an unlisted
@@ -126,6 +141,8 @@ export declare const zh: {
126
141
  shadowedBadge: string;
127
142
  shadowedDetail: string;
128
143
  updatableSection: string;
144
+ /** `{version}` is the shelf's own version text (`versionLabel`), which
145
+ * carries the `v` an npm version takes and no other kind does. */
129
146
  installedVersion: string;
130
147
  latestVersion: string;
131
148
  update: string;
@@ -185,6 +202,14 @@ export declare const en: {
185
202
  all: string;
186
203
  catalog: string;
187
204
  catalogStats: string;
205
+ /** The shelf's order control, and the date each card carries under the two
206
+ * date orders (design 2026-10-12-shelf-orders §4). */
207
+ sortLabel: string;
208
+ sortStars: string;
209
+ sortAdded: string;
210
+ sortUpdated: string;
211
+ listedOn: string;
212
+ updatedOn: string;
188
213
  stars: string;
189
214
  categoryTool: string;
190
215
  categoryProvider: string;
@@ -259,6 +284,9 @@ export declare const en: {
259
284
  nameTakenDetail: string;
260
285
  npmSource: string;
261
286
  githubSource: string;
287
+ npmVersionLabel: string;
288
+ commitVersionLabel: string;
289
+ releaseVersionLabel: string;
262
290
  shadowedBadge: string;
263
291
  shadowedDetail: string;
264
292
  updatableSection: string;
@@ -134,10 +134,13 @@ export declare function readsIncompatible(blockers: readonly {
134
134
  *
135
135
  * The second is the one harness reason that blocks (design
136
136
  * 2026-09-26-dsh-017-readiness, B1, amending "warn, never block"): dsh 0.1.7
137
- * refuses the install itself, before pnpm runs, and the shop cannot install
138
- * past it. The verdict is dsh's own rule on the profile's own exemptions, so
139
- * a disabled button is dsh's answer given early, never the shop's opinion —
140
- * and recording the exemption the card shows is what enables it again.
137
+ * refuses the install itself, an npm entry before pnpm runs and a github
138
+ * entry after it, rolling the profile back, and the shop cannot install past
139
+ * it. The verdict is dsh's own rule on the profile's own exemptions, whichever
140
+ * channel the entry comes from (design
141
+ * 2026-09-28-bundle-components-and-github-peers, section 7.3), so a disabled
142
+ * button is dsh's answer given early, never the shop's opinion — and
143
+ * recording the exemption the card shows is what enables it again.
141
144
  */
142
145
  export declare function refusesInstall(blockers: readonly {
143
146
  kind: BlockerKind;
@@ -174,16 +177,47 @@ export declare const ACKNOWLEDGEMENT_ZH = "\u5B89\u88C5\u540E\uFF0C\u6B64\u63D2\
174
177
  /** Install rejection code → locale key, for the rejected-state code label
175
178
  * (§7.2): every rejection the host can return has a human label here. */
176
179
  export declare function rejectionCodeKey(code: InstallRejectionCode): ShopLocaleKey;
177
- /** The version a card displays: full for npm, the short commit for github. */
178
- export declare function displayVersion(entry: {
179
- source: 'npm' | 'github';
180
- version: string;
181
- }): string;
180
+ /** What a shelf version string is (market borrowings §11): a version npm
181
+ * serves, or the spec a profile manifest holds for one; the commit a github
182
+ * entry is pinned to; or the tag of the release a rescued github entry
183
+ * installs from. */
184
+ export type VersionKind = 'npm' | 'commit' | 'release';
185
+ export interface VersionLabel {
186
+ kind: VersionKind;
187
+ /** What the shelf prints: `v1.2.3`, `4da5ae8`, `v0.29.10`. */
188
+ text: string;
189
+ /** What the accessible name and the tooltip print after the kind's word:
190
+ * the same string without the `v` the shelf adds to an npm version. */
191
+ value: string;
192
+ }
193
+ /**
194
+ * How a version string reads on the shelf, decided by its source and its
195
+ * shape alone (market borrowings §11).
196
+ *
197
+ * Shape, never the entry's `tarball`, because an installed row carries only
198
+ * the pin the shop recorded at install — the catalog `version` of that moment
199
+ * — and no `tarball`. The registry pins a commit as forty lowercase hex and a
200
+ * release by its tag, so the two shapes do not cross; `present.test.ts`
201
+ * checks that against the committed live catalog.
202
+ *
203
+ * - An npm version takes the `v` the shelf has always shown, and only before
204
+ * a digit: an installed row reports the manifest's spec, and a range there
205
+ * (`^1.2.3`) is not a version to prefix.
206
+ * - A commit is cut to the seven characters git abbreviates to, with no `v`:
207
+ * it is not a version number.
208
+ * - A release tag prints exactly as tagged, never cut and never prefixed:
209
+ * cutting `v0.29.10` to seven characters printed `v0.29.1`, another
210
+ * version, and the `v` this function no longer adds doubled the tag's own.
211
+ */
212
+ export declare function versionLabel(source: 'npm' | 'github', version: string): VersionLabel;
213
+ /** The copy that names a version's kind, for the accessible name and the
214
+ * tooltip; its `{version}` takes {@link VersionLabel.value}. */
215
+ export declare function versionLabelKey(kind: VersionKind): ShopLocaleKey;
182
216
  /** The hash pin a stale line names for a repo or release-rescued entry — the
183
217
  * reviewed commit, or the reviewed release tarball sha256 (market borrowings
184
- * §3.1). Shortened to 7 chars like the card's own version; '' only for a
185
- * hand-built fixture carrying no pin. The npm form is a version and keeps the
186
- * version line's `v` prefix instead. */
218
+ * §3.1). Shortened to 7 chars like a commit on the card; '' only for a
219
+ * hand-built fixture carrying no pin. The npm form is a version, which
220
+ * {@link versionLabel} prints instead. */
187
221
  export declare function reviewHashPin(review: NonNullable<CatalogEntry['review']>): string;
188
222
  /**
189
223
  * Whether the license value is the npm idiom `SEE LICENSE IN <file>` — a
@@ -321,6 +355,25 @@ export declare function nextVisibleCount(visible: number, total: number, batch:
321
355
  * view is tracking — a status for an install it never started (idle) is an
322
356
  * unrelated event and a no-op, like any other event it does not recognize. */
323
357
  export declare function reduceInstall(state: InstallView, event: InstallEvent): InstallView;
358
+ /**
359
+ * Which order the shelf is in (design 2026-10-12-shelf-orders). `stars` is
360
+ * the default AND the tie-break of both date orders, so the two date orders
361
+ * are stable re-sorts of it rather than orders with comparators of their own.
362
+ */
363
+ declare const SHELF_ORDER_KEYS: {
364
+ readonly stars: "sortStars";
365
+ readonly added: "sortAdded";
366
+ readonly updated: "sortUpdated";
367
+ };
368
+ /** The order vocabulary as a type, derived from the map above so the client
369
+ * never repeats the three literals. */
370
+ export type ShelfOrder = keyof typeof SHELF_ORDER_KEYS;
371
+ /** The three orders in display order (map insertion order). `readonly`: this
372
+ * is a shared vocabulary, and an order list a caller can push to is a bug
373
+ * waiting for its second caller. */
374
+ export declare const SHELF_ORDERS: readonly ShelfOrder[];
375
+ /** The locale key naming one order (the toggle's buttons). */
376
+ export declare function shelfOrderLocaleKey(order: ShelfOrder): ShopLocaleKey;
324
377
  declare const CATEGORY_KEYS: {
325
378
  readonly tool: "categoryTool";
326
379
  readonly provider: "categoryProvider";
@@ -427,6 +480,31 @@ export declare function starsOf(entry: CatalogEntry, stars: Record<string, numbe
427
480
  * (case-insensitive) on ties (spec 2026-08-26-github-stars-design.md D1).
428
481
  * Display-time only — the catalog's own name sort is untouched. */
429
482
  export declare function sortByStars(entries: CatalogEntry[], stars: Record<string, number>): CatalogEntry[];
483
+ /**
484
+ * The entry's sort key under a date order, or undefined when it has no usable
485
+ * date (design §3): absent, off-form, unparseable, or later than the catalog's
486
+ * own `builtAt`. Milliseconds since the epoch. `'stars'` carries no date, so
487
+ * this answers undefined for it.
488
+ */
489
+ export declare function shelfTime(entry: CatalogEntry, order: ShelfOrder, builtAt: string): number | undefined;
490
+ /**
491
+ * A UTC calendar day for display: "2026-10-12". Derived from the instant the
492
+ * sort ranked by, so a card's label and its position cannot disagree. UTC and
493
+ * never a local getter: a reader in UTC+8 must see the same days the catalog
494
+ * was built in, the calendar the stats row already prints.
495
+ */
496
+ export declare function utcDay(ms: number): string;
497
+ /**
498
+ * The shelf in `order` (design 2026-10-12-shelf-orders §2, §5). `'stars'`
499
+ * returns the array it was handed, so the default path allocates nothing and
500
+ * does not invalidate the memos downstream of it.
501
+ *
502
+ * Each key is computed ONCE per entry rather than inside the comparator, and
503
+ * the sort is stable (ES2019), so entries sharing a date keep the star order
504
+ * they arrived in — which is how the tie-break is defined without a second
505
+ * comparator that could drift from `sortByStars`.
506
+ */
507
+ export declare function orderShelf(starOrdered: readonly CatalogEntry[], order: ShelfOrder, builtAt: string): readonly CatalogEntry[];
430
508
  /** 999 → "999"; 1000 → "1k"; 1234 → "1.2k"; 1500 → "1.5k"; 99999 → "100k". */
431
509
  export declare function formatStars(n: number): string;
432
510
  /**
@@ -181,20 +181,35 @@ export declare function compatibilityMap(entries: readonly (EntryIdentity & {
181
181
  * could not tell".
182
182
  *
183
183
  * The rule is dsh's own (`check`, from the running app-boot), handed the
184
- * manifest dsh's installer reads before it installs a registry spec — `pnpm
185
- * view <spec> name version peerDependencies`, measured in dsh-plugin-manager
186
- * 0.1.7-rc.2 — with the catalog's `dshPeers` for the peers. Those are the
187
- * only peers the rule reads, verbatim. What this cannot see errs toward
188
- * silence, never toward a refusal dsh would not make: a malformed peer range
189
- * elsewhere in the manifest, which dsh rejects only after installing, and
190
- * the plugins a bundle's patch rows name, which it checks then too. Those
191
- * reach the reader as the install's own failure (`installFailureDetail`).
184
+ * manifest dsh's installer judges (`ownManifest`): the name and version dsh
185
+ * keys an exemption by, with the catalog's `dshPeers` for the peers. Those
186
+ * are the only peers the rule reads, verbatim. Both channels are judged, each
187
+ * by the manifest dsh reads for it:
192
188
  *
193
- * npm entries only. dsh keys an exemption by the INSTALLED manifest's
194
- * `name@version`, and a github entry's catalog version is a commit: its
195
- * exemption status cannot be read, and a refusal named here could never be
196
- * cleared from the shop. dsh refuses such an install itself, and says how
197
- * to exempt it.
189
+ * - An npm entry by its catalog `version`. dsh reads that manifest before it
190
+ * installs a registry spec: `pnpm view <spec> name version
191
+ * peerDependencies`, measured in dsh-plugin-manager 0.1.7-rc.2.
192
+ * - A github entry by `manifestVersion`, the version of the manifest dsh
193
+ * installs: at the pinned commit (a subpackage's own), or in the release
194
+ * asset a rescued entry installs from. dsh skips a git or tarball spec
195
+ * before pnpm and judges the installed manifest after it, rolling the
196
+ * install back on a refusal (the same dsh-plugin-manager). The entry's
197
+ * catalog `version` is a commit or a tag, so without `manifestVersion` it
198
+ * is not judged at all: judged by its commit instead, it would miss an
199
+ * exemption the profile holds under its real `name@version`, disabling an
200
+ * install dsh accepts, and could offer no command, since `allow-version`
201
+ * records no commit (design 2026-09-28-bundle-components-and-github-peers,
202
+ * sections 6.1 and 7.3).
203
+ *
204
+ * dsh's key names no channel: an exemption recorded for one `name@version`
205
+ * clears every entry whose manifest carries it, npm or github, here as in
206
+ * dsh.
207
+ *
208
+ * What this cannot see errs toward silence, never toward a refusal dsh would
209
+ * not make: a malformed peer range elsewhere in the manifest, which dsh
210
+ * rejects only after installing, and the plugins a bundle's patch rows name,
211
+ * which it checks then too. Those reach the reader as the install's own
212
+ * failure (`installFailureDetail`).
198
213
  *
199
214
  * A throw from the check forms no verdict for that entry, as a resolver's
200
215
  * throw does in `incompatibilityMap`.
@@ -202,6 +217,7 @@ export declare function compatibilityMap(entries: readonly (EntryIdentity & {
202
217
  export declare function peerVerdictsOf(entries: readonly (EntryIdentity & {
203
218
  version: string;
204
219
  dshPeers?: Record<string, string>;
220
+ manifestVersion?: string;
205
221
  })[], check: PeerCheck, exemptions: Record<string, string[]>, profile: string): Record<string, PeerVerdict>;
206
222
  /**
207
223
  * The command that records dsh's exact-version exemption for one refused
@@ -78,12 +78,26 @@ export interface CatalogEntry {
78
78
  /** The package's peers on the harness itself (`@deepseek-ai/dsh` and
79
79
  * `@deepseek-ai/dsh-*`) with their ranges verbatim, optional ones and empty
80
80
  * ranges included — what dsh 0.1.7 refuses an install on. The host hands
81
- * them to the running dsh's own check (`peerVerdictsOf`). npm entries only:
82
- * dsh keys the exemption that clears its refusal by the installed
83
- * manifest's version, which a github entry's catalog version is not
84
- * (registry `Entry.dshPeers`). Absent when the package declares none, or
85
- * when its catalog predates the field. */
81
+ * them to the running dsh's own check (`peerVerdictsOf`) on both channels:
82
+ * an npm entry is judged by its catalog `version`, a github entry by
83
+ * {@link manifestVersion}, because dsh keys the exemption that clears its
84
+ * refusal by the installed manifest's version, which a github entry's
85
+ * catalog version is not (registry `Entry.dshPeers`; design
86
+ * 2026-09-28-bundle-components-and-github-peers, section 7.3). Absent when
87
+ * the package declares none, or when its catalog predates the field; on a
88
+ * github entry also when its manifest names no usable version, since both
89
+ * keys drop together, and until the registry re-reads its repository under
90
+ * the rule that records them. */
86
91
  dshPeers?: Record<string, string>;
92
+ /** A github entry's installed manifest `version` (registry
93
+ * `Entry.manifestVersion`): the version dsh keys an exemption by, where the
94
+ * entry's catalog `version` is a commit or a release tag. Present exactly
95
+ * when a github entry's {@link dshPeers} is. An npm entry's own `version` is
96
+ * its manifest's, so `peerVerdictsOf` never reads this key on one, and a
97
+ * github entry without it is not judged at all (design
98
+ * 2026-09-28-bundle-components-and-github-peers, section 7.3). Absent when
99
+ * the catalog predates the field. */
100
+ manifestVersion?: string;
87
101
  /** Wire-compatibility key, not a field to read — see {@link installSize},
88
102
  * which the catalog parse fills from this one. It names npm's OWN quantity
89
103
  * (`dist.unpackedSize`), so it is npm-only and the parse REFUSES one on a
@@ -113,6 +127,11 @@ export interface CatalogEntry {
113
127
  * §1.3). github entries only, additive and optional: the shelf badges it
114
128
  * and nothing else — install, update, and enable are unchanged. */
115
129
  archived?: boolean;
130
+ /** Whether GitHub reports the repository as a fork (design 2026-10-11
131
+ * §1.1). github entries only, additive and optional, and informative:
132
+ * nothing reads it here yet — it is telemetry the catalog publishes, not a
133
+ * gate, a badge, or an install decision. */
134
+ forked?: boolean;
116
135
  }
117
136
  export interface DeniedEntry {
118
137
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.8.6",
3
+ "version": "0.8.7-beta.1",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "keywords": [
6
6
  "dsh-plugin",