diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml index 9c9cb9b476..04c3e6a7df 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md -2026-08-09-committed-artifact-citations.md: 31b2d52b423a080579b3ca95cf077859d0bf4c91 -2026-08-09-committed-artifact-citations.zh.md: b69bb8743d10e2d1e194905d6d0de2d3d3667b1d +2026-08-09-committed-artifact-citations.md: 181378aab12a4423d9ac5216bac5304f3cfca11c +2026-08-09-committed-artifact-citations.zh.md: 36f32bb1fd0d6991919959e1ebe6482bf7b19b32 diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md index 31b2d52b42..181378aab1 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md @@ -18,7 +18,7 @@ Durable prose — comments, JSDoc, docs, notes, test comments and titles — cit - Implemented notes state shipped reality: a "deferred to a later PR" claim whose target shipped names the shipped note instead. - Recorded fixtures, snapshots, and archived notes are exempt: recorded model output and sealed history keep their original voice. Inside a note's change-story sections, a historical stage name ("the first cut shipped X") is current-state-safe; indexical stamps ("this cut") stay banned everywhere. -One repo-wide purge applied these rules across the prose surfaces, including the generator-owned templates (`scripts/gen-doc-graphs.ts`, `scripts/gen-tool-catalog.ts`, the typert generator's page notice) with regeneration, the type-equiv source JSDoc with page re-pastes, and the bilingual counterparts with pair re-records. +One repo-wide purge applied these rules across the prose surfaces, including the generator-owned templates (`scripts/gen-doc-graphs.ts`, `scripts/gen-tool-catalog.ts`, the typert generator's page notice) with regeneration, the type-equiv source JSDoc with page re-pastes, and the bilingual counterparts with pair re-records. The [dsh-trim-cot-leakage skill](../../../skills/dsh-trim-cot-leakage/SKILL.md) operationalizes these rules: the audit taxonomy, the committed recall batteries, and few-shot calibration for the keep/delete boundary. ## Alternatives considered @@ -28,7 +28,7 @@ One repo-wide purge applied these rules across the prose surfaces, including the ## Verification -The audit's grep batteries (English and Chinese, comments and prose, `--hidden` for `.agents/`) return no design-ordinal citations outside recorded fixtures and archived notes; `verify-type-equiv`, the `gen-*` freshness checks, and `verify-translation-pairing` pin the regenerated and re-recorded surfaces. Coverage gap: no gate rejects a new ordinal citation — review owns the rule. +The audit's grep batteries (English and Chinese, comments and prose, `--hidden` for `.agents/`) return no design-ordinal citations outside recorded fixtures, archived notes, and the trim skill's own calibration examples; `verify-type-equiv`, the `gen-*` freshness checks, and `verify-translation-pairing` pin the regenerated and re-recorded surfaces. Coverage gap: no gate rejects a new ordinal citation — review owns the rule. ## Consequences diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md index b69bb8743d..36f32bb1fd 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md @@ -18,7 +18,7 @@ Status: implemented - 已实现的 Agent Note 陈述已交付的现实:「推迟到后续 PR」的说法若其目标已经交付,就改为点名那篇已交付的 note。 - 已录制的 fixture(测试前置数据)、快照与已归档的 Agent Note 不受此约束:已录制的模型输出与封存的历史保持原有行文。在 note 的变更故事段落内,历史阶段名称(「首版交付了 X」)属于安全的现状表述;指示性切次戳("this cut")在任何地方都仍被禁止。 -一次全仓库清理把这些规则应用到了各个行文表面,包括生成器持有的模板(`scripts/gen-doc-graphs.ts`、`scripts/gen-tool-catalog.ts`、typert 生成器的页面提示语,改后重新生成)、type-equiv 源码 JSDoc(改后把文档页重新粘贴)以及双语对侧文件(改后重新记录配对)。 +一次全仓库清理把这些规则应用到了各个行文表面,包括生成器持有的模板(`scripts/gen-doc-graphs.ts`、`scripts/gen-tool-catalog.ts`、typert 生成器的页面提示语,改后重新生成)、type-equiv 源码 JSDoc(改后把文档页重新粘贴)以及双语对侧文件(改后重新记录配对)。[dsh-trim-cot-leakage 技能](../../../skills/dsh-trim-cot-leakage/SKILL.md)把这些规则落地为可执行工作流:审计分类法、已提交的成批召回检索,以及校准保留/删除边界的少样本示例。 ## 曾考虑的替代方案 @@ -28,7 +28,7 @@ Status: implemented ## 验证 -审计的成批 grep 检索(英文与中文、注释与行文、对 `.agents/` 加 `--hidden`)在已录制 fixture 与已归档 Agent Note 之外没有命中任何设计序号引用;`verify-type-equiv`、各 `gen-*` 新鲜度检查与 `verify-translation-pairing` 把重新生成与重新记录的表面固定下来。覆盖缺口:没有门禁会拒绝新的序号引用,这条规则由评审把关。 +审计的成批 grep 检索(英文与中文、注释与行文、对 `.agents/` 加 `--hidden`)在已录制 fixture、已归档 Agent Note 与该清理技能自带的校准示例之外没有命中任何设计序号引用;`verify-type-equiv`、各 `gen-*` 新鲜度检查与 `verify-translation-pairing` 把重新生成与重新记录的表面固定下来。覆盖缺口:没有门禁会拒绝新的序号引用,这条规则由评审把关。 ## 后果 diff --git a/.agents/skills/dsh-doc-standards/SKILL.md b/.agents/skills/dsh-doc-standards/SKILL.md index 47b5725c18..dcbf2f2b21 100644 --- a/.agents/skills/dsh-doc-standards/SKILL.md +++ b/.agents/skills/dsh-doc-standards/SKILL.md @@ -37,12 +37,11 @@ Then check constraints that make placement expensive or wrong: After the structural pass, hunt the standard's slop checklist with the cheapest probes first. Verify and fetch the PR's live base, then run `pnpm --silent run change-scope --base ` to identify committed and dirty paths before applying semantic judgment. After a retarget or base merge, rerun the report and audit prose introduced by the new base. 1. Measure: `pnpm run verify-doc-budgets --list`, then `git ls-files '*.md' ':(exclude)vendor/**' | xargs wc -w | sort -rn | head -30` to spot unbudgeted outliers. -2. Hunt narrated history: `rg -n "no longer|used to|previously|was moved|renamed" --glob '*.md' --glob '*.ts' --glob '!vendor/**'` and keep only contrasts against a live alternative. Keep the vendor exclusion last so include globs cannot override it. -3. Inspect long comments for reasoning transcripts: control-flow narration, test walkthroughs, proof of obvious branches, review findings, rejected local alternatives, and the same rationale repeated beside sibling methods. Preserve only a non-obvious contract or durable rationale; otherwise delete the comment. -4. Hunt duplication by grepping distinctive phrases. Keep one home and replace other copies with links. -5. Replace hand-written catalogs, test/status inventories, and JSDoc restatements with the authoritative tree, script, or generated reference. -6. In `implemented/` Agent Notes, remove migration plans, acceptance-task checklists, and future-tense spec language. Keep concise verification contracts that identify the behaviors and tiers pinning the shipped decision, plus named coverage gaps. -7. If removing prose changes a promised behavior rather than its explanation, use a proposed Agent Note first (follow [dsh-find-simplifications](../dsh-find-simplifications/SKILL.md)). +2. Hunt reasoning-transcript leakage — narrated history, dead design-session citations, review choreography, control-flow narration, test walkthroughs — with [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md), which owns the taxonomy, recall batteries, and the keep/delete boundary. Preserve only a non-obvious contract or durable rationale; the same rationale repeated beside sibling methods keeps one home. +3. Hunt duplication by grepping distinctive phrases. Keep one home and replace other copies with links. +4. Replace hand-written catalogs, test/status inventories, and JSDoc restatements with the authoritative tree, script, or generated reference. +5. In `implemented/` Agent Notes, remove migration plans, acceptance-task checklists, and future-tense spec language. Keep concise verification contracts that identify the behaviors and tiers pinning the shipped decision, plus named coverage gaps. +6. If removing prose changes a promised behavior rather than its explanation, use a proposed Agent Note first (follow [dsh-find-simplifications](../dsh-find-simplifications/SKILL.md)). Exclude `.agents/notes/archived/` from corpus audits and edits. Active prose may repair, redirect, or delete an inbound link, but never follow an archive-wide cleanup into the frozen target. diff --git a/.agents/skills/dsh-prose-standard/SKILL.md b/.agents/skills/dsh-prose-standard/SKILL.md index 26de553023..2381d459c2 100644 --- a/.agents/skills/dsh-prose-standard/SKILL.md +++ b/.agents/skills/dsh-prose-standard/SKILL.md @@ -5,7 +5,7 @@ description: Use when writing, reviewing, restoring, trimming, or auditing prose # DeepSeek Harness Prose Standard -Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. This skill owns editorial judgment and required prose coverage; use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates. It is guidance, not a script. +Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. This skill owns editorial judgment and required prose coverage; use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates, and [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md) for hunting and fixing reasoning-transcript leakage. It is guidance, not a script. Comments describe non-obvious contracts or rationale that code cannot express; they do not restate what code already implies. diff --git a/.agents/skills/dsh-trim-cot-leakage/SKILL.md b/.agents/skills/dsh-trim-cot-leakage/SKILL.md new file mode 100644 index 0000000000..1ce776c28e --- /dev/null +++ b/.agents/skills/dsh-trim-cot-leakage/SKILL.md @@ -0,0 +1,45 @@ +--- +name: dsh-trim-cot-leakage +description: Use when auditing or fixing prose that reads like a leaked reasoning transcript — dead design-session citations such as (decision N), audit item codes, or §N of uncommitted drafts; change narration such as "used to", "no longer", "this cut"; stack or review vantage ("a later PR in this stack", "rejected in review"); reviewer-addressed justifications; control-flow narration; or hedged planning residue in comments, JSDoc, docs, or Agent Notes. +--- + +# Trimming Chain-of-Thought Leakage + +Chain-of-thought leakage is prose whose vantage is the authoring session rather than the repository: it cites artifacts only that session could see, narrates the change instead of the state, or argues with a reviewer who has left. The fix is never deletion alone — restate every factual clause so it stands at HEAD, then delete the transcript around it. **REQUIRED BACKGROUND:** [dsh-prose-standard](../dsh-prose-standard/SKILL.md) owns the complete-proposition rule this skill applies; the [committed-artifact-citations note](../../notes/implemented/process/2026-08-09-committed-artifact-citations.md) owns the citation rule's rationale. It is guidance, not a script. + +## The one test + +For every suspect passage ask: **could a reader at HEAD, with no access to any session transcript, PR thread, or uncommitted draft, resolve every reference and verify every claim?** If yes, it is not leakage, however historical it sounds. If no, restate the surviving facts from the repository's vantage and delete the rest. + +## Taxonomy + +1. **Dead design-session citations** — `(decision 7)`, `(audit C2)`, `design §4.7`, `plan §1.4`, phase labels (`T4`, `W3`, `P-I`), "the design ledger", "(B ruling)". If the decision has a committed owner, cite it by name and path; otherwise delete the citation and restate its factual clause to stand alone. +2. **Stack and PR vantage** — "a later PR in this stack", "this PR adds", "the previous commit". State the shipped mechanism or the extension point; deferred work moves to a `TODO` marker or an issue reference. +3. **Change narration and version stamps** — "used to", "no longer", "the old X", and indexical stamps ("v1", "this cut", "today", "now" contrasting with a past state). State the present behavior; a fixed regression becomes a present-tense counterfactual ("without X, Y happens"), never repo history ("used to Y"). +4. **Review choreography** — "Rejected in review:", "the reviewer confirmed", draft ordinals ("v5 of this note"), round attributions. Keep the surviving decision and rationale as plain fact; delete who said it when. +5. **Reviewer-addressed justification** — "the cast is safe — it simply…", "this is correct because…". A comment arguing its own correctness addresses a reviewer, not a maintainer. State the invariant that makes the code safe, or delete the comment if the code shows it. +6. **Restatement and derivation transcripts** — control-flow narration ("first we X, then we Y"), test walkthroughs, proofs of obvious branches. Delete; keep only a non-obvious contract or invariant. +7. **Hedges and planning residue** — "probably fine for now", "should be enough", deferrals with no marker. Promote to `TODO`/`FIXME` or restate as the actual bound; delete the hedge. +8. **Authoring-language slips** — untranslated working-language fragments (端, 设计稿, `---- 私有 ----` separators) in prose whose language is otherwise English, or the reverse in a zh counterpart. Translate or delete. + +## What is not leakage + +The citation boundary is where unaided passes fail in both directions — deleting durable references and keeping dead ones. Apply these keeps as written; [examples](references/examples.md) calibrates each: + +- **Issue references** — `#1470`, `TODO(name):`, "issue #N owns the follow-up" resolve at HEAD; keep them on any surface, including READMEs. Do not relocate them to Agent Notes. +- **Merged-PR and issue citations inside Agent Notes and postmortems** — sanctioned evidence per the [documentation standard](../../../docs/AGENTS.md)'s change-story routing. +- **Suppression justifications** — `oxlint-disable … -- reason`, coverage-ignore reasons, empty-catch explanations are required prose; fix a false reason, never delete it. +- **Counterfactual-present regression pins** — "without X, Y happens", "a naive X would…". +- **Measured bounds** — "(measured: 512 nests ≈ 0.15s)" calibrating a constant; the provenance word "measured" is load-bearing. +- **Runtime old/new states** — "the old connection drains before the new one accepts" is runtime lifecycle, not change history. +- **Historical stage names inside a note's change-story sections** — "the first cut shipped X" is current-state-safe there; indexical stamps ("this cut") stay banned everywhere. +- **External references that resolve outside the repo by design** — standards sections (RFC 9110 §10.1.5), Figma frame names; the §-ban covers uncommitted internal drafts, not external standards or committed docs that own their §-numbering. +- **Project voice and genre forms** — "we" as project voice; a note's Alternatives-considered section. + +## Workflow + +1. Scope and exclusions per [dsh-prose-standard](../dsh-prose-standard/SKILL.md): require an explicit scope; never touch `vendor/`, `.agents/notes/archived/`, or recorded fixtures and snapshots — recorded model output and sealed history keep their original voice. +2. Audit read-only first: run the [recall batteries](references/recall-batteries.md) (with `--hidden` so `.agents/` is searched), then judge every hit semantically. The batteries are probes, not the definition — each review round of the original purge surfaced shapes the batteries missed, so also read the densest prose in scope (module JSDoc, READMEs, Agent Notes) without a pattern in hand. +3. Fix owner-first per surface: generated catalogs → fix the source JSDoc or generator template, then regenerate; type-equivalence fences → fix the source JSDoc, then re-paste both bilingual pages (`verify-type-equiv` pins them); bilingual pairs → update the counterpart and re-record per [dsh-translate-docs](../dsh-translate-docs/SKILL.md); model-visible strings → wording is behavior, so flag for a snapshot-backed change instead of silently rewording. +4. Before deleting anything, enumerate the passage's propositions (prose-standard) and check the [overcorrection traps](references/examples.md#overcorrection-traps): trims that flip an obligation into an endorsement, promote a hypothetical to a shipped feature, delete a true fact, or drop provenance. +5. Verify: re-run the batteries expecting only sanctioned keeps and this skill's own references; confirm every remaining citation resolves at HEAD; run the gates for touched surfaces (`doc-sync` for docs, `verify-type-equiv`, `verify-translation-pairing`). diff --git a/.agents/skills/dsh-trim-cot-leakage/references/examples.md b/.agents/skills/dsh-trim-cot-leakage/references/examples.md new file mode 100644 index 0000000000..f478d90b13 --- /dev/null +++ b/.agents/skills/dsh-trim-cot-leakage/references/examples.md @@ -0,0 +1,253 @@ +# Few-shot leakage examples + +Distilled from the 2026-08 repo-wide purge and its review rounds. Use them to identify the governing principle, not as text templates. This file deliberately quotes leaked shapes as calibration material — the [recall batteries](recall-batteries.md) exclude this directory, and its wording is not a license elsewhere. + +## Dead citations + +### Decision ordinal with a committed owner + +**Leaked:** "Slash input resolves against the visible catalog (decision 21)." + +**Fixed:** "Slash input resolves against the visible catalog — the plain-text-reference decision, owned by the web input-machine note." + +The ordinal resolves nowhere at HEAD; the decision's name and owning note path do. Name the owning note's path at least once per file; later mentions may use the searchable name alone. + +### Decision ordinal without an owner + +**Leaked:** "The registry rejects duplicate names (decision 7: names are flat, no namespacing)." + +**Fixed:** "The registry rejects duplicate names; names are flat, with no namespacing." + +No committed artifact owns "decision 7", so the citation is deleted — but its factual clause (flat names) is restated to stand alone, not deleted with it. + +### Audit item codes + +**Leaked:** "Rendering is pure: same snapshot, same string (audit R3)." + +**Fixed:** "Rendering is pure: same snapshot, same string." + +There is no audit document in the repo; the code is pure session shorthand carrying zero propositions. + +### Section numbers of uncommitted drafts + +**Leaked:** "Layering follows the design (v2 §3.2): `src/core/` is the pure core." + +**Fixed:** "Layering: `src/core/` is the pure core." + +`§N` of a draft nobody committed is unresolvable. Contrast: "escapes per RFC 9110 §10.1.5" stays — an external standard resolves outside the repo by design, and a committed doc that owns its §-numbering may be cited by section. + +### Plan-phase labels + +**Leaked:** "`src/client/` is the shell (T4); the P-I migration owns the adapters." + +**Fixed:** "`src/client/` is the shell; the adapters live in `src/client/adapters/`." + +Phase labels index a plan that never landed. Replace the label with what the phase produced. + +## Stack and PR vantage + +### Stack position in durable prose + +**Leaked:** "A future remote backend implements this interface (the sandbox backend is a later PR in this stack)." + +**Fixed:** "A remote backend can implement this interface without changing the render layer." + +Durable prose cannot see the stack. Keep the extension-point contract; the pending work's home is the PR itself, a `TODO`, or an issue. + +### "This PR" in a README + +**Leaked:** "This PR adds cursor-based pagination to the session list." + +**Fixed:** "The session list paginates by cursor." + +A README outlives every PR; state the mechanism as current fact. + +## Change narration and version stamps + +### War story with a PR number + +**Leaked:** "Colors used to come from `--widget-*` tokens, which nothing defined, so it always rendered the fallbacks; the alias tokens fixed that (PR #88)." + +**Fixed:** "Colors come from the alias tokens; an undefined token renders the fallbacks." + +Both live facts survive — the current mechanism and the standing failure behavior — restated in the present. The bug's biography belongs to the PR and its Agent Note. + +### Removal narration + +**Leaked:** "The `probe` field is gone with the removal cut; badges ride the generic projection pair now." + +**Fixed:** "Badges use the generic projection pair." + +Readers who never saw `probe` learn nothing from its absence. "Now" contrasting with a deleted past is a version stamp. + +### Fixed regression → counterfactual present + +**Leaked:** "This used to double-encode multibyte labels." + +**Fixed:** "Without the byte-length guard, multibyte labels double-encode." + +The regression pin survives as a present-tense counterfactual that names the guard; "used to" pins it to repo archaeology instead. + +### Indexical version stamps + +**Leaked:** "Batch rendering is synchronous this cut; the async path is roadmap work." + +**Fixed:** "Batch rendering is synchronous." (The deferral lives in `TODO(widget-batch):` at the call site.) + +"This cut" / "v1" / "today" go stale the moment they merge. A historical stage name inside an Agent Note's change-story section ("the first cut shipped X") is current-state-safe; the indexical form never is. + +## Review choreography + +### Review verdicts as prose + +**Leaked:** "Rejected in review: caching the resolved spec. We keep resolution per-call." + +**Fixed (in an Agent Note's Alternatives considered):** "**Caching the resolved spec.** Rejected: the spec depends on per-call cwd, so a cache keyed by request would serve stale roots." + +The alternatives-considered genre is the sanctioned home; the reviewer and the round are not part of the rationale. + +### Draft ordinals + +**Leaked:** "As of v5 of this note, the loader also validates manifests." + +**Fixed:** "The loader validates manifests." + +An implemented note states shipped reality; its own revision history lives in git. + +## Reviewer-addressed justification + +### Arguing a cast + +**Leaked:** "The cast is safe — the SDK constructed the object, it simply doesn't declare the optionals strictly enough." + +**Fixed:** "The SDK constructs this object with every optional populated; the declared type is looser than the runtime guarantee." + +State the invariant a maintainer must not break. "It simply…" is a voice answering an objection nobody at HEAD raised. If the invariant is visible in the code, delete the comment instead. + +### Appeal to review authority + +**Leaked:** "This is correct because the reviewer confirmed the wrapping order." + +**Fixed:** (deleted; the wrapping order is stated in the function's `@returns`.) + +Correctness claims cite invariants or tests, never people. + +## Restatement and derivation + +### Control-flow narration + +**Leaked:** "First we normalize the label, then we truncate it, then we wrap it." + +**Fixed:** (deleted.) + +The three lines below the comment say the same thing in code. + +### Test walkthrough + +**Leaked:** "This test creates a session, sends two messages, waits for the second reply, and then asserts the log has four entries." + +**Fixed:** "Two round-trips must produce exactly four log entries — the projection dedupes the shared prefix." + +Keep only the non-obvious assertion rationale; the walkthrough restates the test body. + +## Hedges and planning residue + +### Unmarked deferral + +**Leaked:** "Probably fine to render eagerly for now." + +**Fixed:** (deleted; the deferral already has its `TODO(widget-batch):` marker.) + +A hedge without an owner is planning residue. If no marker exists, write one (`TODO(name): coalesce per animation frame`) instead of keeping the hedge. + +### Vague sizing + +**Leaked:** "A 64 KiB buffer should be enough for most cases." + +**Fixed:** "64 KiB holds the largest observed frame (48 KiB) with headroom; a larger frame fails loudly in `decode`." + +Replace the hedge with the actual bound and the failure behavior when it is exceeded. + +## Authoring-language slips + +**Leaked:** "The renderer runs on the client 端; see the 设计稿 for spacing. ---- 私有 ----" + +**Fixed:** "The renderer runs on the client side; spacing follows the Figma frame `widget-badges`." + +Working-language fragments and session separators are transcription residue. The Figma frame name stays: external provenance that resolves outside the repo by design. + +## Keeps + +### Issue references are durable on every surface + +**Keep:** "The cap applies to the complete rendered value, wrappers included (issue #1470 owns the follow-up)." + +An unaided pass deleted this, reasoning that issue citations belong in Agent Notes. Wrong direction: issues resolve at HEAD from any surface, and "#N owns the follow-up" is the sanctioned home for deferred work in a README. What Agent Notes and postmortems additionally sanction is citing *merged PRs* as evidence. + +### Dead name-drops are not "naming the owner" + +**Delete:** "Badge renderer over the widget seam (see the widget-rendering RFC)." + +An unaided pass kept this as "naming the owning document by topic". The test is resolvability, not form: no committed file answers to "the widget-rendering RFC", so the pointer is dead. Retarget it to the committed owner if one exists; otherwise delete it. + +### Suppression justifications + +**Keep (after fixing):** `// oxlint-disable-next-line no-non-null-assertion -- the one-element literal guarantees index 0.` + +The justification clause is required prose. When the stated reason is false (the original said "the loop guard above proves a frame exists" with no loop in sight), fix the reason; never delete it. + +### Measured bounds + +**Keep:** "Depth cap (measured: 512 nests ≈ 0.15s synchronous; 4096 blocks the loop)." + +The measurement pins the constant against uninformed retuning, and "measured" is the provenance that distinguishes data from a guess. + +### Runtime old/new is not change history + +**Keep:** "The old connection drains before the new one accepts." + +"Old" and "new" here name two live runtime objects during handover, not repository states. The change-narration ban is about repo history, not lifecycle vocabulary. + +## Overcorrection traps + +Every trap below shipped in the original purge and was caught in review. Enumerate a passage's propositions before trimming it. + +### Flipping an obligation into an endorsement + +**Original:** "These direct registrations are exceptions pending migration to slots." + +**Overcorrected:** "These direct registrations are sanctioned exceptions." + +**Right:** "These direct registrations are exceptions pending migration to slots." + +"Pending migration" is an obligation; "sanctioned" blesses the status quo. The trim inverted the sentence's modality while shortening it. + +### Promoting a hypothetical to a shipped feature + +**Original:** "A future IPC-based shell subclasses the executor and overrides `spawn`." + +**Overcorrected:** "An IPC-based shell subclasses the executor and overrides `spawn`." + +**Right:** "A hypothetical IPC-based shell — no such shell exists — would subclass the executor and override `spawn`." + +Deleting the future-marker alone turns a design illustration into a claim that the class ships. Mark the hypothetical explicitly instead of just unmarking the future. + +### Deleting a true fact with the transcript around it + +**Original:** "The gate notice narrates the check order; the notice text is also what `verify-doc-typecheck` compiles against." + +**Overcorrected:** "…" (whole sentence deleted as narration.) + +**Right:** "The notice text is what `verify-doc-typecheck` compiles against." + +Half the sentence was narration; the other half was a load-bearing coupling. Delete clauses, not sentences, when propositions share a line. + +### Dropping provenance while keeping the number + +**Original:** "The 4 MiB ceiling is measured: the largest generated `py-types` module is 3.1 MiB." + +**Overcorrected:** "The ceiling is 4 MiB; the largest generated `py-types` module is 3.1 MiB." + +**Right:** keep "measured". + +Without "measured" the 3.1 MiB reads as a definition rather than an observation, and nobody re-measures before raising the ceiling. diff --git a/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md new file mode 100644 index 0000000000..bacb434d16 --- /dev/null +++ b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md @@ -0,0 +1,42 @@ +# Recall batteries + +Probes for [the taxonomy](../SKILL.md#taxonomy), tuned during the 2026-08 purge. Every hit needs semantic judgment — the batteries over-match by design, and they under-match by nature: each review round of the purge found shapes no battery caught, so pair them with an unpatterned read of the densest prose in scope. + +## Invocation rules + +- Add `--hidden --glob '!.git/**'` so `.agents/` is searched; ripgrep skips dot-directories by default and the purge's biggest miss risk was Agent Notes. +- Exclusions go last so a later include cannot re-admit them: `--glob '!vendor/**' --glob '!node_modules/**' --glob '!.agents/notes/archived/**' --glob '!.agents/skills/dsh-trim-cot-leakage/references/**'` (this directory quotes leaked shapes as calibration), plus recorded fixture and snapshot directories in scope. +- A zero-hit pattern proves nothing until you have seen it match: test it against a known-positive string before trusting the negative. + +## English battery + +```sh +rg -n --hidden '\(decision \d|\(audit [A-Z]\d|design §|plan §|design ledger|\(B ruling|\bP-I\b|\bW\d\b|\bT\d\b' ... +rg -n --hidden 'this PR|this branch|this stack|later PR|previous commit|this commit' ... +rg -n --hidden 'used to |no longer|previously|the old |was renamed|was moved' ... +rg -n --hidden '\bv1\b|this cut|\bcut \d|\btoday\b|\bfor now\b|roadmap' ... +rg -n --hidden -i 'rejected in review|review round|reviewer|as of v\d' ... +rg -n --hidden 'probably |should be enough|should suffice|it simply|is safe —|is safe --' ... +rg -n --hidden '§\d' ... +``` + +## Chinese battery + +```sh +rg -n --hidden '设计稿|评审|上一?轮|旧版|老的|不再|以前|本版|遗留|私有' ... +rg -n --hidden '(^|[^a-zA-Z])端([^a-zA-Z]|$)' --glob '*.md' ... +``` + +## Known false-positive families + +Judged and kept during the purge; expect them again: + +- **Instrumental "used to"** — "the key used to sign requests" is instrumental, not temporal. The temporal form has a subject state before it ("colors used to come from…"). +- **Runtime old/new** — "the old connection drains before the new one accepts" names live objects during handover, not repo states. +- **"This PR" in process docs** — documentation *about* PR workflow ("the PR body should…", templates, this repo's process notes) legitimately says "PR"; the ban is on a doc adopting one PR's vantage about the code. +- **`v1` as protocol or path segment** — `/v1/chat` endpoints and wire-format names are identifiers, not version stamps. +- **`§N` with a committed owner** — external standards (RFC 9110 §10.1.5) and committed docs that own their §-numbering stay citable by section. +- **Contrastive "actually" and noun "wait"** — ordinary English, not hedging. +- **"Today" in generated timestamps and CLI output samples** — recorded output keeps its voice. +- **本版本 in zh prose** — a legitimate rendering of "this release" in versioned-artifact contexts; the banned indexical is 本版 as a bare stamp mirroring "this cut". +- **Alternatives-considered sections** — "rejected" inside an Agent Note's genre slot is the sanctioned home, not review choreography. diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index dd89b9d985..c703c16bf1 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/persistence-catalog.md -persistence-catalog.md: b97c98302e8aee291a39aa9f21e1e8ef618ecfb1 -persistence-catalog.zh.md: 803a01ec9594d7a39c86a834d1f81905f3bb8a1f +persistence-catalog.md: f118c0ba1dac84e24ddff186c3712230fd3c9902 +persistence-catalog.zh.md: 99fe85d9064c76c7b65693a917ee68e8b5b5a18d diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index b97c98302e..f118c0ba1d 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -519,6 +519,8 @@ Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:33`](../packages/s 'schedule/change': ScheduleChange ``` +Types: [ScheduleChange](subsystems/schedule.md) + Source: [`packages/schedule/tool-schedule/src/types.ts:183`](../packages/schedule/tool-schedule/src/types.ts) ### `session/*` diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 803a01ec95..99fe85d906 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -521,6 +521,8 @@ export type SessionEvent = { 'schedule/change': ScheduleChange ``` +类型:[ScheduleChange](subsystems/schedule.md) + 来源:[`packages/schedule/tool-schedule/src/types.ts:183`](../packages/schedule/tool-schedule/src/types.ts) ### `session/*` diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index 2379b7d25b..390bbe911e 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/README.md -README.md: 096fd7de4d2644dac664fac940b6487052115258 -README.zh.md: 6b6758db8f7118a09f6b0998231d3944f44f5052 +README.md: fea77566dd2cf07af04619203ef5a94dad6b2dc0 +README.zh.md: dfcd5a6360d375f710213b3cb944695f19f488d6 diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index 096fd7de4d..fea77566dd 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -12,6 +12,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [scope.md](scope.md) | scoped registration identity, dispatch carriers, and the owned `Scope` context | | [typert.md](typert.md) | Remote invocation descriptors, lookup/Context declarations, TypeRT registries, and the Host Gateway/Client API boundaries | | [goal.md](goal.md) | persisted goal identity, lifecycle snapshots, activation, change records, and round attribution | +| [schedule.md](schedule.md) | Session-local reminder records, durable transitions, active views, and ordinary-conversation delivery | | [commands.md](commands.md) | the human-command registry service: definitions, adapter discovery, direct invocation, results, and parsing views | | [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnTrigger`/`TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events | | [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 6b6758db8f..dfcd5a6360 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -12,6 +12,7 @@ | [scope.md](scope.md) | 作用域注册标识、dispatch 载体,以及拥有的 `Scope` 上下文 | | [typert.md](typert.md) | 远程调用描述符、lookup/Context 声明、TypeRT 注册表,以及 Host Gateway/Client API 边界 | | [goal.md](goal.md) | 持久 goal 标识、生命周期快照、激活、变更记录与 Round 归属 | +| [schedule.md](schedule.md) | 仅限 Session 内的提醒记录、持久转换、活动视图与普通对话交付 | | [commands.md](commands.md) | 人类命令注册表服务:定义、适配器发现、直接调用、结果与解析视图 | | [session.md](session.md) | 完整的 `SessionEventMap` 变体目录、`TurnTrigger`/`TurnEndReason`、`deriveMessages()`、执行封闭与独立事件 | | [persistence.md](persistence.md) | 持久性 seam:`SessionPersistence`、JSONL + SQLite 后端、`session/flush`、崩溃恢复、`SessionHeader` | diff --git a/docs/subsystems/schedule.i18n.yaml b/docs/subsystems/schedule.i18n.yaml new file mode 100644 index 0000000000..17ac53123f --- /dev/null +++ b/docs/subsystems/schedule.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/schedule.md +schedule.md: 4357434fade3b49d6a4704bf8c6b28591e7460c9 +schedule.zh.md: c8291e782068cbff32c9a130a06680506f383c42 diff --git a/docs/subsystems/schedule.md b/docs/subsystems/schedule.md new file mode 100644 index 0000000000..4357434fad --- /dev/null +++ b/docs/subsystems/schedule.md @@ -0,0 +1,100 @@ +# Session-local Schedule + +English | [中文](schedule.zh.md) + +Schedule owns durable reminders that return to the original live Session as ordinary later conversation turns. The [durable Schedule Agent Note](../../.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.md) owns the persistence and lifecycle decisions, and [conversational delivery](../../.agents/notes/implemented/simplification/2026-08-09-conversational-schedule-delivery.md) owns the no-receipt boundary. This page records the durable and model-facing shapes from [`packages/schedule/tool-schedule/src/types.ts`](../../packages/schedule/tool-schedule/src/types.ts); the [package README](../../packages/schedule/tool-schedule/README.md) owns composition, tool behavior, and the exact reminder framing. + +## Durable records + +`ScheduleId` is a [branded id](core.md#branded-ids), unique and never reused within one Session. Version 1 initially supports a positive safe-integer `after_seconds` selector. Creation canonicalizes the selected target into a four-digit-year RFC 3339 UTC `scheduledAt`; the submitted delay remains in the record so list results explain the rule that produced it. + +```ts type-equiv +/** Durable one-shot reminder created from a positive delay. */ +interface AfterScheduleRecord { + /** Session-local stable identity. */ + readonly id: ScheduleId + /** Rule discriminator; v1 supports only delayed one-shot reminders. */ + readonly kind: 'after' + /** Trimmed reminder content supplied at creation. */ + readonly prompt: string + /** Positive safe-integer delay accepted at creation. */ + readonly afterSeconds: number + /** Four-digit-year RFC 3339 UTC target. */ + readonly scheduledAt: string +} +``` + +```ts type-equiv +/** The v1 durable reminder record union. */ +type ScheduleRecord = AfterScheduleRecord +``` + +## Durable changes and replay + +The version-1 `schedule/change` Session event is the only durable Schedule authority. Create stores the complete record. Delete and dispatch are terminal id-only transitions for one-shot reminders; dispatch means the follow-up was synchronously queued, not that a model answer succeeded or the user read it. + +```ts type-equiv +/** Creates one durable reminder record. */ +interface ScheduleCreateChange { + readonly version: 1 + readonly operation: 'create' + readonly schedule: ScheduleRecord +} +``` + +```ts type-equiv +/** Deletes one currently active reminder. */ +interface ScheduleDeleteChange { + readonly version: 1 + readonly operation: 'delete' + readonly id: ScheduleId +} +``` + +```ts type-equiv +/** Records that one active one-shot reminder entered the durable dispatch history. */ +interface ScheduleDispatchChange { + readonly version: 1 + readonly operation: 'dispatch' + readonly id: ScheduleId +} +``` + +```ts type-equiv +/** Strict version-1 durable Schedule mutation union. */ +type ScheduleChange = ScheduleCreateChange | ScheduleDeleteChange | ScheduleDispatchChange +``` + +The strict decoder and fold reject unknown versions, extra fields, reused ids, and delete or dispatch transitions against inactive records. A normal Session folds its complete event stream. A fork folds only events at or after `SessionHeader.seedLength`, so it retains history without adopting the parent Session's active reminders. The `schedule/change` declaration and source location are also indexed in the [persistence catalog](../persistence-catalog.md#schedulechange--log-only). + +## Active views and management + +Tool values combine the durable record with delivery state derived from the current wall clock. `session-local` means the original Session must be live: no external notification channel or cold-session scheduler exists. + +```ts type-equiv +/** Current delivery timing derived from the durable record and wall clock. */ +type ScheduleState = 'scheduled' | 'overdue' +``` + +```ts type-equiv +/** Fixed v1 delivery boundary: the original session must be live. */ +type ScheduleDeliveryMode = 'session-local' +``` + +```ts type-equiv +/** Complete model-facing view of one active after reminder. */ +interface ScheduleView extends AfterScheduleRecord { + /** Whether the target remains in the future. */ + readonly state: ScheduleState + /** Reminder delivery never leaves the owning session. */ + readonly deliveryMode: ScheduleDeliveryMode +} +``` + +The generated [tool catalog](../tool-catalog.md#deepseek-aidsh-tool-schedule) owns the argument and result schemas for `schedule_create`, `schedule_list`, and `schedule_delete`. Management calls serialize with due work in one Agent-scoped queue. Every read or decision first waits for the shared Session persistence barrier; create and an actual delete wait again after appending. A barrier failure reports `persistence_uncertain` instead of guessing whether an eager write committed. The other stable error codes are `invalid_prompt`, `invalid_selector`, `invalid_rule`, `time_out_of_range`, `corrupt_schedule_log`, and `internal_error`. + +## Live delivery + +The process-local owner derives its earliest timer from the durable fold and rereads the wall clock after every bounded wait. Cold Sessions do no work; reopening one reconstructs timers and makes a past target overdue. An overdue reminder waits for the Agent to become fully idle and claims the maintenance phase before it refolds state, queues `followup()`, and appends dispatch. It never calls `steer()` and never interrupts a current turn. + +The admitted follow-up starts one normal later turn and appears only through the ordinary conversation transcript; Schedule has no independent durable Web receipt or browser renderer. If framing or synchronous queue admission fails, no dispatch is recorded and the reminder stays active. The narrow crash interval after admission but before durable dispatch can repeat the reminder after recovery, so the boundary is best-effort at-least-once rather than exactly-once delivery. diff --git a/docs/subsystems/schedule.zh.md b/docs/subsystems/schedule.zh.md new file mode 100644 index 0000000000..c8291e7820 --- /dev/null +++ b/docs/subsystems/schedule.zh.md @@ -0,0 +1,100 @@ +# 仅限 Session 内的 Schedule + +[English](schedule.md) | 中文 + +Schedule 拥有持久提醒;这些提醒会作为普通的后续对话轮次返回原 live Session。[持久 Schedule Agent Note](../../.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.md) 负责持久化与生命周期决策,[对话式交付](../../.agents/notes/implemented/simplification/2026-08-09-conversational-schedule-delivery.md) 负责无回执边界。本页记录 [`packages/schedule/tool-schedule/src/types.ts`](../../packages/schedule/tool-schedule/src/types.ts) 中的持久数据形状和面向模型的数据形状;[包 README](../../packages/schedule/tool-schedule/README.md) 负责组合、工具行为与确切的提醒 framing。 + +## 持久记录 + +`ScheduleId` 是[品牌化 id](core.md#branded-ids),在单个 Session 内唯一且绝不复用。版本 1 最初只支持正的安全整数 `after_seconds` 选择器。创建操作会将选定目标规范化为使用四位年份的 RFC 3339 UTC `scheduledAt`;记录仍保留提交的延时,以便 list 结果说明生成该目标所用的规则。 + +```ts type-equiv +/** Durable one-shot reminder created from a positive delay. */ +interface AfterScheduleRecord { + /** Session-local stable identity. */ + readonly id: ScheduleId + /** Rule discriminator; v1 supports only delayed one-shot reminders. */ + readonly kind: 'after' + /** Trimmed reminder content supplied at creation. */ + readonly prompt: string + /** Positive safe-integer delay accepted at creation. */ + readonly afterSeconds: number + /** Four-digit-year RFC 3339 UTC target. */ + readonly scheduledAt: string +} +``` + +```ts type-equiv +/** The v1 durable reminder record union. */ +type ScheduleRecord = AfterScheduleRecord +``` + +## 持久变更与回放 + +版本 1 的 `schedule/change` 会话事件是 Schedule 唯一的持久权威。create 保存完整记录。delete 与 dispatch 是一次性提醒的终结性、仅含 id 的转换;dispatch 表示 follow-up 已同步入队,而不表示模型答复成功或用户已读取答复。 + +```ts type-equiv +/** Creates one durable reminder record. */ +interface ScheduleCreateChange { + readonly version: 1 + readonly operation: 'create' + readonly schedule: ScheduleRecord +} +``` + +```ts type-equiv +/** Deletes one currently active reminder. */ +interface ScheduleDeleteChange { + readonly version: 1 + readonly operation: 'delete' + readonly id: ScheduleId +} +``` + +```ts type-equiv +/** Records that one active one-shot reminder entered the durable dispatch history. */ +interface ScheduleDispatchChange { + readonly version: 1 + readonly operation: 'dispatch' + readonly id: ScheduleId +} +``` + +```ts type-equiv +/** Strict version-1 durable Schedule mutation union. */ +type ScheduleChange = ScheduleCreateChange | ScheduleDeleteChange | ScheduleDispatchChange +``` + +严格 decoder 与 fold 会拒绝未知版本、额外字段、重复使用的 id,以及针对非活动记录的 delete 或 dispatch 转换。普通 Session 折叠完整事件流。fork 只折叠 `SessionHeader.seedLength` 位置及其后的事件,因此保留历史,但不会接管父 Session 的活动提醒。`schedule/change` 声明和源码位置也编入[持久化目录](../persistence-catalog.md#schedulechange--log-only)。 + +## 活动视图与管理 + +工具值将持久记录与根据当前墙钟派生的交付状态组合起来。`session-local` 表示原 Session 必须处于 live 状态:不存在外部通知渠道或 cold Session scheduler。 + +```ts type-equiv +/** Current delivery timing derived from the durable record and wall clock. */ +type ScheduleState = 'scheduled' | 'overdue' +``` + +```ts type-equiv +/** Fixed v1 delivery boundary: the original session must be live. */ +type ScheduleDeliveryMode = 'session-local' +``` + +```ts type-equiv +/** Complete model-facing view of one active after reminder. */ +interface ScheduleView extends AfterScheduleRecord { + /** Whether the target remains in the future. */ + readonly state: ScheduleState + /** Reminder delivery never leaves the owning session. */ + readonly deliveryMode: ScheduleDeliveryMode +} +``` + +生成的[工具目录](../tool-catalog.md#deepseek-aidsh-tool-schedule)负责 `schedule_create`、`schedule_list` 和 `schedule_delete` 的参数与结果 schema。一条 Agent-scoped 队列将管理调用与到期工作串行化。每次读取或判断都会先等待共享的 Session 持久化 barrier;create 与实际执行的 delete 在追加后还会再次等待。barrier 失败会报告 `persistence_uncertain`,而不是猜测 eager write 是否已提交。其他稳定错误代码是 `invalid_prompt`、`invalid_selector`、`invalid_rule`、`time_out_of_range`、`corrupt_schedule_log` 和 `internal_error`。 + +## Live 交付 + +进程内 owner 根据持久 fold 派生最早的 timer,并在每次有界等待后重新读取墙钟。cold Session 不执行任何工作;重新打开后会重建 timer,并使已经过去的目标进入 overdue 状态。overdue 提醒会先等待 Agent 完全 idle 并认领 maintenance phase,再重新折叠状态、将 `followup()` 排入队列并追加 dispatch。它绝不会调用 `steer()`,也绝不会中断当前轮次。 + +获得准入的 follow-up 会启动一个普通的后续轮次,且只通过普通对话 transcript(文本记录)出现;Schedule 不提供独立的持久 Web 回执或浏览器渲染器。如果 framing 构造或同步队列准入失败,则不会记录 dispatch,提醒仍保持活动。follow-up 获得准入后、持久 dispatch 前的狭窄崩溃窗口可能使提醒在恢复后重复,因此该边界提供的是尽力而为的至少一次交付,而非恰好一次交付。 diff --git a/packages/schedule/README.i18n.yaml b/packages/schedule/README.i18n.yaml index 4185bd68c9..887574d451 100644 --- a/packages/schedule/README.i18n.yaml +++ b/packages/schedule/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/schedule/README.md -README.md: edcd84b11444c596028cbd9ccae3926e4fbfdca8 -README.zh.md: 09e6cb5bdd1a726cfa6c964438df011035ba0a1e +README.md: 7fffe6efb91e92a5664ee30ef9bf7c77581fe346 +README.zh.md: a819dcb0e57cae834813479713d598ef26ce4ed3 diff --git a/packages/schedule/README.md b/packages/schedule/README.md index edcd84b114..7fffe6efb9 100644 --- a/packages/schedule/README.md +++ b/packages/schedule/README.md @@ -9,3 +9,5 @@ The Schedule family owns reminders whose durable state lives in the original Ses | `tool-schedule/` | Versioned Schedule events and fold, model-facing create/list/delete tools, and a live root-Agent timer owner | — | The package deliberately exposes no public Schedule service or mutable database. Tools and runtime append to the Session stream; due work enters the same conversation through the Agent's ordinary follow-up queue. + +See [Session-local Schedule](../../docs/subsystems/schedule.md) for the durable record, transition, view, and delivery contracts. diff --git a/packages/schedule/README.zh.md b/packages/schedule/README.zh.md index 09e6cb5bdd..a819dcb0e5 100644 --- a/packages/schedule/README.zh.md +++ b/packages/schedule/README.zh.md @@ -9,3 +9,5 @@ Schedule 家族负责管理提醒,其持久状态保存在原 Session 日志 | `tool-schedule/` | 版本化 Schedule 事件与 fold、面向模型的创建/列出/删除工具,以及 live 根 Agent timer owner | 无 | 本包有意不公开 Schedule service 或可变数据库。工具与 runtime 向 Session stream 追加事件;到期工作通过 Agent 的普通 follow-up 队列进入同一对话。 + +有关持久记录、转换、视图与交付约定,请参阅[仅限 Session 内的 Schedule](../../docs/subsystems/schedule.md)。 diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index 173d4222cb..f9c7f531fa 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -39,6 +39,7 @@ const LINK_MAP: Record = { CallId: 'core.md', ContentBlock: 'core.md', MessageSource: 'core.md', + ScheduleChange: 'schedule.md', StreamChunk: 'llm-streaming.md', TokenUsage: 'llm-streaming.md', TodoItem: 'session.md', diff --git a/scripts/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts index d3f00f3408..76204092c6 100644 --- a/scripts/project-doc-site.spec.ts +++ b/scripts/project-doc-site.spec.ts @@ -251,7 +251,7 @@ describe('rewriteMarkdown', () => { }) describe('docsPages locale routes', () => { - it('publishes every route in both locales and selects paired sources', () => { + it('publishes every route in both locales and uses every available Chinese counterpart', () => { const byRoute = new Map(docsPages.map(page => [page.route, page])) for (const page of docsPages.filter(page => page.locale === 'root')) { const counterpart = byRoute.get(`en/${page.route}`) @@ -265,6 +265,11 @@ describe('docsPages locale routes', () => { } else { expect(counterpart?.source).toBe(page.source) expect(counterpart?.contentLocale).toBe(page.contentLocale) + const chineseSource = page.source.replace(/\.md$/, '.zh.md') + expect( + existsSync(resolve(repositoryRoot, chineseSource)), + `${page.route} has a Chinese counterpart but projects English`, + ).toBe(false) } } }) @@ -282,20 +287,16 @@ describe('docsPages locale routes', () => { } }) - it('projects translated subsystem pages while retaining explicit English fallbacks', () => { + it('projects every published subsystem page in Chinese', () => { const rootPages = docsPages.filter(page => ( page.locale === 'root' && page.route.startsWith('reference/subsystems/') )) const translated = rootPages.filter(page => page.contentLocale === 'zh-CN') const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US') - expect(translated).toHaveLength(39) + expect(translated).toHaveLength(43) expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true) - expect(fallbacks.map(page => page.source).sort()).toEqual([ - 'docs/subsystems/commands.md', - 'docs/subsystems/goal.md', - 'docs/subsystems/pty.md', - ]) + expect(fallbacks).toEqual([]) }) it('publishes the Cordis core API under matching locale structures', () => { diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index fccabef852..76ea958dfb 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -221,6 +221,51 @@ "symbol": "GoalChanged", "source": "packages/goal/goal/src/domain.ts" }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "AfterScheduleRecord", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleRecord", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleCreateChange", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleDeleteChange", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleDispatchChange", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleChange", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleState", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleDeliveryMode", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, + { + "doc": "docs/subsystems/schedule.md", + "symbol": "ScheduleView", + "source": "packages/schedule/tool-schedule/src/types.ts" + }, { "doc": "docs/subsystems/commands.md", "symbol": "CommandInputDescriptor", diff --git a/website/docs.ts b/website/docs.ts index a8d07a59c2..97a11be25d 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -226,7 +226,7 @@ const develop = pairedPages([ }, ]) -const cordisTutorial = mirroredPages(([ +const cordisTutorial = pairedPages(([ ['index.md', 'Cordis 教程', 'Cordis tutorial'], ['01-first-plugin.md', '1. 第一个插件', '1. Your first plugin'], ['02-lifecycle-and-effects.md', '2. 生命周期与副作用', '2. Lifecycle and effects'], @@ -235,10 +235,9 @@ const cordisTutorial = mirroredPages(([ ['05-config.md', '5. 配置', '5. Configuration'], ['06-composition-and-hmr.md', '6. 组合与热重载', '6. Composition and HMR'], ['07-into-the-harness.md', '7. 进入 Harness', '7. Into the harness'], -] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({ +] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({ source: `docs/cordis-tutorial/${file}`, route: `develop/cordis-tutorial/${file}`, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-develop', en: 'en-develop' }, section: { root: 'Cordis 教程', en: 'Cordis tutorial' }, @@ -308,12 +307,11 @@ const subsystemsReference = pairedPages(([ }))) const reference = [ - ...mirroredPages(([ + ...pairedPages(([ ['docs/architecture.md', 'reference/index.md', '架构', 'Architecture', 0], - ] as const).map(([source, route, rootLabel, enLabel, order]): MirroredPage => ({ + ] as const).map(([source, route, rootLabel, enLabel, order]): PairedPage => ({ source, route, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, section: { root: '概念', en: 'Concepts' }, @@ -369,28 +367,27 @@ const reference = [ section: { root: 'Cordis API', en: 'Cordis Core API' }, order: order + 5, }))), - ...mirroredPages(([ + ...pairedPages(([ ['goal.md', '目标', 'Goals', 14], + ['schedule.md', '定时提醒', 'Scheduled reminders', 15], ['pty.md', 'PTY 会话', 'PTY sessions', 26], ['commands.md', '命令', 'Human commands', 38], - ] as const).map(([file, rootLabel, enLabel, order]): MirroredPage => ({ + ] as const).map(([file, rootLabel, enLabel, order]): PairedPage => ({ source: `docs/subsystems/${file}`, route: `reference/subsystems/${file}`, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, section: { root: '子系统', en: 'Subsystems' }, order, }))), - ...mirroredPages(([ + ...pairedPages(([ ['adding-a-package.md', '新增 Package', 'Adding a package'], ['adding-a-tool.md', '新增 Tool', 'Adding a tool'], ['adding-an-llm-adapter.md', '新增 LLM Adapter', 'Adding an LLM adapter'], ['extension-cookbook.md', '扩展模式', 'Extension patterns'], - ] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({ + ] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({ source: `docs/cookbook/${file}`, route: `reference/cookbook/${file}`, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, section: { root: '开发手册', en: 'Cookbook' },