openfeel 1.2.4 → 1.3.1

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.
Files changed (51) hide show
  1. package/README.en.md +40 -74
  2. package/README.md +39 -62
  3. package/README.zh-CN.md +40 -74
  4. package/dist/commands/setup.d.ts.map +1 -1
  5. package/dist/commands/setup.js +90 -5
  6. package/dist/commands/setup.js.map +1 -1
  7. package/dist/commands/update.d.ts.map +1 -1
  8. package/dist/commands/update.js +7 -2
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/core/config.js +2 -2
  11. package/dist/core/dsh-profile.d.ts +93 -0
  12. package/dist/core/dsh-profile.d.ts.map +1 -0
  13. package/dist/core/dsh-profile.js +400 -0
  14. package/dist/core/dsh-profile.js.map +1 -0
  15. package/dist/core/i18n-data/en.d.ts.map +1 -1
  16. package/dist/core/i18n-data/en.js +21 -2
  17. package/dist/core/i18n-data/en.js.map +1 -1
  18. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  19. package/dist/core/i18n-data/zh-CN.js +21 -2
  20. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  21. package/dist/core/setup.d.ts +12 -1
  22. package/dist/core/setup.d.ts.map +1 -1
  23. package/dist/core/setup.js +25 -10
  24. package/dist/core/setup.js.map +1 -1
  25. package/dist/core/template-loader.d.ts.map +1 -1
  26. package/dist/core/template-loader.js +18 -9
  27. package/dist/core/template-loader.js.map +1 -1
  28. package/dist/core/update.d.ts +2 -0
  29. package/dist/core/update.d.ts.map +1 -1
  30. package/dist/core/update.js +25 -7
  31. package/dist/core/update.js.map +1 -1
  32. package/dsh/ACTION-GUIDE.md +366 -0
  33. package/dsh/ADAPTER.md +65 -0
  34. package/dsh/README.md +264 -0
  35. package/dsh/cordis.patch.yml +26 -0
  36. package/dsh/docs/compat/commands.md +873 -0
  37. package/dsh/docs/compat/kb-skills.md +883 -0
  38. package/dsh/docs/compat/persistence.md +1013 -0
  39. package/dsh/docs/compat/pipeline.md +1565 -0
  40. package/dsh/docs/compat/req.md +1243 -0
  41. package/dsh/docs/compat/stage.md +1437 -0
  42. package/dsh/lib/adapters/cli.js +624 -0
  43. package/dsh/lib/adapters/dashboard.js +393 -0
  44. package/dsh/lib/adapters/workspace.js +150 -0
  45. package/dsh/lib/index.js +189 -0
  46. package/dsh/lib/surface/command.js +186 -0
  47. package/dsh/lib/surface/prompt.js +131 -0
  48. package/dsh/lib/tools/index.js +1245 -0
  49. package/dsh/lib/tools/shared.js +320 -0
  50. package/dsh/package.json +45 -0
  51. package/package.json +9 -2
@@ -0,0 +1,883 @@
1
+ # OpenFeel Compatibility Spec — Knowledge Base, Artifact Templates, and Agent/Skill Deployment
2
+
3
+ Status: behavioral specification extracted read-only from `C:\Users\Liuary\Dev\Mine\AI\OpenFeel` (v1.2.1, HEAD-era sources).
4
+ Scope: (1) the `.openfeel/kb/` knowledge base, (2) the `knowledge` CLI surface, (3) KB dedup, (4) skill + agent template inventory and frontmatter contracts, (5) the agent role/delegation model, (6) the managed-region three-state mechanism, (7) the global deployment model.
5
+ Purpose: an exact contract a DSH-native reimplementation must match (or deliberately diverge from, with the divergence recorded).
6
+
7
+ ## Source map
8
+
9
+ | Concern | Source file | Notes |
10
+ |---|---|---|
11
+ | KB core (layout / parse / index / search) | `src/core/workspace/knowledge.ts` (429 lines) | single authority for the on-disk format |
12
+ | `knowledge` CLI | `src/commands/knowledge.ts` (281 lines) | thin command layer, zero business logic beyond validation |
13
+ | KB dedup | `src/utils/kb-dedup.ts` (228 lines) | Jaccard bag-of-words |
14
+ | Instruction/artifact composition | `src/core/artifact-graph/instruction-loader.ts` (401 lines) | XML/JSON instruction payloads |
15
+ | Template dispatchers | `src/core/template-loader.ts` (8576 lines) | only the dispatcher API matters; the rest is inlined data |
16
+ | Init templates (dev_core/current/decisions) | `src/core/templates.ts` (102 lines) | zh-CN/en |
17
+ | Template asset sources | `src/core/templates-data/{skills,agents-md,claude,opencode}/` | authoritative `.md` sources |
18
+ | Managed region | `src/core/managed-region.ts` (221 lines) | markers, frontmatter merge, user-sovereign keys |
19
+ | Deployment write path | `src/core/update.ts` (`writeManagedFile`, `deployGlobalAsset`, `SKILL_DEFINITIONS`) | three-state logic lives here |
20
+ | Global paths | `src/core/global-paths.ts` (117 lines) | single `os.homedir()` import point |
21
+ | Claude settings injection | `src/core/claude-settings.ts` (129 lines) | strict-JSON deep merge |
22
+ | setup orchestration | `src/core/setup.ts` (144 lines) | pure global deploy |
23
+ | Deployment drift check | `src/core/deployment-check.ts` (113 lines) | read-only 4-state check |
24
+ | Backup primitives | `src/core/backup.ts`, `src/core/update-infos.ts` | `~/.openfeel/backup/**`, `~/.openfeel/update_infos.md` |
25
+ | Phase enum / tiers | `src/core/pipeline-schema.ts` (112 lines) | 15 phases, 3 review tiers |
26
+ | Phase→agent attribution | `src/core/metrics.ts:25` `PHASE_AGENT_MAP` | 10 keys |
27
+ | Build pipeline (asset → constants) | `build.js` | 8 inject steps + consistency validators |
28
+
29
+ ---
30
+
31
+ # 1. The knowledge base
32
+
33
+ ## 1.1 Directory layout
34
+
35
+ ```
36
+ <project>/.openfeel/kb/
37
+ ├── index.md # master index (overview tables + per-category digest)
38
+ ├── architecture.md # 架构决策 / Architecture decisions
39
+ ├── patterns.md # 代码模式 / Code patterns
40
+ ├── troubleshooting.md # 常见问题 / Troubleshooting
41
+ └── setup.md # 环境搭建 / Environment setup
42
+ ```
43
+
44
+ The four category names are a **closed set** — this is the single most important KB invariant:
45
+
46
+ ```ts
47
+ const CATEGORIES = ['architecture', 'patterns', 'troubleshooting', 'setup'] as const;
48
+ const CATEGORY_TITLES = {
49
+ architecture: '架构决策',
50
+ patterns: '代码模式',
51
+ troubleshooting: '常见问题',
52
+ setup: '环境搭建',
53
+ };
54
+ ```
55
+
56
+ - Category → file name is mechanical: `{category}.md`. There is no arbitrary-category escape hatch; `addKnowledgeEntry` **throws** on any other value (`无效分类 "x",有效值:architecture, patterns, troubleshooting, setup`), and `knowledge add` validates first at the command layer and exits 1.
57
+ - There is **no per-entry file** and **no nested directory**. One file per category, entries separated by level-2 headings.
58
+ - Category ordering is fixed everywhere (list / search / digest): `architecture → patterns → troubleshooting → setup`.
59
+
60
+ ## 1.2 Initialization (`initKnowledgeBase`)
61
+
62
+ Idempotent, never overwrites; returns the list of paths it actually created (`.openfeel/kb/`, `.openfeel/kb/index.md`, `.openfeel/kb/{category}.md`).
63
+
64
+ `index.md` template — exactly this text:
65
+
66
+ ```markdown
67
+ # 知识库索引
68
+ > 项目知识库总索引
69
+
70
+ ## 分类概览
71
+
72
+ | 分类 | 描述 |
73
+ |------|------|
74
+ | architecture | 架构决策、设计理由、技术选型 |
75
+ | patterns | 代码模式、项目约定、最佳实践 |
76
+ | troubleshooting | 常见问题、调试流程、已知坑位 |
77
+ | setup | 环境搭建、构建流程、依赖管理 |
78
+
79
+ ## 最近更新
80
+
81
+ | 日期 | 分类 | 标题 |
82
+ |------|------|------|
83
+ ```
84
+
85
+ Category file template — one line only: `# {CATEGORY_TITLES[cat]} ({cat})\n`, e.g. `# 架构决策 (architecture)`.
86
+
87
+ > Real-world drift to tolerate: the OpenFeel repo's own `kb/*.md` start with a hand-written H1 plus a `> 使用 [+] / [-] 标记管理启用/禁用状态。只能标记禁用不能删除。` blockquote, and its `index.md` has grown five sections (`## 项目快速概览`, `## 分类概览`, `## 各分类摘要`, `## 最近更新` plus a hand-maintained 5-column 分类概览 table). Parsers must accept both shapes — see §1.5.
88
+
89
+ ## 1.3 Entry file format
90
+
91
+ **There is no YAML frontmatter in KB entries.** The "frontmatter" role is played by a heading line; the body is the free-form Markdown after it.
92
+
93
+ ```markdown
94
+ # {category title} ({category})
95
+
96
+ ## [+] {title} ({YYYY-MM-DD})
97
+
98
+ {body}
99
+
100
+ ## [-] {old title} ({YYYY-MM-DD})
101
+
102
+ {body}
103
+ ```
104
+
105
+ Contract, item by item:
106
+
107
+ | Element | Rule |
108
+ |---|---|
109
+ | Header line | `## [+] {title} ({YYYY-MM-DD})` or `## [-] …`. `[+]` = enabled, `[-]` = disabled (= "soft deleted", never removed) |
110
+ | Date | `YYYY-MM-DD`, generated as `new Date().toISOString().slice(0, 10)` (UTC date) at add time |
111
+ | Title | free text, **but** `|` is escaped to `\|` and any `\r\n`/`\n` run is collapsed to a single space, then trimmed (protects the `index.md` table and the header regex) |
112
+ | Body | everything from the line after the header up to the next line starting with `## ` or EOF, then `.trim()` |
113
+ | Separator | a blank line precedes every header, i.e. the appended chunk is `\n## [+] {title} ({date})\n\n{content}\n` |
114
+ | Deletion | never. Disable with `[-]`. Retrieval skills skip `[-]` entries; `knowledge list` still shows them as `禁用` |
115
+ | Ordering | append-only, chronological within a category file. No sorting, no compaction |
116
+
117
+ Two header regexes exist and they are **not** identical — reproduce both:
118
+
119
+ ```ts
120
+ // knowledge.ts (list/search path) — exactly one space after `##`
121
+ /^## (\[[+-]\]) (.+?) \((\d{4}-\d{2}-\d{2})\)$/gm
122
+
123
+ // kb-dedup.ts (dedup path) — tolerant of extra whitespace
124
+ /^##\s+\[([+-])\]\s+(.+?)\s+\((\d{4}-\d{2}-\d{2})\)$/
125
+ ```
126
+
127
+ Consequence to preserve (or intentionally fix): an entry written by hand as `## [+] Title (2026-01-01)` is invisible to `list`/`search` but visible to `dedup`.
128
+
129
+ Line-ending handling also differs by path:
130
+
131
+ - `knowledge.ts` reads raw; `\r` stays in the file content, so a CRLF file makes the `$`-anchored header regex fail (known defect `L8`, documented in `kb/troubleshooting.md`).
132
+ - `kb-dedup.ts` normalizes `readFileSync(...).replace(/\r\n?/g, '\n')` before splitting (the fix that took `patterns.md` from 2 → 105 visible entries).
133
+
134
+ ## 1.4 Write path (`addKnowledgeEntry`)
135
+
136
+ 1. Validate category (throw otherwise).
137
+ 2. If `.openfeel/kb/` is missing → `initKnowledgeBase(projectPath)` (auto-bootstrap). If the dir exists but `index.md` does not, the category file is still written and the index step is silently skipped.
138
+ 3. Sanitize title (§1.3), compute `today`.
139
+ 4. Build `entryText = "\n## [+] {title} ({today})\n\n{content}\n"`.
140
+ 5. Inside `withFileLock(projectLockPath(project, 'kb'))` — i.e. lock file `.openfeel/tmp/locks/kb.lock`:
141
+ - category file: read → `content + entryText` → `atomicWriteFileSync` (read-concat-atomic-write, **not** `appendFileSync`, to avoid torn writes);
142
+ - `index.md`: insert one row into `## 最近更新` (see §1.5). Failure to locate the section is a silent no-op.
143
+
144
+ `atomicWriteFileSync` = write temp + rename. Never mutate KB files outside this function.
145
+
146
+ ## 1.5 `index.md` maintenance and tolerant parsing
147
+
148
+ `addKnowledgeEntry` inserts into the `最近更新` / `Recent Updates` section:
149
+
150
+ 1. Locate the section header with `/^##\s*(?:最近更新|Recent Updates)\s*$/im`. Not found → **no index update at all** (degraded, no error).
151
+ 2. Parse the first `|`-line of the section body as headers; `cols = headers.length || 3`.
152
+ 3. Build the data row `| {today} | {category} | {title} |` (padded/truncated to `cols` cells).
153
+ 4. If a separator line (`/^\|[\s:|-]+\|\s*$/m`) exists in the body → insert the new row immediately after it (newest-first).
154
+ 5. Else synthesize `|` + `------|`×cols after the header line, then the row.
155
+
156
+ Reading (`getKnowledgeIndex`) is deliberately lenient:
157
+
158
+ - Section header aliases: `分类概览` | `Categories` | `Category Overview` for categories; `最近更新` | `Recent Updates` for updates. Matched as `/^##\s*(?:alias1|alias2)\s*$/im`, body = up to the next `^## ` or EOF.
159
+ - Table columns are resolved by alias with **exact match first, then substring**: name column ← `分类|名称|category|name`, description ← `描述|说明|description`; date ← `日期|date`, category ← `分类|category`, title ← `标题|title`. Separator rows (`---`, `:---:`) are skipped.
160
+ - Missing section, missing header, or unresolvable name column → return `[]` for that part; **never throws**.
161
+ - `index.md` missing entirely → categories fall back to the four `CATEGORIES` with their Chinese titles, `recentUpdates: []`.
162
+
163
+ Known real-repo mismatch worth honoring: the repo's own `最近更新` table is `| 日期 | 操作 | 描述 |`, so `category` and `title` come back as empty strings and only `date` is populated. Do not "fix" this by tightening the parser; fix the file if desired.
164
+
165
+ ## 1.6 Lookup and search semantics
166
+
167
+ `listKnowledge(project, category?)` — pure scan, no index:
168
+
169
+ - `category` given → only that file; otherwise all four in fixed order.
170
+ - Missing dir → `[]`; missing category file → skip.
171
+ - Returns `KnowledgeEntry { title, category, date, content, enabled }`, file order preserved.
172
+
173
+ `searchKnowledge(project, query, limit = 10, offset = 0)`:
174
+
175
+ 1. `entries = listKnowledge(project)` (all categories).
176
+ 2. `q = query.toLowerCase()`; keep entries where `title.toLowerCase().includes(q) || content.toLowerCase().includes(q)`. **Substring match only** — no tokenization, no ranking, no fuzzy matching, no vector search.
177
+ 3. Highlight: for each surviving entry, wrap every case-insensitive occurrence in `title` **and** `content` with `**…**` (query regex-escaped). The highlight is applied *before* pagination, so page-2 results are highlighted identically.
178
+ 4. `slice(offset, offset + limit)`.
179
+
180
+ There is therefore no relevance ordering: results come out category-major, then file order. The `knowledge dedup` command is the only similarity-ranked surface (§1.8).
181
+
182
+ ## 1.7 `knowledge` subcommands and exact output shapes
183
+
184
+ All output goes to `stdout` (`console.log`); validation failures go to `stderr` + `process.exit(1)`. Every string is i18n-routed (`zh-CN` / `en`, selected by `getCliLang(process.cwd())`).
185
+
186
+ ### `openfeel knowledge list [--type <category>]`
187
+
188
+ Fixed-width ASCII table (columns padded with `padEnd`, joined ` | `; separator = per-column `-`×width joined `-+-`):
189
+
190
+ ```
191
+ 分类 | 标题 | 日期 | 状态
192
+ -----------+-------------------------------+------------+-----
193
+ patterns | 控制区标记模式 | 2026-09-25 | 启用
194
+ ```
195
+
196
+ Columns: `分类` / `标题` / `日期` / `状态` (`启用` | `禁用`). Empty result → single line `暂无知识条目` (en: `No knowledge entries`), exit 0.
197
+
198
+ ### `openfeel knowledge add <category> <title> [--content <text>]`
199
+
200
+ - Content source: `--content`, else stdin when `!process.stdin.isTTY` (piped), else error `错误:请使用 --content 提供内容,或通过管道提供内容。` exit 1.
201
+ - Whitespace-only content → `错误:内容不能为空。` exit 1.
202
+ - Invalid category → `错误:无效分类 "{category}",有效值:architecture, patterns, troubleshooting, setup` exit 1.
203
+ - Success → `✓ 已添加知识条目: [{category}] {title}` (en: `✓ Knowledge entry added: [{category}] {title}`), exit 0. Content is `.trim()`ed before write.
204
+
205
+ ### `openfeel knowledge search <query> [--limit <n>] [--offset <n>]`
206
+
207
+ - `limit = Math.max(1, parseInt(limit) || 10)`, `offset = Math.max(0, parseInt(offset) || 0)`.
208
+ - Header: `找到 {n} 条匹配` (en `Found {n} match(es)`).
209
+ - Per entry, three lines (third is empty):
210
+
211
+ ```
212
+ [{category}] {title} ({date}) [{启用|禁用}]
213
+ {first 100 chars of content, with **…** highlights}{… if truncated}
214
+
215
+ ```
216
+
217
+ - No matches → `未找到与 "{query}" 相关的知识条目。`; additionally, when `offset > 0`, `(偏移量 {offset},已越界)`. Exit 0 in all cases.
218
+
219
+ ### `openfeel knowledge index`
220
+
221
+ ```
222
+ === 分类概览 ===
223
+ architecture 架构决策
224
+ patterns 代码模式
225
+ troubleshooting 常见问题
226
+ setup 环境搭建
227
+
228
+ === 最近更新 ===
229
+
230
+ {表格:日期 | 分类 | 标题}
231
+ ```
232
+
233
+ Category line format: two leading spaces + `name.padEnd(20)` + space + `description`. Empty categories → `(无分类)`; empty updates → `暂无更新记录。`.
234
+
235
+ ### `openfeel knowledge dedup [content] [--project <path>] [--category <name>] [--threshold <n>]`
236
+
237
+ Read-only advisory (never writes). Content from positional arg or stdin; neither → `未提供内容:请传入内容参数或通过标准输入` exit 1. `--threshold` must parse to `[0,1]` else `阈值非法:{value}(应为 0~1 之间的数字)` exit 1. `--category` must be one of the four else `分类非法:{value}(允许:architecture | patterns | troubleshooting | setup)` exit 1.
238
+
239
+ `basePath = resolve(projectPath ?? cwd, '.openfeel', 'kb')` — the command layer computes the path, the core never resolves cwd itself.
240
+
241
+ Output:
242
+
243
+ ```
244
+ 相似条目建议:
245
+ [{category}] {title}(相似度 {pct}%){flag}
246
+ ...
247
+ 未发现相似条目(阈值 {threshold}) # only when nothing with similarity > 0
248
+ 本命令仅输出建议,不会修改 kb 文件
249
+ ```
250
+
251
+ - `pct = (similarity * 100).toFixed(1)`.
252
+ - `flag` = ` ← 建议合并/更新` only when `similarity > threshold`; entries with `0 < similarity <= threshold` are still printed (without flag).
253
+ - Entries with `similarity <= 0` are filtered out client-side.
254
+ - The final read-only note line is always printed.
255
+
256
+ ## 1.8 Dedup rules (`src/utils/kb-dedup.ts`)
257
+
258
+ Purpose: before an archiver adds a KB entry, check whether a near-duplicate already exists; above the threshold the protocol is **update, not append**.
259
+
260
+ ```ts
261
+ const SIMILARITY_THRESHOLD = 0.8; // strictly greater-than
262
+ export function shouldUpdate(sim) { return sim > 0.8 }
263
+ ```
264
+
265
+ Algorithm:
266
+
267
+ 1. Read the category file, normalize CRLF→LF, split lines, walk with the tolerant header regex; each entry = `{ title, date, status, content (trimmed body), rawLine }`. Body = lines until the next line starting with `## `.
268
+ 2. Tokenize both the new text and every existing entry's `content`:
269
+
270
+ ```ts
271
+ function tokenize(text: string): Set<string> {
272
+ const cleaned = text.replace(/^\[[+-]\](?!\s*\()/gm, ''); // strip state markers
273
+ return new Set(
274
+ cleaned.toLowerCase()
275
+ .split(/[\s,,。.、;;::!!??()()【】\[\]{}""''""\/\\|@#$%^&*+=~`<>]+/)
276
+ .filter(t => t.length > 0 && /[\w\u4e00-\u9fff]/.test(t))
277
+ );
278
+ }
279
+ ```
280
+
281
+ Note: CJK text is **not** word-segmented — a whole Chinese sentence without punctuation becomes one token. Practical effect: similarity is high for near-verbatim entries and low otherwise. Reproduce as-is unless you deliberately upgrade to a real similarity metric (then record the divergence: the 0.8 threshold and `> 0.8` semantics must stay compatible with archiver prompts).
282
+
283
+ 3. `jaccardSimilarity = |A∩B| / |A∪B|`; both-empty (`union == 0` with both sizes 0) ⇒ `1.0`; `union == 0` otherwise ⇒ `0.0`.
284
+ 4. Return all entries with `similarity > 0`, sorted descending. Unknown category ⇒ `[]`.
285
+
286
+ Merge primitive (`mergeEntry`) — the "update instead of append" shape:
287
+
288
+ - If trimmed new content equals existing content ⇒ no change (returns a copy).
289
+ - Else append `"\n\n> **更新于 {YYYY-MM-DD}**:{newContent}"` to the existing body; **keep the original `[+]/[-]` status, original date, and original header line**. The date in the header is the creation date; the update date lives only inside the `> **更新于 …**` block. This exact convention is visible in the real `kb/troubleshooting.md`.
290
+
291
+ Agent-side protocol (from `openfeel-archiver` / `openfeel-check-kb`): run `openfeel knowledge dedup "<new entry text>"` before writing; `> 0.8` ⇒ rewrite the existing entry with an 更新于 block; otherwise `openfeel knowledge add`.
292
+
293
+ ## 1.9 Retrieval protocol (agent side, not CLI)
294
+
295
+ `openfeel-check-kb` skill defines the progressive lookup that agents must follow:
296
+
297
+ 1. Read `.openfeel/kb/index.md` for the digest.
298
+ 2. Map the task to categories: new feature/architecture → `architecture.md`; coding/refactor → `patterns.md`; build/runtime errors → `troubleshooting.md`; env/deps → `setup.md`; unfamiliar → all index digests.
299
+ 3. Read matching category files; **do not load `[-]` entries**.
300
+ 4. Emit `📁 知识库查阅结果({分类})` + relevant entries; empty ⇒ `知识库中暂无与当前任务相关的记录。`
301
+ 5. Semantic fallback when nothing matches: requires `.openfeel/tmp/vectors/index.json` and `python scripts/search_kb.py` / `scripts/build_kb_index.py`; if the index is missing, print the install hint and stop. **Gap to note:** neither Python script nor the vectors directory exists in the OpenFeel repo — the fallback is aspirational. A DSH reimplementation should either ship a real semantic search or delete step 5.
302
+ 6. If ≥1 relevant entry was found, append the mandatory reminder: `⚠️ 执行任务前,若知识库中有相关条目,务必参考。重复踩过的坑不可再犯。`
303
+
304
+ `openfeel-search-kb` is a standalone version of the same fallback (vector/graph traversal, `--top-k`, low-score warning `< 0.3`); it is documented as already folded into `openfeel-check-kb`, so callers should not load both.
305
+
306
+ ---
307
+
308
+ # 2. Instruction / artifact template composition
309
+
310
+ `src/core/artifact-graph/instruction-loader.ts` turns a `Schema` artifact into a structured prompt. It is independent of the skill/agent templates and is the "artifact template" surface.
311
+
312
+ Inputs: `Schema` (artifacts with `id`, `description`, `generates`, `instruction`, `template`, `depends_on`), `changeName`, `artifactId`, `projectPath`, optional `completed: Set<string>`.
313
+
314
+ Pipeline:
315
+
316
+ 1. Resolve the artifact (throw `Schema "{name}" 中找不到 artifact "{id}"` if absent).
317
+ 2. `completed ?? await detectCompletedArtifacts(schema, projectPath)` — filesystem scan.
318
+ 3. `readConfig(projectPath)` for project context.
319
+ 4. `new ArtifactGraph(schema)` for unlock computation; `collectHardDeps(artifact)` (single implementation, reused) for dependencies.
320
+ 5. `artifact.template` resolution (`isFilePath` heuristic): starts with `(` → literal text; starts with `.` or `/` → path; ends with `.md|.yaml|.yml|.json|.xml` → path. Paths are resolved **relative to the project root** and read from disk; unreadable/missing → fall back to the literal string. No template at all → `(无模板)`.
321
+ 6. `artifact.instruction` empty → generated 5-step fallback (understand → check deps → write → output to `generates` → verify).
322
+ 7. All interpolated text is XML-escaped (`& < > " '` → entities).
323
+
324
+ XML output (exact tag order, `xmlEscape`d values):
325
+
326
+ ```xml
327
+ <?xml version="1.0" encoding="UTF-8"?>
328
+ <artifact id="{id}" change="{change}" schema="{schema}">
329
+ <task>{instruction ?? description ?? '请完成此 artifact'}</task>
330
+ <project_context>
331
+ <tech_stack>{config.meta.tech_stack ?? 'TypeScript'}</tech_stack>
332
+ <workspace>.openfeel/ 工作区已初始化</workspace>
333
+ <project>{config.meta.project ?? 'OpenFeel'}</project>
334
+ <config>{执行模式: …; 自动推进: …; 合并模式: … | 无特殊配置}</config>
335
+ </project_context>
336
+ <rules>
337
+ <rule>遵循 TypeScript 编码规范</rule>
338
+ <rule>产物生成到 generates 字段指定的路径</rule>
339
+ <rule>完成所有 hard 依赖后再开始当前 artifact</rule>
340
+ <!-- + schema.rules[] strings -->
341
+ </rules>
342
+ <dependencies>
343
+ <dependency id="{depId}" status="done|pending">
344
+ <path>{dep.generates}</path>
345
+ <description>{dep.description ?? '(无描述)'}</description>
346
+ </dependency>
347
+ </dependencies>
348
+ <output>{generates}</output>
349
+ <instruction>{instruction}</instruction>
350
+ <template>{template}</template>
351
+ <unlocks>Completing this artifact enables: {id(desc), …}</unlocks>
352
+ </artifact>
353
+ ```
354
+
355
+ `<dependencies></dependencies>` when no hard deps; `<unlocks>` fallback text = `完成此 artifact 后暂无直接解锁的下游 artifact`. Trailing newline after the closing tag.
356
+
357
+ `generateInstructionsJson` returns the structurally identical object: `{ artifact:{id,change,schema}, task, project_context:{tech_stack,workspace,project,config:{execution_mode,auto_advance,merge_mode}}, rules[], dependencies[], output, instruction, template, unlocks[] }` (raw text, not XML-escaped; `template` may be `null`).
358
+
359
+ Note: this is the only place where "artifact markdown templates" are loaded from **project** files at runtime. The shipped 22 skills and 10 agents are **not** loaded from disk by the CLI — they are inlined into TS constants at build time (§3.1).
360
+
361
+ ---
362
+
363
+ # 3. Skills and agents — inventory and contracts
364
+
365
+ ## 3.1 Where the templates live and how they become code
366
+
367
+ Authoritative sources:
368
+
369
+ ```
370
+ src/core/templates-data/
371
+ ├── skills/{skill-name}/SKILL.md # 22 shared skills, neutral (both harnesses)
372
+ ├── agents-md/{zh-CN,en}.md # global AGENTS.md constraint layer
373
+ ├── opencode/
374
+ │ ├── agents/{zh-CN,en}/{agent}.md # 10 opencode-format agents per language
375
+ │ └── ADAPTER.{zh-CN,en}.md # adapter doc (Base64-inlined)
376
+ └── claude/
377
+ ├── agents/{zh-CN,en}/{agent}.md # 10 Claude-format agents per language
378
+ └── constraint/{zh-CN,en}.md # CLAUDE.md constraint layer
379
+ ```
380
+
381
+ `build.js` runs 8 generate steps that rewrite regions delimited by `// AUTO-GENERATED-BEGIN: {NAME}` / `// AUTO-GENERATED-END: {NAME}` (manual edits inside those regions are forbidden; a hand-written comment section outside the anchors is the documented trap):
382
+
383
+ | Step | Target constant | Anchor / file |
384
+ |---|---|---|
385
+ | 1 | `AGENT_TEMPLATES` | `template-loader.ts` (opencode agents) |
386
+ | 2 | `AGENTS_MD_TEMPLATES` | `template-loader.ts` |
387
+ | 3 | `SKILL_DEFINITIONS` | **`update.ts`** (this is the one people miss) |
388
+ | 4 | `OPENCODE_AGENT_TEMPLATES` | `template-loader.ts` |
389
+ | 5 | `OPENCODE_SKILL_DEFINITIONS` | `template-loader.ts` |
390
+ | 6 | `OPENCODE_CONFIG_TEMPLATES` (adapter, Base64) | `template-loader.ts` |
391
+ | 7 | `CLAUDE_AGENT_TEMPLATES` | `template-loader.ts` |
392
+ | 8 | `CLAUDE_CONSTRAINT_TEMPLATES` | `template-loader.ts` |
393
+
394
+ Validators enforce: source↔constant content parity, key-set parity between opencode and Claude agent records, no resurrected redundant template tree, and negative assertions (Claude templates must not contain opencode-only tokens such as `permission:`, `reasoning_effort:`, `mode:`).
395
+
396
+ Runtime dispatchers (all fall back to `zh-CN` when a language key is missing; a missing agent/skill id throws):
397
+
398
+ ```ts
399
+ loadAgentTemplate(lang, agentId): string
400
+ listAgentIds(lang): string[]
401
+ loadTemplate(lang, 'agents-md'): string
402
+ loadOpencodeAgentTemplate(lang, agentId): string
403
+ loadOpencodeSkillTemplate(skillName): string
404
+ loadOpencodeConfigTemplate(lang, 'adapter'): string // Base64-decoded
405
+ listOpencodeAgentIds(lang): string[]
406
+ listOpencodeSkillNames(): string[]
407
+ loadClaudeConstraintTemplate(lang): string
408
+ loadClaudeAgentTemplate(lang, agentId): string
409
+ listClaudeAgentIds(lang): string[]
410
+ ```
411
+
412
+ Counts are **10 agents** (`feel` + 9 `openfeel-*`) × 2 harnesses × 2 languages = 40 agent files, **22 skills** (one shared copy, deployed to both harnesses), plus 4 constraint files and 2 adapter docs. Any literal `9 agent` / `18 skill` text still present in comments is stale.
413
+
414
+ ## 3.2 Skill inventory (22)
415
+
416
+ All under `src/core/templates-data/skills/{name}/SKILL.md`; `name` == directory name == frontmatter `name`.
417
+
418
+ | # | Skill | One-line purpose |
419
+ |---|---|---|
420
+ | 1 | `openfeel-agent-model-check` | Diagnose/repair "Model not found": auth.json, provider-key matching, model capability, vision-specific guide, Claude `/model` & gateways |
421
+ | 2 | `openfeel-bug-acceptance` | Standardized Bug acceptance checklist for testers/coders |
422
+ | 3 | `openfeel-check-kb` | Progressive KB lookup by task type + semantic fallback + mandatory "consult KB" reminder |
423
+ | 4 | `openfeel-cli-usage` | Authoritative CLI snapshot: commands, args, 15 phases + transitions, stageId→directory mapping, per-version capability notes |
424
+ | 5 | `openfeel-context-hygiene` | Context hygiene: load-on-demand, tool-result governance, concise sub-session returns, long-task segmentation, controllable/uncontrollable split |
425
+ | 6 | `openfeel-delegation` | Delegation + pipeline discipline: dual dispatch channels, boundaries, no-skipped-stage, Handoff, multimodal delegation, degradation |
426
+ | 7 | `openfeel-get-bugs` | List `open`/`fixing` bugs for the current module |
427
+ | 8 | `openfeel-get-stage-status` | Read `status.md`: status, responsible agent, dependency readiness, auto-advance eligibility, next-step mapping |
428
+ | 9 | `openfeel-health` | Load pipeline health-check results (flow.json ↔ workspace consistency) |
429
+ | 10 | `openfeel-logging` | Logging discipline: public/private log layout, mandatory events, entry format, skeleton files, prohibitions, mechanical archival checklist |
430
+ | 11 | `openfeel-memory` | Memory & write-back: load priority, preference merge, conflict arbitration, `update_infos` review, decision append, session-end write-back |
431
+ | 12 | `openfeel-model-check` | Session-start model self-check across all agents; expected vs actual; guide user configuration |
432
+ | 13 | `openfeel-model-config` | Model configuration locations/priority chain, how to find model names, multimodal (vision) caveats |
433
+ | 14 | `openfeel-recover` | Cross-session context recovery at session start |
434
+ | 15 | `openfeel-req` | Requirement layer reference: 4-layer model, 7 statuses, `req` subcommands, claim protocol, REQ-NNN body vs flow.json authority, relation to plan/`workflow_mode` |
435
+ | 16 | `openfeel-roadmap` | Load the project roadmap (version plan, milestones) |
436
+ | 17 | `openfeel-search-kb` | Standalone semantic/vector KB search with graph traversal (superseded by `check-kb` step 5) |
437
+ | 18 | `openfeel-sync-status` | Aggregate all members' progress views (`dev_last` indices + flow.json) |
438
+ | 19 | `openfeel-tool-usage` | The four core tools (`todowrite`/`question`/`task`/`skill`): triggers, requirements, prohibitions, priority table, scale thresholds, cross-harness tool-name map |
439
+ | 20 | `openfeel-update-stage-status` | Standardized `status.md` updates (status / responsible agent / state record) |
440
+ | 21 | `openfeel-wizard` | Interactive terminal pipeline wizard |
441
+ | 22 | `openfeel-workspace` | Workspace self-check + directory semantics (public/private) + skeleton templates (current.md, dev_last index/topics, REV entries, bug tree) |
442
+
443
+ `feel`'s Skill Index lists 16 of these (the operational subset); the other six (`agent-model-check`, `bug-acceptance`, `model-check`, `search-kb`, `sync-status`, `update-stage-status`) are invoked from subagents or side flows.
444
+
445
+ ## 3.3 `SKILL.md` frontmatter contract
446
+
447
+ Exactly two fields, both required, no optional keys, no nested objects:
448
+
449
+ ```yaml
450
+ ---
451
+ name: openfeel-req # MUST equal the containing directory name
452
+ description: <one paragraph> # single line; states scope + concrete triggers ("当需要…时加载")
453
+ ---
454
+ ```
455
+
456
+ - No `allowed-tools`, `license`, `version`, `model`, `metadata`. The harness derives everything else from the directory (`{skills-dir}/{name}/SKILL.md`).
457
+ - `description` is the retrieval key: it is written as `{what it covers}。{when to load it}。` — the trailing trigger sentence is what the orchestrator matches against.
458
+ - Frontmatter is **byte-identical across harnesses and languages** (skills are neutral, Chinese-authored, never translated — only agents/constraints are bilingual).
459
+
460
+ Body conventions (observed in all sampled skills):
461
+
462
+ ```markdown
463
+ # {Title}
464
+
465
+ > ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 `node bin/openfeel.js <cmd>`;安装后使用 `openfeel <cmd>`。 ← optional, self-hosting note
466
+
467
+ ## 输入
468
+ - {what the caller must supply}
469
+
470
+ ## 执行步骤
471
+ ### 1. {step}
472
+ ### 2. {step}
473
+ ...
474
+
475
+ ## 输出
476
+ - {what to return}
477
+ ```
478
+
479
+ Additional structural conventions:
480
+
481
+ - Numbered `###` steps; heavy use of Markdown tables for layouts, mappings, checklists.
482
+ - Explicit **边界声明** paragraphs stating what the skill does *not* cover and which other skill does (single-ownership rule; e.g. logging ≠ workspace skeleton shapes).
483
+ - Cross-skill pointers use backticked skill names, never duplicated prose.
484
+ - Harness-specific content is isolated into `### opencode 适配器` / `### Claude Code 适配器` subsections; the rest is platform-neutral (tool names expressed as capabilities, e.g. "以 `Skill` 工具加载" in the Claude subsection).
485
+ - `openfeel-tool-usage` carries the canonical 10-row tool-name map: `bash/Bash, read/Read, write/Write, edit/Edit, glob/Glob, grep/Grep, todowrite/TodoWrite, skill/Skill, task/Task, question/AskUserQuestion`.
486
+
487
+ ## 3.4 Agent inventory (10), both harnesses
488
+
489
+ ### 3.4.1 Frontmatter contracts
490
+
491
+ **Claude format** (`~/.claude/agents/{name}.md`) — fields:
492
+
493
+ ```yaml
494
+ ---
495
+ name: openfeel-planner # required, equals file name
496
+ description: "…: role summary; read-only, does not edit source." # quoted, imperative
497
+ tools: Bash, Read, Glob, Grep, Skill # comma-separated allowlist; OMITTED for feel (inherits all)
498
+ effort: max # low | medium | max
499
+ color: blue # purple | green | blue | pink | cyan | yellow
500
+ model: inherit # always `inherit` on the Claude side (never a vendor id)
501
+ ---
502
+ ```
503
+
504
+ **opencode format** (`~/.config/opencode/agents/{name}.md`) — fields:
505
+
506
+ ```yaml
507
+ ---
508
+ description: <role summary, one line, no quotes needed>
509
+ mode: primary # `primary` for feel, `subagent` for the other 9
510
+ model: deepseek/deepseek-flash # optional; omitted = inherit the primary model
511
+ reasoning_effort: medium # low | medium | max
512
+ color: "#8B5CF6" # quoted hex
513
+ permission: # map of tool → "allow" | "deny"; only listed tools are declared
514
+ bash: "allow"
515
+ read: "allow"
516
+ glob: "allow"
517
+ grep: "allow"
518
+ task: "allow"
519
+ external_directory: "allow"
520
+ ---
521
+ ```
522
+
523
+ Field mapping applied when generating the Claude variant from the opencode source (documented and asserted by build):
524
+
525
+ | opencode | Claude | Rule |
526
+ |---|---|---|
527
+ | `description` | `description` | rewritten/quoted |
528
+ | `mode` | — | dropped (`primary` → omitted `tools`, i.e. inherit; `subagent` → explicit allowlist) |
529
+ | `permission:` | `tools:` | allowlist projection, capitalized names |
530
+ | `reasoning_effort` | `effort` | renamed, same value domain |
531
+ | `color: "#RRGGBB"` | `color: <name>` | hex → nearest named color |
532
+ | `model` | `model: inherit` | vendor ids never leak into Claude templates |
533
+ | — | `name` | added |
534
+
535
+ Two deliberate asymmetries: (a) `feel` has **no** `tools` key on the Claude side (inherits the full toolset, R-13); (b) `external_directory` has no Claude equivalent and is explicitly **not simulated** — cross-directory access is governed by session permissions + the tools allowlist.
536
+
537
+ ### 3.4.2 Roster with roles and read-only constraints
538
+
539
+ | Agent | Role | read-only? | Claude `tools` | opencode `permission` keys | model / effort |
540
+ |---|---|---|---|---|---|
541
+ | `feel` | Orchestrator / dispatcher (main session). Owns intent classification, dispatch, pipeline management, stuck-state arbitration | writes only via whitelist (git add/rm, cp/mv, mkdir, rm non-source, cat, base64, diff, simple sed on non-`.ts`, `npm run build/test`); **never** edits source or flow.json | *omitted* (inherits all) | bash, read, glob, grep, task, todowrite, skill, webfetch, external_directory | inherit / medium |
542
+ | `openfeel-planner` | Planning officer: roadmap → work stages, `plan.md`, `deps.yaml`, dependency types | **yes** (no Edit/Write) | Bash, Read, Glob, Grep, Skill | bash, read, glob, grep, task, external_directory | inherit / max |
543
+ | `openfeel-schemer` | Scheme officer: stage → `op-NNN` operation schemes with self-test checklists | **yes** | Bash, Read, Glob, Grep, Skill | bash, read, glob, grep, task, external_directory | inherit / max |
544
+ | `openfeel-executor` | Implements strictly per op scheme + self-tests, no scope expansion | no | Bash, Read, Glob, Grep, Edit, Write, TodoWrite | bash, read, glob, grep, task, external_directory | explicit flash / medium |
545
+ | `openfeel-reviewer` | Cross-review plan/scheme/code, file `REV-*` entries; reviews only | **yes** | Bash, Read, Glob, Grep | bash, read, glob, grep, task, external_directory | explicit flash / medium |
546
+ | `openfeel-feel-tester` | Formal test acceptance, files `BUG-*` entries | no (writes BUG records) | Bash, Read, Glob, Grep, Edit, Write, Skill | bash, read, glob, grep, task, skill, **webfetch: deny**, external_directory | inherit / medium |
547
+ | `openfeel-archiver` | Archive op records, extract knowledge into `kb/`, maintain `manual/` | no (writes docs/kb, never source) | Bash, Read, Glob, Grep, Edit, Write | bash, read, glob, grep, task, external_directory | inherit / low |
548
+ | `openfeel-explorer` | Read-only exploration / issue locating; structured conclusions, no side effects | **yes — hard** | Bash, Read, Glob, Grep | **exactly** bash, read, glob, grep, external_directory (no `task`, no write) | explicit flash / medium |
549
+ | `openfeel-utility` | Mechanical file ops, format conversion, build/test helper; escalates to executor | no | Bash, Read, Glob, Grep, Edit, Write | bash, read, glob, grep, task, **edit: allow**, external_directory | explicit flash / low |
550
+ | `openfeel-vision` | Multimodal: images / UI screenshots / diagrams / error stacks → structured analysis | effectively read-only | Bash, Read, Glob, Grep | bash, read, glob, grep, task, external_directory | explicit flash / medium |
551
+
552
+ Read-only enforcement is **by tool-set subtraction**, not by prompt text: planners/schemers/reviewers/explorer have no write/edit tool, so the constraint is structural. `openfeel-explorer` is additionally the "read-only general worker" used instead of the harness's built-in `Explore`/`general` subagents; its permission set is asserted to be exactly 5 keys and its Claude `tools` exactly 4 items.
553
+
554
+ Colour constants: feel `#8B5CF6`/purple, planner `#6A8DFF`/blue, schemer `#4A90D9`/blue, executor `#4CAF50`/green, reviewer `#EC4899`/pink, utility `#8B9DC3`/blue, vision `#06B6D4`/cyan, feel-tester `#22D3EE`/cyan, archiver `#50C878`/green, explorer `#FFB020`/yellow.
555
+
556
+ ### 3.4.3 Standard agent body sections
557
+
558
+ Every agent body follows the same skeleton (post stage-91 "prompt slimming": a **skill index shell + minimal constraints**, not full procedures):
559
+
560
+ ```
561
+ <one-line identity: "You are {agent}, the {role} in the OpenFeel pipeline…">
562
+ ## Core Responsibilities (numbered, 4-7 items)
563
+ ## Skill Index (markdown table: skill | one line) ← only 1-3 rows per subagent
564
+ ## Workflow Mode Awareness (fast) (full vs fast expectations; fast = lightweight but never omit task table/deps/acceptance)
565
+ ## Requirement Awareness (planner only; REQ-NNN reading + plan header annotation)
566
+ ## Dispatch and Handoff (who this agent may dispatch + [HANDOFF: …] sentence)
567
+ ## Model Selection (why inherit vs explicit)
568
+ ## Notes / Output Contract (invariants + return format)
569
+ ```
570
+
571
+ `feel` additionally carries: Direct Operation Whitelist, Auto-Advance Decision Rules, Workflow Mode (full/fast/general + fast discipline + terminology disambiguation), Requirement Lifecycle, Archiving and History Reading, Command Index, Memory & Write-Back, Version Control Suggestion, New Version Start Rule, Output Contract.
572
+
573
+ ---
574
+
575
+ # 4. Agent role model, phase ownership, delegation protocol
576
+
577
+ ## 4.1 The 15-phase pipeline
578
+
579
+ ```ts
580
+ PIPELINE_PHASES = ['plan_pending','plan_review','plan_passed',
581
+ 'scheme_pending','scheme_review','scheme_passed',
582
+ 'exec_running','review_pending','review_failed',
583
+ 'review_passed','test_pending','test_failed',
584
+ 'test_passed','archiving','done']
585
+ META_PHASES = ['active','paused','done'] // global pipeline.phase: macro state only
586
+ ```
587
+
588
+ `flow.json` is a **multi-stage independent state machine**: each `stages.{id}.phase` carries its own phase; the global `pipeline.phase` is metadata. Dispatch always traverses `stages` and picks an active stage (`phase != 'done'`).
589
+
590
+ Transition keys may combine sources with `|` (`'test_passed|review_passed': ['archiving']`), parsed by a single control point (`parseTransitionKey` / `transitionKeyMatches`).
591
+
592
+ ## 4.2 Phase → agent ownership (single table)
593
+
594
+ From `src/core/metrics.ts:25` `PHASE_AGENT_MAP` (used by `recordRun` for `flow metrics`):
595
+
596
+ | Phase | Owning agent |
597
+ |---|---|
598
+ | `plan_pending`, `plan_review` | `openfeel-planner` |
599
+ | `scheme_pending`, `scheme_review` | `openfeel-schemer` |
600
+ | `exec_running` | `openfeel-executor` |
601
+ | `review_pending`, `review_failed` | `openfeel-reviewer` |
602
+ | `test_pending`, `test_failed` | `openfeel-feel-tester` |
603
+ | `archiving` | `openfeel-archiver` |
604
+
605
+ Unmapped (`*_passed`, `done`, meta phases) = no run recorded. `feel` owns no phase; it owns **orchestration** (dispatch + `openfeel flow advance`).
606
+
607
+ ## 4.3 Three workflow modes
608
+
609
+ | Mode | Chain | Notes |
610
+ |---|---|---|
611
+ | `full` | planner → plan_review (reviewer) → schemer → executor → reviewer → tester → archiver | 15 phases reachable |
612
+ | `fast` | planner (lightweight) → plan_review (**single mandatory plan review**) → executor → reviewer (single round) → archiver (lightweight) | `openfeel-schemer` and `openfeel-feel-tester` are **not dispatched**; 6 phases unreachable (`scheme_*`, `test_*`); legal phase enumeration still 15 ("reachable ≠ legal"); reviewer still runs **twice** (plan review + code review); planner/archiver must not omit task table / dependencies / acceptance points / state record / KB staging decision |
613
+ | `general` | none (empty transition table) | dispatch general workers (executor/explorer) on demand; valid only at the **requirement level**, not stage/deployment level; may be explicitly upgraded to fast/full, never auto-downgraded |
614
+
615
+ Mode resolution is single-point (`resolveStageWorkflowMode`), precedence `status.md > requirement-level > config.yaml > profile > builtin`. Terminology rule: "Workflow Fast Line" (`workflow_mode=fast`) ≠ "Fast Track / FAST-PASS" (review-depth downgrade) — never cross-reference.
616
+
617
+ Review tiers (orthogonal third axis, `flow advance --review-tier full|lean|skip`): prune only transitions — `lean` prunes `scheme_review`, `skip` prunes `plan_review` + `scheme_review`; `review_pending`, test and archive gates are never pruned; the 15-value phase enum is never reduced.
618
+
619
+ ## 4.4 Delegation / handoff protocol
620
+
621
+ Two independent channels — mixing them is a discipline violation:
622
+
623
+ **Channel ① — direct dispatch to general workers.** Any agent (including subagents) may send a dispatch request straight to a general worker, no multi-level referral:
624
+
625
+ - `openfeel-explorer` — read-only exploration, issue locating.
626
+ - `openfeel-executor` — implementation, modification, self-test.
627
+ - `openfeel-feel-tester` is also directly dispatchable (same semantics) though not classified as a "general worker".
628
+
629
+ Platform mapping:
630
+
631
+ - opencode: nested `task` tool. **Measured no-go at default config** — platform `subagent_depth = 1` blocks the nested call; raising `subagent_depth: 3` proved the permission extension works and the only blocker is the platform threshold.
632
+ - Claude Code: `Task` tool. **Measured yes** on 2.1.300 — a subagent whose allowlist includes `Task` can spawn one level down (`spawnDepth: 2`; runtime tool name `Agent`).
633
+
634
+ The skills deliberately **do not name a tool**: they say "dispatch through the platform's dispatch capability", so both outcomes stay valid.
635
+
636
+ **Channel ② — `[HANDOFF: agent_name]` marker.** A returned result may carry this marker (target + context); the **caller** parses it and dispatches the target agent with the original context, then relays the result back and records the dispatch event. Every subagent body carries the canonical sentence:
637
+
638
+ > For subtasks beyond this Agent's responsibility that require referral to another Agent: include a `[HANDOFF: agent_name]` marker in the returned result (target + context), which the Handoff delegation mechanism will parse and dispatch.
639
+
640
+ **Delegation prompt contract** — three required parts, any missing part means the prompt is invalid: (a) task description (what + acceptance criteria), (b) expected return (format and granularity; standard: ≤10-line summary + full report written to disk), (c) boundary (read-only vs writable, with a path allowlist).
641
+
642
+ **Degradation**: if direct dispatch is unavailable (no such capability / permission denied), fall back to asking an upstream agent to relay ("this session has no direct dispatch capability, please relay"), log the degraded path, and return to channel ① when capability is restored.
643
+
644
+ **Mandatory-delegation table** (any hit ⇒ must delegate, never self-perform): plan/stages → planner; operation scheme → schemer; source coding / cross-file refactor / dependency change → executor; code review → reviewer; formal test acceptance → feel-tester; bulk search/exploration → explorer; mechanical file ops → utility; archival/knowledge extraction → archiver; visual/multimodal → vision.
645
+
646
+ **Direct-operation whitelist** (the only self-performable actions): `git add`/`git rm`, `cp`/`mv`, `mkdir`, `rm` (non-source), `cat`, base64 encode/decode, `diff`, simple `sed` on non-`.ts` files, `npm run build`, `npm test` (verification only). Explicitly forbidden: editing source content, cross-file refactoring, `install`/`uninstall`. Each whitelisted action must be a single self-contained command with no dependency chain.
647
+
648
+ **No-skipped-stage discipline** (hard rules, each stage must produce an artifact even if the artifact is "pass, no changes"): no self-written `plan.md`; no executor work without a scheme; no self-review; no `npm test` as a substitute for tester acceptance; no self-serve `kb/` updates at archive time. Review failures must be fixed through "schemer → executor → reviewer re-acceptance", even for whitespace-level REVs. When delegating without a scheme, the delegation prompt must require the executor to first create a minimal op file (`# op-NNN`, change target, file list) — the op file is the audit-chain evidence.
649
+
650
+ **Reviewer-session health probe**: on the reviewer's first round, require a minimal tool self-check (version query + read a known file and report its content). Failure/mismatch ⇒ the session is unusable: abort, restart it, and demote its already-written REV/acceptance records to **pending re-review** with a confidence statement.
651
+
652
+ **Parallelism**: only for independent subtasks (no dependency chain, no write-set overlap, not sharing an API/data structure/agent template file). Split via `openfeel req split` → claim by requirement (`req claim`); each branch is independently reviewed and archived.
653
+
654
+ ---
655
+
656
+ # 5. The managed-region mechanism
657
+
658
+ Purpose: inject framework content into **user-owned** files (`AGENTS.md`, `CLAUDE.md`, `.gitignore`) without clobbering user content, and make re-deployment idempotent.
659
+
660
+ ## 5.1 Marker syntax (exact)
661
+
662
+ | File type | begin marker | end marker |
663
+ |---|---|---|
664
+ | Markdown (`.md`) | `<!-- openfeel:begin -->` | `<!-- openfeel:end -->` |
665
+ | `.gitignore` (exactly that basename) | `# openfeel:begin` | `# openfeel:end` |
666
+ | `.jsonc` | none — deep merge (handled by `opencode-config.ts`, not by the region code) | — |
667
+ | anything else | unsupported → `writeManagedFile` throws | — |
668
+
669
+ Rules:
670
+
671
+ - Markers are matched as **whole lines after `trim()`**, so leading/trailing indentation is tolerated; CRLF/CR is normalized to LF before parsing.
672
+ - Wrapped region serialization: `wrapRegion(content)` → `` `${begin}\n${content.trim()}\n${end}\n` ``.
673
+ - `replaceRegion(existing, incoming)` requires `parseRegion(existing).status === 'ok'` (throws otherwise — a deliberate guard against deleting user content). It preserves the prefix (all lines before begin) and the suffix (all lines after end) **byte-for-byte**, including the original trailing newline: the result satisfies `replaceRegion(x, extractRegion(x)) === normalize(x)`.
674
+ - Distinct from the unrelated single-line build marker `openfeel:generated` (whole-file artifact declaration). Different token, different form; the two are deliberately not unified.
675
+
676
+ ## 5.2 The three states
677
+
678
+ `parseRegion(existing, type)` counts marker lines and returns one of:
679
+
680
+ | State | `status` | Condition | Deployment behavior |
681
+ |---|---|---|---|
682
+ | **ABSENT** | `'none'` | neither marker appears | Two sub-cases, hash-gated: ① recorded hash in `update_state.files[stateKey]` **equals** `hashContent(existing)` ⇒ the file is a pristine copy of a previously deployed framework file → rewrite the whole file as frontmatter + wrapped region, action `updated` (a.k.a. "adopt"); ② no record or hash differs ⇒ the file is user-authored → **append** `wrapRegion(incomingBody)` at EOF (one `\n` separator if the file lacks a trailing newline), action `appended`, and record an `appended` entry in `~/.openfeel/update_infos.md` for session-start review |
683
+ | **PRESENT-AND-OWNED** | `'ok'` | exactly one begin, one end, `beginIndex < endIndex` | `status.md`-style in-place update: merge frontmatter (§5.3), `replaceRegion` the body, compare the **whole assembled content** to `normalize(existing)`; identical ⇒ `skipped` (byte-idempotent, no backup, no write); otherwise back up the target, write atomically, action `updated` |
684
+ | **PRESENT-BUT-DRIFTED** | `'ok'` + differing content | the same `ok` geometry, but region content (or frontmatter) differs from the incoming template | Same as above: backup → region replace → `updated`. Drift is *normal* and self-healing; only the region is touched, so user edits outside the region survive |
685
+ | **MALFORMED** | `'malformed'` | begin/end counts unequal, or >1 pair, or end before begin | **No write, no append.** Record an `anomaly` entry in `update_infos.md`, action `skipped`. This is the anti-infinite-append guard: appending on a malformed file would duplicate the region on every run. After the human fixes the markers, the next run takes the `ok` or `none` path normally |
686
+
687
+ Frontmatter is split off before region parsing, so markers inside Frontmatter-delimited files behave the same as in plain Markdown:
688
+
689
+ - `---\n…\n---\n` at the very start with a closing `\n---\n` ⇒ parsed (YAML); empty frontmatter (`---\n---\n`) ⇒ `{}`; YAML syntax error ⇒ treated as "no frontmatter" (degraded, never throws).
690
+ - Serialization always ends with `---\n` (guards against `yaml.stringify` omitting the final newline and gluing onto the closing fence).
691
+
692
+ ## 5.3 Frontmatter merge and the user-sovereign-key rule
693
+
694
+ ```ts
695
+ export const USER_SOVEREIGN_KEYS: readonly string[] = ['model'];
696
+
697
+ mergeFrontmatter(existing, incoming, preserveKeys = []) {
698
+ merged = { ...existing, ...incoming }; // framework keys win
699
+ for (key of preserveKeys)
700
+ if (hasOwn(existing, key)) merged[key] = existing[key]; // user value wins, even if null/empty
701
+ return merged;
702
+ }
703
+ ```
704
+
705
+ - Semantics: **key-level "create if absent, never overwrite if present"**. Only `model` is user-sovereign today. All other framework keys (`name`, `description`, `tools`, `permission`, `effort`, `color`, `mode`, `reasoning_effort`, …) are overwritten by the template on every deployment.
706
+ - Existing-only keys (user additions) pass through untouched: `mergeFrontmatter` is a shallow merge with passthrough, no key filtering, no reordering guarantees beyond JS insertion order for the serialized YAML.
707
+ - Scope: **deployment paths only** (`writeManagedFile`'s `ok` branch). `openfeel model set --scope default` deliberately does **not** pass the whitelist, so its overwrite semantics stay literal.
708
+ - If the incoming template has no frontmatter, the existing frontmatter is kept as-is (prevents silent loss on `AGENTS.md`/`core.md`).
709
+
710
+ ## 5.4 Non-destructive guarantees (write path checklist)
711
+
712
+ 1. Target missing ⇒ create (`created`); no backup needed.
713
+ 2. Target exists and content will actually change ⇒ `backupFileBeforeWrite` runs **before** the write and **outside** the target-content lock (avoids lock nesting); result recorded as a `backed` entry plus a TTY notice.
714
+ 3. Backup failure (`BackupError`) ⇒ **the write is skipped** (`skipped`), an `anomaly` entry with `note: backup_failed` is recorded, the command continues with the remaining assets and the exit code stays successful. This is a hard "never overwrite without a backup" invariant.
715
+ 4. Atomic writes everywhere (`temp + rename`), advisory file locks around read-modify-write (`~/.openfeel/locks/{name}.lock` for global, `.openfeel/tmp/locks/{name}.lock` for project).
716
+ 5. Malformed markers ⇒ never append, never write; only an anomaly entry.
717
+
718
+ Backup layout (`~/.openfeel/backup/{yyyyMMddTHHmmssSSS}/`):
719
+
720
+ ```
721
+ <ts>/manifest.json
722
+ <ts>/project/{basename}-{sha8(projectPath)}/{project-relative path}
723
+ <ts>/global/{HOME-relative path}
724
+ <ts>/global-external/{sha8(absPath)}/{segment…} # HOME-external paths, reserved chars sanitized
725
+ ```
726
+
727
+ `manifest.json` entries: `{ backupRel, hash, command, time }`.
728
+
729
+ `~/.openfeel/update_infos.md` — three sections, three entry kinds:
730
+
731
+ ```markdown
732
+ ## 追加(无标记 → 末尾追加受管区) / ## 异常(…需人工修复标记) / ## 备份(…已备份,待检查)
733
+ - [ ] `{absolutePath or relativePath (项目: root)}`({timestamp})
734
+ - [x] `…`({timestamp})(备份: `{backupRel}`,来源: {command})
735
+ - [ ] `…`({timestamp})(原因: backup_failed)
736
+ ```
737
+
738
+ `anomaly` entries are de-duplicated per path while unresolved (prevents unbounded growth); resolution is a manual `- [ ]` → `- [x]` tick, mirrored by `resolveUpdateInfo`.
739
+
740
+ ---
741
+
742
+ # 6. Deployment model
743
+
744
+ ## 6.1 Global path table
745
+
746
+ | Constant | Path | Purpose |
747
+ |---|---|---|
748
+ | `getOpencodeGlobalDir()` | `~/.config/opencode` | opencode adapter root |
749
+ | `getGlobalAgentsDir()` | `~/.config/opencode/agents` | 10 opencode agents |
750
+ | `getGlobalSkillsDir()` | `~/.config/opencode/skills` | 22 skills (`{name}/SKILL.md`) |
751
+ | `getGlobalAgentsMdPath()` | `~/.config/opencode/AGENTS.md` | **sole authority** for the framework constraint layer (opencode) |
752
+ | `getGlobalOpencodeJsoncPath()` | `~/.config/opencode/opencode.jsonc` | platform config (deep-merged) |
753
+ | `getGlobalCoreMdPath()` | `~/.config/opencode/openfeel/core.md` | deprecated since v1.1.1 — detect/clean only |
754
+ | `getClaudeConfigDir()` | `$CLAUDE_CONFIG_DIR` (non-blank, read inside the function) else `~/.claude` | Claude adapter root |
755
+ | `getClaudeAgentsDir()` | `<claude>/agents` | 10 Claude agents |
756
+ | `getClaudeSkillsDir()` | `<claude>/skills` | same 22 skills |
757
+ | `getClaudeMdPath()` | `<claude>/CLAUDE.md` | constraint layer (Claude) |
758
+ | `getClaudeSettingsPath()` | `<claude>/settings.json` | strict-JSON deep merge |
759
+ | `getGlobalUpdateStatePath()` | `~/.openfeel/update_state.json` | deployed-asset hashes + `openfeel_version` + `last_update` |
760
+ | `getGlobalUpdateInfosPath()` | `~/.openfeel/update_infos.md` | append / anomaly / backup ledger |
761
+ | `getGlobalBackupRootPath()` | `~/.openfeel/backup` | pre-write backups |
762
+ | `getGlobalProfilePath()` | `~/.config/openfeel/profile.yaml` | cross-project user profile |
763
+ | `getGlobalSchemasDir()` | `~/.openfeel/schemas` | schema home-directory layer |
764
+ | `getGlobalLockPath(name)` | `~/.openfeel/locks/{name}.lock` | global write locks |
765
+ | `getCliLogsDir()` | `~/.openfeel/cli/logs` | CLI runtime logs |
766
+ | `getAuthJsonPath()` | `~/.local/share/opencode/auth.json` | model provider validation |
767
+
768
+ `os.homedir()` is imported in exactly one module (`global-paths.ts`) so tests can mock a single point.
769
+
770
+ ## 6.2 What `setup` deploys
771
+
772
+ `setupGlobalFramework(lang = 'zh-CN', tools = ['opencode'])` — **pure global**, creates no project `.openfeel/`. Every file goes through `deployGlobalAsset` (managed-region three-state, idempotent, re-runnable) and returns `{ created, updated, skipped, appended }`.
773
+
774
+ opencode branch:
775
+
776
+ 1. `~/.config/opencode/AGENTS.md` ← `loadTemplate(lang,'agents-md')` (constraint layer: code of conduct, task-type routing, 7 core constraints, knowledge constraint "consult the KB first", operational conventions, coding/comment style, cross-agent boundary table, `.openfeel` workspace constraints, pointer block to skills).
777
+ 2. `~/.config/opencode/agents/{id}.md` × 10.
778
+ 3. `~/.config/opencode/skills/{name}/SKILL.md` × 22.
779
+ 4. `~/.config/opencode/opencode.jsonc` — deep merge, under the global lock, with a pre-write backup: framework object is `{ $schema, default_agent: 'feel', agent: { 'openfeel-vision': {model: 'deepseek/deepseek-flash'}, 'openfeel-reviewer': {model: 'deepseek/deepseek-flash'} } }`. Merge semantics: `instructions` arrays are unioned, `agent` keys are filled **only when the user has not defined the agent key at all** (never rewriting existing agent fields), `skills` is never written, and stale `core.md` references are pruned. Parse failure ⇒ keep the original file, warn, continue.
780
+
781
+ Claude branch (only when `tools` includes `claude`):
782
+
783
+ 1. `<claude>/CLAUDE.md` ← `loadClaudeConstraintTemplate(lang)`.
784
+ 2. `<claude>/agents/{id}.md` × 10.
785
+ 3. `<claude>/skills/{name}/SKILL.md` × 22 (same shared skill text).
786
+ 4. `<claude>/settings.json` — strict-JSON deep merge injecting exactly `{ agent: 'feel', permissions: { defaultMode: 'auto' } }`; all other user keys (`env`, `model`, `modelPicker`, `theme`, `apiKeyHelper`, …) preserved verbatim; output `JSON.stringify(obj, null, 2) + '\n'` (insertion-order–dependent byte idempotence); empty file ⇒ `{}`; parse failure / read failure / backup failure ⇒ **no write**, `anomaly` entry, action `skipped`.
787
+
788
+ Finally `setup` records hashes for every created/updated/appended path and refreshes `openfeel_version = getOpenfeelVersion()` (the deployed-version fact source).
789
+
790
+ `update` runs the same two branches with `deployGlobalAsset` (plus legacy/deprecated-key remapping, project-level files, and `conflicts` reporting) — the deployment primitives are shared, so the three-state semantics are identical between `setup` and `update`.
791
+
792
+ ## 6.3 Non-destructive guarantees and drift detection
793
+
794
+ - User-authored files are never replaced wholesale: content goes inside markers, or the file is only appended to, or it is skipped.
795
+ - Every overwrite is preceded by a backup; a backup failure aborts that single write (never the whole command).
796
+ - User `settings.json` / `opencode.jsonc` keys are preserved; only framework-owned keys are written.
797
+ - `model` is user-sovereign on the deployment path (agent frontmatter), so a locally configured model (e.g. a gateway alias, or a heterogeneous reviewer model) survives `update`.
798
+ - `shouldRunDeployCheck({argv, isTTY, env, alreadyWarned})` gates a passive drift warning. `checkGlobalDeployment()` returns four states: `ok` (versions equal), `mismatch` (unequal, including downgrade), `missing` (`update_state.json` absent), `unknown` (`schema invalid`/read error — silent). The warning is emitted on **stderr**, at most once per process, and never changes the exit code. It is suppressed when: non-TTY, already warned, `--json`, `--quiet`, `--version`/`-v`, `--help`/`-h`, first non-option token ∈ `{setup, update, init, migrate}`, or `CI` / `OPENFEEL_NO_UPDATE_CHECK` is truthy (`'1'`/`'true'`, case-insensitive, trimmed). `openfeel setup --check [--json]` is the explicit active diagnostic (ok ⇒ exit 0, other states ⇒ exit 1).
799
+ - Assets are deployed per file; a `skipped` result is a normal, expected, non-error outcome.
800
+
801
+ ---
802
+
803
+ # 7. What a DSH-native reimplementation should reuse vs replace
804
+
805
+ ## 7.1 Reuse nearly verbatim (portable content/semantics)
806
+
807
+ | Item | Why | Porting note |
808
+ |---|---|---|
809
+ | The 22 `SKILL.md` bodies | Deep, hard-won operational rules (pipeline discipline, logging, memory, requirements, context hygiene). Nothing in them is opencode/Claude-specific except tool names and the `### … 适配器` subsections. | Copy `templates-data/skills/**` as-is; rewrite only the harness subsections and the tool-name map. Keep frontmatter as exactly `name` + `description` (DSH's skill loader can consume the same contract). |
810
+ | KB directory layout, entry format, `[+]`/`[-]` convention, append-only discipline | The archiver prompts, `openfeel-check-kb` and the on-disk reality of any migrated project all assume it. | Reimplement `knowledge.ts` parsing/writing 1:1 (including the two header regexes, the title sanitization, the atomic read-concat-write under `kb.lock`). |
811
+ | `index.md` tolerant parser (section aliases, alias-column matching, degrade-to-empty) | Real index files in the wild are all different shapes. | Port `extractSection` / `parseMarkdownTable` / `colIndex` / `parseRecentUpdatesTable` directly. |
812
+ | Dedup math + merge convention (`Jaccard`, `> 0.8`, `> **更新于 date**:`) | Encoded in agent prompts and in existing KB files; changing it silently changes archival behavior. | Port `tokenize`/`jaccardSimilarity`/`shouldUpdate`/`mergeEntry` literally; if you upgrade to embeddings, keep the threshold semantics and add an explicit compatibility note. |
813
+ | `managed-region.ts` primitives + the three-state decision table + `USER_SOVEREIGN_KEYS=['model']` | This is the entire "don't destroy the user's file" contract; it is harness-independent. | Port as a library; keep marker tokens identical so existing files stay recognized (`<!-- openfeel:begin -->` / `# openfeel:begin`). |
814
+ | Frontmatter split/serialize + shallow merge + key-level sovereign rule | Applies to any agent-definition format, including DSH's. | Keep `model` (and any future DSH-specific user-owned key) in the whitelist; document the whitelist as an explicit constant. |
815
+ | Backup + `update_infos.md` ledger | Provides auditability and the "backup failed ⇒ don't write" invariant. | Port path scheme and the three entry kinds; keep the `- [ ]` / `- [x]` checklist convention. |
816
+ | Phase enum, transition `|`-combining, review tiers | The role model is the actual product value; phases/agents are harness-agnostic concepts. | Port phase/tier **semantics** as constants; keep 15 phases + "reachable ≠ legal" wording. **Do not port `PHASE_AGENT_MAP` into an adapter** — consume `flow next --json.responsibleAgent` as the single authority (v1.2.2+). |
817
+ | Agent body prose (roles, whitelists, no-skipped-stage rules, delegation prompt contract, health probe) | These are the behavioral spec; only the delegation *transport* is harness-specific. | Copy bodies, swap the dispatch sentence for DSH's mechanism (§7.2). |
818
+ | `artifact-graph/instruction-loader` XML shape | Consumed by downstream tooling/tests; cheap to keep. | Port tag names and escaping rules exactly if any existing artifact schema is reused. |
819
+
820
+ ## 7.2 Replace (harness-specific — do not port)
821
+
822
+ | Item | Why it must be replaced |
823
+ |---|---|
824
+ | `~/.config/opencode/{agents,skills,AGENTS.md,opencode.jsonc}` deployment | opencode-only paths, `permission:` map semantics, `mode: primary/subagent`, `external_directory`, `default_agent`. DSH has none of these. |
825
+ | `~/.claude/{agents,skills,CLAUDE.md,settings.json}` deployment + `agent: 'feel'` / `permissions.defaultMode: 'auto'` injection | Claude Code-only. The `tools:` allowlist + `effort`/`color` frontmatter are Claude conventions. |
826
+ | opencode `permission:` blocks as the read-only enforcement mechanism | Replace with DSH's tool allowlisting (per-agent tool set / policy). Keep the *intent table* (§3.4.2) as the source of truth and map it to DSH tools. |
827
+ | Nested `task` dispatch / Claude `Task`+`spawnDepth` | Replace with DSH `subagent` / `subagent_fork` (or `workflow` for fan-out). Keep the `[HANDOFF: agent_name]` marker contract, since it is transport-neutral and is parsed by the *caller*. |
828
+ | `skill("openfeel-x")` / `Skill` tool loading | DSH loads skills through its own `skill` tool + catalog; the `{skills}/{name}/SKILL.md` layout can still be mirrored if the DSH loader supports it, otherwise map 1:1 to DSH skill entries. |
829
+ | Adapter docs (`ADAPTER.{lang}.md`), Base64 template inlining, `build.js` dual-generation into `template-loader.ts` **and** `update.ts` | Build-time inlining exists only because the published npm package must ship templates inside TS constants. A DSH plugin can read `.md` assets at runtime — drop the generator, but keep a key-set/content validator if you keep multiple harness copies. |
830
+ | Global path module, `deployment-check.ts` 4-state drift check, `claude-settings.ts` | Replace with DSH equivalents (plugin install/update, profile-level assets). Keep the *semantics*: 4-state read-only check, non-blocking warning, user-key preservation. |
831
+ | `openfeel-check-kb` §5 semantic fallback + `openfeel-search-kb` | Reference non-existent `scripts/build_kb_index.py` / `scripts/search_kb.py` and `.openfeel/tmp/vectors/index.json`. Replace with a real DSH capability (embedding index or tool-based search) or delete the step. |
832
+ | `openfeel-model-config` / `model-check` / `agent-model-check` provider specifics (`auth.json`, `opencode.jsonc` model resolution, Claude `/model`, gateway aliases) | Provider plumbing is harness-owned. Keep the *generic* guidance (frontmatter beats config; multimodal agents need a vision model) and rewrite the concrete commands. |
833
+
834
+ ## 7.3 Divergences a DSH port should fix (and record)
835
+
836
+ 1. **Two header regexes** (strict in list/search, tolerant in dedup) — unify on the tolerant one, and normalize CRLF in *all* KB read paths (the `L8` defect).
837
+ 2. **`index.md` `最近更新` columns** — the shipped template is `| 日期 | 分类 | 标题 |` but the real repo file is `| 日期 | 操作 | 描述 |`, so `knowledge index` prints empty category/title. Pick one canonical schema and validate it in `add`.
838
+ 3. **No relevance ranking in `knowledge search`** — substring + file order only. Consider ranking, but keep `--limit/--offset` semantics and the `**highlight**` output marker if scripts depend on them.
839
+ 4. **CJK tokenization in dedup** — sentence-level tokens make `> 0.8` almost all-or-nothing for Chinese entries.
840
+ 5. **Stale counts in comments/docs** (`9 agent`, `18 skill`) — the real numbers are 10 and 22; assert counts from the data, never from literals.
841
+ 6. **Duplicated dispatch constants** — `AGENT_TEMPLATES` and `OPENCODE_AGENT_TEMPLATES` currently hold the same 10 agents in the same format; a port should have exactly one registry per harness.
842
+ 7. **`external_directory` has no Claude equivalent** and is deliberately unsimulated — carry that note forward rather than inventing a fake permission.
843
+
844
+ ---
845
+
846
+ ## Appendix A — Quick-reference constants
847
+
848
+ ```ts
849
+ CATEGORIES = ['architecture','patterns','troubleshooting','setup']
850
+ CATEGORY_TITLES = { architecture:'架构决策', patterns:'代码模式',
851
+ troubleshooting:'常见问题', setup:'环境搭建' }
852
+ KB entry header = /^## (\[[+-]\]) (.+?) \((\d{4}-\d{2}-\d{2})\)$/gm // strict
853
+ /^##\s+\[([+-])\]\s+(.+?)\s+\((\d{4}-\d{2}-\d{2})\)$/ // dedup
854
+ KB add chunk = "\n## [+] {title} ({YYYY-MM-DD})\n\n{content}\n"
855
+ KB title sanitize = title.replace(/\|/g,'\\|').replace(/\s*[\r\n]+\s*/g,' ').trim()
856
+ KB index row = "| {date} | {category} | {title} |"
857
+ SIMILARITY_THRESHOLD = 0.8 // > (strict)
858
+ entry merge block = "\n\n> **更新于 {YYYY-MM-DD}**:{newContent}"
859
+ markers.markdown = { begin:'<!-- openfeel:begin -->', end:'<!-- openfeel:end -->' }
860
+ markers.gitignore = { begin:'# openfeel:begin', end:'# openfeel:end' }
861
+ wrapRegion(x) = "{begin}\n{x.trim()}\n{end}\n"
862
+ USER_SOVEREIGN_KEYS = ['model']
863
+ projectLockPath(p,n) = {p}/.openfeel/tmp/locks/{n}.lock
864
+ globalLockPath(n) = ~/.openfeel/locks/{n}.lock
865
+ ```
866
+
867
+ ## Appendix B — Counts
868
+
869
+ | Thing | Count |
870
+ |---|---|
871
+ | KB categories | 4 (fixed) |
872
+ | KB files per project | 5 (`index.md` + 4 categories) |
873
+ | `knowledge` subcommands | 5 (`list`, `add`, `search`, `index`, `dedup`) |
874
+ | Skills | 22 (one shared text, deployed to both harnesses) |
875
+ | Agents | 10 (`feel` + `openfeel-{planner,schemer,executor,reviewer,feel-tester,archiver,explorer,utility,vision}`) |
876
+ | Agent source files | 40 (10 × 2 harnesses × 2 languages) |
877
+ | Constraint files | 4 (`agents-md` ×2, `claude/constraint` ×2) + 2 adapter docs |
878
+ | Pipeline phases | 15 (+ 3 meta phases) |
879
+ | Workflow modes | 3 (`full`, `fast`, `general`) |
880
+ | Review tiers | 3 (`full`, `lean`, `skip`) |
881
+ | Update-info kinds | 3 (`appended`, `anomaly`, `backed`) |
882
+ | Managed-region states | 3 (`ok`, `none`, `malformed`) |
883
+ | Deploy-check states | 4 (`ok`, `mismatch`, `missing`, `unknown`) |