mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge PR #500 into CI optimization
This commit is contained in:
@@ -40,10 +40,12 @@ interface PackageManifest {
|
||||
bin?: string | Record<string, string>
|
||||
exports?: Record<
|
||||
string,
|
||||
| string
|
||||
| {
|
||||
types?: string
|
||||
default?: string
|
||||
}
|
||||
| null
|
||||
| undefined
|
||||
>
|
||||
files?: string[]
|
||||
@@ -115,13 +117,41 @@ function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
|
||||
'lib/invariant.js',
|
||||
...manifest.bin ? ['lib/bin.js'] : [],
|
||||
...manifest.exports?.['./worker'] ? ['lib/worker.cjs'] : [],
|
||||
// UI plugin packages ship their browser bundle beside the node lib
|
||||
// (single-artifact ruling: dist/ retired, ./client resolves lib/client.js).
|
||||
// Keyed on the artifact path, not the subpath name: apiproxy's ./client is
|
||||
// a browser-safe source channel, not a bundle.
|
||||
...exportDefault(manifest, './client') === './lib/client.js' ? ['lib/client.js'] : [],
|
||||
// runtime's shell-held loader subpath ships as its own bundle beside the client half.
|
||||
...exportDefault(manifest, './loader') === './lib/loader.js' ? ['lib/loader.js'] : [],
|
||||
// web-react's store subpath ships its own bundle (single-entry builds; no shared chunk).
|
||||
...exportDefault(manifest, './store') === './lib/store/index.js' ? ['lib/store/index.js'] : [],
|
||||
...extras,
|
||||
// Subpaths whose runtime default is the tsc-emitted tree (lib/types/*.js —
|
||||
// browser-safe source channels rehomed off src so plain Node can import
|
||||
// them without type stripping) publish the emitted JS alongside the
|
||||
// declarations.
|
||||
...usesEmittedTreeDefaults(manifest) ? ['lib/types/**/*.js'] : [],
|
||||
'lib/types/**/*.d.ts',
|
||||
'lib/types/**/*.d.ts.map',
|
||||
'src',
|
||||
]
|
||||
}
|
||||
|
||||
/** Runtime target of an export entry: conditional `default`, or the bare-string shorthand. */
|
||||
function exportDefault(manifest: PackageManifest, subpath: string): string | undefined {
|
||||
const entry = manifest.exports?.[subpath]
|
||||
if (typeof entry === 'string') return entry
|
||||
if (typeof entry === 'object' && entry !== null) return entry.default
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** Whether any export's runtime default points into the tsc-emitted lib/types tree. */
|
||||
function usesEmittedTreeDefaults(manifest: PackageManifest): boolean {
|
||||
return Object.keys(manifest.exports ?? {}).some(subpath =>
|
||||
exportDefault(manifest, subpath)?.startsWith('./lib/types/') === true)
|
||||
}
|
||||
|
||||
function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
|
||||
const errors: string[] = []
|
||||
const label = manifest.name ?? dir
|
||||
@@ -155,13 +185,16 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
|
||||
if (manifest.types !== 'lib/types/index.d.ts') {
|
||||
errors.push(`${label}: package.json must set "types": "lib/types/index.d.ts"`)
|
||||
}
|
||||
if (manifest.exports?.['.']?.types !== './lib/types/index.d.ts') {
|
||||
const rootExport = manifest.exports?.['.']
|
||||
const rootEntry = typeof rootExport === 'object' && rootExport !== null ? rootExport : undefined
|
||||
if (rootEntry?.types !== './lib/types/index.d.ts') {
|
||||
errors.push(`${label}: package.json exports["."].types must be "./lib/types/index.d.ts"`)
|
||||
}
|
||||
if (manifest.exports?.['.']?.default !== './lib/index.js') {
|
||||
if (rootEntry?.default !== './lib/index.js') {
|
||||
errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`)
|
||||
}
|
||||
const invariantExport = manifest.exports?.['./invariant']
|
||||
const invariantRaw = manifest.exports?.['./invariant']
|
||||
const invariantExport = typeof invariantRaw === 'object' && invariantRaw !== null ? invariantRaw : undefined
|
||||
if (invariantExport?.types !== undefined && invariantExport.types !== './lib/types/invariant.d.ts') {
|
||||
errors.push(`${label}: package.json exports["./invariant"].types must be "./lib/types/invariant.d.ts"`)
|
||||
}
|
||||
|
||||
60
scripts/client-bundle-purity.spec.ts
Normal file
60
scripts/client-bundle-purity.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Pins the client-bundle purity gate (tsdown preset resolveId classifier):
|
||||
* a bare-name import of a module-table package must rewrite to its /client
|
||||
* external form (inlining it duplicates runtime identity — the P0
|
||||
/* leak that is not an
|
||||
* inline-safe wire layer must fail the build loudly.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { CLIENT_EXTERNALS, clientBundle } from '../packages/client/tsdown.client.ts'
|
||||
|
||||
type ResolveId = (source: string) => null | { id: string; external: boolean }
|
||||
|
||||
function purityResolveId(): ResolveId {
|
||||
// libEntry is spelled at every call site (no default) so the
|
||||
// package-invariants text check can see the invariant entry per package.
|
||||
const configs = clientBundle('@deepseek-ai/dsh-client-test', ['lib/types/index.js', 'lib/types/invariant.js'])
|
||||
const plugins = (configs[1] as { plugins: { name: string; resolveId?: unknown }[] }).plugins
|
||||
const gate = plugins.find(p => p.name === 'dsh-client-bundle-purity')
|
||||
if (gate?.resolveId === undefined) throw new Error('purity plugin missing from client config')
|
||||
return gate.resolveId as ResolveId
|
||||
}
|
||||
|
||||
describe('client bundle purity gate', () => {
|
||||
const resolveId = purityResolveId()
|
||||
|
||||
it('leaves table entries and non-scoped specifiers alone', () => {
|
||||
expect(resolveId('@deepseek-ai/dsh-client-ui-slots')).toBeNull()
|
||||
expect(resolveId('@deepseek-ai/dsh-client-runtime/client')).toBeNull()
|
||||
expect(resolveId('react')).toBeNull()
|
||||
expect(resolveId('zod')).toBeNull()
|
||||
})
|
||||
|
||||
it('rewrites a bare table-package name to its external /client form (duplicate-instance prevention)', () => {
|
||||
expect(resolveId('@deepseek-ai/dsh-client-connection')).toEqual({
|
||||
id: '@deepseek-ai/dsh-client-connection/client',
|
||||
external: true,
|
||||
})
|
||||
expect(resolveId('@deepseek-ai/dsh-client-ui-layout')).toEqual({
|
||||
id: '@deepseek-ai/dsh-client-ui-layout/client',
|
||||
external: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('lets inline-safe wire layers inline', () => {
|
||||
expect(resolveId('@deepseek-ai/dsh-host-apiproxy/api')).toBeNull()
|
||||
expect(resolveId('@deepseek-ai/dsh-session/surface')).toBeNull()
|
||||
expect(resolveId('@deepseek-ai/dsh-brand')).toBeNull()
|
||||
})
|
||||
|
||||
it('throws on any other @deepseek-ai leak', () => {
|
||||
expect(() => resolveId('@deepseek-ai/dsh-agent')).toThrow(/purity/)
|
||||
expect(() => resolveId('@deepseek-ai/dsh-client-web')).toThrow(/purity/)
|
||||
})
|
||||
|
||||
it('every /client external has no bare-name twin in the table (the rewrite assumption)', () => {
|
||||
for (const entry of CLIENT_EXTERNALS) {
|
||||
if (entry.endsWith('/client')) expect(CLIENT_EXTERNALS).not.toContain(entry.slice(0, -'/client'.length))
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,22 @@
|
||||
/** Map one workspace source alias target to its declaration-build target. */
|
||||
export function builtDeclarationPath(candidate: string): string {
|
||||
// Two workspace shapes exist: whole-package entries end in /src, subpath
|
||||
// wildcards (apiproxy's browser-safe /api and /client channels) in /src/*.
|
||||
if (candidate.endsWith('/src')) {
|
||||
return `${candidate.slice(0, -'/src'.length)}/lib/types`
|
||||
}
|
||||
if (candidate.endsWith('/src/*')) {
|
||||
return `${candidate.slice(0, -'/src/*'.length)}/lib/types/*`
|
||||
}
|
||||
const sourceFile = /^(.*)\/src\/(.+)\.ts$/.exec(candidate)
|
||||
if (sourceFile?.[1] && sourceFile[2]) {
|
||||
return `${sourceFile[1]}/lib/types/${sourceFile[2]}.d.ts`
|
||||
}
|
||||
// Directory subpath entries (web-react's /store, runtime's /client): the
|
||||
// source dir maps to the same dir under lib/types (index resolution applies).
|
||||
const sourceDir = /^(.*)\/src\/(.+)$/.exec(candidate)
|
||||
if (sourceDir?.[1] && sourceDir[2]) {
|
||||
return `${sourceDir[1]}/lib/types/${sourceDir[2]}`
|
||||
}
|
||||
throw new Error(`doc-typecheck: cannot map workspace source path to built declarations: ${candidate}`)
|
||||
}
|
||||
|
||||
@@ -177,6 +177,9 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
||||
CompactAgentContext: 'compaction service input is owned by packages/compact/compact/src/index.ts',
|
||||
CreateAgentOptions: 'agent creation contract is owned by packages/core/agent/README.md',
|
||||
InvariantInstaller: 'service-local contribution contract is owned by packages/support/invariants/README.md',
|
||||
LocaleDict: 'service-local dictionary shape is owned by packages/client/i18n/src/index.ts',
|
||||
ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts',
|
||||
Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts',
|
||||
InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md',
|
||||
PresetOption: 'deployment menu metadata is owned by packages/ui/permission/README.md',
|
||||
PresetSpec: 'deployment preset composition is owned by packages/ui/permission/README.md',
|
||||
|
||||
@@ -20,6 +20,8 @@ type Mode =
|
||||
| 'ci-windows-complete'
|
||||
| 'ci-windows-observational'
|
||||
| 'node-compat'
|
||||
| 'pre-push'
|
||||
| 'manual-push'
|
||||
| 'doc-sync'
|
||||
type GateStatus = 'pending' | 'running' | 'passed' | 'failed' | 'skipped'
|
||||
|
||||
@@ -95,20 +97,22 @@ function parseMode(raw: string | undefined): Mode {
|
||||
case 'ci-windows-complete':
|
||||
case 'ci-windows-observational':
|
||||
case 'node-compat':
|
||||
case 'pre-push':
|
||||
case 'manual-push':
|
||||
case 'doc-sync':
|
||||
return raw
|
||||
default:
|
||||
throw new Error(
|
||||
`run-gates: expected mode ci-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | doc-sync, got ${JSON.stringify(raw)}.`,
|
||||
`run-gates: expected mode ci-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | pre-push | manual-push | doc-sync, got ${JSON.stringify(raw)}.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function defaultConcurrency(selectedMode: Mode, total: number): ConcurrencyDefault {
|
||||
const available = availableParallelism()
|
||||
// The local doc mode caps workers: several gates each build a full ts.Program,
|
||||
// Local modes cap workers: several doc gates each build a full ts.Program,
|
||||
// so an uncapped default on a large host trades wall clock for memory blowups.
|
||||
const localCap = selectedMode === 'doc-sync'
|
||||
const localCap = selectedMode === 'pre-push' || selectedMode === 'doc-sync'
|
||||
const modeLimit = localCap ? Math.min(4, available) : available
|
||||
return {
|
||||
workers: Math.min(total, modeLimit),
|
||||
@@ -186,6 +190,24 @@ function gatesForMode(selected: Mode): Gate[] {
|
||||
return ciWindowsObservationalGates()
|
||||
case 'node-compat':
|
||||
return nodeCompatGates()
|
||||
case 'pre-push': return []
|
||||
case 'manual-push':
|
||||
return [
|
||||
pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }),
|
||||
pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }),
|
||||
pnpmScript('client-domain-graph', 'verify-client-domain-graph', { label: 'client domain graph' }),
|
||||
pnpmScript('test', 'test'),
|
||||
pnpmScript('duplication', 'duplication'),
|
||||
snapshotGate(),
|
||||
pnpmScript('build', 'build'),
|
||||
pnpmScript('build:web', 'build:web'),
|
||||
...hygieneLeafGates({ artifactNeeds: ['build'] }),
|
||||
...docSyncLeafGates({
|
||||
docTypecheckNeeds: ['build'],
|
||||
docTypecheckEnv: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' },
|
||||
}),
|
||||
pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }),
|
||||
]
|
||||
case 'doc-sync':
|
||||
return docSyncLeafGates()
|
||||
}
|
||||
@@ -361,8 +383,8 @@ function coverageGate(): Gate {
|
||||
}
|
||||
|
||||
// The snapshot suite boots the example bins in `lib` mode (built artifact under plain Node,
|
||||
// plugins via real exports). CI pairs it with `build`, so it exercises what ships rather than
|
||||
// the tsx/source path dev uses and therefore waits on `build`.
|
||||
// plugins via real exports) — CI and pre-push already build, so they exercise what ships rather
|
||||
// than the tsx/source path dev uses. It therefore waits on `build`.
|
||||
function snapshotGate(): Gate {
|
||||
return pnpmScript('snapshot', 'test:snapshot', {
|
||||
env: { DSH_EXAMPLE_MODE: 'lib' },
|
||||
@@ -394,6 +416,21 @@ function flagEnabled(envName: string): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
function hygieneLeafGates(options: { artifactNeeds?: string[] } = {}): Gate[] {
|
||||
const artifactOptions = options.artifactNeeds === undefined ? {} : { needs: options.artifactNeeds }
|
||||
return [
|
||||
pnpmScript('knip', 'knip'),
|
||||
pnpmScript('publint', 'publint', artifactOptions),
|
||||
pnpmScript('constraints', 'constraints'),
|
||||
pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }),
|
||||
builtPackageInvariantsGate(options.artifactNeeds),
|
||||
pnpmScript('node-next-types', 'verify-node-next-types', {
|
||||
label: 'node-next types',
|
||||
...artifactOptions,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
function docSyncLeafGates(options: {
|
||||
docTypecheckNeeds?: string[]
|
||||
docTypecheckEnv?: Record<string, string | undefined>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"requiredSince": "2026-07-14",
|
||||
"required": [
|
||||
".agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md",
|
||||
".agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md",
|
||||
".agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md",
|
||||
".agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md",
|
||||
".agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md",
|
||||
".agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
|
||||
".agents/notes/implemented/process/2026-07-19-web-styling-system.md",
|
||||
"README.md",
|
||||
"docs/cookbook/adding-a-package.md",
|
||||
"docs/cookbook/adding-a-tool.md",
|
||||
@@ -23,9 +30,6 @@
|
||||
"docs/user/guide/index.md",
|
||||
"docs/user/guide/quickstart.md",
|
||||
"docs/user/index.md",
|
||||
".agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md",
|
||||
".agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md",
|
||||
".agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
|
||||
"python/README.md",
|
||||
"python/sdk-runtime/README.md",
|
||||
"python/sdk/README.md"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
102
scripts/verify-client-domain-graph.ts
Normal file
102
scripts/verify-client-domain-graph.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Enforce intra-package domain layering inside `packages/client/*\/src/client/`.
|
||||
* verify-module-graph covers package-level edges; this gate covers the
|
||||
* directory level the future package split will land on: domain directories
|
||||
* may import `contract/` and never each other, and only the assembly point
|
||||
* (`apply.ts` / `index.ts`) may import across domains.
|
||||
*
|
||||
* Layer model (lower may not import higher):
|
||||
* 0 contract/ shared contract surface (types + slot declarations)
|
||||
* 1 <domain>/ + service domain implementations (skeleton/, chat/, ...)
|
||||
* 2 apply.ts, index.ts assembly point and re-export shell
|
||||
*
|
||||
* Not yet wired into the gate sequence (loose-gate window); run directly:
|
||||
* pnpm exec tsx scripts/verify-client-domain-graph.ts
|
||||
*/
|
||||
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
const CLIENT_DIR = join(root, 'packages/client')
|
||||
|
||||
/** Directory names treated as the shared contract layer (importable by all). */
|
||||
const CONTRACT_DIRS = new Set(['contract'])
|
||||
/** Top-level client files allowed to import across domains (assembly layer). */
|
||||
const ASSEMBLY_FILES = new Set(['apply.ts', 'index.ts', 'index.tsx'])
|
||||
|
||||
interface Violation { file: string; imported: string; reason: string }
|
||||
|
||||
/** Recursively list .ts/.tsx files under dir (relative paths). */
|
||||
function listSources(dir: string, prefix = ''): string[] {
|
||||
const out: string[] = []
|
||||
for (const name of readdirSync(dir)) {
|
||||
const full = join(dir, name)
|
||||
const rel = prefix ? `${prefix}/${name}` : name
|
||||
if (statSync(full).isDirectory()) out.push(...listSources(full, rel))
|
||||
else if (/\.tsx?$/.test(name) && !/\.legacy\./.test(name)) out.push(rel)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** First path segment of a client-relative file, or '' for top-level files. */
|
||||
function domainOf(rel: string): string {
|
||||
const ix = rel.indexOf('/')
|
||||
return ix === -1 ? '' : rel.slice(0, ix)
|
||||
}
|
||||
|
||||
function checkPackage(pkgName: string, clientDir: string): Violation[] {
|
||||
const violations: Violation[] = []
|
||||
const files = listSources(clientDir)
|
||||
for (const rel of files) {
|
||||
const fromDomain = domainOf(rel)
|
||||
const isAssembly = fromDomain === '' && ASSEMBLY_FILES.has(rel)
|
||||
if (isAssembly) continue
|
||||
const source = readFileSync(join(clientDir, rel), 'utf8')
|
||||
for (const match of source.matchAll(/from\s+['"](\.[^'"]+)['"]/g)) {
|
||||
const spec = match[1]
|
||||
if (spec === undefined) continue
|
||||
// Resolve the relative specifier against the importing file's directory
|
||||
// to a client-dir-relative path.
|
||||
const fromDir = rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : ''
|
||||
const parts = (fromDir ? fromDir.split('/') : [])
|
||||
for (const seg of spec.split('/')) {
|
||||
if (seg === '.') continue
|
||||
if (seg === '..') parts.pop()
|
||||
else parts.push(seg)
|
||||
}
|
||||
const target = parts.join('/')
|
||||
if (target.startsWith('..')) continue // out of client dir (package root) — package-level rules govern
|
||||
const toDomain = domainOf(target)
|
||||
if (toDomain === '' || CONTRACT_DIRS.has(toDomain)) continue // top-level shared file or contract layer
|
||||
if (fromDomain === toDomain) continue // inside one domain
|
||||
violations.push({
|
||||
file: `${pkgName}/src/client/${rel}`,
|
||||
imported: spec,
|
||||
reason: fromDomain === ''
|
||||
? `top-level non-assembly file imports domain "${toDomain}" (only apply/index may assemble)`
|
||||
: `domain "${fromDomain}" imports sibling domain "${toDomain}" (route shared surface through contract/)`,
|
||||
})
|
||||
}
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
const violations: Violation[] = []
|
||||
for (const pkg of readdirSync(CLIENT_DIR)) {
|
||||
const clientDir = join(CLIENT_DIR, pkg, 'src/client')
|
||||
try {
|
||||
if (!statSync(clientDir).isDirectory()) continue
|
||||
} catch {
|
||||
// No client half in this package — nothing to layer-check.
|
||||
continue
|
||||
}
|
||||
violations.push(...checkPackage(pkg, clientDir))
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error(`verify-client-domain-graph: ${violations.length} violation(s):`)
|
||||
for (const v of violations) console.error(` ${v.file} -> ${v.imported}\n ${v.reason}`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('verify-client-domain-graph: client domain layering clean.')
|
||||
@@ -152,15 +152,29 @@ function localPackageDirectories(): Map<string, string> {
|
||||
}
|
||||
|
||||
function rootProjectReferences(): Set<string> {
|
||||
const config = ts.readConfigFile(resolve(root, 'tsconfig.json'), path => ts.sys.readFile(path))
|
||||
if (config.error !== undefined) {
|
||||
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
|
||||
// Typecheck runs two sibling aggregates (root = host program,
|
||||
// tsconfig.client.json = client program; the two sides merge cordis Context
|
||||
// under the same keys, so one program cannot see both). Seed both and follow
|
||||
// any nested aggregate references to collect the covered leaf project set.
|
||||
const collected = new Set<string>()
|
||||
const queue = [resolve(root, 'tsconfig.json'), resolve(root, 'tsconfig.client.json')]
|
||||
const seen = new Set<string>()
|
||||
for (let file = queue.pop(); file !== undefined; file = queue.pop()) {
|
||||
if (seen.has(file)) continue
|
||||
seen.add(file)
|
||||
const config = ts.readConfigFile(file, path => ts.sys.readFile(path))
|
||||
if (config.error !== undefined) {
|
||||
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
|
||||
}
|
||||
const references = (config.config as { references?: Array<{ path?: unknown }> }).references ?? []
|
||||
for (const reference of references) {
|
||||
if (typeof reference.path !== 'string') continue
|
||||
const target = resolve(dirname(file), reference.path)
|
||||
if (target.endsWith('.json')) queue.push(target)
|
||||
else collected.add(target)
|
||||
}
|
||||
}
|
||||
const references = (config.config as { references?: Array<{ path?: unknown }> }).references ?? []
|
||||
return new Set(references.flatMap((reference) => {
|
||||
if (typeof reference.path !== 'string') return []
|
||||
return [resolve(root, reference.path)]
|
||||
}))
|
||||
return collected
|
||||
}
|
||||
|
||||
function packageNameFromSpecifier(specifier: string): string | undefined {
|
||||
|
||||
@@ -45,11 +45,26 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/bash/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' },
|
||||
'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' },
|
||||
'packages/code-runtime/code-runtime-worker': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
|
||||
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/web-react': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/connection': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/runtime': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/ui-layout': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/ui-theme': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/i18n': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
|
||||
'packages/examples/agent-spine-demo': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
|
||||
'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' },
|
||||
'packages/fs/fs-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
|
||||
'packages/fs/fs-sandbox': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
|
||||
'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' },
|
||||
'packages/host/apiproxy': { kind: 'none', reason: 'The wire contract and fetch carriers move already-composed messages and register no model surface.' },
|
||||
'packages/host/runtime': { kind: 'indirect', reason: 'The assembly mounts model-facing plugins and injects provider/model defaults into agents.' },
|
||||
'packages/host/webserver': { kind: 'none', reason: 'The HTTP carrier bridges browser and API handler and registers no model surface.' },
|
||||
'packages/llm/llm': { kind: 'none', reason: 'The adapter registry forwards already-assembled requests unchanged.' },
|
||||
'packages/llm/token-meter': { kind: 'indirect', reason: 'The measurement service leaves model-visible changes to its consumers.' },
|
||||
'packages/lsp/lsp': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-lsp.' },
|
||||
|
||||
Reference in New Issue
Block a user