@morlay/dsh-preset 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,12 +6,16 @@
6
6
 
7
7
  ## 内容
8
8
 
9
- | 文件 | 作用 |
10
- | -------------------------- | --------------------------------------------------------------------------------- |
11
- | `cordis.patch.yml` | bundle patch:禁用官方 preset、注册本包 preset 为默认、声明个人 `llm-pi-ai` route |
12
- | `tool/generate-presets.ts` | 从上游生成 preset 的模块 + tsdown hooks |
13
- | `dist/presets/standard/` | 构建产物:自定义 preset「标准模式」(由上游 `standard` 生成) |
14
- | `dist/presets/ptc/` | 构建产物:自定义 preset「PTC 模式」(由上游 `ptc` 生成) |
9
+ | 文件 | 作用 |
10
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
11
+ | `cordis.patch.yml` | bundle patch:禁用官方 preset、注册本包 preset 为默认、声明个人 `llm-pi-ai` route、插入 `prompt-reminder` 行 |
12
+ | `tool/generate-presets.ts` | 从上游生成 preset 的模块 + tsdown hooks |
13
+ | `dist/presets/standard/` | 构建产物:自定义 preset「标准模式」(由上游 `standard` 生成) |
14
+ | `dist/presets/ptc/` | 构建产物:自定义 preset「PTC 模式」(由上游 `ptc` 生成) |
15
+
16
+ `cordis.patch.yml` 里插入的 `prompt-reminder`(`@morlay/dsh-prompt-reminder`)把被
17
+ 裁掉的系统提示词 section 降级为 `<system-reminder>` user 消息,见「工具说明降级为
18
+ reminder」。
15
19
 
16
20
  ## 禁用官方 preset
17
21
 
@@ -47,10 +51,75 @@ preset 自带的 `persona` 行会在 agent scope 注册 `deployment:persona-pref
47
51
  固化了该语义),所以生成器把那一行**删掉**,而不是复制一份改过的 persona——同一份
48
52
  文案只维护一处。
49
53
 
54
+ ### 指令遵循设计
55
+
56
+ `personaPrefix` 位于 order 0(prompt 最前,紧跟被关掉的 harness identity 之后):
57
+ 声明指令优先级(直接用户指令 > 工作区 `AGENTS.md` > 本系统提示词)与执行协议。其中
58
+ 点名 `AGENTS.md` 是**强制约束**——官方 `agent-instructions` 注入模板的措辞
59
+ ("may be relevant" / "as guidance" / "do not override…")是上游的通用保守表述,会让
60
+ 模型把仓库规则当参考而不是规则,所以这里显式反制。协议一行覆盖:动手前读相关
61
+ `AGENTS.md`、过程自检、交付前核对红线与验证证据、无法遵守时明确说明、派发子代理时
62
+ 把约束写进任务说明。
63
+
64
+ `personaSuffix` 位于 order 10200(prompt 最后):一句话复述"结束任务前对照本提示词与
65
+ `AGENTS.md` 自检"。长 prompt 里中段指令会被稀释,首尾各放一次最关键的约束,用近因
66
+ 位置兜住遵循度;细节只在 prefix 维护,避免两处重复。
67
+
68
+ 改这两段后**必须重启 dsh 才能生效**:profile 在进程启动时装载,`system-prompt` 的
69
+ section 在插件构造时注册;dev 模式重启 `just custom dev`/`just custom desktop`,打包
70
+ 形态需重新 `just custom bundle`(patch 随 seed 快照复制)。
71
+
50
72
  自建 preset 仍然必要,但只为别的事:`includeShippedRoot: false` 之后需要一份自己的
51
73
  roster(id 沿用官方名以走客户端语言字典),以及桌面形态下 preset 必须落在 dsh 包的
52
74
  `config/agent-presets` 挂载点(见 dsh-desktopify 的 `dsh.desktop.agentPresets`)。
53
75
 
76
+ ## 工具说明降级为 reminder,不进系统提示词
77
+
78
+ 上游把每个工具的跨调用说明(`tool:bash`、`tool:read`、…)、Agent Teams 协作规则、
79
+ harness 源码位置、Web GUI 说明都注册成系统提示词的 section。本部署实测它们占
80
+ 9200 字符里的约 7400:条数多、篇幅大、挤在上下文最前面,稀释真正要遵守的那几行
81
+ 纪律。`cordis.patch.yml` 因此在 host plane 插一行:
82
+
83
+ ```yaml
84
+ - insert:
85
+ - id: prompt-reminder
86
+ name: "@morlay/dsh-prompt-reminder"
87
+ ```
88
+
89
+ 一处定义覆盖全部 preset(standard / ptc 以及后续新增的)——这是与具体 preset 无关
90
+ 的部署级策略,preset composition 不各自带这一行,生成器也就不必碰它。该插件注册
91
+ 在 root scope,会收到每个 agent 的装配与 pre-step 事件(含子 agent):
92
+
93
+ - 装配(`system-prompt/assemble` 瀑布)时保留 `keep` 名单——默认是部署级
94
+ `deployment:persona-prefix` / `deployment:persona-suffix`——内的 section,其余
95
+ 非空 section 从 assembly 移除并按 order 捕获文本;
96
+ - pre-step 时把捕获的文本作为一条 `<system-reminder>` user 消息注入,紧随本轮
97
+ 用户消息之后;文本不变不重复注入,`plan:policy` / `tools:ptc-only` 这类按模式
98
+ 渲染的 section 变化时追加一条新的全量 reminder(首行声明最新一条覆盖更早的)。
99
+
100
+ 于是系统提示词只剩首尾两段个人提示词,其余内容仍在上下文里、但不再占系统提示词
101
+ 的位置,也不再随模式切换改动。**为什么不用 `persona` 行的 `complete: true`**:那会
102
+ 连 suffix 一起丢,且子 agent 自己注册同名 persona 时 `complete` 语义消失,保留 /
103
+ 降级会不一致;本插件按 `keep` 名单匹配 section 名,两种 agent 行为一致。完整行为与
104
+ 配置见 `packages/preset/dsh-prompt-reminder/README.md`。
105
+
106
+ ## 工作区指令走官方行为,候选收紧为 AGENTS 系列
107
+
108
+ preset 里的 `agent-instructions` 行是官方 `@deepseek-ai/dsh-agent-instructions`:
109
+ baseline 按官方语义作为 user 消息注入一次。生成器只覆盖该行 config 的两个字段:
110
+
111
+ ```yaml
112
+ instructionFileCandidates: ["AGENTS.md"] # 上游默认 ['AGENTS.md','CLAUDE.md']
113
+ localInstructionFileCandidates: ["AGENTS.local.md"] # 上游默认还含 CLAUDE.local.md
114
+ ```
115
+
116
+ 即**不读 CLAUDE 系列**(本部署不需要 CLAUDE 兼容);`maxBytes` 等其余字段跟随上游。
117
+
118
+ 本仓库曾有一个把 baseline 落点改成 system-prompt section 的 fork
119
+ (`@morlay/dsh-agent-instructions-as-prompt`),一直未接入默认打包;提示词分层定为
120
+ 「系统提示词只留 persona、其余走 reminder」之后它已删除——baseline 留在 user 消息
121
+ 通道与这个分层一致。
122
+
54
123
  ## preset 由构建生成,不是手工副本
55
124
 
56
125
  产物落在 `dist/`,由 tsdown 的 `build:done` hook 在每次 `pnpm build` 时生成
@@ -68,11 +137,11 @@ pnpm --filter @morlay/dsh-preset run generate-presets [outDir]
68
137
  ```
69
138
 
70
139
  脚本把上游 composition 当**数据**读入:`js-yaml` 用 include 的
71
- `entryListSchema` 解析(`!!js` 标签保留为表达式节点)→ 在 JS 里改两处(删掉
72
- `persona` 行、把 `agent-instructions` 行指向本仓库的 fork)→ `dump` 回 YAML。
73
- 因此上游任何结构性改动(新增 / 重命名 row、改字段)都自动跟随,不依赖易碎的文本
74
- 锚点;上游若删掉 `persona` 行,目标(preset 不自带 persona)本就达成,脚本照常
75
- 通过。
140
+ `entryListSchema` 解析(`!!js` 标签保留为表达式节点)→ 在 JS 里改三处(删掉
141
+ `persona` 行、把 `agent-instructions` 行的候选收紧为 AGENTS 系列、插入
142
+ `prompt-reminder` 行)→ `dump` 回 YAML。因此上游任何结构性改动(新增 / 重命名
143
+ row、改字段)都自动跟随,不依赖易碎的文本锚点;上游若删掉 `persona` 行,目标
144
+ (preset 不自带 persona)本就达成,脚本照常通过。
76
145
 
77
146
  脚本先**整目录清空**输出目录再生成:产物完全派生自脚本,残留目录(改过
78
147
  `source`、旧命名)不该留下——否则 discovery 会把它们当有效 preset 扫出来。
@@ -85,7 +154,7 @@ pnpm --filter @morlay/dsh-preset run generate-presets [outDir]
85
154
 
86
155
  1. 升级 `DEEPSEEK_HARNESS_VERSION` 并 sync/patch/build。
87
156
  2. `pnpm --filter @morlay/dsh-preset run build`(build:done 会重新生成)
88
- 3. `pnpm exec vitest run packages/preset/dsh-preset` 确认无 drift
157
+ 3. `pnpm exec vitest run packages/preset` 确认无 drift(含 reminder 插件的行为测试)。
89
158
 
90
159
  ## 装配
91
160
 
@@ -126,6 +195,9 @@ profile 里把内容物化到该挂载点;`includeShippedRoot: false` 两种
126
195
  ## 维护注意
127
196
 
128
197
  - `package.json` 的 `files` 必须含 `dist`(preset 在其中)与 `tool`,否则发布产物缺内容。
198
+ - `cordis.patch.yml` 插入的 `@morlay/dsh-prompt-reminder` 必须能被 **profile** 的依赖
199
+ 树解析:示例 app 已在 `dependencies` 声明;换工作区时要一并声明,否则该行加载失败、
200
+ 工具说明会留在系统提示词里。
129
201
  - 生成器显式设 `quotingType: '"'`:`yaml.dump` 默认单引号而仓库 oxfmt 偏好双引号,
130
202
  不指定会让生成器与 formatter 来回改;产物在 gitignore 的 dist 里,本就不参与 fmt。
131
203
  - **dev 模式需要先构建**:`just dev` / `just desktop` / `just bundle` 都先跑
package/cordis.patch.yml CHANGED
@@ -2,15 +2,16 @@
2
2
  config:
3
3
  includeHarnessIdentity: false
4
4
  includeRuntimeContext: false
5
- personaSuffix: "你的工作目录在 `{{cwd}}`"
5
+ personaSuffix: |-
6
+ 你的工作目录在 `{{cwd}}`,工作目录内的 AGENTS.md 的规则非常重要,必须严格遵守
6
7
  personaPrefix: |-
7
8
  你是一个经验丰富的编程专家,YAGNI 是你的编程哲学,PDCA 是你的行为规范
8
9
 
9
- ## 语言与行为
10
+ ## 语言和行为
10
11
 
11
12
  - 所有思考、分析、推理过程、工具调用描述等必须使用中文,专有名词除外
12
13
  - 思考不要陷入重复循环,一旦循环,立即退出
13
- - 思考聚焦需求理解与方案设计(要点、流程、验证策略),不预演具体代码实现;代码在 Do 阶段基于实际文件状态输出,正确性由 Check 验证。
14
+ - 思考聚焦需求理解与方案设计(要点、流程、验证策略),不预演具体代码实现;代码在 Do 阶段基于实际文件状态输出,正确性由 Check 验证
14
15
 
15
16
  - id: agent-presets
16
17
  config:
@@ -56,3 +57,10 @@
56
57
  low: low
57
58
  high: high
58
59
  max: max
60
+
61
+ # 提示词分层:系统提示词只留上面的 persona,工具说明 / Agent Teams 规则 / plan
62
+ # 规则等由该插件降级为 <system-reminder> user 消息。部署级策略,在这里定义一次
63
+ # 即覆盖全部 preset(含子 agent),preset composition 不再各自带这一行。
64
+ - insert:
65
+ - id: prompt-reminder
66
+ name: "@morlay/dsh-prompt-reminder"
@@ -1,8 +1,12 @@
1
1
  # 本文件由 packages/dsh-preset/tool/generate-presets.ts 生成,请勿手工编辑
2
2
  - id: agent-instructions
3
- name: "@morlay/dsh-agent-instructions-as-prompt"
3
+ name: "@deepseek-ai/dsh-agent-instructions"
4
4
  config:
5
5
  maxBytes: 65536
6
+ instructionFileCandidates:
7
+ - AGENTS.md
8
+ localInstructionFileCandidates:
9
+ - AGENTS.local.md
6
10
  - id: tool-bash
7
11
  name: "@deepseek-ai/dsh-tool-bash"
8
12
  disabled: !!js process.platform === 'win32'
@@ -1,8 +1,12 @@
1
1
  # 本文件由 packages/dsh-preset/tool/generate-presets.ts 生成,请勿手工编辑
2
2
  - id: agent-instructions
3
- name: "@morlay/dsh-agent-instructions-as-prompt"
3
+ name: "@deepseek-ai/dsh-agent-instructions"
4
4
  config:
5
5
  maxBytes: 65536
6
+ instructionFileCandidates:
7
+ - AGENTS.md
8
+ localInstructionFileCandidates:
9
+ - AGENTS.local.md
6
10
  - id: tool-bash
7
11
  name: "@deepseek-ai/dsh-tool-bash"
8
12
  disabled: !!js process.platform === 'win32'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/dsh-preset",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Personal dsh profile bundle: disables the shipped agent presets, builds generated standard / ptc presets into dist, and declares personal llm-pi-ai provider routes.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -26,7 +26,7 @@
26
26
  "./cordis.patch.yml": "./cordis.patch.yml"
27
27
  },
28
28
  "dependencies": {
29
- "@morlay/dsh-agent-instructions-as-prompt": "^0.0.1"
29
+ "@morlay/dsh-prompt-reminder": "^0.0.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@deepseek-ai/cordis-plugin-include": "^1.0.7",
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * 生成产物的回归测试:`dist/presets/standard` / `dist/presets/ptc` 必须等于
3
- * 「上游 composition persona prefix 替换版」。
3
+ * 「上游 composition persona 行、指令候选收紧为 AGENTS 系列」的版本。
4
4
  *
5
5
  * 断言方式是把产物与**现算的**期望值比较,而不是与一份快照比——上游升级后
6
- * 忘记重跑构建时,这条测试会失败并指出 drift,而不是继续绿着。
6
+ * 忘记重跑构建时,这条测试会失败并指出 drift,而不是继续绿着。期望值由测试
7
+ * 自己解析上游推出、不经过被测的 `renderComposition`:生成器多删 / 改任何
8
+ * 一行都会失败,而不是自证。
7
9
  *
8
10
  * 产物在 dist(构建输出,gitignore),因此测试自身调用 `generatePresets` 到
9
11
  * 临时目录,不依赖 build 是否跑过。
@@ -13,47 +15,112 @@
13
15
  import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
14
16
  import { tmpdir } from "node:os";
15
17
  import { join } from "node:path";
18
+ import { entryListSchema } from "@deepseek-ai/cordis-plugin-include";
19
+ import yaml from "js-yaml";
16
20
  import { afterAll, describe, expect, it } from "vitest";
17
21
  import {
18
22
  PRESET_SOURCES,
19
- INSTRUCTIONS_PLUGIN,
20
23
  PERSONA_ROW_ID,
21
24
  UPSTREAM_PRESETS,
22
25
  generatePresets,
23
- renderComposition,
24
26
  renderMetadata,
25
27
  } from "../../tool/generate-presets.ts";
26
28
 
29
+ /** 上游 preset 里承载工作区指令的行 id。 */
30
+ const INSTRUCTIONS_ROW_ID = "agent-instructions";
31
+
32
+ /** 上游那行指向的官方插件(产物须保持官方名;提示词分层由 profile 的 patch 承担)。 */
33
+ const UPSTREAM_INSTRUCTIONS_PLUGIN = "@deepseek-ai/dsh-agent-instructions";
34
+
35
+ /** 产物里指令候选的文件名(字面量:不读 CLAUDE 系列)。 */
36
+ const PRODUCT_INSTRUCTION_CANDIDATES = ["AGENTS.md"];
37
+ const PRODUCT_LOCAL_INSTRUCTION_CANDIDATES = ["AGENTS.local.md"];
38
+
39
+ /** 上游 composition 的一行;比较只看结构。 */
40
+ type CompositionRow = {
41
+ id?: string;
42
+ name?: string;
43
+ config?: Record<string, unknown>;
44
+ [key: string]: unknown;
45
+ };
46
+
27
47
  const OUT_DIR = mkdtempSync(join(tmpdir(), "dsh-preset-"));
28
48
  afterAll(() => {
29
49
  rmSync(OUT_DIR, { recursive: true, force: true });
30
50
  });
31
51
  generatePresets(OUT_DIR);
32
52
 
53
+ /** 独立解析 composition 文本(include 的 schema,`!!js` 标签保留为表达式节点)。 */
54
+ function parseRows(text: string): CompositionRow[] {
55
+ return yaml.load(text, { schema: entryListSchema }) as CompositionRow[];
56
+ }
57
+
58
+ /** 解析上游 preset 与生成产物。 */
59
+ function readPair(source: string): { upstream: CompositionRow[]; product: CompositionRow[] } {
60
+ return {
61
+ upstream: parseRows(readFileSync(join(UPSTREAM_PRESETS, source, "agent.cordis.yml"), "utf8")),
62
+ product: parseRows(readFileSync(join(OUT_DIR, source, "agent.cordis.yml"), "utf8")),
63
+ };
64
+ }
65
+
66
+ /** 生成器对上游 composition 的期望改动:删 persona 行、收紧指令候选。 */
67
+ function expectedRows(upstream: CompositionRow[]): CompositionRow[] {
68
+ return upstream
69
+ .filter((row) => row.id !== PERSONA_ROW_ID)
70
+ .map((row) =>
71
+ row.id === INSTRUCTIONS_ROW_ID
72
+ ? {
73
+ ...row,
74
+ config: {
75
+ ...row.config,
76
+ instructionFileCandidates: PRODUCT_INSTRUCTION_CANDIDATES,
77
+ localInstructionFileCandidates: PRODUCT_LOCAL_INSTRUCTION_CANDIDATES,
78
+ },
79
+ }
80
+ : row,
81
+ );
82
+ }
83
+
33
84
  describe("generated presets", () => {
34
85
  it.each(PRESET_SOURCES)(
35
- "$source matches its upstream source with only the persona and instructions rows replaced",
86
+ "$source equals its upstream source with only the persona row dropped and the instruction candidates narrowed",
36
87
  (entry) => {
37
- const upstream = readFileSync(
38
- join(UPSTREAM_PRESETS, entry.source, "agent.cordis.yml"),
39
- "utf8",
40
- );
41
- const actual = readFileSync(join(OUT_DIR, entry.source, "agent.cordis.yml"), "utf8");
42
- expect(actual).toBe(renderComposition(entry.source, upstream));
88
+ const { upstream, product } = readPair(entry.source);
89
+ // 期望由测试自行推出:上游删 persona 行 + 收紧候选,其余逐行不动。
90
+ expect(product).toEqual(expectedRows(upstream));
43
91
  },
44
92
  );
45
93
 
46
- it.each(PRESET_SOURCES)("$source loads the fork for workspace instructions", (entry) => {
47
- const text = readFileSync(join(OUT_DIR, entry.source, "agent.cordis.yml"), "utf8");
48
- // preset 是会话级 composition:不在这里换掉,上游插件仍会把 baseline 作为
49
- // user 消息注入一次(profile 级的 disable 管不到 preset)。
50
- expect(text).toContain(`name: "${INSTRUCTIONS_PLUGIN}"`);
51
- expect(text).not.toContain('"@deepseek-ai/dsh-agent-instructions"');
52
- });
94
+ it.each(PRESET_SOURCES)(
95
+ "$source keeps the agent-instructions row on the upstream plugin with AGENTS-only candidates",
96
+ (entry) => {
97
+ const { upstream, product } = readPair(entry.source);
98
+ const upstreamRow = upstream.find((row) => row.id === INSTRUCTIONS_ROW_ID);
99
+ const productRow = product.find((row) => row.id === INSTRUCTIONS_ROW_ID);
100
+ // 官方插件名保持(fork 一旦接入即失败,改名也不失效)。
101
+ expect(upstreamRow?.name).toBe(UPSTREAM_INSTRUCTIONS_PLUGIN);
102
+ expect(productRow?.name).toBe(upstreamRow?.name);
103
+ // 候选收紧为 AGENTS 系列:CLAUDE 系列不再被读取。
104
+ expect(productRow?.config?.instructionFileCandidates).toEqual(["AGENTS.md"]);
105
+ expect(productRow?.config?.localInstructionFileCandidates).toEqual(["AGENTS.local.md"]);
106
+ // 其余 config 字段跟随上游(如 maxBytes 的字节预算)。
107
+ expect(productRow?.config?.maxBytes).toBe(upstreamRow?.config?.maxBytes);
108
+ // 提示词分层(prompt-reminder)是 profile 的 bundle patch,不进 preset 产物。
109
+ expect(
110
+ product.some((row) => typeof row.name === "string" && row.name.startsWith("@morlay/")),
111
+ ).toBe(false);
112
+ },
113
+ );
53
114
 
54
115
  it.each(PRESET_SOURCES)("$source carries generated metadata", (entry) => {
55
116
  const actual = readFileSync(join(OUT_DIR, entry.source, "preset.yml"), "utf8");
56
117
  expect(actual).toBe(renderMetadata(entry));
118
+ // 字段级校验:多写 / 漏写字段在这里失败(上面的逐字比较是自指的)。
119
+ expect(yaml.load(actual)).toEqual({
120
+ name: entry.name,
121
+ description: entry.description,
122
+ order: entry.order,
123
+ });
57
124
  });
58
125
 
59
126
  it("drops the persona row: the deployment system-prompt owns the persona", () => {
@@ -0,0 +1,41 @@
1
+ // 装配守卫:prompt-reminder 是 host plane 的唯一装配点。被误删 / 改名时提示词分层
2
+ // 静默失效(工具说明回到系统提示词),这里 fail loud;persona 是 keep 名单的唯一
3
+ // 提供者,删掉它系统提示词会变空。
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { entryListSchema } from "@deepseek-ai/cordis-plugin-include";
8
+ import yaml from "js-yaml";
9
+ import { describe, expect, it } from "vitest";
10
+
11
+ const PATCH_PATH = join(process.cwd(), "packages/preset/dsh-preset/cordis.patch.yml");
12
+
13
+ /** patch 的一行;只用到覆盖目标与 insert 的新增行。 */
14
+ interface PatchRow {
15
+ id?: string;
16
+ config?: Record<string, unknown>;
17
+ insert?: { id?: string; name?: string }[];
18
+ }
19
+
20
+ // 用 include 的 entry schema 解析:patch 里含 `!!js` 表达式,默认 schema 会拒绝。
21
+ const rows = yaml.load(readFileSync(PATCH_PATH, "utf8"), {
22
+ schema: entryListSchema,
23
+ }) as PatchRow[];
24
+
25
+ describe("dsh-preset patch wiring", () => {
26
+ it("declares the prompt-reminder row exactly once, on the local plugin", () => {
27
+ const inserted = rows
28
+ .flatMap((row) => row.insert ?? [])
29
+ .filter((row) => row.id === "prompt-reminder");
30
+
31
+ expect(inserted).toHaveLength(1);
32
+ expect(inserted[0]?.name).toBe("@morlay/dsh-prompt-reminder");
33
+ });
34
+
35
+ it("keeps the deployment persona the reminder keeps in the system prompt", () => {
36
+ const config = rows.find((row) => row.id === "system-prompt")?.config;
37
+
38
+ expect(config?.personaPrefix).toBeTruthy();
39
+ expect(config?.personaSuffix).toBeTruthy();
40
+ });
41
+ });
@@ -6,8 +6,11 @@
6
6
  * 必须随包发布;但手工维护副本会与上游 drift。这里把上游 `standard` / `ptc`
7
7
  * 当数据读入——`js-yaml` 用 include 的 `entryListSchema` 解析(`!!js` 标签
8
8
  * 因此保留为表达式节点)——在 JS 里改两处(删掉自带的 `persona` 行、把
9
- * `agent-instructions` 行指向本仓库的 fork),再 `dump` 回 YAML。上游任何结构性
10
- * 改动(新增/重命名 row、改字段)都自动跟随,不依赖易碎的文本锚点。
9
+ * `agent-instructions` 行的指令候选收紧为 AGENTS 系列),再 `dump` 回 YAML
10
+ * 上游任何结构性改动(新增/重命名 row、改字段)都自动跟随,不依赖易碎的文本锚点。
11
+ *
12
+ * `agent-instructions` 行是上游官方插件:工作区指令的 baseline 留在 user 消息通道,
13
+ * 与「系统提示词只留 persona、其余走 reminder」的分层一致。
11
14
  *
12
15
  * 产物落在 `dist/` 而非源码树:dist 是构建输出(gitignore),既不会与 oxfmt
13
16
  * 互相改格式,也不会把派生文件混进源码。
@@ -75,39 +78,41 @@ export const PERSONA_ROW_ID = "persona";
75
78
  export const INSTRUCTIONS_ROW_ID = "agent-instructions";
76
79
 
77
80
  /**
78
- * 承载工作区指令的插件:本仓库的 fork(baseline system prompt)。
81
+ * 工作区指令的候选文件:只要 AGENTS 系列,不要 CLAUDE 系列。
79
82
  *
80
- * preset 是**会话级** composition,profile 级的 patch(禁用上游行 + 挂 fork)
81
- * 管不到它——不在这里替换,每个会话仍会由上游插件把 baseline 作为 user 消息
82
- * 注入一次。
83
+ * 上游默认是 `['AGENTS.md','CLAUDE.md']` `['AGENTS.local.md','CLAUDE.local.md']`;
84
+ * 本部署不读 CLAUDE 系文件(覆盖上游 config 的这两个字段,其余字段如 `maxBytes`
85
+ * 保持上游值)。行本身仍是官方 `@deepseek-ai/dsh-agent-instructions`——fork 暂未
86
+ * 接入,见模块注释。
83
87
  */
84
- export const INSTRUCTIONS_PLUGIN = "@morlay/dsh-agent-instructions-as-prompt";
88
+ export const INSTRUCTIONS_CONFIG = {
89
+ instructionFileCandidates: ["AGENTS.md"],
90
+ localInstructionFileCandidates: ["AGENTS.local.md"],
91
+ } as const;
85
92
 
86
93
  /**
87
- * 把上游 composition 文本渲染成产物文本:解析 → persona instructions 两行 → dump。
88
- * @param source - 上游 preset id,用于 header 与诊断。
94
+ * 把上游 composition 文本渲染成产物文本:解析 → persona 行、收紧指令候选 → dump。
89
95
  * @param upstream - 上游 composition 文本。
90
96
  * @returns 带 header 的产物文本。
91
97
  */
92
- export function renderComposition(source: string, upstream: string): string {
98
+ export function renderComposition(upstream: string): string {
93
99
  const rows = yaml.load(upstream, {
94
100
  schema: entryListSchema,
95
101
  }) as CompositionRow[];
96
- const instructions = rows.find((row) => row.id === INSTRUCTIONS_ROW_ID);
97
- if (instructions === undefined) {
98
- throw new Error(
99
- `generate-presets: upstream \`${source}\` has no \`${INSTRUCTIONS_ROW_ID}\` row; ` +
100
- "upstream changed — re-check which plugin loads the workspace instructions",
101
- );
102
- }
103
-
104
- instructions.name = INSTRUCTIONS_PLUGIN;
105
102
  // 删掉 preset 自带的 `persona` 行:它会在 agent scope 注册
106
103
  // `deployment:persona-prefix` 并**遮蔽**部署级 persona(system-prompt 的
107
104
  // personaPrefix,见上游 system-prompt 的 Config 文档)。个人提示词只由
108
105
  // `cordis.patch.yml` 的 system-prompt 行提供,这里不再复制一份。
109
106
  const persona = rows.findIndex((row) => row.id === PERSONA_ROW_ID);
110
107
  if (persona >= 0) rows.splice(persona, 1);
108
+ const instructions = rows.find((row) => row.id === INSTRUCTIONS_ROW_ID);
109
+ if (instructions === undefined) {
110
+ throw new Error(
111
+ `generate-presets: upstream composition has no \`${INSTRUCTIONS_ROW_ID}\` row; ` +
112
+ "upstream changed — re-check which plugin loads the workspace instructions",
113
+ );
114
+ }
115
+ instructions.config = { ...instructions.config, ...INSTRUCTIONS_CONFIG };
111
116
  // `quotingType: '"'` 是刻意选择:yaml.dump 默认用单引号,而仓库的 oxfmt 会把
112
117
  // YAML 单引号改成双引号——不显式指定就会 fmt 与生成器来回改。指定后产物与
113
118
  // `oxfmt --check` 零差异(实测),故 `presets/**` 无需 fmt 忽略。
@@ -144,7 +149,7 @@ export function generatePresets(outDir: string = join(PACKAGE_ROOT, PRESETS_OUT_
144
149
  const dir = join(outDir, entry.source);
145
150
  mkdirSync(dir, { recursive: true });
146
151
  const compositionPath = join(dir, "agent.cordis.yml");
147
- writeFileSync(compositionPath, renderComposition(entry.source, upstream));
152
+ writeFileSync(compositionPath, renderComposition(upstream));
148
153
  const metadataPath = join(dir, "preset.yml");
149
154
  writeFileSync(metadataPath, renderMetadata(entry));
150
155
  written.push(compositionPath, metadataPath);