Skip to content

feat(dsh-plugin): integrate Obelisk through a plugin-owned skill - #115

Merged
tommy0103 merged 11 commits into
tommy0103:mainfrom
KinomotoMio:fix/dsh-plugin-canonical-skill
Aug 28, 2026
Merged

feat(dsh-plugin): integrate Obelisk through a plugin-owned skill#115
tommy0103 merged 11 commits into
tommy0103:mainfrom
KinomotoMio:fix/dsh-plugin-canonical-skill

Conversation

@KinomotoMio

@KinomotoMio KinomotoMio commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

This PR incorporates the work proposed in #61 and revises it into an upstream merge candidate based on the current main branch. It preserves the original product goal—making Obelisk available inside DeepSeek Harness—while keeping Obelisk's retrieval contract consistent across agent harnesses and giving the DSH integration a deliberate lifecycle of its own.

Summary

  • add the opt-in @obelisk/dsh-obelisk-plugin package with a plugin-owned DSH-facing obelisk skill
  • register that skill through DSH's runtime skill API so project-local overrides remain strongest, the plugin wins over user-global same-name installations, and the model sees one definition
  • keep discovery and loading on DSH's existing skill catalog and skill tool
  • keep execution on the standard Bash path used by other Obelisk integrations
  • make the local package an installable DSH bundle with one-command install and uninstall instructions
  • record the durable integration contract in ADR-0012 without changing the DeepSeek Harness repository

Discussion

The implementation in #61 was written before the Obelisk session adapter for DSH existed. It came from a constrained prompt-driven exercise intended to demonstrate three declared DSH plugin aspects without first inspecting DSH source: register a tool, append model guidance, and add a branded browser card. The generated implementation was mechanically coherent with those prompts, but the visual goal ended up deciding the model protocol. The shared skill taught Bash while the plugin introduced a second invocation path with its own temporary-file handling, synchronous subprocess, timeout, truncation, and error semantics.

During review we separated the cross-agent contract from DSH-specific presentation. The dedicated tool had mainly become necessary because a tool name could own a distinctive frontend renderer. DSH currently cannot let an external plugin decorate only selected Bash calls, and identifying Obelisk inside arbitrary shell pipelines would not provide a reliable attribution boundary anyway. We therefore chose consistency over branding: this revision accepts the ordinary Bash presentation instead of changing DSH, copying its Bash renderer, or keeping a second tool identity.

The review also exposed a less visible integration boundary. A user may already have installed a global obelisk skill for another agent. DSH can discover that same name from its user skill roots, so merely bundling another candidate would leave the selected instructions dependent on filesystem precedence. The plugin now registers its own DSH-facing definition at runtime: it intentionally shadows a user-global copy while still allowing a project to make an explicit local override.

Those choices require product and architecture judgment beyond satisfying an implementation checklist. Obelisk exists to help people recover the decisions behind prior work; this integration should follow the same discipline by separating presentation, model protocol, ownership, and precedence instead of allowing whichever adapter mechanism is easiest to become the architecture.

The resulting decisions are:

  • the plugin does not modify any DSH code
  • the plugin owns the DSH-facing skill and may evolve it for DSH while preserving the shared CLI, evidence, and human-approved memory boundaries
  • DSH's existing skill catalog and skill tool handle discovery and loading
  • duplicate resolution is project-local skill, then plugin runtime skill, then user-global skill
  • Obelisk continues through standard Bash, preserving command recording, session attribution, CLI behavior, and DSH's permission flow
  • custom presentation waits for a clean plugin-owned attribution or decoration seam
  • this integration introduces no settings page; richer plugin-native product surfaces can be considered separately
  • a future standalone plugin repository may be a GitHub Actions distribution mirror of this directory, not a submodule or a second hand-maintained source

Implementation

@obelisk/dsh-obelisk-plugin depends only on DSH's standard skills service. It reads the packaged skill/SKILL.md, registers the parsed definition through ctx.skills.register(...), and exposes the complete plugin-owned skill directory as its resource base. The checked-in copy is byte-for-byte identical to the canonical Obelisk skill at introduction; it is checked in under the plugin so future DSH-specific guidance is reviewed as part of the integration rather than selected implicitly from a user's global installation.

The build copies that tree to dist/skill/. Runtime path resolution is lazy and checks the packaged tree first, so a malformed publication fails with a plugin-specific missing-bundle error rather than an opaque filesystem error. Tests import the actual dist/index.js, prove that it loads dist/skill, exercise registration and disposal against the real DSH registry, prove the intended duplicate-name precedence, verify every referenced resource, and cover the missing-package-tree failure.

The package declares obelisk.cordis.yml through dsh.bundle. Installing the local checkout links the dependency and adds its plugin row; removing the package withdraws both. The README provides one-line local install and uninstall commands, including pnpm's explicit workspace-root flag.

The plugin build is implemented as a cross-platform Node script. Root tests build the package before running, and the CLI workflow now has a dedicated plugin build and test step on Linux, macOS, and Windows so the distributable package cannot silently fall outside the enumerated test files.

ADR-0012 follows the repository's established Context / Decision / Verification / Consequences structure. It describes the future architecture positively; implementation-review history remains in this PR discussion.

Verification

  • npm test — 554 tests passed
  • npm run typecheck
  • npx eslint packages/dsh-plugin/src/index.ts packages/dsh-plugin/scripts/build.mjs tests/dsh-plugin.test.mjs
  • npm run build --workspace @obelisk/dsh-obelisk-plugin
  • node --experimental-test-module-mocks --test tests/dsh-plugin.test.mjs — 8 tests passed
  • diff -qr packages/dsh-plugin/skill packages/dsh-plugin/dist/skill
  • diff -qr skill-doc packages/dsh-plugin/skill — verifies the complete canonical seed before any future DSH-specific evolution
  • npm pack --workspace @obelisk/dsh-obelisk-plugin --dry-run --json — the package contains the bundle manifest, built entry point, SKILL.md, and every referenced resource, including the two canonical compatibility pointers
  • installed and removed the local package against an isolated current DSH profile; the dependency and bundle layer appeared and disappeared together
  • rebuilt the bundle, started the current local DSH checkout with the web profile, and confirmed the application served successfully with the plugin mounted and no bundle or skill resolution error

…Harness

ADR-0009 (Mode 0): one read-only obelisk_query tool plus a guidance
section, mounting the cross-tool archive and memory layer beside the
built-in session history search. The tool runs bounded JavaScript
through the obelisk CLI sandbox, caps output, and stays a thin
translation layer; the obelisk skill keeps owning query semantics.

- packages/dsh-plugin: cordis plugin (tool + system prompt section),
  schemastery config, sample overlay patch, package README
- tests/dsh-plugin.test.mjs + fake CLI fixture: registration, JSON
  round-trip, failure/stderr surfacing, non-JSON passthrough, output
  cap, spawn errors
Presentation-only frontend contribution per ADR-0009: the package now
declares dsh.client (platform web) and ships a lib/client.js bundle that
registers a keyed toolview for the obelisk_query wire name — monolith
glyph, Obelisk label, bounded query summary, expandable QUERY/RESULT
sections on DSW theme tokens.

Invariants: the model-facing surface and the durable session record are
unchanged; only the human-facing card differs, and only the
obelisk_query key is claimed. Skill-driven bash invocations keep the
standard row (documented; conditional takeover needs a future generic
capability in the harness).

- src/client: row-model (pure derivation), obelisk-row card, slot
  registration, CSS module
- tsdown.config.ts: self-contained client bundle build (closure-factory
  contract + inline CSS modules, adapted from deepseek-harness MIT preset)
- tests/dsh-plugin-client-model.test.mjs: 6 model tests
- README: frontend-presentation section and rc.6 alignment fix
Supplementary decision: the plugin carries its own browser half via the
dsh.client dual-face mechanism and styles first-party obelisk_query calls.
Three invariants are recorded — model-facing surface unchanged, durable
session record unchanged, only the obelisk_query key claimed — and the
bash-invocation recognition is deferred behind a future generic
keyed-select capability in the harness.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@KinomotoMio KinomotoMio changed the title feat(dsh-plugin): integrate Obelisk through the canonical skill feat(dsh-plugin): integrate Obelisk through a plugin-owned skill Aug 27, 2026

@tommy0103 tommy0103 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: request changes

The overall direction is sound — the runtime-skill registration approach, the precedence design (project-local > plugin > user-global), and the decision to stay on the standard Bash path are all well-reasoned, and ADR-0012 documents the contract clearly. A few things need fixing before merge:

Blocking

  1. The new test never runs in CI. .github/workflows/cli.yml enumerates test files explicitly, and tests/dsh-plugin.test.mjs was not added. Local npm test picks it up via the tests/*.test.mjs glob, but CI won't — this violates the verification contract in CONTRIBUTING.md, and #113 set the precedent of adding new test files to the workflow. Please add it.

  2. The packaged dist/skill path is untested, despite ADR-0012's Verification section promising build/pack verification. tests/dsh-plugin.test.mjs imports src/index.ts and asserts resourceBase against the source packages/dsh-plugin/skill tree, so the PACKAGED_SKILL_ROOT branch (dist/index.jsdist/skill) can never be exercised. Compounding this, skillRoot() / bodyUrl / resourceBase are computed at module load (src/index.ts:78-80): in a published tarball (files: [dist, obelisk.cordis.yml]), a missing or stale dist/skill silently falls back to a nonexistent ../skill, and the failure surfaces later as an opaque ENOENT inside apply(). Please compute these lazily in apply() (or fail fast with a clear error) and add a test that builds the package and loads dist/index.js.

Non-blocking suggestions

  • Precedence test uses assumed ranks. The duplicate-name test mocks competitors with hardcoded rank: 500 / rank: 100; the real guarantee depends on DSH's shipped filesystem provider ranking project roots below 250 and user roots above. The test proves registry mechanics, not precedence against the real discovery path the ADR's Context section motivates. Consider asserting against the actual rank constants from dsh-skill, or at least adding a comment pinning the assumption.
  • Two dangling reference files ship in the bundle. skill/references/recap-patterns.md (93 lines) and skill/references/recap-writing.md are compatibility-pointer stubs referenced by nothing in the tree (SKILL.md → recap/overview.md → per-card files only). They're verbatim copies inherited from the skill-doc/ seed, but since the plugin now owns this copy, either link them or drop them.
  • Housekeeping:
    • Root tsconfig.json include enumerates each package's src explicitly; packages/dsh-plugin/src wasn't added (currently only covered transitively via the test import).
    • packages/dsh-plugin/tsconfig.json sets "erasableSyntaxOnly": false, overriding the root's true, but the code uses no erasable-syntax constructs — suggest dropping the override.
    • '@obelisk/dsh-obelisk-plugin' appears as two separate literals in src/index.ts (name and PROVIDER_NAME).
    • Metadata nits: version: "0.1.0" vs the 0.0.0 convention for private packages; "AGPL-3.0-only" vs the root/cli "AGPL-3.0".
    • Three commit messages still reference "ADR-0009", which on main is a different document; not worth rewriting history over, but worth knowing.

Minor note: the hand-rolled frontmatter parser in parseSkill and the test's bodyOf regex are two independent implementations whose agreement is asserted, not guaranteed — acceptable for now, but they diverge on edge cases like --- inside a YAML block scalar.

Verified locally against current main (4d5a620): diff touches only packages/dsh-plugin/**, tests/dsh-plugin.test.mjs, docs/adr/0012-*, and package-lock.json; no DSH code is modified, and the earlier obelisk_query browser card from 6819f04 was fully removed by 0ea4d2a. Once the two blocking items are addressed this looks good to merge.

@KinomotoMio

Copy link
Copy Markdown
Collaborator Author

Thanks for the careful review. I addressed the two blocking gaps in two focused commits:

  • 198a9db adds explicit three-platform CI coverage for the plugin package, replaces the shell-only build command with a cross-platform Node build, imports the real dist/index.js against dist/skill, and verifies that a missing packaged skill tree fails with a clear plugin-specific error.
  • c0dc864 documents and covers the DSH precedence ranks used by the integration, removes the two unreferenced plugin-only compatibility pointers, and aligns the plugin's TypeScript and package internals with the repository defaults.

Final-head verification includes npm test (554/554), root typecheck, focused ESLint, plugin build, 8/8 plugin tests, and a dry-run package inspection of the 22 published entries. I also rebuilt the bundle and started the current local DSH checkout successfully with the plugin mounted; no DSH source was modified.

I kept 0.1.0 because the repository's private packages are not uniformly versioned as 0.0.0, and kept AGPL-3.0-only because it precisely matches the repository license and source headers. I also left the accepted parser and existing commit history intact rather than broadening this review fix into an unrelated rewrite.

The PR description now reflects the packaged-runtime checks and the intentionally owned plugin skill tree. Ready for re-review.

@KinomotoMio
KinomotoMio requested a review from tommy0103 August 27, 2026 10:39
@KinomotoMio

Copy link
Copy Markdown
Collaborator Author

Follow-up correction: a889eb8 restores recap-patterns.md and recap-writing.md. Although they are not referenced by the current recap graph, they are intentional compatibility entry points, and the initial plugin-owned skill should remain a complete byte-for-byte copy of the canonical Obelisk skill rather than introducing an unnecessary DSH-specific difference.

This supersedes the removal noted in my previous reply. skill-doc and packages/dsh-plugin/skill now match completely; the built dist/skill matches the plugin source; and the package dry run contains all 24 entries. The PR description has been corrected accordingly.

@tommy0103 tommy0103 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: approve

Re-reviewed at a889eb8. Both blocking items from my previous review are resolved, and I verified locally in a clean worktree: build succeeds, tests/dsh-plugin.test.mjs passes 8/8 (including the two new tests covering the packaged dist/skill path and the clear-failure case), the full suite passes 554/554, and both root and workspace typechecks are clean.

Specifically:

  • CI now builds the plugin and runs its test explicitly, and the root pretest keeps local npm test working. ✓
  • skillRoot() is now computed lazily in apply() and fails fast with a clear message naming both expected paths instead of silently falling back. ✓
  • The rank assumptions are now pinned as named constants with a comment citing @deepseek-ai/dsh-skill-filesystem 0.0.1-rc.1, and all four ranks are exercised. ✓
  • Root tsconfig.json includes packages/dsh-plugin/src, the redundant erasableSyntaxOnly override is gone, and the duplicated package-name literal now reuses name. ✓
  • Moving the build to scripts/build.mjs is a nice portability improvement over the rm -rf/cp -R shell one-liner.

Two minor leftovers, not worth another round — fine to clean up here or in a follow-up:

  • skill/references/recap-patterns.md and recap-writing.md are still unreferenced by anything in the tree but ship in the bundle.
  • Metadata nits remain: version: "0.1.0" vs the 0.0.0 convention for private packages, and "AGPL-3.0-only" vs the root/cli "AGPL-3.0".

LGTM.

@tommy0103
tommy0103 merged commit 84e8932 into tommy0103:main Aug 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants