fix(dsh): убрать импорты DSH-пакетов из бандла
This commit is contained in:
parent
7e143be61a
commit
7665218247
@ -34,7 +34,7 @@
|
|||||||
- [x] Bootstrap `using-superpowers`: резолв версии, marker, dedupe, compaction.
|
- [x] Bootstrap `using-superpowers`: резолв версии, marker, dedupe, compaction.
|
||||||
- [x] `/role`: парсинг, два слоя, инъекция, followup.
|
- [x] `/role`: парсинг, два слоя, инъекция, followup.
|
||||||
- [x] `dsh/install.sh`, `mcp.codegraph.yml`, README/PORT.
|
- [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 тестов зелёные (регрессий нет).
|
- [x] `bash tests/run.sh` — 630 тестов зелёные (регрессий нет).
|
||||||
- [ ] Живая проверка в DSH: маркер, guard на main, `/role`, acceptance
|
- [ ] Живая проверка в DSH: маркер, guard на main, `/role`, acceptance
|
||||||
superpowers.
|
superpowers.
|
||||||
|
|||||||
20
dsh/PORT.md
20
dsh/PORT.md
@ -90,10 +90,14 @@ dsh/
|
|||||||
mcp.codegraph.yml # optional CodeGraph MCP row
|
mcp.codegraph.yml # optional CodeGraph MCP row
|
||||||
```
|
```
|
||||||
|
|
||||||
`index.js` is the only file importing DSH packages
|
The bundle imports only Node builtins and files inside this package. It
|
||||||
(`@deepseek-ai/dsh-llm`, `@deepseek-ai/schemastery`). The pure modules are
|
deliberately does not import `@deepseek-ai/dsh-llm` or
|
||||||
testable with plain Node; this is why the port's 22 tests run without a
|
`@deepseek-ai/schemastery`: a local-directory profile install resolves imports
|
||||||
DSH process.
|
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
|
### 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
|
- **DSH is 0.2.1-rc.3.** The plugin/event API is not frozen; pin the version and
|
||||||
re-run the tests after upgrades.
|
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
|
- **`ctx.tools.guard` is synchronous.** The guard resolves cwd through
|
||||||
`workingDirectory.get(session)` (sync) and runs the hooks with
|
`workingDirectory.get(session)` (sync) and runs the hooks with
|
||||||
`execFileSync`. If a future DSH version changes the guard contract or makes
|
`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.
|
* 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'
|
import { buildRoleText, listRoles, parseRole } from './lib/role.js'
|
||||||
|
|
||||||
function currentDirectory(workingDirectory, agent) {
|
function currentDirectory(workingDirectory, agent) {
|
||||||
|
|||||||
@ -11,37 +11,25 @@
|
|||||||
*
|
*
|
||||||
* Everything else (CLAUDE.md, roles, OpenSpec, Gitea, codegraph, project
|
* Everything else (CLAUDE.md, roles, OpenSpec, Gitea, codegraph, project
|
||||||
* templates) stays in the repository and is consumed as ordinary files.
|
* 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 { existsSync } from 'node:fs'
|
||||||
import { homedir } from 'node:os'
|
import { homedir } from 'node:os'
|
||||||
import { dirname, join } from 'node:path'
|
import { dirname, join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
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 { registerRoleCommand } from './command-role.js'
|
||||||
import { createGuard } from './lib/guards.js'
|
import { createGuard } from './lib/guards.js'
|
||||||
import { isBootstrapVisible, prepareBootstrap } from './lib/bootstrap.js'
|
import { isBootstrapVisible, prepareBootstrap } from './lib/bootstrap.js'
|
||||||
|
import { createUserMessage } from './lib/message.js'
|
||||||
import { DEFAULT_ROLE_NAMES } from './lib/role.js'
|
import { DEFAULT_ROLE_NAMES } from './lib/role.js'
|
||||||
|
|
||||||
export const name = 'dsh-harness'
|
export const name = 'dsh-harness'
|
||||||
export const inject = ['tools', 'workingDirectory']
|
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. */
|
/** Resolve the hook directory for both a linked checkout and a packed package. */
|
||||||
function resolveHooksDir(bundleDir, configured) {
|
function resolveHooksDir(bundleDir, configured) {
|
||||||
if (configured) return 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, /createGuard/)
|
||||||
assert.match(source, /prepareBootstrap/)
|
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