The acp built-bin smoke killed its child and immediately rm'd the temp
consumer dir; POSIX tolerates unlinking a live process's cwd, Windows fails
EBUSY while the child still holds its cwd and session-log handles (the CI
windows job's only red step). Await the child's exit after SIGKILL and give
both smokes' rm a brief retry for the OS handle-release lag.
glob/globSync returns host-separator paths on Windows. Nine scripts that consume these paths for split('/'), manifest-key comparison, startsWith/includes exclusion checks, or committed-output rendering now normalize with .map(s => s.split(sep).join('/')) at ingestion. This replaces the previous replaceAll('\\\\', '/') with an explicit, self-documenting OS-separator-to-POSIX conversion.
The ::warning:: wrapper kept the lane job green on failure — honest
about not gating merges, but a Windows regression was visible only as
an annotation buried in the run summary. GitHub has no yellow job
state, so the choice is green+annotation (too hidden) or a red X on a
non-required job (visible, still non-blocking). Take the red X:
job-level continue-on-error, plain 'Run gates' step, one less wrapper.
The step stays on the runner's native pwsh — never shell: bash — per
the MSYS-parent leak recorded in the windows-support RFC.
Also remove the temporary Windows-only failing demo test that
exercised the wrapper's annotation path (REVERT ME commit a496b9ae).
The lane-split merge moved the Run gates step to `shell: bash`, which
broke it twice over. First, GHA's bash shell runs with -e, so a failing
gate aborted the step before the ::warning::/exit 0 lines — the lane
went red X instead of the intended yellow warning. Second, and worse,
Git Bash as the PARENT of the gate run leaks MSYS environment into the
suite itself, producing 8 real test failures the pwsh-launched runs
(and the DSec VM runs) never saw:
- bash exports PWD; the MSYS runtime rewrites it to Windows form for
native children, dsh-bash-local's adaptEnv passes it through, and the
executor's MSYS bash adopts it — `pwd` prints `D:/a/...` where the
tests (and the executor's MSYS dialect) expect `/d/a/...` (7 tests).
- cygwin enables SeBackupPrivilege on the runner's admin token; children
inherit the enabled state, and libuv's FILE_FLAG_BACKUP_SEMANTICS read
opens then pierce the dwShareMode=0 lock the jsonl EBUSY test holds —
loadLive resolves instead of rejecting (1 test).
Evidence: run 28918325498 (pwsh step, pre-merge) failed only the two
hooks dispose tests since fixed by f8fd8c00; run 28921741006 (bash
step) fixed those and failed exactly the 8 above, with zero relevant
source diff between them.
Fix: run the wrapper in pwsh — a native command's failure doesn't abort
pwsh, so $LASTEXITCODE capture + ::warning:: + exit 0 works without an
errexit dance, and the gates start from a native Windows shell as they
do everywhere else Windows CI has been validated.
Docs: the windows-support RFC drops the stale continue-on-error wording
(replaced by the warning wrapper) and records the launch-environment
limitation — native shell required today; making an MSYS parent a
supported launch environment (PWD scrub in adaptEnv, privilege-explicit
tests) is a future improvement direction.
The Windows CI was a single job running the full ci-windows inventory
(check:ci:windows), while Linux splits into 5 lanes (static/lint/coverage/
snapshot/artifacts) per the parallel-gates RFC. The single-job shape was a
transitional artifact from when Windows CI was added as a non-required
observation lane; its rationale ('keep gate parallelism modest so coverage
is not starved') conflated run-gates intra-job concurrency
(DSH_GATE_CONCURRENCY) with GitHub job fan-out — orthogonal concerns.
Split Windows into 4 lanes mirroring Linux (snapshot absent: its goldens
are Linux-recorded and self-skip on Windows). Each lane is a separate
GitHub job so a Windows regression is attributable to one lane, not
buried in one job's log. Concurrency is NOT throttled versus Linux: the
lane is non-blocking (continue-on-error), and the observational stance
is to actively expose Windows-arm issues rather than hide them behind
reduced parallelism.
- scripts/run-gates.ts: add ci-windows:static/lint/coverage/artifacts
modes; ci-windows (full inventory) is retained as the local one-process
entry, symmetric with Linux's ci-primary.
- .github/workflows/ci.yml: windows job becomes a matrix over the 4 lanes.
- package.json: check:ci:windows:{static,lint,coverage,artifacts} scripts.
- AGENTS.md + windows-support RFC: document the per-lane, non-blocking,
unthrottled stance.
Verified: scripts/caohuanqi-private/run-ci.py --windows (full
check:ci:windows) — all gates green except the known hooks-claude
bridge.spec waitFor timeout (pre-existing Windows subprocess-timing
flake, unrelated).
Add a Windows CI job alongside the existing Linux checks. Runs the full
test suite (without the Linux-only coverage gate) plus typecheck, lint,
doc-sync, build, hygiene, and demo smoke under PowerShell. Developer
Mode is enabled via registry for symlink support (fs-local tests,
verify-node-next-types).
Per the windows-support RFC transition plan: step (2) — non-required
Windows CI job to observe stability.
_node_launch_args builds the launcher path from segments, so the package
rename to dsh-jsonrpc-demo did not reach it via the path sweep. A freshly
deployed node closure ships @deepseek-ai/dsh-jsonrpc-demo, so node mode
raised FileNotFoundError. The docstring and exe ENTRY_BIN already use the
new name.
Repair cross-group README links whose targets changed groups
(ui/jsonrpc, ui/app-boot -> examples; examples/* -> ui seams),
re-record the bilingual-pair consistency hashes for the docs the
rename touched on both language sides, and condense the AGENTS.md and
packages/README.md group entries. The packages/README ceiling rises
710 -> 760 for the mandated new examples/ hierarchy row (5% headroom).
The @deepseek-ai/dsh-* wildcard maps each package name to its group's
src; the new packages/examples/ group needs its own glob, or the four
moved packages fall back to unbuilt lib/ and vitest cannot resolve them.
Also update the moved specs' plugin export-name assertions (acp-agent ->
acp-demo, stdio-agent -> stdio-demo, agent-core -> agent-spine-demo).
Add the packages/examples/ group README, drop the moved packages from the
core/ and ui/ group READMEs, and add the examples/ row to the packages
hierarchy table and the repo-layout in AGENTS.md.
Move the agent-spine bundle and the stdio/ACP/JSON-RPC app packages out of
core/ and ui/ into a new packages/examples/ group, renamed with a -demo
suffix so the npm name marks them as non-product surface:
core/agent-core -> examples/agent-spine-demo (dsh-agent-spine-demo)
ui/stdio-agent -> examples/stdio-demo (dsh-stdio-demo)
ui/acp-agent -> examples/acp-demo (dsh-acp-demo)
ui/jsonrpc-agent -> examples/jsonrpc-demo (dsh-jsonrpc-demo)
Update every code/config/test reference and reference-only doc mentions, and
regenerate module-graph, config-catalog, and doc-graphs. The jsonrpc bin
(dsh-jsonrpc-agent) and single-file exe (dsh-jsonrpc-agent-pkg) keep their
names; the SDK runtime-startup surface is reconciled separately.
Runs `pnpm install` + `pnpm run build` (tsc -b + tsdown) on windows-2025,
and is listed in all-checks-passed `needs` so a Windows build regression
cannot land silently. Windows path/shell support is still partial, so this
lane covers the build surface only; tests and gates are not run here yet.
shell:true space-joins the executable and args UNESCAPED (Node DEP0190), so
an absolute .cmd path breaks whenever the repo path contains spaces and
`pnpm run hygiene` fails. Invoke publint's (`node_modules/publint/src/cli.js`)
and tsc's (`node_modules/typescript/bin/tsc`) JS entry through process.execPath
instead — no shell, extension-agnostic, identical on every platform, matching
the pattern already used by doc-typecheck.ts.
Addresses ds-review-bot on #324.
execFileSync on a .cmd shim returns EINVAL on recent Node without shell:true. Same bug class as install-lefthook.mjs. Affected publint-all.ts and verify-node-next-types.ts.
(cherry picked from commit 5ae40bee1c840fbbdd197ee15907aa660a343c52)
Pairs with .gitattributes: eol=lf pins what git produces at checkout;
.editorconfig pins what editors write to disk - the one CRLF vector
git's filters cannot reach (git never rewrites the working tree, so an
editor-written CRLF file would persist with a clean status while the
doc gates misbehave on it). insert_final_newline declares the existing
one-trailing-newline policy (AGENTS.md, gated by git diff --check) at
the editor layer too.
(cherry picked from commit 7a09602fd76efff81a0875fb177289004c3dfc9b)
execFileSync('node_modules/.bin/tsc') spawns an extensionless shim
that is not executable on Windows (the CVE-2024-27980 class the
sibling scripts hit); the catch treated the spawn failure as a compile
failure with empty diagnostics. The .cmd shim would need shell:true,
which concatenates args unescaped - a hazard for the temp project
path - so invoke typescript/bin/tsc through the current node instead;
identical behavior on every platform.
Note this gate had never actually run on this Windows checkout: with
the pre-eol=lf CRLF working copy the fence regex matched no ts blocks
('.' does not match \\r), so it reported 'no ts code blocks to check'
and exited green. The LF working tree surfaced the spawn bug; with
this fix the gate compiles all 21 blocks on Windows.
(cherry picked from commit b49993c28e068c2beb411eae1f0c8ac4985aa3ae)
The repo's committed content is already 100% LF (verified: 802/802
text files); until now the working-tree form depended on each
contributor's core.autocrlf, and autocrlf=true checkouts produced CRLF
working copies that byte-level gates had to tolerate (fence parsing,
consistency-record parsing, blob hashing, README splice comparison).
eol=lf removes the smudge boundary entirely: attributes override any
local autocrlf, so every checkout on every host sees the repo's
canonical form. git add --renormalize confirmed a zero-change no-op -
no committed blob (including vendor/) is rewritten. The script-side
CRLF tolerances remain as defense in depth for editor-introduced CRLF
in not-yet-committed files. If a file class ever needs CRLF in the
working tree (.bat/.cmd), a per-pattern eol=crlf override keeps the
in-repo form LF while smudging those checkouts only.
(cherry picked from commit 5d21ebee20391c3d5c1d3812bd3aaa92bc652973)
spawnSync on a .cmd shim returns EINVAL/null status on recent Node (CVE-2024-27980) unless shell:true, which made postinstall fail and blocked every 'pnpm run' on Windows.
(cherry picked from commit 65a08f889ff9738ddaceeeb724e6e24f3be4b2ea)
The web-editor conflict resolution renamed the cordis snapshot keys to
rc.7 but left the mcp-client importer pointing at the deleted
cordis@4.0.0-rc.6(...rc.4) key, so every CI lane failed at
'pnpm install --frozen-lockfile' with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY.
Align the mcp-client cordis range with the repo-wide ^4.0.0-rc.7 sweep
from master and re-resolve the lockfile with pnpm.