From 815a25346edfd796c9d32dd0659f4e10c27db112 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 27 Oct 2026 00:44:49 +0000 Subject: [PATCH 1/2] Test the dsh-pstack bundle in the real DSH harness. CI installs @deepseek-ai/dsh@next, mounts the checkout on a throwaway profile, and fails unless that profile lists the required skills. Co-authored-by: Kaitian Xie --- .github/prove-dsh-profile.mjs | 77 +++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 45 ++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 .github/prove-dsh-profile.mjs diff --git a/.github/prove-dsh-profile.mjs b/.github/prove-dsh-profile.mjs new file mode 100644 index 0000000..e3100df --- /dev/null +++ b/.github/prove-dsh-profile.mjs @@ -0,0 +1,77 @@ +#!/usr/bin/env node +// Boot a profile that already ran `dsh plugin --profile add ` +// and fail unless dsh-pstack's skill provider lists the required skills. +// There is no dsh flag that prints the skill catalog; `dsh --help` only offers +// plugin (pnpm) and --dump-config. This uses the public profile-boot export. +import { execFileSync } from 'node:child_process' +import { mkdtempSync, realpathSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { pathToFileURL } from 'node:url' + +const profile = process.env.DSH_PROFILE ?? 'ci' +const required = ['poteto-mode', 'setup-pstack', 'deslop', 'control-cli', 'control-ui'] + +function dshPackageRoot() { + const bin = execFileSync('sh', ['-c', 'command -v dsh'], { encoding: 'utf8' }).trim() + if (bin === '') throw new Error('dsh is not on PATH') + // npm links bin/dsh -> /lib/bin.js + return dirname(dirname(realpathSync(bin))) +} + +if (process.env.DSH_HOME === undefined || process.env.DSH_HOME.trim() === '') { + throw new Error('DSH_HOME must point at the home used for `dsh plugin add`') +} +if (process.env.DSH_AGENTS_HOME === undefined || process.env.DSH_AGENTS_HOME.trim() === '') { + process.env.DSH_AGENTS_HOME = mkdtempSync(join(tmpdir(), 'dsh-agents-')) +} + +const listCwd = mkdtempSync(join(tmpdir(), 'dsh-list-')) +const dshPkg = dshPackageRoot() +const { runProfile } = await import(pathToFileURL(join(dshPkg, 'lib/profile-boot.js')).href) +const { loadLayeredEnv } = await import(pathToFileURL(join(dshPkg, 'node_modules/@deepseek-ai/dsh-app-boot/lib/index.js')).href) + +let code = 0 +let shutdown +try { + const booted = await runProfile({ + environment: loadLayeredEnv('dsh', listCwd), + profile, + patchFiles: [], + args: [], + }) + shutdown = booted.shutdown + const bundles = booted.ctx.get('profileContext')?.startedBundles ?? [] + console.log(`started bundles: ${bundles.join(', ')}`) + if (!bundles.includes('dsh-pstack')) { + console.error('profile did not mount dsh-pstack') + code = 1 + } + if (typeof booted.ctx.skills?.list !== 'function') { + console.error('profile booted without a skill registry') + code = 1 + } else { + const skills = await booted.ctx.skills.list({ cwd: listCwd }) + const pstack = skills.filter((skill) => skill.provider === 'pstack').map((skill) => skill.name).sort() + console.log(`pstack skills (${pstack.length}):`) + for (const name of pstack) console.log(name) + const missing = required.filter((name) => !pstack.includes(name)) + if (missing.length > 0) { + console.error(`missing required pstack skills: ${missing.join(', ')}`) + code = 1 + } + } +} catch (error) { + console.error(error instanceof Error ? error.stack ?? error.message : error) + code = 1 +} finally { + if (shutdown !== undefined) { + try { + await shutdown.shutdown(code) + } catch (error) { + console.error(error instanceof Error ? error.stack ?? error.message : error) + code = 1 + } + } +} +process.exit(code) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a8baee..8ee2d19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: node --check lib/index.js node --check lib/parse.js node --check scripts/validate.mjs + node --check .github/prove-dsh-profile.mjs - name: Check shell scripts run: | @@ -93,3 +94,47 @@ jobs: - name: Run statechart tests run: node --test .agents/skills/statechart/scripts/test/statechart.test.mjs + + harness: + name: Mount bundle in DSH + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install bundle dependencies + run: npm ci --omit=peer + + - name: Install pnpm and the latest DSH + run: | + set -euo pipefail + prefix="${RUNNER_TEMP}/dsh-prefix" + # npm dist-tag latest trails next. @next is the newest published runtime. + npm install -g --prefix "$prefix" pnpm@10 @deepseek-ai/dsh@next + echo "$prefix/bin" >> "$GITHUB_PATH" + "$prefix/bin/dsh" --version + "$prefix/bin/pnpm" --version + + - name: Mount the checkout and list pstack skills + env: + DSH_HOME: ${{ runner.temp }}/dsh-home + DSH_PROFILE: ci + run: | + set -euo pipefail + mkdir -p "$DSH_HOME" + dsh plugin --profile ci add "$GITHUB_WORKSPACE" + dsh plugin --profile ci list --depth 0 + dump="$DSH_HOME/dump-config.yml" + dsh --profile ci --dump-config > "$dump" + grep -F '# == dsh-pstack' "$dump" + grep -F -- '- id: pstack' "$dump" + grep -F 'name: dsh-pstack' "$dump" + node .github/prove-dsh-profile.mjs From a166c7793ff64fbf243ec456c423c384fe492f4a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 27 Oct 2026 00:46:45 +0000 Subject: [PATCH 2/2] Pin the harness CI runtime to dsh 0.2.1-rc.4. The skill-list helper imports internal profile-boot files, so the workflow names that version instead of the next tag. Co-authored-by: Kaitian Xie --- .github/prove-dsh-profile.mjs | 3 ++- .github/workflows/ci.yml | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/prove-dsh-profile.mjs b/.github/prove-dsh-profile.mjs index e3100df..5134e44 100644 --- a/.github/prove-dsh-profile.mjs +++ b/.github/prove-dsh-profile.mjs @@ -2,7 +2,8 @@ // Boot a profile that already ran `dsh plugin --profile add ` // and fail unless dsh-pstack's skill provider lists the required skills. // There is no dsh flag that prints the skill catalog; `dsh --help` only offers -// plugin (pnpm) and --dump-config. This uses the public profile-boot export. +// plugin (pnpm) and --dump-config. imports dsh's internal profile-boot module +// (lib/profile-boot.js and the nested dsh-app-boot file); tied to the pinned dsh version. import { execFileSync } from 'node:child_process' import { mkdtempSync, realpathSync } from 'node:fs' import { tmpdir } from 'node:os' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ee2d19..9743660 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,12 +113,12 @@ jobs: - name: Install bundle dependencies run: npm ci --omit=peer - - name: Install pnpm and the latest DSH + - name: Install pnpm and DSH 0.2.1-rc.4 run: | set -euo pipefail prefix="${RUNNER_TEMP}/dsh-prefix" - # npm dist-tag latest trails next. @next is the newest published runtime. - npm install -g --prefix "$prefix" pnpm@10 @deepseek-ai/dsh@next + # pinned to the runtime this proof was written against. bump it in its own pr. + npm install -g --prefix "$prefix" pnpm@10 @deepseek-ai/dsh@0.2.1-rc.4 echo "$prefix/bin" >> "$GITHUB_PATH" "$prefix/bin/dsh" --version "$prefix/bin/pnpm" --version