Pārlūkot izejas kodu

chore(sdk): drop dsh-plugin-fetch (giget/pacote) — #2 will use native npm/pnpm deps

External-plugin creation will add a package-manager-native dependency
(github:owner/repo#ref or pkg@version) plus a cordis mount instead of fetching
tarballs into a temp dir, so the giget/pacote fetch package is no longer needed.
imccyu 2 mēneši atpakaļ
vecāks
revīzija
8052370155

+ 0 - 3
docs/module-graph.md

@@ -138,7 +138,6 @@ flowchart TD
   end
   subgraph group_sdk["packages/sdk"]
     pkg_helper["helper"]
-    pkg_plugin_fetch["plugin-fetch"]
     pkg_scripts["scripts"]
     pkg_telemetry["telemetry"]
   end
@@ -154,7 +153,6 @@ flowchart TD
   pkg_llm --> pkg_brand
   pkg_code_runtime_worker --> pkg_code_runtime
   pkg_helper --> pkg_brand
-  pkg_plugin_fetch --> pkg_brand
   pkg_scripts --> pkg_app_boot
   pkg_telemetry --> pkg_brand
   pkg_llm_deepseek --> pkg_llm
@@ -446,7 +444,6 @@ flowchart TD
 | [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand) |
 | [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime) |
 | [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand) |
-| [`plugin-fetch`](../packages/sdk/plugin-fetch) | `sdk` | [`brand`](../packages/util/brand) |
 | [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot) |
 | [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand) |
 | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`llm`](../packages/llm/llm) |

+ 0 - 1
packages/sdk/README.md

@@ -9,7 +9,6 @@ The [feature RFC](../../docs/rfc/proposed/feature/2026-07-14-sdk-developer-proje
 | [`helper`](helper/README.md) | Project aggregate, edit session, builtin features, project documents, templates, package managers, and prompt abstraction |
 | [`scripts`](scripts/README.md) | The `dsh-sdk` launcher: `start`, `dev`, `build`, and interactive `config` |
 | [`create-sdk`](create-sdk/README.md) | The `npm create @deepseek-ai/sdk` initializer |
-| [`plugin-fetch`](plugin-fetch/README.md) | Fetch an external plugin (github/npm) into a temp dir — pinned and un-executed — for `dsh-sdk create` |
 
 `@deepseek-ai/create-sdk` is the one package-name exception to the repository's `@deepseek-ai/dsh-*` rule: npm's scoped initializer convention requires that name for `npm create @deepseek-ai/sdk`.
 

+ 0 - 32
packages/sdk/plugin-fetch/README.md

@@ -1,32 +0,0 @@
-# `@deepseek-ai/dsh-plugin-fetch`
-
-Fetch an external Cordis plugin into a temp directory — pinned to an immutable commit or integrity and never executed — for the forthcoming `dsh-sdk create <source>` command.
-
-The package parses a source spec into a `PluginSource`, dispatches to the matching `PluginFetcher`, and returns a common `FetchedPlugin` (temp dir + immutable provenance) that the wiring step pins into `package.json`, mounts in `cordis.yml`, and installs with `--ignore-scripts`.
-
-| Export | Role |
-|---|---|
-| `resolvePluginSource(spec)` → `PluginSource` | Parse `owner/repo[/subdir]#ref` (github) or `pkg@version` (npm); fail loud on an ambiguous or malformed spec |
-| `PluginFetcher<S>` | The fetch seam: resolve the pin BEFORE download, extract without executing pulled code |
-| `GigetFetcher` / `createGigetFetcher()` | Github fetcher over `@bluwy/giget-core`: resolve `#ref` to a commit SHA, download that SHA |
-| `PacoteFetcher` / `createPacoteFetcher()` | Npm fetcher over `pacote`: resolve the manifest, then extract the tarball verified against its integrity |
-| `fetchPlugin(source, fetchers)` → `FetchedPlugin` | Dispatch one source to its fetcher by discriminant tag |
-
-## Safety model — confirm-before-run, not run-on-fetch
-
-A fetch only downloads and unpacks; it runs no install, no `postinstall`/`prepare`, and no degit-style template actions.
-
-- **github** uses `@bluwy/giget-core` (one runtime dependency, `modern-tar`; no CLI, install, or JSON-registry surface) so a fetch can only download and untar a tarball. The commit is pinned first: `GigetFetcher` resolves `#ref` — or the default branch when absent — to an immutable SHA via the GitHub commits API, then downloads that SHA. Provenance carries the SHA so wiring pins `github:owner/repo#<sha>`.
-- **npm** uses `pacote`. Registry-only is enforced upstream: `resolvePluginSource` produces only a `name@version` registry spec, so pacote never sees a git/file/dir spec whose lifecycle scripts would run, and a registry tarball extract is a plain untar. The manifest is resolved first so extract verifies the artifact against the registry-published integrity (a mismatch raises `EINTEGRITY`). Provenance carries the exact version, resolved URL, and integrity.
-
-Both network boundaries (giget download, GitHub ref resolution, pacote, temp-dir allocation) are constructor-injected, so the fetch logic is unit-tested without network; the `create*Fetcher()` factories wire the real libraries.
-
-## Model Experience
-
-None, as this developer tooling acquires plugin sources for the SDK launcher and registers no live agent or model surface.
-
-## Known Limitations and Deferred Work
-
-- **Wiring is not here yet** — pinning `package.json`, mounting `cordis.yml` through `ProjectEditSession` with a confirmed diff, and `install --ignore-scripts` land with the `dsh-sdk create` command. This package stops at a fetched, pinned temp directory.
-- **npm registry authentication** — `PacoteFetcher` targets a public or default-configured registry; private-registry auth beyond pacote's ambient npm config is deferred.
-- **Template-repo initialization** — the whole-project init mode (`dsh-sdk create` from a template repository) is out of scope; this package fetches a single plugin into an existing project.

+ 0 - 37
packages/sdk/plugin-fetch/package.json

@@ -1,37 +0,0 @@
-{
-  "name": "@deepseek-ai/dsh-plugin-fetch",
-  "description": "Fetch an external Cordis plugin (github or npm) into a temp dir, pinned and un-executed, for dsh-sdk create",
-  "version": "0.0.1",
-  "private": true,
-  "type": "module",
-  "main": "lib/index.js",
-  "types": "lib/types/index.d.ts",
-  "exports": {
-    ".": {
-      "types": "./lib/types/index.d.ts",
-      "default": "./lib/index.js"
-    },
-    "./src/*": "./src/*",
-    "./package.json": "./package.json"
-  },
-  "files": [
-    "lib/index.js",
-    "lib/types/**/*.d.ts",
-    "lib/types/**/*.d.ts.map",
-    "src"
-  ],
-  "license": "BSD-3-Clause",
-  "dependencies": {
-    "@bluwy/giget-core": "^0.1.7",
-    "pacote": "^22.0.0"
-  },
-  "peerDependencies": {
-    "@deepseek-ai/dsh-brand": "^0.0.1",
-    "cordis": "^4.0.0-rc.7"
-  },
-  "devDependencies": {
-    "@deepseek-ai/dsh-brand": "workspace:^",
-    "@types/pacote": "^11.1.8",
-    "cordis": "^4.0.0-rc.7"
-  }
-}

+ 0 - 90
packages/sdk/plugin-fetch/src/fetcher.ts

@@ -1,90 +0,0 @@
-/**
- * The `PluginFetcher` seam, its common `FetchedPlugin` result, and the
- * tag-dispatched entry point. A fetcher acquires one plugin source into a fresh
- * temp directory WITHOUT executing any pulled code, and reports immutable
- * provenance the wiring step pins the dependency to.
- *
- * @module @deepseek-ai/dsh-plugin-fetch/fetcher
- */
-
-import { mkdtemp } from 'node:fs/promises'
-import { tmpdir } from 'node:os'
-import { join } from 'node:path'
-import { assertNever } from './never.ts'
-import type { CommitSha, Integrity } from './ids.ts'
-import type { GithubSource, NpmSource, PluginSource } from './source.ts'
-
-/** Immutable pin for a github fetch: the resolved commit the tarball came from. */
-export interface GithubProvenance {
-  readonly kind: 'github'
-  readonly sha: CommitSha
-}
-
-/** Immutable pin for an npm fetch: exact version, tarball URL, and integrity. */
-export interface NpmProvenance {
-  readonly kind: 'npm'
-  /** Concrete resolved version (e.g. `1.2.3`), never the requested range/tag. */
-  readonly version: string
-  /** Tarball URL the artifact resolved to. */
-  readonly resolved: string
-  /** Subresource integrity the artifact was verified against. */
-  readonly integrity: Integrity
-}
-
-/** Provenance a fetch records so wiring can pin an immutable dependency. */
-export type PluginProvenance = GithubProvenance | NpmProvenance
-
-/** The common result of fetching any plugin source. */
-export interface FetchedPlugin {
-  /** Absolute temp directory holding the extracted, UN-executed source. */
-  readonly dir: string
-  /** The source that produced this fetch, echoed for the wiring step. */
-  readonly source: PluginSource
-  /** Immutable provenance to pin the dependency during wiring. */
-  readonly provenance: PluginProvenance
-}
-
-/**
- * A fetcher for one source kind. Implementations resolve the immutable pin
- * BEFORE download and must never run lifecycle scripts or template actions.
- */
-export interface PluginFetcher<S extends PluginSource = PluginSource> {
-  /** The single source kind this fetcher handles. */
-  readonly kind: S['kind']
-  /**
-   * Fetch one source into a fresh temp directory.
-   * @param source - the resolved source to fetch.
-   * @returns the temp dir plus immutable provenance.
-   */
-  fetch(source: S): Promise<FetchedPlugin>
-}
-
-/** The per-kind fetchers {@link fetchPlugin} dispatches across. */
-export interface PluginFetchers {
-  readonly github: PluginFetcher<GithubSource>
-  readonly npm: PluginFetcher<NpmSource>
-}
-
-/**
- * Dispatch one source to its fetcher by discriminant tag.
- * @param source - the resolved plugin source.
- * @param fetchers - the per-kind fetchers to route across.
- * @returns the fetch result from the matching fetcher.
- */
-export function fetchPlugin(source: PluginSource, fetchers: PluginFetchers): Promise<FetchedPlugin> {
-  switch (source.kind) {
-    case 'github': return fetchers.github.fetch(source)
-    case 'npm': return fetchers.npm.fetch(source)
-    default: return assertNever(source, 'fetchPlugin')
-  }
-}
-
-/**
- * Create a fresh, empty temp directory for one fetch — the default temp-dir
- * seam shared by the concrete fetchers.
- * @param prefix - a `mkdtemp` name prefix identifying the fetch kind.
- * @returns the absolute path of the created directory.
- */
-export function createTempDir(prefix: string): Promise<string> {
-  return mkdtemp(join(tmpdir(), prefix))
-}

+ 0 - 116
packages/sdk/plugin-fetch/src/giget-fetcher.ts

@@ -1,116 +0,0 @@
-/**
- * The github {@link PluginFetcher}, backed by `@bluwy/giget-core`.
- *
- * `@bluwy/giget-core` is chosen over unjs `giget`: it carries a single runtime
- * dependency (`modern-tar`) versus giget's CLI/registry stack, and it dropped
- * the `install` and JSON-registry options entirely, so a fetch can only ever
- * download and untar a tarball — never run install or degit-style actions. That
- * is exactly the "extract, never execute" guarantee this feature needs.
- *
- * The commit is pinned BEFORE download: {@link GigetFetcher} resolves `#ref` to
- * an immutable SHA (default via the GitHub commits API), then downloads that
- * SHA. Provenance carries the SHA so wiring pins `github:owner/repo#<sha>`.
- *
- * @module @deepseek-ai/dsh-plugin-fetch/giget-fetcher
- */
-
-import { downloadTemplate } from '@bluwy/giget-core'
-import { createTempDir, type FetchedPlugin, type PluginFetcher } from './fetcher.ts'
-import { commitSha, type CommitSha } from './ids.ts'
-import type { GithubSource } from './source.ts'
-
-/** Temp-dir name prefix for github fetches. */
-export const GITHUB_TEMP_PREFIX = 'dsh-plugin-github-'
-
-/** Downloads a giget input string into `dir`; the tarball-extraction seam. */
-export type DownloadTemplate = (
-  input: string,
-  options: { dir: string; force: 'clean' },
-) => Promise<{ dir: string }>
-
-/** Resolves a github source's ref to an immutable commit SHA before download. */
-export type ResolveRef = (source: GithubSource) => Promise<CommitSha>
-
-/** The injected collaborators a {@link GigetFetcher} needs. */
-export interface GigetFetcherDeps {
-  /** Downloads a pinned giget input into a directory. */
-  download: DownloadTemplate
-  /** Resolves `source.ref` (or the default branch) to a commit SHA. */
-  resolveRef: ResolveRef
-  /** Allocates the fresh temp directory to download into. */
-  createTempDir: (prefix: string) => Promise<string>
-}
-
-/** Build the giget input string that pins a github source to a commit SHA. */
-function gigetInput(source: GithubSource, sha: CommitSha): string {
-  const path = source.subdir ? `${source.owner}/${source.repo}/${source.subdir}` : `${source.owner}/${source.repo}`
-  return `${path}#${sha}`
-}
-
-/** A human-readable label for one github source, for error messages. */
-function githubLabel(source: GithubSource): string {
-  return `${source.owner}/${source.repo}#${source.ref ?? 'HEAD'}`
-}
-
-/**
- * Resolve a github source's ref to an immutable SHA via the GitHub commits API.
- * Uses the `application/vnd.github.sha` media type, which returns the resolved
- * commit id as plain text.
- * @param source - the github source; an absent `ref` resolves the default branch (`HEAD`).
- * @param token - optional bearer token for private repositories.
- * @returns the resolved immutable commit SHA.
- * @throws if the GitHub API rejects the request.
- */
-export async function defaultResolveRef(source: GithubSource, token?: string): Promise<CommitSha> {
-  const ref = source.ref ?? 'HEAD'
-  const url = `https://api.github.com/repos/${source.owner}/${source.repo}/commits/${ref}`
-  const headers: Record<string, string> = { Accept: 'application/vnd.github.sha' }
-  if (token !== undefined) headers.Authorization = `Bearer ${token}`
-  const response = await fetch(url, { headers })
-  if (!response.ok) {
-    throw new Error(`cannot resolve github ref ${githubLabel(source)}: HTTP ${response.status}`)
-  }
-  return commitSha((await response.text()).trim())
-}
-
-/** Fetches a github plugin source by pinning `#ref` to a commit SHA, then downloading it. */
-export class GigetFetcher implements PluginFetcher<GithubSource> {
-  readonly kind = 'github' as const
-  private readonly deps: GigetFetcherDeps
-
-  /** Construct with injected download, ref-resolution, and temp-dir seams. */
-  constructor(deps: GigetFetcherDeps) {
-    this.deps = deps
-  }
-
-  async fetch(source: GithubSource): Promise<FetchedPlugin> {
-    const sha = await this.deps.resolveRef(source)
-    const dir = await this.deps.createTempDir(GITHUB_TEMP_PREFIX)
-    await this.deps.download(gigetInput(source, sha), { dir, force: 'clean' })
-    return { dir, source, provenance: { kind: 'github', sha } }
-  }
-}
-
-/** Options for the production github fetcher. */
-export interface GithubFetchOptions {
-  /** Bearer token for private repositories; defaults to `GITHUB_TOKEN`. */
-  token?: string
-}
-
-/**
- * Build the production github fetcher wired to `@bluwy/giget-core` and the
- * GitHub commits API.
- * @param options - optional token override (else `process.env.GITHUB_TOKEN`).
- * @returns a {@link GigetFetcher} using the real download and ref-resolution seams.
- */
-export function createGigetFetcher(options: GithubFetchOptions = {}): GigetFetcher {
-  const token = options.token ?? process.env.GITHUB_TOKEN
-  return new GigetFetcher({
-    download: (input, downloadOptions) => downloadTemplate(input, {
-      ...downloadOptions,
-      ...token !== undefined ? { providerOptions: { auth: token } } : {},
-    }),
-    resolveRef: source => defaultResolveRef(source, token),
-    createTempDir,
-  })
-}

+ 0 - 39
packages/sdk/plugin-fetch/src/ids.ts

@@ -1,39 +0,0 @@
-/**
- * Branded provenance identities owned by the plugin-fetch layer. Both cross the
- * fetch → wiring boundary and are opaque tokens that must not be confused with
- * ordinary strings (a package name, a URL) at that seam.
- *
- * @module @deepseek-ai/dsh-plugin-fetch/ids
- */
-
-import type { Branded } from '@deepseek-ai/dsh-brand'
-
-/** An immutable git commit object id a github fetch pins to. */
-export type CommitSha = Branded<'CommitSha'>
-
-/**
- * Construct a {@link CommitSha}, validating the hexadecimal object-id shape.
- * @param value - lowercase hex of an abbreviated or full commit id (7–64 chars, covering SHA-1 and SHA-256).
- * @returns the branded commit id.
- */
-export function commitSha(value: string): CommitSha {
-  if (!/^[0-9a-f]{7,64}$/.test(value)) {
-    throw new Error(`invalid commit sha: ${JSON.stringify(value)}`)
-  }
-  return value as CommitSha
-}
-
-/** A Subresource Integrity string an npm fetch pins to. */
-export type Integrity = Branded<'Integrity'>
-
-/**
- * Construct an {@link Integrity}, validating the SRI `<algorithm>-<base64>` shape.
- * @param value - a single SRI entry using sha256, sha384, or sha512.
- * @returns the branded integrity string.
- */
-export function integrity(value: string): Integrity {
-  if (!/^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$/.test(value)) {
-    throw new Error(`invalid subresource integrity: ${JSON.stringify(value)}`)
-  }
-  return value as Integrity
-}

+ 0 - 48
packages/sdk/plugin-fetch/src/index.ts

@@ -1,48 +0,0 @@
-/**
- * Fetch an external Cordis plugin (github or npm) into a temp directory —
- * pinned to an immutable commit/integrity and never executed — for the
- * `dsh-sdk create <source>` command. Parses a source spec, dispatches to the
- * matching fetcher, and returns a common {@link FetchedPlugin} the wiring step
- * pins and mounts.
- *
- * @module @deepseek-ai/dsh-plugin-fetch
- */
-
-export { resolvePluginSource } from './source.ts'
-export type { GithubSource, NpmSource, PluginSource } from './source.ts'
-export { commitSha, integrity } from './ids.ts'
-export type { CommitSha, Integrity } from './ids.ts'
-export { createTempDir, fetchPlugin } from './fetcher.ts'
-export type {
-  FetchedPlugin,
-  GithubProvenance,
-  NpmProvenance,
-  PluginFetcher,
-  PluginFetchers,
-  PluginProvenance,
-} from './fetcher.ts'
-export {
-  createGigetFetcher,
-  defaultResolveRef,
-  GigetFetcher,
-  GITHUB_TEMP_PREFIX,
-} from './giget-fetcher.ts'
-export type {
-  DownloadTemplate,
-  GigetFetcherDeps,
-  GithubFetchOptions,
-  ResolveRef,
-} from './giget-fetcher.ts'
-export {
-  createPacoteFetcher,
-  NPM_TEMP_PREFIX,
-  PacoteFetcher,
-} from './pacote-fetcher.ts'
-export type {
-  NpmFetchOptions,
-  PacoteApi,
-  PacoteExtractResult,
-  PacoteFetcherDeps,
-  PacoteFetchOptions,
-  PacoteResolution,
-} from './pacote-fetcher.ts'

+ 0 - 19
packages/sdk/plugin-fetch/src/never.ts

@@ -1,19 +0,0 @@
-/**
- * Exhaustiveness helper for this package's closed unions. Kept local so the
- * SDK plugin-fetch tooling stays free of the model-runtime `dsh-llm` dependency
- * that owns the shared `assertNever`.
- *
- * @module @deepseek-ai/dsh-plugin-fetch/never
- */
-
-/**
- * Mark an unreachable closed-union branch. A newly unhandled variant fails
- * compilation at the call site; a value that escaped its type throws at runtime.
- * @param value - the impossible value; typed `never` so a new variant fails to compile at every call site.
- * @param context - optional label prefixed into the throw message.
- * @returns never — it always throws, rendering the offending value.
- */
-export function assertNever(value: never, context?: string): never {
-  const rendered = (JSON.stringify(value) as string | undefined) ?? String(value)
-  throw new Error(`unreachable variant${context ? ` in ${context}` : ''}: ${rendered}`)
-}

+ 0 - 129
packages/sdk/plugin-fetch/src/pacote-fetcher.ts

@@ -1,129 +0,0 @@
-/**
- * The npm {@link PluginFetcher}, backed by `pacote`.
- *
- * Supply-chain safety comes from three layers: (1) {@link resolvePluginSource}
- * only ever produces a registry `name@version` spec, so pacote classifies it as
- * a registry source and cannot be steered to a git/file/dir spec whose
- * lifecycle scripts would run; (2) a registry tarball extract is a plain untar —
- * pacote runs no `prepare`/`postinstall` during {@link PacoteFetcher.fetch}; and
- * (3) the later wiring step installs with `--ignore-scripts`. The manifest is
- * resolved first so extract verifies the tarball against the registry-published
- * integrity (a mismatch raises `EINTEGRITY`).
- *
- * @module @deepseek-ai/dsh-plugin-fetch/pacote-fetcher
- */
-
-import { extract as pacoteExtract, manifest as pacoteManifest } from 'pacote'
-import { createTempDir, type FetchedPlugin, type PluginFetcher } from './fetcher.ts'
-import { integrity } from './ids.ts'
-import type { NpmSource } from './source.ts'
-
-/** Temp-dir name prefix for npm fetches. */
-export const NPM_TEMP_PREFIX = 'dsh-plugin-npm-'
-
-/** The subset of pacote options this fetcher passes through. */
-export interface PacoteFetchOptions {
-  /** Registry to resolve against; absent uses pacote's default. */
-  registry?: string
-  /** Known resolved tarball URL, forwarded to extract. */
-  resolved?: string
-  /** Expected integrity, forwarded to extract for `EINTEGRITY` verification. */
-  integrity?: string
-}
-
-/** The resolved registry manifest fields this fetcher pins from. */
-export interface PacoteResolution {
-  /** Resolved tarball URL. */
-  _resolved: string
-  /** Registry-published integrity. */
-  _integrity: string
-  /** Concrete resolved version. */
-  version: string
-}
-
-/** The extract result fields this fetcher pins from. */
-export interface PacoteExtractResult {
-  /** Resolved tarball URL of the extracted artifact. */
-  resolved: string
-  /** Integrity of the extracted artifact. */
-  integrity: string
-}
-
-/** The pacote surface a {@link PacoteFetcher} depends on; the fetch seam. */
-export interface PacoteApi {
-  /** Resolve a registry spec to its pinned manifest fields. */
-  manifest: (spec: string, options?: PacoteFetchOptions) => Promise<PacoteResolution>
-  /** Untar a registry spec into `dest`, verifying integrity when supplied. */
-  extract: (spec: string, dest: string, options?: PacoteFetchOptions) => Promise<PacoteExtractResult>
-}
-
-/** The injected collaborators a {@link PacoteFetcher} needs. */
-export interface PacoteFetcherDeps {
-  /** The pacote resolve/extract surface. */
-  pacote: PacoteApi
-  /** Allocates the fresh temp directory to extract into. */
-  createTempDir: (prefix: string) => Promise<string>
-  /** Registry to resolve against; absent uses pacote's default. */
-  registry?: string
-}
-
-/** Fetches an npm plugin source by resolving its manifest, then extracting the verified tarball. */
-export class PacoteFetcher implements PluginFetcher<NpmSource> {
-  readonly kind = 'npm' as const
-  private readonly deps: PacoteFetcherDeps
-
-  /** Construct with injected pacote, temp-dir, and optional registry seams. */
-  constructor(deps: PacoteFetcherDeps) {
-    this.deps = deps
-  }
-
-  async fetch(source: NpmSource): Promise<FetchedPlugin> {
-    const spec = `${source.name}@${source.version}`
-    const registryOptions: PacoteFetchOptions = this.deps.registry !== undefined
-      ? { registry: this.deps.registry }
-      : {}
-    const resolution = await this.deps.pacote.manifest(spec, registryOptions)
-    const dir = await this.deps.createTempDir(NPM_TEMP_PREFIX)
-    const extracted = await this.deps.pacote.extract(spec, dir, {
-      ...registryOptions,
-      resolved: resolution._resolved,
-      integrity: resolution._integrity,
-    })
-    return {
-      dir,
-      source,
-      provenance: {
-        kind: 'npm',
-        version: resolution.version,
-        resolved: extracted.resolved,
-        integrity: integrity(extracted.integrity),
-      },
-    }
-  }
-}
-
-/** Options for the production npm fetcher. */
-export interface NpmFetchOptions {
-  /** Registry to resolve against; absent uses pacote's default. */
-  registry?: string
-}
-
-/**
- * Build the production npm fetcher wired to `pacote`.
- * @param options - optional registry override.
- * @returns a {@link PacoteFetcher} using the real pacote resolve/extract seam.
- */
-export function createPacoteFetcher(options: NpmFetchOptions = {}): PacoteFetcher {
-  const pacote: PacoteApi = {
-    manifest: async (spec, pacoteOptions) => {
-      const resolved = await pacoteManifest(spec, pacoteOptions)
-      return { _resolved: resolved._resolved, _integrity: resolved._integrity, version: resolved.version }
-    },
-    extract: (spec, dest, pacoteOptions) => pacoteExtract(spec, dest, pacoteOptions),
-  }
-  return new PacoteFetcher({
-    pacote,
-    createTempDir,
-    ...options.registry !== undefined ? { registry: options.registry } : {},
-  })
-}

+ 0 - 126
packages/sdk/plugin-fetch/src/source.ts

@@ -1,126 +0,0 @@
-/**
- * The `PluginSource` discriminated union and the resolver that parses one CLI
- * spec string into it. Ambiguous or malformed specs fail loud here — the single
- * earliest resolvable point — rather than surfacing as a confusing fetch error.
- *
- * Grammar:
- * - github: `owner/repo[/subdir]#ref` — a `#` unambiguously marks a github ref;
- *   `ref` is optional and, when omitted, the fetcher pins the default branch.
- * - npm: `pkg@version` (scoped `@scope/pkg@version`) — the `@version` is the
- *   only disambiguator from a bare `owner/repo` github locator.
- *
- * @module @deepseek-ai/dsh-plugin-fetch/source
- */
-
-/** A plugin pulled from a github (git tarball) repository. */
-export interface GithubSource {
-  readonly kind: 'github'
-  /** Repository owner (user or org). */
-  readonly owner: string
-  /** Repository name. */
-  readonly repo: string
-  /** Path within the repository to extract; absent means the repository root. */
-  readonly subdir?: string
-  /** Branch, tag, or commit; absent means the repository's default branch. */
-  readonly ref?: string
-}
-
-/** A plugin pulled from an npm registry by exact package and version spec. */
-export interface NpmSource {
-  readonly kind: 'npm'
-  /** Package name, including any `@scope/` prefix. */
-  readonly name: string
-  /** Registry version, range, or dist-tag (non-empty). */
-  readonly version: string
-}
-
-/** Every plugin origin `dsh-sdk create <source>` understands. */
-export type PluginSource = GithubSource | NpmSource
-
-/** One `/`-separated github name segment (owner, repo, or subdir component). */
-function isNameSegment(segment: string): boolean {
-  return /^[A-Za-z0-9._-]+$/.test(segment) && segment !== '.' && segment !== '..'
-}
-
-/** A git ref: branch, tag, or commit; permits `/`-nested names, rejects traversal. */
-function isGitRef(ref: string): boolean {
-  return /^[A-Za-z0-9._/-]+$/.test(ref)
-    && !ref.includes('..')
-    && !ref.startsWith('/')
-    && !ref.endsWith('/')
-}
-
-/** An npm package name, scoped (`@scope/name`) or unscoped. */
-function isNpmPackageName(name: string): boolean {
-  const segment = /^[a-z0-9][a-z0-9._-]*$/
-  if (name.startsWith('@')) {
-    const slash = name.indexOf('/')
-    if (slash < 2 || slash === name.length - 1) return false
-    return segment.test(name.slice(1, slash)) && segment.test(name.slice(slash + 1))
-  }
-  return segment.test(name)
-}
-
-/** Parse a `owner/repo[/subdir]` locator with an optional already-split ref. */
-function tryParseGithubLocator(locator: string, ref: string | undefined): GithubSource | undefined {
-  if (!/^[^\s@#]+$/.test(locator)) return undefined
-  const [owner, repo, ...subdirSegments] = locator.split('/')
-  if (owner === undefined || repo === undefined) return undefined
-  if (!isNameSegment(owner) || !isNameSegment(repo)) return undefined
-  if (subdirSegments.some(segment => !isNameSegment(segment))) return undefined
-  if (ref !== undefined && !isGitRef(ref)) return undefined
-  const subdir = subdirSegments.join('/')
-  return {
-    kind: 'github',
-    owner,
-    repo,
-    ...subdir.length > 0 ? { subdir } : {},
-    ...ref !== undefined ? { ref } : {},
-  }
-}
-
-/** Parse `pkg@version` (scoped or unscoped); undefined when it is not npm-shaped. */
-function tryParseNpmSource(spec: string): NpmSource | undefined {
-  if (/[\s#]/.test(spec)) return undefined
-  // A scoped spec's version `@` follows the scope's `/`; an unscoped spec's is
-  // the first `@`. A leading `@` with no version `@` yields index 0 (rejected).
-  const versionAt = spec.startsWith('@') ? spec.indexOf('@', spec.indexOf('/') + 1) : spec.indexOf('@')
-  if (versionAt <= 0) return undefined
-  const name = spec.slice(0, versionAt)
-  const version = spec.slice(versionAt + 1)
-  if (version.length === 0 || version.includes('/')) return undefined
-  if (!isNpmPackageName(name)) return undefined
-  return { kind: 'npm', name, version }
-}
-
-/**
- * Parse one `dsh-sdk create <source>` spec into a {@link PluginSource}.
- * @param spec - the raw source argument.
- * @returns the discriminated source.
- * @throws if the spec is empty, malformed, or ambiguous between github and npm.
- */
-export function resolvePluginSource(spec: string): PluginSource {
-  const trimmed = spec.trim()
-  if (trimmed.length === 0) throw new Error('plugin source must not be empty')
-
-  const hashIndex = trimmed.indexOf('#')
-  if (hashIndex !== -1) {
-    const ref = trimmed.slice(hashIndex + 1)
-    if (ref.length === 0) {
-      throw new Error(`github plugin source is missing a ref after '#': ${JSON.stringify(spec)}`)
-    }
-    const source = tryParseGithubLocator(trimmed.slice(0, hashIndex), ref)
-    if (!source) {
-      throw new Error(`invalid github plugin source: ${JSON.stringify(spec)} — expected "owner/repo[/subdir]#ref"`)
-    }
-    return source
-  }
-
-  const npm = tryParseNpmSource(trimmed)
-  if (npm) return npm
-  const github = tryParseGithubLocator(trimmed, undefined)
-  if (github) return github
-  throw new Error(
-    `unrecognized plugin source: ${JSON.stringify(spec)} — expected "owner/repo[/subdir]#ref" (github) or "pkg@version" (npm)`,
-  )
-}

+ 0 - 64
packages/sdk/plugin-fetch/tests/fetcher.spec.ts

@@ -1,64 +0,0 @@
-import { rm, stat } from 'node:fs/promises'
-import { tmpdir } from 'node:os'
-import { describe, expect, it, vi } from 'vitest'
-import {
-  createTempDir,
-  fetchPlugin,
-  type FetchedPlugin,
-  type PluginFetchers,
-} from '../src/fetcher.ts'
-import { commitSha } from '../src/ids.ts'
-import type { GithubSource, NpmSource, PluginSource } from '../src/source.ts'
-
-function stubFetchers(): { fetchers: PluginFetchers; github: ReturnType<typeof vi.fn>; npm: ReturnType<typeof vi.fn> } {
-  const result = (dir: string): FetchedPlugin => ({
-    dir,
-    source: { kind: 'github', owner: 'o', repo: 'r' },
-    provenance: { kind: 'github', sha: commitSha('a'.repeat(40)) },
-  })
-  const github = vi.fn(async (source: GithubSource) => result(`github:${source.repo}`))
-  const npm = vi.fn(async (source: NpmSource) => result(`npm:${source.name}`))
-  return {
-    fetchers: { github: { kind: 'github', fetch: github }, npm: { kind: 'npm', fetch: npm } },
-    github,
-    npm,
-  }
-}
-
-describe('fetchPlugin', () => {
-  it('routes a github source to the github fetcher', async () => {
-    const { fetchers, github, npm } = stubFetchers()
-    const source: GithubSource = { kind: 'github', owner: 'o', repo: 'r' }
-    const result = await fetchPlugin(source, fetchers)
-    expect(github).toHaveBeenCalledWith(source)
-    expect(npm).not.toHaveBeenCalled()
-    expect(result.dir).toBe('github:r')
-  })
-
-  it('routes an npm source to the npm fetcher', async () => {
-    const { fetchers, github, npm } = stubFetchers()
-    const source: NpmSource = { kind: 'npm', name: 'plugin', version: '1.0.0' }
-    const result = await fetchPlugin(source, fetchers)
-    expect(npm).toHaveBeenCalledWith(source)
-    expect(github).not.toHaveBeenCalled()
-    expect(result.dir).toBe('npm:plugin')
-  })
-
-  it('throws on an unknown source kind', () => {
-    const { fetchers } = stubFetchers()
-    const bogus = { kind: 'svn' } as unknown as PluginSource
-    expect(() => fetchPlugin(bogus, fetchers)).toThrow(/unreachable variant in fetchPlugin/)
-  })
-})
-
-describe('createTempDir', () => {
-  it('creates a fresh empty directory under the OS temp root', async () => {
-    const dir = await createTempDir('dsh-plugin-fetch-test-')
-    try {
-      expect(dir.startsWith(tmpdir())).toBe(true)
-      expect((await stat(dir)).isDirectory()).toBe(true)
-    } finally {
-      await rm(dir, { recursive: true, force: true })
-    }
-  })
-})

+ 0 - 145
packages/sdk/plugin-fetch/tests/giget-fetcher.spec.ts

@@ -1,145 +0,0 @@
-import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'
-import { downloadTemplate } from '@bluwy/giget-core'
-import {
-  createGigetFetcher,
-  defaultResolveRef,
-  GigetFetcher,
-  GITHUB_TEMP_PREFIX,
-  type GigetFetcherDeps,
-} from '../src/giget-fetcher.ts'
-import type { CommitSha } from '../src/ids.ts'
-import type { GithubSource } from '../src/source.ts'
-
-vi.mock('@bluwy/giget-core', () => ({ downloadTemplate: vi.fn(async (_input: string, options: { dir: string }) => ({ dir: options.dir, source: '', info: { name: '', tar: '' } })) }))
-
-const SHA = 'a'.repeat(40)
-
-/** A `fetch` mock typed with the call signature the assertions destructure. */
-function fetchReturning(response: Response): Mock<(url: string, init?: RequestInit) => Promise<Response>> {
-  return vi.fn((_url: string, _init?: RequestInit) => Promise.resolve(response))
-}
-
-function fakeDeps(overrides: Partial<GigetFetcherDeps> = {}): {
-  deps: GigetFetcherDeps
-  download: ReturnType<typeof vi.fn>
-  resolveRef: ReturnType<typeof vi.fn>
-  createTempDir: ReturnType<typeof vi.fn>
-} {
-  const download = vi.fn(async () => ({ dir: '/tmp/x' }))
-  const resolveRef = vi.fn(async () => SHA as CommitSha)
-  const createTempDir = vi.fn(async () => '/tmp/dsh-plugin-github-abc')
-  return { deps: { download, resolveRef, createTempDir, ...overrides }, download, resolveRef, createTempDir }
-}
-
-describe('GigetFetcher.fetch', () => {
-  it('pins the ref to a SHA, downloads that SHA, and reports provenance', async () => {
-    const { deps, download, resolveRef, createTempDir } = fakeDeps()
-    const source: GithubSource = { kind: 'github', owner: 'unjs', repo: 'template', ref: 'main' }
-    const result = await new GigetFetcher(deps).fetch(source)
-
-    expect(resolveRef).toHaveBeenCalledWith(source)
-    expect(createTempDir).toHaveBeenCalledWith(GITHUB_TEMP_PREFIX)
-    expect(download).toHaveBeenCalledWith(`unjs/template#${SHA}`, { dir: '/tmp/dsh-plugin-github-abc', force: 'clean' })
-    expect(result).toEqual({
-      dir: '/tmp/dsh-plugin-github-abc',
-      source,
-      provenance: { kind: 'github', sha: SHA },
-    })
-  })
-
-  it('includes the subdir in the download input', async () => {
-    const { deps, download } = fakeDeps()
-    const source: GithubSource = { kind: 'github', owner: 'o', repo: 'r', subdir: 'packages/plugin' }
-    await new GigetFetcher(deps).fetch(source)
-    expect(download).toHaveBeenCalledWith(`o/r/packages/plugin#${SHA}`, expect.anything())
-  })
-
-  it('exposes its source kind', () => {
-    expect(new GigetFetcher(fakeDeps().deps).kind).toBe('github')
-  })
-})
-
-describe('defaultResolveRef', () => {
-  afterEach(() => vi.unstubAllGlobals())
-
-  it('resolves the default branch (HEAD) with no auth header', async () => {
-    const fetchMock = fetchReturning(new Response(`${SHA}\n`, { status: 200 }))
-    vi.stubGlobal('fetch', fetchMock)
-    const sha = await defaultResolveRef({ kind: 'github', owner: 'o', repo: 'r' })
-    expect(sha).toBe(SHA)
-    const [url, init] = fetchMock.mock.calls[0]!
-    expect(url).toBe('https://api.github.com/repos/o/r/commits/HEAD')
-    expect((init as RequestInit).headers).toEqual({ Accept: 'application/vnd.github.sha' })
-  })
-
-  it('resolves an explicit ref and sends a bearer token', async () => {
-    const fetchMock = fetchReturning(new Response(SHA, { status: 200 }))
-    vi.stubGlobal('fetch', fetchMock)
-    const sha = await defaultResolveRef({ kind: 'github', owner: 'o', repo: 'r', ref: 'v1.2.3' }, 'secret')
-    expect(sha).toBe(SHA)
-    const [url, init] = fetchMock.mock.calls[0]!
-    expect(url).toBe('https://api.github.com/repos/o/r/commits/v1.2.3')
-    expect((init as RequestInit).headers).toEqual({
-      Accept: 'application/vnd.github.sha',
-      Authorization: 'Bearer secret',
-    })
-  })
-
-  it('throws with the HEAD label when the API rejects an unref-ed source', async () => {
-    vi.stubGlobal('fetch', fetchReturning(new Response('', { status: 404 })))
-    await expect(defaultResolveRef({ kind: 'github', owner: 'o', repo: 'r' })).rejects.toThrow(
-      /cannot resolve github ref o\/r#HEAD: HTTP 404/,
-    )
-  })
-
-  it('throws with the explicit-ref label when the API rejects', async () => {
-    vi.stubGlobal('fetch', fetchReturning(new Response('', { status: 403 })))
-    await expect(
-      defaultResolveRef({ kind: 'github', owner: 'o', repo: 'r', ref: 'main' }),
-    ).rejects.toThrow(/cannot resolve github ref o\/r#main: HTTP 403/)
-  })
-})
-
-describe('createGigetFetcher', () => {
-  const downloadMock = vi.mocked(downloadTemplate)
-  let savedToken: string | undefined
-
-  beforeEach(() => {
-    downloadMock.mockClear()
-    savedToken = process.env.GITHUB_TOKEN
-    delete process.env.GITHUB_TOKEN
-  })
-
-  afterEach(() => {
-    vi.unstubAllGlobals()
-    if (savedToken === undefined) delete process.env.GITHUB_TOKEN
-    else process.env.GITHUB_TOKEN = savedToken
-  })
-
-  it('wires the real download without provider auth when no token is present', async () => {
-    vi.stubGlobal('fetch', fetchReturning(new Response(SHA, { status: 200 })))
-    await createGigetFetcher().fetch({ kind: 'github', owner: 'o', repo: 'r', ref: 'main' })
-    const [input, options] = downloadMock.mock.calls[0]!
-    expect(input).toBe(`o/r#${SHA}`)
-    expect(options?.dir).toContain(GITHUB_TEMP_PREFIX)
-    expect(options?.force).toBe('clean')
-    expect(options?.providerOptions).toBeUndefined()
-  })
-
-  it('passes an explicit token to both ref resolution and provider auth', async () => {
-    const fetchMock = fetchReturning(new Response(SHA, { status: 200 }))
-    vi.stubGlobal('fetch', fetchMock)
-    await createGigetFetcher({ token: 'tok' }).fetch({ kind: 'github', owner: 'o', repo: 'r' })
-    expect((fetchMock.mock.calls[0]![1] as RequestInit).headers).toMatchObject({ Authorization: 'Bearer tok' })
-    const [, options] = downloadMock.mock.calls[0]!
-    expect(options).toMatchObject({ providerOptions: { auth: 'tok' } })
-  })
-
-  it('reads GITHUB_TOKEN from the environment', async () => {
-    process.env.GITHUB_TOKEN = 'from-env'
-    vi.stubGlobal('fetch', fetchReturning(new Response(SHA, { status: 200 })))
-    await createGigetFetcher().fetch({ kind: 'github', owner: 'o', repo: 'r' })
-    const [, options] = downloadMock.mock.calls[0]!
-    expect(options).toMatchObject({ providerOptions: { auth: 'from-env' } })
-  })
-})

+ 0 - 39
packages/sdk/plugin-fetch/tests/ids.spec.ts

@@ -1,39 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { commitSha, integrity } from '../src/ids.ts'
-
-describe('commitSha', () => {
-  it('accepts abbreviated and full lowercase hex object ids', () => {
-    expect(commitSha('abc1234')).toBe('abc1234')
-    expect(commitSha('a'.repeat(40))).toBe('a'.repeat(40))
-    expect(commitSha('0'.repeat(64))).toBe('0'.repeat(64))
-  })
-
-  it.each([
-    ['too short', 'abc123'],
-    ['uppercase', 'ABCDEF1'],
-    ['non-hex', 'ghijklm'],
-    ['too long', 'a'.repeat(65)],
-    ['empty', ''],
-  ])('rejects an invalid sha (%s)', (_label, value) => {
-    expect(() => commitSha(value)).toThrow(/invalid commit sha/)
-  })
-})
-
-describe('integrity', () => {
-  it.each([
-    'sha512-abcABC123+/==',
-    'sha384-abcABC123+/',
-    'sha256-Zm9vYmFy',
-  ])('accepts a valid SRI entry (%s)', (value) => {
-    expect(integrity(value)).toBe(value)
-  })
-
-  it.each([
-    ['missing algorithm', 'abcABC123'],
-    ['unsupported algorithm', 'sha1-abcABC123'],
-    ['illegal base64 char', 'sha512-abc*def'],
-    ['empty', ''],
-  ])('rejects an invalid integrity (%s)', (_label, value) => {
-    expect(() => integrity(value)).toThrow(/invalid subresource integrity/)
-  })
-})

+ 0 - 19
packages/sdk/plugin-fetch/tests/never.spec.ts

@@ -1,19 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { assertNever } from '../src/never.ts'
-
-describe('assertNever', () => {
-  it('throws with the rendered value and a context label', () => {
-    expect(() => assertNever('surprise' as never, 'demo')).toThrow(
-      /unreachable variant in demo: "surprise"/,
-    )
-  })
-
-  it('omits the context clause when none is given', () => {
-    expect(() => assertNever(7 as never)).toThrow(/unreachable variant: 7$/)
-  })
-
-  it('falls back to String() when the value is not JSON-serializable', () => {
-    // JSON.stringify(undefined) is undefined, exercising the String() fallback.
-    expect(() => assertNever(undefined as never)).toThrow(/unreachable variant: undefined$/)
-  })
-})

+ 0 - 108
packages/sdk/plugin-fetch/tests/pacote-fetcher.spec.ts

@@ -1,108 +0,0 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { extract as pacoteExtract, manifest as pacoteManifest } from 'pacote'
-import {
-  createPacoteFetcher,
-  NPM_TEMP_PREFIX,
-  PacoteFetcher,
-  type PacoteApi,
-  type PacoteFetcherDeps,
-} from '../src/pacote-fetcher.ts'
-import type { NpmSource } from '../src/source.ts'
-
-vi.mock('pacote', () => ({ manifest: vi.fn(), extract: vi.fn() }))
-
-const INTEGRITY = 'sha512-abcABC123+/=='
-const RESOLVED = 'https://registry.npmjs.org/plugin/-/plugin-1.2.3.tgz'
-
-function fakePacote(): PacoteApi {
-  return {
-    manifest: vi.fn(async () => ({ _resolved: RESOLVED, _integrity: INTEGRITY, version: '1.2.3' })),
-    extract: vi.fn(async () => ({ resolved: RESOLVED, integrity: INTEGRITY })),
-  }
-}
-
-function deps(overrides: Partial<PacoteFetcherDeps> = {}): PacoteFetcherDeps {
-  return {
-    pacote: fakePacote(),
-    createTempDir: vi.fn(async () => '/tmp/dsh-plugin-npm-abc'),
-    ...overrides,
-  }
-}
-
-const SOURCE: NpmSource = { kind: 'npm', name: 'plugin', version: '^1.0.0' }
-
-describe('PacoteFetcher.fetch', () => {
-  it('resolves the manifest, extracts with integrity, and reports provenance', async () => {
-    const d = deps()
-    const result = await new PacoteFetcher(d).fetch(SOURCE)
-
-    expect(d.pacote.manifest).toHaveBeenCalledWith('plugin@^1.0.0', {})
-    expect(d.createTempDir).toHaveBeenCalledWith(NPM_TEMP_PREFIX)
-    expect(d.pacote.extract).toHaveBeenCalledWith('plugin@^1.0.0', '/tmp/dsh-plugin-npm-abc', {
-      resolved: RESOLVED,
-      integrity: INTEGRITY,
-    })
-    expect(result).toEqual({
-      dir: '/tmp/dsh-plugin-npm-abc',
-      source: SOURCE,
-      provenance: { kind: 'npm', version: '1.2.3', resolved: RESOLVED, integrity: INTEGRITY },
-    })
-  })
-
-  it('forwards a configured registry to both manifest and extract', async () => {
-    const d = deps({ registry: 'https://npm.internal/' })
-    await new PacoteFetcher(d).fetch(SOURCE)
-    expect(d.pacote.manifest).toHaveBeenCalledWith('plugin@^1.0.0', { registry: 'https://npm.internal/' })
-    expect(d.pacote.extract).toHaveBeenCalledWith('plugin@^1.0.0', '/tmp/dsh-plugin-npm-abc', {
-      registry: 'https://npm.internal/',
-      resolved: RESOLVED,
-      integrity: INTEGRITY,
-    })
-  })
-
-  it('rejects a registry integrity that is not a valid SRI', async () => {
-    const pacote = fakePacote()
-    pacote.extract = vi.fn(async () => ({ resolved: RESOLVED, integrity: 'not-sri' }))
-    await expect(new PacoteFetcher(deps({ pacote })).fetch(SOURCE)).rejects.toThrow(
-      /invalid subresource integrity/,
-    )
-  })
-
-  it('exposes its source kind', () => {
-    expect(new PacoteFetcher(deps()).kind).toBe('npm')
-  })
-})
-
-describe('createPacoteFetcher', () => {
-  const manifestMock = vi.mocked(pacoteManifest)
-  const extractMock = vi.mocked(pacoteExtract)
-
-  beforeEach(() => {
-    manifestMock.mockReset()
-    extractMock.mockReset()
-    // The real overloaded pacote manifest returns a much wider shape; the fetcher reads only these fields.
-    manifestMock.mockResolvedValue(
-      { _resolved: RESOLVED, _integrity: INTEGRITY, version: '1.2.3' } as unknown as Awaited<
-        ReturnType<typeof pacoteManifest>
-      >,
-    )
-    extractMock.mockResolvedValue({ from: 'plugin@1.2.3', resolved: RESOLVED, integrity: INTEGRITY })
-  })
-
-  afterEach(() => vi.clearAllMocks())
-
-  it('wires the real pacote resolve/extract surface', async () => {
-    const result = await createPacoteFetcher().fetch(SOURCE)
-    expect(manifestMock).toHaveBeenCalledWith('plugin@^1.0.0', {})
-    expect(extractMock).toHaveBeenCalledWith('plugin@^1.0.0', expect.stringContaining(NPM_TEMP_PREFIX), {
-      resolved: RESOLVED,
-      integrity: INTEGRITY,
-    })
-    expect(result.provenance).toEqual({ kind: 'npm', version: '1.2.3', resolved: RESOLVED, integrity: INTEGRITY })
-  })
-
-  it('forwards a configured registry through the real surface', async () => {
-    await createPacoteFetcher({ registry: 'https://npm.internal/' }).fetch(SOURCE)
-    expect(manifestMock).toHaveBeenCalledWith('plugin@^1.0.0', { registry: 'https://npm.internal/' })
-  })
-})

+ 0 - 107
packages/sdk/plugin-fetch/tests/source.spec.ts

@@ -1,107 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { resolvePluginSource, type GithubSource, type NpmSource } from '../src/source.ts'
-
-describe('resolvePluginSource — github', () => {
-  it('parses owner/repo with a ref', () => {
-    expect(resolvePluginSource('unjs/template#main')).toEqual<GithubSource>({
-      kind: 'github', owner: 'unjs', repo: 'template', ref: 'main',
-    })
-  })
-
-  it('parses a bare owner/repo without a ref', () => {
-    expect(resolvePluginSource('deepseek-ai/plugin')).toEqual<GithubSource>({
-      kind: 'github', owner: 'deepseek-ai', repo: 'plugin',
-    })
-  })
-
-  it('parses a nested subdir with a ref', () => {
-    expect(resolvePluginSource('owner/repo/packages/plugin#v1.2.3')).toEqual<GithubSource>({
-      kind: 'github', owner: 'owner', repo: 'repo', subdir: 'packages/plugin', ref: 'v1.2.3',
-    })
-  })
-
-  it('parses a subdir without a ref', () => {
-    expect(resolvePluginSource('owner/repo/sub')).toEqual<GithubSource>({
-      kind: 'github', owner: 'owner', repo: 'repo', subdir: 'sub',
-    })
-  })
-
-  it('accepts a slash-nested ref', () => {
-    expect(resolvePluginSource('owner/repo#feature/x')).toEqual<GithubSource>({
-      kind: 'github', owner: 'owner', repo: 'repo', ref: 'feature/x',
-    })
-  })
-
-  it('trims surrounding whitespace before parsing', () => {
-    expect(resolvePluginSource('  owner/repo#main  ')).toEqual<GithubSource>({
-      kind: 'github', owner: 'owner', repo: 'repo', ref: 'main',
-    })
-  })
-
-  it.each([
-    ['empty ref after hash', 'owner/repo#'],
-    ['single locator segment with hash', 'owner#main'],
-    ['owner with @ and a hash', 'own@er/repo#main'],
-    ['ref with whitespace', 'owner/repo#bad ref'],
-    ['ref with traversal', 'owner/repo#a..b'],
-    ['ref with a leading slash', 'owner/repo#/main'],
-    ['ref with a trailing slash', 'owner/repo#main/'],
-    ['ref with an illegal char', 'owner/repo#ma:in'],
-  ])('rejects a malformed github spec (%s)', (_label, spec) => {
-    expect(() => resolvePluginSource(spec)).toThrow(/github plugin source|missing a ref/)
-  })
-})
-
-describe('resolvePluginSource — npm', () => {
-  it('parses an unscoped name@version', () => {
-    expect(resolvePluginSource('react@18.2.0')).toEqual<NpmSource>({
-      kind: 'npm', name: 'react', version: '18.2.0',
-    })
-  })
-
-  it('parses a scoped name@version', () => {
-    expect(resolvePluginSource('@deepseek-ai/dsh-tool-foo@0.0.1')).toEqual<NpmSource>({
-      kind: 'npm', name: '@deepseek-ai/dsh-tool-foo', version: '0.0.1',
-    })
-  })
-
-  it('accepts a dist-tag as the version', () => {
-    expect(resolvePluginSource('some-plugin@latest')).toEqual<NpmSource>({
-      kind: 'npm', name: 'some-plugin', version: 'latest',
-    })
-  })
-
-  it('accepts a range as the version', () => {
-    expect(resolvePluginSource('some-plugin@^1.0.0')).toEqual<NpmSource>({
-      kind: 'npm', name: 'some-plugin', version: '^1.0.0',
-    })
-  })
-})
-
-describe('resolvePluginSource — failures', () => {
-  it.each([
-    ['empty', ''],
-    ['whitespace only', '   '],
-  ])('rejects a blank spec (%s)', (_label, spec) => {
-    expect(() => resolvePluginSource(spec)).toThrow(/must not be empty/)
-  })
-
-  it.each([
-    ['bare word', 'plugin'],
-    ['internal whitespace', 'owner repo'],
-    ['empty npm version', 'pkg@'],
-    ['scoped without version', '@scope/pkg'],
-    ['scoped with empty scope', '@/pkg@1'],
-    ['unscoped name with slash and version', 'foo/bar@1'],
-    ['version containing a slash', 'foo@1/2'],
-    ['uppercase unscoped name', 'FOO@1.0.0'],
-    ['uppercase scope segment', '@Scope/pkg@1'],
-    ['uppercase scoped name segment', '@scope/PKG@1'],
-    ['empty scoped name segment', '@scope/@1'],
-    ['dot-only owner', './repo'],
-    ['traversal subdir segment', 'owner/repo/../x'],
-    ['double slash subdir', 'owner/repo//sub'],
-  ])('rejects an unrecognized/ambiguous spec (%s)', (_label, spec) => {
-    expect(() => resolvePluginSource(spec)).toThrow(/unrecognized plugin source|github plugin source/)
-  })
-})

+ 0 - 15
packages/sdk/plugin-fetch/tsconfig.json

@@ -1,15 +0,0 @@
-{
-  "extends": "../../../tsconfig.base.json",
-  "compilerOptions": {
-    "rootDir": "src",
-    "outDir": "lib/types"
-  },
-  "include": [
-    "src"
-  ],
-  "references": [
-    {
-      "path": "../../util/brand"
-    }
-  ]
-}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 531
pnpm-lock.yaml


+ 0 - 1
scripts/verify-package-readme-model-experience.ts

@@ -52,7 +52,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
   'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' },
   'packages/sdk/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' },
   'packages/sdk/helper': { kind: 'none', reason: 'The project domain edits files and registers no live agent or model surface.' },
-  'packages/sdk/plugin-fetch': { kind: 'none', reason: 'The fetcher acquires plugin sources into a temp dir and registers no live agent or model surface.' },
   'packages/sdk/scripts': { kind: 'indirect', reason: 'The launcher delegates model context to the loaded project plugin tree.' },
   'packages/sdk/telemetry': { kind: 'none', reason: 'The launcher-side reporter sends developer-cycle telemetry and registers no live agent or model surface.' },
   'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers no model surface.' },

+ 0 - 1
tsconfig.build.json

@@ -99,7 +99,6 @@
     { "path": "./packages/sdk/helper" },
     { "path": "./packages/sdk/scripts" },
     { "path": "./packages/sdk/create-sdk" },
-    { "path": "./packages/sdk/plugin-fetch" },
     { "path": "./packages/sdk/telemetry" }
   ]
 }

+ 0 - 1
tsconfig.json

@@ -110,7 +110,6 @@
     { "path": "./packages/sdk/helper" },
     { "path": "./packages/sdk/scripts" },
     { "path": "./packages/sdk/create-sdk" },
-    { "path": "./packages/sdk/plugin-fetch" },
     { "path": "./packages/sdk/telemetry" }
   ]
 }

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels