fix(dsh): убрать импорты DSH-пакетов из бандла
This commit is contained in:
parent
7e143be61a
commit
7665218247
@ -34,7 +34,7 @@
|
||||
- [x] Bootstrap `using-superpowers`: резолв версии, marker, dedupe, compaction.
|
||||
- [x] `/role`: парсинг, два слоя, инъекция, followup.
|
||||
- [x] `dsh/install.sh`, `mcp.codegraph.yml`, README/PORT.
|
||||
- [x] `bash dsh/tests/run.sh` — 22 теста зелёные.
|
||||
- [x] `bash dsh/tests/run.sh` — 25 тестов зелёные.
|
||||
- [x] `bash tests/run.sh` — 630 тестов зелёные (регрессий нет).
|
||||
- [ ] Живая проверка в DSH: маркер, guard на main, `/role`, acceptance
|
||||
superpowers.
|
||||
|
||||
20
dsh/PORT.md
20
dsh/PORT.md
@ -90,10 +90,14 @@ dsh/
|
||||
mcp.codegraph.yml # optional CodeGraph MCP row
|
||||
```
|
||||
|
||||
`index.js` is the only file importing DSH packages
|
||||
(`@deepseek-ai/dsh-llm`, `@deepseek-ai/schemastery`). The pure modules are
|
||||
testable with plain Node; this is why the port's 22 tests run without a
|
||||
DSH process.
|
||||
The bundle imports only Node builtins and files inside this package. It
|
||||
deliberately does not import `@deepseek-ai/dsh-llm` or
|
||||
`@deepseek-ai/schemastery`: a local-directory profile install resolves imports
|
||||
from the profile, not from the DSH installation, so a DSH-internal import fails
|
||||
at activation with `ERR_MODULE_NOT_FOUND`. `lib/message.js` is a local copy of
|
||||
the tiny `createUserMessage` constructor, and `Config` was replaced by defaults
|
||||
normalized in `apply()`. The pure modules are testable with plain Node; this is
|
||||
why the port's 25 tests run without a DSH process.
|
||||
|
||||
### Guard flow
|
||||
|
||||
@ -217,6 +221,14 @@ profile was removed; the real `desktop` profile was not touched.
|
||||
|
||||
- **DSH is 0.2.1-rc.3.** The plugin/event API is not frozen; pin the version and
|
||||
re-run the tests after upgrades.
|
||||
- **Local profile imports are not resolved from the DSH installation.** The
|
||||
first install attempt failed at activation with
|
||||
`ERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/schemastery'`. The
|
||||
bundle now imports only Node builtins and local files: `lib/message.js`
|
||||
replaces `createUserMessage` from `@deepseek-ai/dsh-llm`, and `Config` was
|
||||
dropped in favor of defaults normalized in `apply()`. Any future DSH service
|
||||
must be reached through `ctx` (injected service), never through an
|
||||
`@deepseek-ai/*` import; a package test enforces this.
|
||||
- **`ctx.tools.guard` is synchronous.** The guard resolves cwd through
|
||||
`workingDirectory.get(session)` (sync) and runs the hooks with
|
||||
`execFileSync`. If a future DSH version changes the guard contract or makes
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
* the five-line role summary. The command's own result is UI-only.
|
||||
*/
|
||||
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage } from './lib/message.js'
|
||||
import { buildRoleText, listRoles, parseRole } from './lib/role.js'
|
||||
|
||||
function currentDirectory(workingDirectory, agent) {
|
||||
|
||||
@ -11,37 +11,25 @@
|
||||
*
|
||||
* Everything else (CLAUDE.md, roles, OpenSpec, Gitea, codegraph, project
|
||||
* templates) stays in the repository and is consumed as ordinary files.
|
||||
*
|
||||
* The bundle intentionally has no `@deepseek-ai/*` imports: a local-directory
|
||||
* profile install resolves imports from the profile, not from the DSH
|
||||
* installation, so only Node builtins and files in this package are safe.
|
||||
*/
|
||||
|
||||
import { existsSync } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { z } from '@deepseek-ai/schemastery'
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { registerRoleCommand } from './command-role.js'
|
||||
import { createGuard } from './lib/guards.js'
|
||||
import { isBootstrapVisible, prepareBootstrap } from './lib/bootstrap.js'
|
||||
import { createUserMessage } from './lib/message.js'
|
||||
import { DEFAULT_ROLE_NAMES } from './lib/role.js'
|
||||
|
||||
export const name = 'dsh-harness'
|
||||
export const inject = ['tools', 'workingDirectory']
|
||||
|
||||
export const Config = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
guards: z.boolean().default(true),
|
||||
bootstrap: z.boolean().default(true),
|
||||
bootstrapSubagents: z.boolean().default(false),
|
||||
roleCommand: z.boolean().default(true),
|
||||
hooksDir: z.string().default(''),
|
||||
rolesDir: z.string().default(''),
|
||||
projectRolesDir: z.string().default(''),
|
||||
roleNames: z.array(z.string()).default(DEFAULT_ROLE_NAMES),
|
||||
bootstrapSkillPath: z.string().default(''),
|
||||
toolMappingPath: z.string().default(''),
|
||||
bootstrapMaxBytes: z.number().default(65_536),
|
||||
})
|
||||
|
||||
/** Resolve the hook directory for both a linked checkout and a packed package. */
|
||||
function resolveHooksDir(bundleDir, configured) {
|
||||
if (configured) return configured
|
||||
|
||||
28
dsh/bundle/lib/message.js
Normal file
28
dsh/bundle/lib/message.js
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Minimal local copy of the DSH user-message constructor.
|
||||
*
|
||||
* The bundle must load in a profile whose module resolution does not include
|
||||
* the DSH installation's internal packages. `createUserMessage` in
|
||||
* `@deepseek-ai/dsh-llm` is only `{ ...input, role: "user", id: randomUUID() }`
|
||||
* plus a deep freeze, so keeping a local copy removes the only runtime import
|
||||
* outside this package.
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
function deepFreeze(value) {
|
||||
if (value === null || typeof value !== 'object' || Object.isFrozen(value)) return value
|
||||
Object.freeze(value)
|
||||
for (const child of Object.values(value)) deepFreeze(child)
|
||||
return value
|
||||
}
|
||||
|
||||
/** Create an immutable user-role message with a fresh identity. */
|
||||
export function createUserMessage(input) {
|
||||
const message = {
|
||||
...structuredClone(input),
|
||||
role: 'user',
|
||||
id: randomUUID(),
|
||||
}
|
||||
return deepFreeze(message)
|
||||
}
|
||||
24
dsh/tests/message.test.mjs
Normal file
24
dsh/tests/message.test.mjs
Normal file
@ -0,0 +1,24 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { createUserMessage } from '../bundle/lib/message.js'
|
||||
|
||||
test('createUserMessage returns a frozen user message with a fresh id', () => {
|
||||
const message = createUserMessage({
|
||||
content: [{ type: 'text', text: 'hello' }],
|
||||
source: { kind: 'user' },
|
||||
})
|
||||
|
||||
assert.equal(message.role, 'user')
|
||||
assert.match(message.id, /^[0-9a-f-]{36}$/)
|
||||
assert.deepEqual(message.content, [{ type: 'text', text: 'hello' }])
|
||||
assert.deepEqual(message.source, { kind: 'user' })
|
||||
assert.ok(Object.isFrozen(message))
|
||||
assert.ok(Object.isFrozen(message.content))
|
||||
assert.ok(Object.isFrozen(message.content[0]))
|
||||
})
|
||||
|
||||
test('createUserMessage does not reuse an id', () => {
|
||||
const a = createUserMessage({ content: [], source: { kind: 'user' } })
|
||||
const b = createUserMessage({ content: [], source: { kind: 'user' } })
|
||||
assert.notEqual(a.id, b.id)
|
||||
})
|
||||
@ -50,3 +50,17 @@ test('index.js registers all three integration points', () => {
|
||||
assert.match(source, /createGuard/)
|
||||
assert.match(source, /prepareBootstrap/)
|
||||
})
|
||||
|
||||
test('runtime files have no external @deepseek-ai imports', () => {
|
||||
for (const file of [
|
||||
'index.js',
|
||||
'command-role.js',
|
||||
'lib/message.js',
|
||||
'lib/guards.js',
|
||||
'lib/bootstrap.js',
|
||||
'lib/role.js',
|
||||
]) {
|
||||
const source = readFileSync(join(bundleDir, file), 'utf8')
|
||||
assert.doesNotMatch(source, /from ['"]@deepseek-ai\//, `${file} must not import DSH internals`)
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user