Commit Graph

34 Commits

Author SHA1 Message Date
Tianyi Cui
e2bde2902c refactor(examples): extract the app spine into dsh-agent-core + app packages
Implements docs/rfc/.../2026-06-20-extract-example-app-packages.md. Each
example was thick — a hand-rolled start.ts, an infra preamble, nested
base.yml/base-core.yml/acp-tail.yml includes, and a coupled front-door
cluster enforced only by prose. This moves the composition into packages so
each example is a thin leaf cordis.yml: pick the swappable backends, load one
app package.

New packages:
  - @deepseek-ai/dsh-agent-core (packages/core/agent-core): one bundle plugin
    that loads the providerless/executor-less/UI-less spine (timer + llm +
    sessions + system-prompt + tools + agents + invariants + tool-bash +
    agent-loop) via ctx.plugin(...) inside apply(), and forwards agent-loop's
    `agents` list as its own Config (export const Config = AgentLoop.Config,
    default []).
  - @deepseek-ai/dsh-stdio-agent (packages/ui/stdio-agent): terminal chat APP —
    agent-core + console logger + readline UI + a pre-created `main` agent, with
    a bin. The demo:echo/coding front door.
  - @deepseek-ai/dsh-acp-agent (packages/ui/acp-agent): ACP server APP —
    agent-core + JSONL persistence + the acp bridge, NO stdout logger, with a
    bin. The stdout-purity footgun is structurally unreachable from the leaf.

Amendment to the RFC: hmr stays a LEAF cordis.yml entry, not baked into
dsh-stdio-agent. hmr is a Loader-only dev plugin (throws without
--expose-internals; the in-process test tier can't even import its decorator
form), so a package statically importing it could never carry the per-file
coverage gate. Unlike the console logger, a stray hmr is not a stdout-purity
footgun, so leaving it at the leaf costs no safety. With hmr out, all three new
packages carry in-process unit specs at 100%.

Boot glue (Loader tail, .env load, snapshot-mode selection, stdin-dispose
lifecycle) moves into each app's bin; start.ts and base.yml/base-core.yml/
acp-tail.yml are deleted. Each app package gets a keyless real-load-path test
that boots through its bin + the cordis Loader (guarding the unwrapExports
export-shape bug class, postmortem 0001). ACP snapshot replay stays green
against the existing committed goldens (pure boot restructuring). RFC moved
proposed->implemented with the amendment recorded; package/example/architecture
docs and the module graph updated.
2026-06-21 12:06:38 +08:00
Tianyi Cui
ca2207e26c Fix doc cross-links for the hierarchy; add package-path + shape gates
Merge brought in the RFC-classification reorg and two new doc gates;
rewrite every drifted packages/<name> cross-link (Markdown link targets,
moved-README relative depths, and .ts comment paths) to the grouped paths.

Add two doc-sync/hygiene gates so the manual checks this restructure
needed become automated:
- verify-package-paths.ts: flags a packages/<path> reference (in Markdown
  or a .ts comment/string) that does not resolve AND names a real package
  in a segment — i.e. a stale path to a MOVED package. A path naming a
  non-existent package (a forward-looking proposal) is left alone, so it
  applies uniformly across proposed/implemented/rejected.
- check-workspace-constraints: assert the packages/<group>/<pkg> depth-2
  shape (group dirs carry no package.json; no flat or over-nested
  packages). Group names stay open; only the shape is fixed.
2026-06-20 23:12:14 +08:00
Tianyi Cui
08f6f17cc1 Merge remote-tracking branch 'origin/master' into worktree-package-hierarchy 2026-06-20 22:55:38 +08:00
Tianyi Cui
d02e9f1bd6 Reorganize packages into a modular hierarchy
Move the 18 flat packages/<name> packages into role-grouped dirs:
core/, llm/, bash/, session-persistence/, ui/, support/. Group dirs are
pure containers; each package keeps its @deepseek-ai/dsh-* name.

Collapse the per-package tsconfig paths maps (base + typecheck) into one
@deepseek-ai/dsh-* wildcard with a candidate per group, and derive the
publint list from the hierarchy. Update all depth-coupled globs/configs
(workspace, tsdown, vitest, eslint, knip, tsconfig includes/refs,
per-package tsconfigs, generators, doc-script scopes, type-equiv manifest)
and the cross-package/script relative imports in tests.

Fix doc-typecheck's workspacePaths() to parse tsconfig JSONC via the
TypeScript API instead of a regex comment-strip, which corrupted the
new wildcard `/*/` path candidates.

WIP: doc cross-links and package/RFC docs still to update.
2026-06-20 22:55:20 +08:00
Tianyi Cui
8c16524b90 chore(knip): drop dead entry pattern and fail on config hints
examples/acp-agent has no src/ directory — its plugins come from real
packages wired via cordis.yml, and its only local entry (start.ts) is
already auto-discovered. The examples/acp-agent/src/*.ts entry pattern
matched nothing, which knip surfaced as a config hint that exited 0 and
so went unnoticed.

Remove the dead pattern, and pass --treat-config-hints-as-errors so a
future stale entry (a renamed/removed path) fails the hygiene gate
instead of degrading to a silent hint.
2026-06-20 22:33:27 +08:00
Tianyi Cui
605587e79c docs(rfc): classify RFCs by kind via path-encoded subdirectories
Add a second axis to every RFC — its class (feature, bug-fix,
simplification, architecture, process, testing) — encoded in the path
as docs/rfc/{lifecycle}/{class}/file.md. The folder is the label, so
the closed set is enforced by structure rather than a parsed field.

Two new doc-sync gates back it:
- verify-rfc-classification: every RFC sits in a valid class folder and
  the README index lists it under the matching lifecycle→class heading.
- verify-doc-refs: every docs/*.md path cited in a packages|examples TS
  comment resolves — closes a drift class verify-md-links can't see, and
  catches the four comment refs this reorg moved.

The README gains a Classification section explaining the taxonomy and
per-class index sub-sections. A self-referential process RFC records why
the scheme is path-encoded and gated.
2026-06-20 22:29:45 +08:00
Tianyi Cui
4e5c08ef82 docs: generated cordis events + services catalog
Add scripts/gen-cordis-catalog.ts: a fully-generated docs/cordis-catalog/
events-and-services.md cataloging every cordis event (exact signature + @mode)
and ctx.<key> service (exact interface), modeled on gen-module-graph's
--write/--check freshness gate. The harness tier renders in full from the
interface Events / interface Context declarations and their JSDoc; the inherited
cordis-core/loader/hmr/timer surface renders tersely from a curated table.

The generator hard-errors on a missing @mode tag and on a tag that contradicts
a conclusive signature shape (a trailing next param is structurally a
waterfall). Signature blocks use a ts cordis-catalog fence that doc-typecheck
skips. Type tokens cross-link to the core-data-structures catalog.

This supersedes the hand-maintained event-taxonomy table: verify-event-taxonomy
is deleted and verify-cordis-catalog joins doc-sync. architecture.md keeps the
Event taxonomy heading (TOC anchor) but points at the catalog; the Service-map
role table stays. RFC, AGENTS.md @mode authoring rule, and dependent doc/skill
references updated. Negative gate tests cover the missing-tag and
tag/shape-contradiction paths.
2026-06-20 19:47:09 +08:00
Tianyi Cui
07048983e0 build(doc-sync): add verify-type-equiv gate for verbatim type pastes
Introduce a `ts type-equiv` Markdown fence: a verbatim paste of a source type
definition that `scripts/verify-type-equiv.ts` drift-checks against the source
symbol via the TypeScript parser, with provenance in a central
`scripts/type-equiv.manifest.json` kept 1:1 with the blocks. doc-typecheck
recognizes the same fence, skips compiling it (not standalone-compilable), and
excludes it from the opt-out ratio. Wired into the `doc-sync` chain.
2026-06-20 16:24:37 +08:00
Tianyi Cui
7731bc80c3 Merge remote-tracking branch 'origin/master' into feat/acp-snapshot-tests 2026-06-19 10:03:04 +08:00
Tianyi Cui
81d434896d feat(acp-example): snapshot harness, normalizers, wiring, and handshake scenario
Adds the snapshot-test harness and the keyless replay pipeline end-to-end.

- snapshot-harness.ts: boots the real acp-agent subprocess via the cordis
  Loader (preserving TSX_TSCONFIG_PATH so unbuilt dsh-* imports resolve from a
  temp cwd), tees raw stdout into an SDK ClientSideConnection, interprets a
  per-scenario input.json DSL (initialize / newSession capturing the random
  sessionId / prompt / cancel), closes stdin to trigger graceful shutdown, and
  harvests the persisted session.jsonl. Failure-safe: a finally block SIGKILLs
  a live child, awaits its exit, and removes both temp dirs even on a thrown
  step or harvest. Raw bytes are buffered and decoded once (no multibyte split).
- snapshot-normalize.ts (+ spec): two pure normalizers (stdout frames + session
  JSONL) scrub cwd, session ids / UUIDs, and JSON-RPC ids, and zero time /
  createdAt — but keep `seq` (deterministic by contract). normalizeStdout throws
  on a non-JSON line (the stdout-purity check).
- start.ts: selects cordis.snapshot.yml (replay, providerless) or
  cordis.snapshot-record.yml (record, real adapter) from DSH_SNAPSHOT, skips
  .env in replay, and disposes the ctx on stdin end so persistence flushes
  before exit (harvest-after-flush, not on the prompt response).
- acp.snapshot.ts: asserts the normalized stdout golden (and, for model
  scenarios, the re-persisted JSONL golden) via toMatchFileSnapshot; record mode
  writes the harvested log back to the scenario fixture; an orphan-fixture guard
  fails on an unregistered scenario dir.
- handshake scenario: initialize + session/new (no model call; a header-only
  session.jsonl, since session/new persists no events).
- vitest.snapshot.config.ts, test:snapshot / test:snapshot:record scripts, a
  pre-push snapshot job, and the knip entry.

Incorporates Codex review: record-fixture writeback, failure-safe teardown,
seq-not-scrubbed, harvest-after-flush. Per docs/rfc/implemented/2026-06-19.
2026-06-19 03:36:12 +08:00
Tianyi Cui
7fa113be0e Merge remote-tracking branch 'origin/master' into codex/pr48-repo-hardening-rfcs
# Conflicts:
#	docs/adr/README.md
#	docs/rfc/009-session-persistence-and-resumability.md
#	docs/rfc/README.md
#	docs/rfc/implemented/2026-06-11-doc-sync-enforcement.md
#	docs/rfc/proposed/2026-06-14-acp-agent-client-protocol.md
#	examples/acp-agent/tests/acp.e2e.ts
#	packages/acp/README.md
#	packages/acp/src/index.ts
#	packages/acp/tests/stream-update.spec.ts
#	packages/agent-loop/src/loop.ts
#	packages/tools/src/index.ts
2026-06-18 23:41:14 +08:00
Tianyi Cui
86ec067bff Merge remote-tracking branch 'origin/master' into feat/acp-2-bridge
# Conflicts:
#	.agents/skills/dsh-code-review/SKILL.md
#	AGENTS.md
#	docs/rfc/proposed/2026-06-14-acp-agent-client-protocol.md
2026-06-18 03:31:04 +08:00
Tianyi Cui
7c400e9c02 docs: unify ADR/RFC trees into one lifecycle-organized RFC tree
Collapse docs/adr/ and docs/rfc/ into a single docs/rfc/ with proposed/,
implemented/, and rejected/ subfolders. Every file is renamed to
yyyy-mm-dd-topic-title.md, where the date is when the topic was first
proposed (from git history). ADRs and RFCs that covered exactly the same
topic are merged (property-based testing, session persistence); the
umbrella RFC 005 stays split across its three implemented decisions, and
RFC 006's deferred part-3 (API extractor reports) splits into its own
proposed RFC. All cross-references become machine-checkable relative
links instead of bare "ADR NNNN" / "RFC NNN" prose.

Add a verify-md-links doc-sync gate (scripts/verify-md-links.ts) that
checks every relative Markdown cross-link resolves, wired into doc-sync
alongside verify-md-wrap. This makes the reorganization self-verifying:
the same change that rewrote ~forty inter-doc links adds the check that
proves none dangle. Document the cross-link convention in a new
docs/AGENTS.md and record the gate as an implemented RFC.

doc-sync, typecheck, lint, and the full test suite (667) all pass.
2026-06-18 02:18:24 +08:00
Tianyi Cui
513e16f9dc fix(dev): make hooks and bash seams safer 2026-06-17 21:26:44 +08:00
Tianyi Cui
a9d5a5ba68 Merge branch 'feat/acp-1-max-tokens-turn-end' into feat/acp-2-bridge 2026-06-17 15:20:36 +08:00
Tianyi Cui
c2f8af30da Merge branch 'feat/acp-1-max-tokens-turn-end' into feat/acp-2-bridge
# Conflicts:
#	AGENTS.md
#	docs/cookbook/extension-cookbook.md
#	yarn.lock
2026-06-16 23:40:23 +08:00
Tianyi Cui
63425a2b87 refactor: detect md hard-wraps via mdast AST, not regex
Per review feedback (use a real markdown parser with an AST linked to
source positions), rewrite verify-md-wrap to parse each file with
mdast-util-from-markdown (the CommonMark parser behind remark) + the GFM
extension, then flag any `paragraph` node whose source span covers more
than one line.

Why a parser over the hand-rolled line scanner:
- It is a checker, not a formatter — it reports and never rewrites, so
  zero cosmetic churn (no emphasis-marker or table-delimiter
  normalization, which is why Prettier was rejected for this).
- The AST owns every structural exemption (fenced code of any fence
  length, tables, lists, blockquotes, HTML, headings, reference defs),
  fixing both bugs the regex version had: it now catches wrapped
  list-item / blockquote prose (a `paragraph` inside those nodes) and no
  longer false-positives on a longer ```` fence wrapping an inner ```.

Also unwrap two pre-existing hard-wrapped blockquotes (architecture.md,
adding-a-tool.md) that the stricter AST check correctly surfaced.
2026-06-16 23:35:33 +08:00
Tianyi Cui
67447fcdc3 feat: enforce merge-commit policy and markdown wrap convention
- AGENTS.md: require merging PRs with a merge commit (gh pr merge
  --merge), never squash/rebase — the per-PR commit history (review-fix
  and regression-test commits) is intentional record.
- Add scripts/verify-md-wrap.ts: a doc-sync gate that fails on
  hard-wrapped prose paragraphs (one physical line per paragraph), with
  smart exemptions for fenced code, tables, lists, blockquotes, headings,
  HTML comments, hrs, and reference defs. Scope covers README.md,
  docs/**/*.md, packages/*/README.md, plus AGENTS.md / packages/AGENTS.md
  (the files doc-sync did not previously cover). Folded into doc-sync so
  it rides the existing pre-push and CI gates.
- Sync AGENTS.md and docs/development.md doc-sync descriptions and
  command lists to include verify-md-wrap.
2026-06-16 22:14:25 +08:00
Tianyi Cui
4c8c1da8b3 feat: generate module dependency graph with freshness gate
Add scripts/gen-module-graph.ts, which derives the inter-package
dependency graph from each package's @deepseek-ai/dsh-* peerDependencies
and renders docs/module-graph.md (a GitHub-native Mermaid graph plus a
dependency table). Output is deterministic so a regenerate-and-diff
check is stable.

Wire a freshness gate the same way doc-sync is wired (ADR 0007: hooks
and CI run the same package.json scripts): verify-module-graph runs in
pre-push (lefthook) and as a CI step. It fails if the committed file
drifts from what the generator would produce.
2026-06-16 21:00:24 +08:00
Tianyi Cui
fb9636db44 feat(acp): ACP bridge — drive the coding agent from an editor over JSON-RPC stdio
Implements the RFC 010 MVP: a new `@deepseek-ai/dsh-acp` package bridges the
harness agent to the Agent Client Protocol (JSON-RPC 2.0 over newline-delimited
stdio), so Zed and other ACP editors can drive the coding agent — streaming
render, tool-call display, and resumable sessions via `session/load`.

- packages/acp: AgentSideConnection wiring; initialize/newSession/loadSession/
  prompt/cancel; a total TurnEndReason→StopReason codec; settle-once with a
  fallback chain (agent/turn-end → logged turn/end → idle); single-session
  guard; cwd-must-equal-launch-dir validation; load replays from the persisted
  event log (assistant/chunk→agent_message_chunk, tool/call/result→tool_call*).
- agent: add Agent.whenIdle() quiescence signal to the interface; LoopAgent
  implements it (resolves on the first running→idle/disposed transition). The
  bridge awaits it on disposal so teardown reaches quiescence, not just abort.
- examples: extract the shared provider/tool core into examples/base.yml;
  coding-agent nest-includes it; new examples/acp-agent serves the agent over
  ACP with JSONL persistence and no stdout logger (stdout is the protocol).
- Permission gate deferred (TODO(rfc010-permission-gate)): tools run with the
  executor's full authority; only the Agent→sessionId ownership seam is laid
  down. Cancel is best-effort for a not-yet-started queued turn
  (TODO(rfc010-cancel-prestep)). RFC 010 stays `proposed`.
- Docs: package README + Zed snippet; client-driver cookbook section; root and
  packages layout/commands; RFC 010 implementation-status note.

48 bridge tests + whenIdle coverage; 100% per-file coverage; e2e boots the
example as a subprocess and verifies a written file on disk (key-gated, with a
no-key stdout-purity check).
2026-06-16 18:44:31 +08:00
07akioni
dabc2ff411 feat: migrate to pnpm 2026-06-16 14:55:37 +08:00
Tianyi Cui
fa7d1df6f2 build: run doc-sync gates in the local pre-push hook too
The doc-sync gates were CI-only, so the AGENTS.md doc-sync promise could be
missed locally until after push. Add a shared `doc-sync` package.json script
(doc-typecheck + verify-event-taxonomy) wired into the lefthook pre-push job,
and point the CI step at the same script — one source of truth per ADR 0007.
Addresses PR review finding.
2026-06-14 10:40:20 +08:00
Tianyi Cui
6a528be569 build: doc-sync gates — typecheck doc code blocks + verify event taxonomy (RFC 006 pts 1-2)
Two tsx CI gates make doc/code drift fail fast:
- doc-typecheck extracts every fenced ts block from README/docs/package READMEs,
  compiles them with tsc --noEmit against a temp project (vendor->lib, harness->src
  paths from tsconfig.typecheck.json), and fails on errors. Deliberate sketches opt
  out with ```ts ignore-check; the opt-out ratio is reported and capped.
- verify-event-taxonomy asserts the docs/architecture.md taxonomy table names
  exactly the events declared in the interface Events blocks. This surfaced three
  events the table had been missing (tools/change, llm/adapter-change,
  system-prompt/change), now added.

Doc snippets made compilable with stub imports/declares (1 genuine sketch ignored).
Wired into CI after typecheck. API reports (RFC 006 pt 3) deferred. Graduates RFC
006 pts 1-2 -> ADR 0014.
2026-06-14 00:47:38 +08:00
Tianyi Cui
2f6d3b8539 test: property-based tests for protocol-shaped code (RFC 001)
Adds fast-check + one tests/properties.spec.ts per protocol-shaped package
(llm/BlockAssembler, session, tools/schema DSL, agent-loop scheduling). The
tools suite includes the RFC 001<->005 composition property (generated args
satisfying a spec pass validateArgs), closing the validator/InferArgs drift
risk from ADR 0011. Loop properties are deterministic (settle on agent/status,
no sleeps).

The BlockAssembler suite found a real bug on first run: a duplicate block-end
at the same index overwrote an already-flushed block, so the streamed prefix
disagreed with final blocks(). Fixed (first close wins, matching the existing
straggler rule) + regression test. Graduates RFC 001 -> ADR 0013.
2026-06-14 00:06:25 +08:00
Tianyi Cui
39b3db4b9c docs: accuracy sweep, architecture restructure, two ADRs, review skill
- AGENTS.md Commands: fix typecheck/build descriptions; add lint, lint:fix,
  test:coverage, knip, publint, hygiene (were undocumented).
- Drop the bare `yarn demo` for explicit `demo:echo` + `demo:coding`; update
  README, examples READMEs (and document coding-agent in examples/README).
- New cookbook guide: adding-a-vendored-package.md (the missing "add" half of
  vendor/README's update-only procedure).
- architecture.md: add a table-of-contents and extract the Extension cookbook
  to docs/cookbook/extension-cookbook.md (link-preserving); drop the completed
  "restructure this document" TODO.
- ADR 0009 (capability seams) + 0010 (twin LLM adapters), and a "when to write
  an ADR" standard in adr/README.
- Add a committed dsh-code-review skill under .agents/skills, exposed to Claude
  Code via a tracked .claude/skills symlink (gitignore carve-out).
2026-06-13 22:05:34 +08:00
Tianyi Cui
e98c1c5d42 Add examples/coding-agent and the docs cookbook
The first real agent wiring: DeepSeek V4 + the bash tool suite + stdio
chat + JSONL persistence, runnable via yarn demo:coding (reads the
gitignored repo-root .env through process.loadEnvFile).

- examples/coding-agent: cordis.yml wiring both real plugin families
  (llm-deepseek with !!js env secrets; bash-local + tool-bash), a
  bash-only coding system prompt, a max-steps-guard plugin (bounds
  runaway turns via the agent/turn-continuation waterfall — abort()
  from step-end is a no-op by then), and a stdio UI with dimmed
  reasoning and exit-on-idle for piped stdin.
- e2e (yarn test:e2e, key-gated): full-loop.e2e.ts runs a real model
  against the real bash tool; coding-task.e2e.ts is the swebench-style
  smoke — the model fixes a buggy add.js in a temp dir and the test
  re-runs node add.test.js itself rather than trusting the agent.
- docs/cookbook: adding-a-package (the verified checklist),
  adding-a-tool (execute() contract, background pattern, seams),
  adding-an-llm-adapter (protocol obligations, mock-server testing,
  e2e policy). AGENTS.md layout/commands/secrets sections updated;
  architecture.md points at both examples and the cookbook.
- vitest.e2e.config.ts: serialize test files + retry twice — parallel
  e2e files trip the shared internal key's concurrency quota.
- fix: the !js YAML tag spelling in docs/JSDoc is actually !!js
  (js-yaml resolves custom tags under tag:yaml.org,2002:js).
2026-06-13 18:30:50 +08:00
Tianyi Cui
ab19fed77c Add two DeepSeek LLM adapters: dsh-llm-deepseek and dsh-llm-pi-ai
The first real LlmAdapter implementations, shipped as a deliberate pair:
same models and wire protocol, completely different internals, so the
StreamChunk protocol is verified across independent implementations.

- dsh-llm-deepseek: hand-rolled fetch + SSE parser + chunk-translation
  state machine against the official chat-completions format (thinking
  mode via top-level thinking/reasoning_effort; the empty-string
  reasoning_content first chunk; usage attached to the finish chunk or
  trailing; reasoning_content passback on tool-call turns; disjoint
  cache-token accounting).
- dsh-llm-pi-ai: the same endpoint through @earendil-works/pi-ai,
  mapping its event vocabulary (parsed tool arguments, in-stream error
  events, folded reasoning tokens) onto the same chunks.

The agent loop now honors the in-band error path: an adapter that ends
its stream with finish {kind:error|aborted} (the only option for
adapters that can't throw mid-stream, like pi-ai) is translated into a
step error, so the turn ends error/aborted with a logged error event
instead of a normal completed assistant message. This makes the
StreamChunk error contract real for both adapters; docs/architecture.md
and the StreamChunk doc are updated accordingly.

New yarn test:e2e (vitest.e2e.config.ts, *.e2e.ts) runs key-gated
real-API matrices for both adapters across V4 Flash/Pro and all
thinking/effort levels; it self-skips without DEEPSEEK_API_KEY. Unit
suites run against local node:http mock SSE servers at 100% per-file
coverage.
2026-06-13 18:30:03 +08:00
Tianyi Cui
630bbddf9a Replace dumble with tsdown for JS bundling
dumble (0.2.x, ~530 dl/wk, single-maintainer) was a bus-factor risk as
the load-bearing bundler. tsdown (rolldown-based, ~2.5M dl/wk, actively
maintained) replaces it while output stays list-identical, verified by
snapshot diff: 17 JS bundles, externals preserved, schemastery dual
.mjs/.cjs and logger-console node+browser entries intact.

Root tsdown.config.ts uses workspace globs ['vendor/*', 'packages/*']
(explicit, so examples/* stays excluded); two per-package overrides in
vendor/ cover the special shapes and are logged in vendor/README.md as
ours (not upstream sync surface). scripts/build.ts (dumble
orchestration) is deleted; yarn build = tsc -b && tsdown. tsc -b keeps
owning declarations (dts: false, clean: false).

Rationale recorded in ADR 0008 (also covers the direct-esbuild and
pkgroll alternatives).

Gates: lint, typecheck, 134 tests, hygiene (knip/publint/constraints),
demo smoke all green.
2026-06-11 22:34:43 +08:00
Tianyi Cui
9d20a36cc4 Add lefthook git hooks with a vendor-manifest guard
pre-commit: ESLint --fix on staged files (vendored source excluded),
incremental typecheck, and the vendor-manifest guard — any staged
change under vendor/*/src must be accompanied by a vendor/README.md
update in the same commit, mechanizing the local-modification log
discipline. pre-push: tests + hygiene (knip/publint/constraints).
Hooks call the same package.json scripts CI runs (single source of
truth); installed automatically via postinstall.
2026-06-11 15:07:55 +08:00
Tianyi Cui
6796a3922d Add repo hygiene gates: knip, publint, yarn constraints
knip (workspace-aware config) fails on unused files, exports, and
dependencies in our code — vendor/ excluded, duplicate default+named
exports allowed (intentional API shape). publint checks every
packages/* package.json for publishing correctness (scripts/
publint-all.ts). Yarn constraints (yarn.config.cjs) mechanize the
AGENTS.md package rules: everything private, dsh-* packages declare
cordis as matching peer+dev dependency, uniform 0.0.1 version, ESM.

yarn hygiene runs all three.
2026-06-11 15:04:50 +08:00
Tianyi Cui
bfb034830f Enforce 100% per-file test coverage on packages/*/src
vitest coverage (v8 provider) with per-file 100% thresholds for
statements, branches, functions, and lines. Scope: our runtime source
only — types-only files, vendor/ (upstream code), and examples/
(exercised by the demo smoke test) are excluded. yarn test:coverage
runs the gate.

59 tests added to close every gap: llm generate-waterfall and adapter
disposal; assembler edge protocol (duplicate block-start, stragglers
after block-end, id fallback, usage omission, invariant violation);
the whole Inbox surface incl. the wakeup-overwrite race; LoopAgent
disposed-state throws and double-stop idempotence; config-driven agent
creation; loop backstop catches (throwing turn-start/turn-end
listeners, non-Error throws, non-JSON tool arguments); system-prompt
dynamic sections and disposer paths; tools errorMessage fallbacks and
the full schema-DSL emission matrix. Genuinely unreachable defensive
guards carry /* v8 ignore */ comments with stated reasons rather than
deletion (132 tests total).
2026-06-11 14:58:36 +08:00
Tianyi Cui
cb6bee3d03 Add ESLint: typescript-eslint strict-type-checked + stylistic formatting
Flat config with two layers. Correctness (type-checked): the headline
rules for this codebase are no-floating-promises / no-misused-promises
(a lost promise in the agent loop is our primary bug class),
switch-exhaustiveness-check (we switch over merge-extensible unions
everywhere), no-unnecessary-condition, require-await, and
no-explicit-any. Style (@stylistic): 2-space, no semicolons, single
quotes, trailing commas, max-len 140 — the existing house style, now
enforced instead of drifting between agents. vendor/ is excluded
(vendored source keeps upstream style); tests relax the rules that
fight test ergonomics (non-null assertions after expects, async mock
signatures, non-Error throws).

Code adjusted to pass: registry disposers wrap ctx.effect's
promise-returning disposer behind a sync () => void (our public API),
BlockAssembler gains an invariant-checking mustGet instead of non-null
assertions, lastTurnNumber uses findLast, waterfall tails return
Promise.resolve instead of async-without-await arrows, and the two
deliberate suppressions (non-exhaustive derivation switch, unbound
execute pass-through) carry justification comments.

yarn lint / yarn lint:fix added.
2026-06-11 14:17:58 +08:00
Tianyi Cui
ef45ca823a Fix schema-DSL findings from the second Codex review
InferArgs now produces genuinely optional keys: required/optional
properties are split at the key level (RequiredKeys + mapped `?`), so
{ limit: { type: 'number' } } infers as { limit?: number } and callers
can omit it — previously the key stayed required with `| undefined`.
Array item inference recurses (arrays of objects infer their element
shape instead of Record<string, unknown>), matching the generated
JSON Schema.

Tool execution error reporting handles non-Error throws again:
`throw { message: 'denied' }` reports the message instead of
"[object Object]" (errorMessage helper).

The new schema tests now actually typecheck: schema literals use
`satisfies SchemaSpec` (the standalone-literal widening made
schemaSpecToJsonSchema reject the suite's own examples), and the
ToolSchema probe cast goes through unknown. Tests-and-examples
typechecking is now part of `yarn typecheck` via the new
tsconfig.typecheck.json (resolves vendor packages by their built
declarations, so vendor's relaxed-strictness source stays out of
scope) — vitest never typechecks, so this gate is what catches such
breakage. +4 regression tests (typed omission, array-of-objects
inference both type- and runtime-level, non-Error throw message).
2026-06-11 13:46:01 +08:00
Tianyi Cui
ae2e08b4d6 Set up monorepo infra: Yarn 4 workspaces, tsc -b + dumble build, vitest
Workspaces are vendor/* (Cordis framework vendored as source) and
packages/* (@deepseek-ai/dsh-* harness packages). Dev/test/demo run
unbuilt via tsx + root tsconfig paths; build = tsc -b (declarations)
+ dumble (JS bundles); tests = vitest + vite-tsconfig-paths.
2026-06-11 10:52:45 +08:00