Skip to content

Commit c07827e

Browse files
authored
Merge pull request #46 from omdsh-dev/docs/align-topdocs-ci
docs: add root AGENTS.md + CONCEPTS.md, align CI with sibling
2 parents cf34eec + 23926ce commit c07827e

3 files changed

Lines changed: 188 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ on:
55
push:
66
branches:
77
- main
8+
workflow_dispatch:
89

910
concurrency:
10-
group: ci-${{ github.ref }}
11+
group: ci-${{ github.workflow }}-${{ github.ref }}
1112
cancel-in-progress: true
1213

1314
jobs:
1415
validate:
1516
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
timeout-minutes: 20
1620
steps:
1721
- uses: actions/checkout@v4
1822

AGENTS.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# AGENTS.md — dsh-advisor
2+
3+
Project-level rules for coding agents in this repository. This file is the
4+
**project layer**; the Morning Star harness lives under `.mstar/` (see
5+
[Process artifacts](#process-artifacts)).
6+
7+
## Source priority
8+
9+
1. The current user instruction
10+
2. This file (project rules)
11+
3. `.mstar/` harness rules (`mstar-*` skills)
12+
4. Role references (`mstar-roles`)
13+
14+
## Repository
15+
16+
`dsh-advisor` is a standalone TypeScript plugin bundle for dsh (DeepSeek
17+
Harness), porting the omp "advisor" subsystem: a **per-session reviewer
18+
model** that observes the primary transcript, reviews each stepped turn with
19+
an explicitly configured model (provider + model are required), and injects
20+
severity-ranked advice (nit / concern / blocker) back into the session —
21+
without polluting or recursively reviewing itself. It is advisory only: it
22+
never approves or rejects the primary agent's actions.
23+
24+
- Mounts into dsh as a **bundle** (`dsh.bundle.patch``cordis.patch.yml`)
25+
inserting one plugin row (`id: advisor`) — no host patching, no
26+
postinstall step.
27+
- Runtime: Node ^22.19 || >=24, pnpm 11.21+ (project stack: pnpm 11.21.0 in
28+
CI). Private `@deepseek-ai/*` packages are `peerDependencies` ONLY,
29+
resolved from the npm registry (see [Code constraints](#code-constraints)).
30+
- License: MIT (`LICENSE` is authoritative).
31+
32+
## Build & test interface
33+
34+
- `pnpm typecheck` — full typecheck (`tsc --noEmit` over the main, client,
35+
and spec tsconfigs).
36+
- `pnpm build``tsc -p tsconfig.build.json``node scripts/build-client.mjs`.
37+
- `pnpm test` — vitest run (full suite; includes the release-script
38+
contract tests in `tests/prepare-release.test.ts`).
39+
- **Release prep — no `pnpm release:*` scripts**: the Release prep workflow
40+
(`.github/workflows/release-prep.yml`) runs
41+
`node scripts/prepare-release.mjs [version]` (see
42+
[Release flow](#release-flow)).
43+
- Local workflow lint (not in CI): `actionlint .github/workflows/*.yml`
44+
(ci + release-prep + release).
45+
46+
## Changelog
47+
48+
`CHANGELOG.md` is **auto-generated from git log** by
49+
`scripts/prepare-release.mjs` during release prep: it resolves the commit
50+
range since the previous release tag and writes a `## [X.Y.Z] - YYYY-MM-DD`
51+
section (existing sections are preserved; re-running the same version does
52+
not duplicate it). There is **no `.changes/` fragment model** (unlike the
53+
sibling dsh-llm-fallbacks) — nothing to write per change; the changelog
54+
section is derived from the commits that ship in the release PR.
55+
56+
## Release flow
57+
58+
PR-driven, two steps — merging the release PR is the ONLY publish path
59+
(no `push:tags` auto-publish):
60+
61+
1. **Release prep** (manual: Actions → Release prep → Run workflow): runs
62+
`scripts/prepare-release.mjs`, bumps `package.json` (+ lockfile sync),
63+
validates (typecheck + build + test), and opens/updates a
64+
`release vX.Y.Z` PR (base `main`, label `release`). Pass an explicit
65+
version (`X.Y.Z` or `X.Y.Z-alpha.N`) or leave the input empty for an
66+
auto bump that **stays on the current prerelease line**
67+
(`0.1.3-alpha.3``0.1.3-alpha.4`; it never silently drops the suffix
68+
to a formal version). An empty commit range (no releaseable content)
69+
or an already-tagged version fails the prep.
70+
2. **Merge the PR**`release.yml` runs on the merge commit: validate →
71+
sanity gate (typecheck + build + test) → `npm publish --provenance
72+
--access public` (OIDC trusted publishing, no npm secrets) → tag
73+
`vX.Y.Z` → GitHub Release. Prerelease versions publish to their
74+
prerelease dist-tag (`alpha`, never touching `latest`) and are marked
75+
Pre-release on GitHub; formal versions publish to `latest`.
76+
77+
> **Current policy (maintainer-owned; full callouts in `docs/release.md`):**
78+
> release ONLY alpha prereleases (`X.Y.Z-alpha.N`) until further notice,
79+
> and ONLY when the merged content carries substantive changes (code /
80+
> behavior / user-visible docs or config). Trivial commits merge straight
81+
> to `main` and ride the next substantive release.
82+
83+
Secrets: **zero long-term secrets**. npm auth is OIDC trusted publishing
84+
(tokenless); workflows use only the built-in `GITHUB_TOKEN` (release-prep
85+
additionally uses a PAT for the label/PR steps). Full SOP and rollback →
86+
`docs/release.md`.
87+
88+
## Code constraints
89+
90+
- **Mount-only**: the plugin never modifies the dsh source tree (bundle
91+
insert only; no patches, no postinstall step). Keep it that way.
92+
- **Peers from the public registry**: all `@deepseek-ai/*` packages are
93+
`peerDependencies` ONLY — resolved from the npm registry at dev time
94+
(`autoInstallPeers` in `pnpm-workspace.yaml` + user-level `~/.npmrc`
95+
token), never added to `dependencies`/`devDependencies`, never linked
96+
locally. `tests/peer-deps.test.ts` enforces this contract.
97+
- **English commit messages**, conventional style (`feat:`, `fix:`,
98+
`docs:`, `chore:`, ...).
99+
- **Feature branch → PR → main** for all changes; never commit directly to
100+
`main`.
101+
- Match existing patterns and keep diffs surgical.
102+
103+
## Process artifacts
104+
105+
`.mstar/` is the Morning Star harness's local process SSOT: process artifacts
106+
(`plans/`, `sdd/`, `status.json`, `iterations/`, ...) are **gitignored**;
107+
tracked results are `.mstar/knowledge/` and `.mstar/specs/` (the frozen
108+
`advisor-plugin.md` spec is the `primary_spec` anchor). Harness rules live
109+
in the `mstar-*` skills; harness-layer details (path symbols, content
110+
boundaries) are in `.mstar/AGENTS.md`. This file intentionally carries no
111+
dynamic state, current progress, or review details.

CONCEPTS.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# CONCEPTS
2+
3+
本仓库(dsh-advisor)领域词汇。供 `{KNOWLEDGE_DIR}` 与 AGENTS.md 引用,避免重复定义。
4+
5+
## 顾问机制(advisor mechanism)
6+
7+
### advisor fiber / reviewer claim(评审者认领)
8+
宿主会组合多个 dsh-advisor fiber(实测 3 个 active);若每个 fiber 都挂全局 `session/event` 订阅,就会对每个回合产生 N×review / N×model-call。**第一个**成功 `apply` 的 fiber 通过 `globalThis['__dshAdvisorReviewer__']``REVIEWER_KEY`)认领评审者角色,负责接线 observer / runtime / delivery 与 `/advisor` 命令;后续 fiber 只尝试 settings 注册并保持惰性。认领在构造期配置门禁之后才进行(被拒绝的首行不会留下已认领标记),fiber dispose 时释放,允许重新挂载接管。标记骑在 `globalThis` 上,即使模块拷贝分歧也保持唯一。
9+
*Avoid:* 每个 fiber 都挂全局订阅(N×评审 / N×调用);绕过认领做多实例并行评审
10+
11+
### severity ranks(严重度等级)
12+
每条建议携带的闭集严重度 `nit | concern | blocker``AdviceSeverity`),升级序 nit < concern < blocker。nit = 非唤醒的小问题;concern = 值得打断的实质风险;blocker = 保留给「继续明显浪费工作」(违背显式用户指令、原地打转、根本性不健全)。缺失 / 非法 severity 的回复按 nit 处理(最小侵入默认,与 omp「省略即 nit」一致)。
13+
*Avoid:* 自造第四档;把 blocker 当普通提醒使用
14+
15+
### advice delivery(建议投递)
16+
severity → 通道的路由:nit → `agent.inject`(非唤醒,下一个 step 边界消费);concern/blocker → `agent.steer`(唤醒——空闲 driver 开跑、运行中的 driver 在下一 step 边界消费)。投递消息为 user-role、`source.kind === 'advisor'``MessageSourceMap` merge 扩展,见 `src/kinds.ts`)、内容自描述 `[advisor:{severity}] {note}`——主系统提示词不提 advisor,前缀是主模型判断「建议,不要盲从」的唯一提示。投递同步 fire-and-forget,throw 由 runtime 的包含 seam 兜住。
17+
*Avoid:* 用 inject 承载 concern/blocker(破坏唤醒语义);把建议当「已批准动作」注入
18+
19+
### immuneTurns cooldown(免疫回合冷却)
20+
一次 concern/blocker 被**真实 steer** 后,接下来 `immuneTurns`(默认 3)个 stepped 主回合完成前,不允许再有一条打断性 note 触发 steer;窗口内的打断性 note 降级为 inject。围栏只在真实 steer 投递时武装,观察者的 `onSteppedTurnEnd` / `onRewrite` 钩子驱动计数与 KD-5 重置。
21+
*Avoid:* 把窗口当成「每次注入后都冷却」(只有真实 steer 武装围栏);跳过回合计数直接恢复
22+
23+
### explicit model gate(显式模型门禁)
24+
`enabled: true``provider``model` 缺失 / 为空 → advisor **永不发起模型调用**,解析为 disabled-with-reason(`/advisor status` 展示原因)。硬门禁而非警告;`resolveAdvisorConfig` 是包括 gateway、runtime 在内所有消费者的 SSOT。未知配置键在 schema 层拒绝(严格 schema)。
25+
*Avoid:* enabled 缺 provider/model 时静默改用默认模型;把门禁降级为 warning
26+
27+
### JSON frame(建议帧)
28+
advisor 回复必须恰好是一个 JSON 对象 `{"note": "<text>", "severity": "nit"|"concern"|"blocker"}`(severity 可省略 = nit)。提取:定位回复中第一个平衡 `{…}`(容忍前后散文 / markdown 围栏,跳过字符串字面量内的花括号)后 `JSON.parse`。note 非空字符串校验失败 → drop + log(绝不 crash drain);解析失败**不重试**(重试预算只留给传输错误)。调用带 token 上限(`ADVISOR_MAX_TOKENS`),单条 note 有长度上限(`ADVISOR_NOTE_MAX_CHARS`,超长以 `` 截断)。
29+
*Avoid:* 让 advisor 自由文本回复(帧契约失效);解析失败无限重试
30+
31+
### transcript delta / self-review exclusion(转录增量与自审排除)
32+
DeltaRenderer 用 cursor 在 `session.events` 序列上推进 + 已投递前缀的 fingerprint;检测到前缀重写(fingerprint 失配或 `surfaceOp replace`)→ 重置并全量重放。渲染为 role 标注的 markdown(`**user:**` / `**agent:**`,assistant 文本加 tool intent、tool 结果标 `[tool result]`、reasoning 排除)。`source.kind === 'advisor'` 的消息**永不**进入后续增量——advisor 不会读回自己的注入。`maxDeltaMessages`(默认 60,0 = 无界)约束重放窗口,超限保留最近 N 条并前置 `… <earlier messages omitted>` 标记。
33+
*Avoid:* 把 advisor 自己的注入读回增量(自审污染);长会话无界全量重放
34+
35+
### emission guard(发射守卫)
36+
T4 提取与 T6 投递之间的守门员:normalize(小写 + NFKC + 非字母数字折叠为单空格)、content-free 短语抑制(`stop`/`done`/`complete`/`lgtm` 等)、one-note-per-update 限流、FIFO 去重(上限 4096,omp parity)带**升级放行**——同文重复在等 / 低 severity 被抑制,真实升级(nit→concern→blocker)放行并更新记忆。`accept` 返回 false 时静默抑制,调用方无法区分接受与抑制,也不抛错。
37+
*Avoid:* 绕过守卫直接投递(噪音 + 重复注入);把升级误判为重复而抑制
38+
39+
### failure policy(失败策略 / no-stall)
40+
异步 drain + 有界 backlog(默认 32):transient → 1 次重试后 drop;连续 3 次 drop → flush backlog;quota / rate-limit → pause(状态 `quota_exhausted`)并 requeue,无自动恢复定时器;permanent(`invalid_request_error` / model-not-found)→ halt 该会话 advisor;in-flight 调用在 dispose 时经 signal abort。整次调用有 deadline(`ADVISOR_CALL_TIMEOUT`)。**主循环永不 park**(MVP 无 `syncBacklog` 等待)——失败的 advisor 绝不阻塞主回合。
41+
*Avoid:* 让 advisor 失败阻塞 / 拖慢主循环;无界重试
42+
43+
### reset triggers / seed-on-enable(重置触发与启用播种)
44+
重置(renderer cursor + emission guard + immuneTurns 闩锁):任何 `compact/*` 事件(`compact/start` | `summary` | `end`)、或带 `surfaceOp.op === 'replace'``user/message`(KD-5 权威触发;fingerprint 检查兜底)。`/advisor on` 会话中途启用 → `seedTo(currentLength)` 只把 cursor 播到当前转录长度,不全量回放(与 omp 一致)。
45+
*Avoid:* 把 compaction 重写当普通追加继续增量(增量基准失效)
46+
47+
## 插件与宿主集成
48+
49+
### settings gateway(设置命名空间 + 配置网关)
50+
`advisor` settings namespace(`settingsNamespace('advisor')`):插件行配置是组合**基底**,settings 服务挂载时用户层叠加(schema defaults → 基底 → 用户层),runtime 经 bridge source 读**实时**组合值。宿主侧 `AdvisorConfigGateway`(`TypertRemoteService` 基类 + **显式 `ctx.typert.register(contribution)`**,非 `@Remote` SRC 标记——SRC 发现读模块私有 WeakMap,link 插件与 dlx 宿主物理分离永不共享)声明 `/api/advisor/get` + `/api/advisor/set`;client 经 `connection.rpc.call('/api', '<ns>/<method>', { args })` 读写。apiproxy `exposedNamespaces()` 白名单**不含** advisor 命名空间(上游 dsh 无注册级 opt-in,`exposeToWebClients` 不存在),gateway 是 mount-only 下 web 配置读写的唯一路径;`set` 先过 `Config` schema(未知键拒绝)再经进程内 `ctx.settings.update` 写用户层——进程内写无 exposed-namespace 检查,白名单闸只在 apiproxy wire 层。settings 服务缺省时 gateway `get` 仍工作(bridge source 回落 entry),`set` 明确报错(KD-G5 兜底)。
51+
*Avoid:* 依赖 apiproxy `describe` 读写 advisor 配置(白名单不含 advisor);把 gateway 当唯一配置源而不回退 bridge
52+
53+
### mount-only bundle(纯挂载 bundle)
54+
交付约束:对 dsh 源码树**零本地修改**`dsh.bundle.patch``cordis.patch.yml` 向 profile 插入一行插件(`id: advisor`);registry / tarball 安装携带构建产物(`lib/` + `cordis.patch.yml`),无 install / postinstall 脚本、无需构建权限。无 `patches/`、无 autopatch 链路;升级 dsh 无需重打。本地 `dsh plugin add .` 走 pnpm `link:`,pnpm 不跑 prepare——需先 `pnpm install` / `pnpm build` 出 bundle。
55+
*Avoid:* patch 交付 / 本地修改交付(历史方案,已移除)
56+
57+
### registry peers(开发期依赖解析)
58+
私有 `@deepseek-ai/*`****`peerDependencies`(绝不进 `dependencies` / `devDependencies`):`pnpm-workspace.yaml``autoInstallPeers: true` + 用户级 `~/.npmrc` registry 认证令牌从 npm registry 解析 `@deepseek-ai/*@0.1.0-rc.6`(pnpm 11 起项目级 `.npmrc` 不再展开 `${NPM_TOKEN}`;无本地 link farm,`nodeLinker: hoisted`)。`tests/peer-deps.test.ts` 数据驱动钉住该契约(peer-only、rc.6 钉版、autoInstallPeers、scoped schemastery、prepare 仅 build)。运行时值 import 保持 external,由宿主 in-box / 扁平 profile 模块 fallback 解析。
59+
*Avoid:* peer-stubs / tsconfig paths / 本地 link farm(历史方案,已移除)
60+
61+
### /advisor commands
62+
`/advisor on | off | status | usage`(T7):session-scoped 临时 override(不写持久配置),runtime gate 读取;`status` 展示运行态(running / paused / quota_exhausted / halted / disabled)、门禁 disabled 原因、resolved provider/model、pending 数与最近一次接受 note 的活动时间。命令经条件 `ctx.inject(['commands'], ...)` 子 fiber 注册,宿主无 commands 服务时静默不注册。
63+
*Avoid:*`/advisor` 当持久配置写入入口(override 是临时的、会话级)
64+
65+
## 已决歧义
66+
67+
- `nit` / `concern` / `blocker` 三档 severity 是**闭集**:缺失 / 非法值按 `nit`(最小侵入默认),不要在代码里新增第四档。
68+
- `inject``steer`:前者非唤醒、后者唤醒;免疫窗口内打断性 note **降级为 inject**,不是丢弃。
69+
- `immuneTurns` 只在**真实 steer** 后武装:仅注入(inject)不启动冷却。
70+
- advisor 命名空间在 apiproxy 白名单之外:web 配置读写只走 gateway RPC 通道(`/api/advisor/get|set`);进程内 `ctx.settings.update` 无白名单检查,`exposedNamespaces()` 只管 apiproxy wire 路径。
71+
- 配置组合:schema defaults → 插件行基底 → settings 用户层;`resolveAdvisorConfig` 是硬门禁的 SSOT,所有读取(runtime / gateway / status)都过它。
72+
- `source.kind === 'advisor'` 消息的双重角色:投递时标记(会话流可见)与自审排除(不进后续 delta)——两者都 key 在同一 kind 上。

0 commit comments

Comments
 (0)