Merge branch 'feat/windows-pwsh-default' into feat/windows-acl-sandbox

# Conflicts:
#	.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.i18n.yaml
#	.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.md
#	.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/bash/tool-pwsh/README.i18n.yaml
#	packages/bash/tool-pwsh/README.md
#	packages/bash/tool-pwsh/README.zh.md
This commit is contained in:
Huanqi Cao
2026-08-09 23:19:14 +08:00
1035 changed files with 14752 additions and 6866 deletions

View File

@@ -10,7 +10,6 @@
* @module @deepseek-ai/dsh/windows-shell
*/
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import type { PatchOptions } from '@cordisjs/plugin-include'
import { loadOverlayPatches, type ProfileLayer } from '@deepseek-ai/dsh-app-boot'
@@ -36,8 +35,9 @@ export interface WindowsShellLayer {
* @param binName - the diagnostic prefix on thrown errors (`dsh`).
* @returns the pwsh layer on win32, else `undefined`. A custom profile that
* mounts no base bundle is skipped (it owns its shell stack); a base
* bundle that ships no Windows shell patch fails loud — the shipped
* package always carries it, so a miss is a broken installation.
* bundle whose Windows shell patch is missing fails loud in
* {@link loadOverlayPatches} — the shipped package always carries it, so
* a miss is a broken installation.
*/
export function resolveWindowsShellLayer(
platform: NodeJS.Platform,
@@ -48,8 +48,5 @@ export function resolveWindowsShellLayer(
const base = layers.find(layer => layer.packageName === BASE_BUNDLE)
if (base === undefined) return undefined
const label = join(base.packageDir, WINDOWS_SHELL_PATCH_FILENAME)
if (!existsSync(label)) {
throw new Error(`${binName}: ${BASE_BUNDLE} ships no ${WINDOWS_SHELL_PATCH_FILENAME}`)
}
return { label, patches: loadOverlayPatches(binName, label) }
}

View File

@@ -64,11 +64,11 @@ describe('parseDshArgs', () => {
.toEqual({ mode: 'dump-config', profile: 'web', defaultOnly: true, patches: [] })
})
it('rejects missing profile, removed flags, and contradictory inputs', () => {
it('rejects missing profile, flags outside the current grammar, and contradictory inputs', () => {
expect(exitCode([])).toBe(1)
expect(exitCode(['tui'])).toBe(1) // a bare word is a task without --profile
expect(exitCode(['--config', 'c.yml'])).toBe(1) // removed
expect(exitCode(['-p', 'task'])).toBe(1) // removed
expect(exitCode(['--config', 'c.yml'])).toBe(1) // outside the current grammar
expect(exitCode(['-p', 'task'])).toBe(1) // outside the current grammar
expect(exitCode(['--profile', 'headless', 'task'])).toBe(1) // tasks belong to `run`
expect(exitCode(['run'])).toBe(1)
expect(exitCode(['run', ''])).toBe(1)

View File

@@ -174,7 +174,7 @@ function createEnvironmentProbeProfile(home: string, project: string): void {
}
describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', () => {
it('requires --profile and rejects removed commands', async () => {
it('requires --profile and rejects inputs outside the current grammar', async () => {
const bare = await runBuiltBin()
expect(bare.code).toBe(1)
expect(bare.stdout).toBe('')
@@ -185,8 +185,8 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
expect(help.stdout).toContain('dsh run "run the tests"')
expect(help.stdout).toContain('dsh plugin --profile')
expect(help.stdout).not.toMatch(/^\s+(?:tui|meta|upgrade)\b/mu)
for (const removed of [['tui'], ['--config', 'x.yml'], ['-p', 'task'], ['--profile', 'headless', 'task']]) {
const result = await runBuiltBin(removed)
for (const outsideGrammar of [['tui'], ['--config', 'x.yml'], ['-p', 'task'], ['--profile', 'headless', 'task']]) {
const result = await runBuiltBin(outsideGrammar)
expect(result.code).toBe(1)
}
}, 30_000)
@@ -297,9 +297,9 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}, 30_000)
it('reports a patch-overlay boot failure without hanging', async () => {
// The HMR main watcher's initial scan once refreshed the include
// mid-initial-apply, deadlocking the failing apply's rollback against the
// refresh drain: dsh exited 13 with no diagnostic instead of settling
// An HMR main-watcher initial scan that refreshes the include
// mid-initial-apply deadlocks the failing apply's rollback against the
// refresh drain: dsh exits 13 with no diagnostic instead of settling
// ([Agent Note](../../../.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md)).
const home = mkdtempSync(join(tmpdir(), 'dsh-invalid-patch-'))
try {

View File

@@ -60,8 +60,10 @@ describe('resolveWindowsShellLayer', () => {
it('fails loud when the base bundle ships no Windows shell patch', () => {
const base = tempBase()
mkdirSync(base, { recursive: true })
// The overlay loader owns the fail-loud contract: the caller named this
// file, so its absence is a misconfiguration, not "no overlay".
expect(() => resolveWindowsShellLayer('win32', [fakeLayer(BASE_BUNDLE, base)], 'dsh'))
.toThrow(/@deepseek-ai\/dsh-base ships no windows\.cordis\.patch\.yml/)
.toThrow(/dsh: failed to read overlay .*windows\.cordis\.patch\.yml/)
})
})