@double-codeing/flow2spec 3.1.0 → 3.1.2

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 (30) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/cli.js +122 -11
  4. package/docs/en/architecture.md +2 -2
  5. package/docs/en/commands-reference.md +14 -10
  6. package/docs/en/design-principles.md +8 -5
  7. package/docs/en/directory-conventions.md +4 -0
  8. package/docs/en/usage-guide.md +12 -4
  9. package/docs/en/usage-scenarios.md +2 -2
  10. package/docs//344/275/223/347/263/273/344/270/216/345/216/237/347/220/206.md +2 -2
  11. package/docs//344/275/277/347/224/250/346/241/210/344/276/213-/346/250/241/346/213/237/345/257/271/350/257/235.md +1 -1
  12. package/docs//344/275/277/347/224/250/350/257/264/346/230/216.md +11 -4
  13. package/docs//345/221/275/344/273/244/350/257/264/346/230/216.md +13 -10
  14. package/docs//347/233/256/345/275/225/344/270/216/350/267/257/345/276/204/347/272/246/345/256/232.md +4 -0
  15. package/docs//350/256/276/350/256/241/350/257/264/346/230/216.md +86 -53
  16. package/lib/claudeSettingsAdapter.js +133 -29
  17. package/lib/flow2specConfig.js +32 -6
  18. package/lib/init.js +148 -3
  19. package/package.json +1 -1
  20. package/templates/AGENTS.codex-stub.md +2 -0
  21. package/templates/AGENTS.md +13 -0
  22. package/templates/flow2spec.config.json +5 -2
  23. package/templates/hooks/f2s-config-inject.js +9 -147
  24. package/templates/hooks/f2s-config-session.js +95 -0
  25. package/templates/hooks/f2s-update-check.js +187 -0
  26. package/templates/knowledge/topics/f2s-config-precheck.md +2 -2
  27. package/templates/rules/f2s-config-check.mdc +3 -1
  28. package/templates/rules/f2s-flow2spec-unified-entry.mdc +13 -0
  29. package/templates/skills/f2s-git-commit/SKILL.md +21 -5
  30. package/templates/skills/f2s-kb-upgrade/SKILL.md +4 -0
@@ -27,12 +27,12 @@
27
27
 
28
28
  仓内 **四环**(规则环与技能环分列,勿合并为「规则+技能」):
29
29
 
30
- | 环 | 落点 | 职责 |
31
- | --- | --- | --- |
32
- | 知识环 | `.Knowledge/` | 路由 + 主题 + 存量/需求文档 |
33
- | 任务环 | `.task/` | 跨会话续作、用户代办 |
30
+ | 环 | 落点 | 职责 |
31
+ | ------ | ---------------------------- | ------------------------------------ |
32
+ | 知识环 | `.Knowledge/` | 路由 + 主题 + 存量/需求文档 |
33
+ | 任务环 | `.task/` | 跨会话续作、用户代办 |
34
34
  | 规则环 | 各工具 `rules` / `AGENTS.md` | 怎么读、怎么做(缺口闸门、路由顺序) |
35
- | 技能环 | `f2s-*` / `skills/` | 维护知识、触发 feat/fix/sync 等 |
35
+ | 技能环 | `f2s-*` / `skills/` | 维护知识、触发 feat/fix/sync 等 |
36
36
 
37
37
  ```mermaid
38
38
  graph TB
@@ -72,13 +72,13 @@ graph TB
72
72
  L2 --- V
73
73
  ```
74
74
 
75
- | 层级 | 路径 | 作用 |
76
- | --- | --- | --- |
77
- | L0 | `manifest-routing.json` | 机读路由、依赖声明 |
78
- | L1 | `matchers/*.json` | 关键词命中,**match** 只读一片 |
79
- | L2 | `topics/*.md` | 硬约束摘要;**expand** 拉依赖 |
80
- | L3 | `stock-docs/`、`req-docs/` | 长文档,按需下钻 |
81
- | 纵链 | `topicDependencies` | 主题级前置,所有任务共享 |
75
+ | 层级 | 路径 | 作用 |
76
+ | ---- | -------------------------- | ------------------------------ |
77
+ | L0 | `manifest-routing.json` | 机读路由、依赖声明 |
78
+ | L1 | `matchers/*.json` | 关键词命中,**match** 只读一片 |
79
+ | L2 | `topics/*.md` | 硬约束摘要;**expand** 拉依赖 |
80
+ | L3 | `stock-docs/`、`req-docs/` | 长文档,按需下钻 |
81
+ | 纵链 | `topicDependencies` | 主题级前置,所有任务共享 |
82
82
 
83
83
  读取流水线:`match → expand → verify → act`(详见 [体系与原理 §4](./体系与原理.md))。
84
84
 
@@ -105,8 +105,6 @@ graph LR
105
105
  note2["规则随工具升级"] -.-> R
106
106
  ```
107
107
 
108
-
109
-
110
108
  ### 2. 渐进式路由
111
109
 
112
110
  ```mermaid
@@ -120,8 +118,6 @@ graph LR
120
118
  M -->|未命中| FB[fallback-triage\n结构化分诊]
121
119
  ```
122
120
 
123
-
124
-
125
121
  ### 3. 技能维护闭环
126
122
 
127
123
  <p><img src="./images/flow-1.png" alt="技能维护闭环" style="max-width:100%;" /></p>
@@ -154,9 +150,7 @@ graph LR
154
150
 
155
151
  </details>
156
152
 
157
- 七条入口 · `f2s-git-commit` 是提交时的知识纪律收口 · `.Knowledge/` 是唯一汇聚点 · 知识驱动 AI,AI 驱动下轮开发
158
-
159
-
153
+ 七条入口 · `f2s-git-commit` 是提交时的知识纪律收口 · `.Knowledge/` 是唯一汇聚点 · 知识驱动 AI,AI 驱动下轮开发
160
154
 
161
155
  ### 4. 任务清单与跨会话续作
162
156
 
@@ -170,10 +164,53 @@ graph LR
170
164
  LD --> RS[按原技能约束继续]
171
165
  ```
172
166
 
173
- 任务不因会话结束丢失 · 关键词自动续作,无需重新说明上下文 · 技能约束完整恢复
167
+ 任务不因会话结束丢失 · 关键词自动续作,无需重新说明上下文 · 技能约束完整恢复
174
168
 
175
169
  ---
176
170
 
171
+ ### 5.知识库更新检测
172
+
173
+ ```mermaid
174
+ flowchart LR
175
+ A[用户执行 flow2spec init codex] --> B[写入 .codex/hooks/f2s-update-check.js]
176
+ A --> C[写入 .codex/hooks.json]
177
+
178
+ C --> D[注册 Codex SessionStart hook]
179
+ D --> E[matcher: startup|resume]
180
+ E --> F[command: node .codex/hooks/f2s-update-check.js]
181
+
182
+ G[Codex 新 session 启动或恢复] --> H{项目 hook 已信任?}
183
+ H -- 否 --> H1[Codex 提示通过 /hooks 审核并信任]
184
+ H1 --> H2[本次 hook 不自动执行]
185
+ H -- 是 --> F
186
+
187
+ F --> I{CI 环境?}
188
+ I -- 是 --> Z[静默跳过]
189
+ I -- 否 --> J{updateCheck.enabled=false?}
190
+
191
+ J -- 是 --> Z
192
+ J -- 否 --> K{.Knowledge/update-check.json 今天已检查?}
193
+
194
+ K -- 是 --> K1{缓存显示需升级?}
195
+ K1 -- 否 --> Z
196
+ K1 -- 是 --> Q
197
+ K -- 否 --> L[读取 .Knowledge/manifest-routing.json version]
198
+
199
+ L --> M{有 manifest version?}
200
+ M -- 否 --> Z
201
+ M -- 是 --> N[npm view 当前包名 version]
202
+
203
+ N --> O[写入 .Knowledge/update-check.json 缓存]
204
+ O --> P{manifest version < npm latest?}
205
+
206
+ P -- 否 --> Z
207
+ P -- 是 --> Q[stdout 输出 JSON]
208
+
209
+ Q --> R[hookSpecificOutput.additionalContext]
210
+ R --> S[Codex 把提示注入会话上下文]
211
+ S --> T[提示用户执行 f2s-kb-upgrade]
212
+ ```
213
+
177
214
  ## 设计亮点
178
215
 
179
216
  ### 一、路由与上下文加载
@@ -220,7 +257,7 @@ taskC → [main] ← 漏写了
220
257
  └──────────────────────────────────────────┘
221
258
  ```
222
259
 
223
- 路由层保持轻量 · 执行细节按需加载 · 两者独立更新
260
+ 路由层保持轻量 · 执行细节按需加载 · 两者独立更新
224
261
 
225
262
  #### 4. 禁止全量扫描是硬约束
226
263
 
@@ -251,7 +288,7 @@ description: >
251
288
  用户输入 → Agent 扫 description 做语义匹配 → 触发对应技能
252
289
  ```
253
290
 
254
- 触发词在 description 里 · 不在正文里 · 命中率更高 · 双语覆盖减少漏触发
291
+ 触发词在 description 里 · 不在正文里 · 命中率更高 · 双语覆盖减少漏触发
255
292
 
256
293
  ---
257
294
 
@@ -300,7 +337,7 @@ git log .Knowledge/
300
337
  代码变更 + 知识变更 → 同一 commit 或相邻 commit
301
338
  ```
302
339
 
303
- 知识有版本 · 可 review · 可回溯 · 可 blame
340
+ 知识有版本 · 可 review · 可回溯 · 可 blame
304
341
 
305
342
  #### 4. 禁止历史否定堆砌
306
343
 
@@ -313,7 +350,7 @@ git log .Knowledge/
313
350
  → 原写法已废弃,现改为 Bean 注入
314
351
  ```
315
352
 
316
- 每次修复原位改写 · 不叠加历史 · 知识库永远只描述现在
353
+ 每次修复原位改写 · 不叠加历史 · 知识库永远只描述现在
317
354
 
318
355
  ---
319
356
 
@@ -337,7 +374,7 @@ implement-tech-design 执行流
337
374
  输出待完成清单与提醒 ← 不可跳过
338
375
  ```
339
376
 
340
- 建议 → 可以被跳过 · 约束 → 必须明确处理才能继续
377
+ 建议 → 可以被跳过 · 约束 → 必须明确处理才能继续
341
378
 
342
379
  #### 2. fallback 本身是有程序的 topic
343
380
 
@@ -351,9 +388,7 @@ graph TD
351
388
  Q -->|不确定| STOP[停止执行\n等待明确指令]
352
389
  ```
353
390
 
354
-
355
-
356
- 未命中 ≠ 静默失败 · 降级本身有明确程序
391
+ 未命中 ≠ 静默失败 · 降级本身有明确程序
357
392
 
358
393
  #### 3. manifest / index 写权硬约束
359
394
 
@@ -380,7 +415,7 @@ matchers/*.json(diff 模式)
380
415
  ❌ 整文件重写严格禁止
381
416
  ```
382
417
 
383
- 原因:文档需要保证「现行真值覆盖 / 文风一致 / 禁历史否定堆砌」 · 要求写的人看到全文上下文
418
+ 原因:文档需要保证「现行真值覆盖 / 文风一致 / 禁历史否定堆砌」 · 要求写的人看到全文上下文
384
419
 
385
420
  #### 5. 任务清单与跨会话续作
386
421
 
@@ -413,7 +448,7 @@ todo.json 写权约束
413
448
  原因:多子 agent 并行落盘时,并发写会导致条目互相覆盖
414
449
  ```
415
450
 
416
- 生命周期由技能驱动 · 关键词路由实现跨会话自动续作 · linkedSkill 保证技能约束完整恢复
451
+ 生命周期由技能驱动 · 关键词路由实现跨会话自动续作 · linkedSkill 保证技能约束完整恢复
417
452
 
418
453
  ---
419
454
 
@@ -435,7 +470,7 @@ todo.json 写权约束
435
470
  └────────────┴─────────────────┘
436
471
  ```
437
472
 
438
- 两个维度正交 · 独立配置 · 默认左下角
473
+ 两个维度正交 · 独立配置 · 默认左下角
439
474
 
440
475
  #### 2. 确认权不可下放子 agent
441
476
 
@@ -450,9 +485,7 @@ graph LR
450
485
  S3[步骤3: 落盘] -->|subAgent=true 可并行| SUB2[子 agent]
451
486
  ```
452
487
 
453
-
454
-
455
- 用户对话只经过主 agent · 确认决策不可绕过用户 · 子 agent 只做执行不做决策
488
+ 用户对话只经过主 agent · 确认决策不可绕过用户 · 子 agent 只做执行不做决策
456
489
 
457
490
  #### 3. 技能可覆盖全局 subAgent 配置
458
491
 
@@ -466,7 +499,7 @@ subAgent: true 本技能默认不拆子:
466
499
  拆子会断上下文,导致澄清质量下降
467
500
  ```
468
501
 
469
- 全局配置是允许拆的上限 · 技能自己判断是否适合拆 · 配置 true 不等于一定拆
502
+ 全局配置是允许拆的上限 · 技能自己判断是否适合拆 · 配置 true 不等于一定拆
470
503
 
471
504
  #### 4. f2s-kb-sync 先出大纲,确认后再写
472
505
 
@@ -479,9 +512,7 @@ graph LR
479
512
  U -->|取消| STOP[不写入]
480
513
  ```
481
514
 
482
-
483
-
484
- 写入是破坏性操作 · 大纲是用户唯一的纠错机会 · 确认前不落盘
515
+ 写入是破坏性操作 · 大纲是用户唯一的纠错机会 · 确认前不落盘
485
516
 
486
517
  #### 5. 零输入推断
487
518
 
@@ -496,18 +527,20 @@ f2s-kb-sync 三种输入方式
496
527
  本次实现了什么、有什么值得沉淀
497
528
  ```
498
529
 
499
- 会话上下文本身就是信息源 · 不需要用户整理再输入
530
+ 会话上下文本身就是信息源 · 不需要用户整理再输入
500
531
 
501
532
  #### 5.1 执行开关如何进入 Agent(多端提示)
502
533
 
503
534
  `flow2spec.config.json` 决定 **`subAgent` / `switchAgentVerification` / `changeTracking`**,但各 AI 产品**不保证**会话启动即自动打开该文件。设计上用 **多条弱约束叠加** 降低「未读配置就开跑 `f2s-*`」的概率,同时避免在 `.Knowledge` 再维护一份与 `.codex/topics/f2s-config-check.md` 逐字重复的长文:
504
535
 
505
- | 机制 | 设计意图 |
506
- | --- | --- |
507
- | **Cursor `f2s-config-check.mdc`** | 规则层强制「技能正文前先 Read」。 |
508
- | **Claude `f2s-config-inject` PreToolUse** | 在调用 **`f2s-*` Skill** 时注入解析结果;**缺文件 / 坏 JSON / hook 异常**仍输出说明与默认语义,避免静默。 |
509
- | **Codex `AGENTS.md` + `renderProjectConfigBlock`** | 顶部 **Read** 硬约束 + **init 快照表**(与磁盘不一致时以 Read 为准)。 |
510
- | **知识库 `config-precheck` 主题** | 路由命中时只提供**摘要**与链向 Codex 长文,**不**替代 Read JSON。 |
536
+ | 机制 | 设计意图 |
537
+ | ----------------------------------------------------------- | --------------------------------------------------------------------------------- |
538
+ | **Cursor `f2s-config-check.mdc`** | 规则层强制「技能正文前先 Read」;Cursor hook 仅用于版本更新检测,不自动读取配置。 |
539
+ | **Claude `f2s-config-session` SessionStart** | 会话开始时注入一次配置摘要,降低遗忘概率。 |
540
+ | **Claude `f2s-config-inject` PreToolUse** | 仅在调用 **`f2s-*` Skill** 时做守门提示,提醒首步必须 Read;不反复注入完整配置。 |
541
+ | **Codex `AGENTS.md` / `.codex/topics/f2s-config-check.md`** | 文本层强制「技能正文前先 Read」;Codex hook 仅用于版本更新检测,不自动读取配置。 |
542
+ | **Codex `AGENTS.md` + `renderProjectConfigBlock`** | 顶部 **Read** 硬约束 + **init 快照表**(与磁盘不一致时以 Read 为准)。 |
543
+ | **知识库 `config-precheck` 主题** | 路由命中时只提供**摘要**与链向 Codex 长文,**不**替代 Read JSON。 |
511
544
 
512
545
  **权威仍为**项目根 JSON 的 **Read** 结果;各层为提示而非第二份真值源。操作侧完整表格与路径见 **[使用说明 § 一、`f2s-*` 与 `flow2spec.config.json`](./使用说明.md)**;口述节奏见 **[Flow2Spec-演讲稿 Slide 13b](./Flow2Spec-演讲稿.md)**。
513
546
 
@@ -540,7 +573,7 @@ flow2spec init cursor codex ← 跳过 Claude
540
573
  .Knowledge/ 始终不变,工具随时加减
541
574
  ```
542
575
 
543
- 同一份 `.Knowledge/` 驱动所有工具 · 加减工具不影响知识内容 · 新工具接入零重建
576
+ 同一份 `.Knowledge/` 驱动所有工具 · 加减工具不影响知识内容 · 新工具接入零重建
544
577
 
545
578
  #### 2. 知识主题可插拔:增删不连带
546
579
 
@@ -555,7 +588,7 @@ flow2spec init cursor codex ← 跳过 Claude
555
588
  其他主题完全不受影响
556
589
  ```
557
590
 
558
- 新主题只需在 `topicDependencies` 里声明依赖 · 不声明则彼此独立 · 删除无副作用
591
+ 新主题只需在 `topicDependencies` 里声明依赖 · 不声明则彼此独立 · 删除无副作用
559
592
 
560
593
  #### 3. 技能可插拔:自包含单元,项目级可覆盖包级
561
594
 
@@ -569,13 +602,13 @@ f2s-doc-arch/SKILL.md my-review-skill/SKILL.md
569
602
  名字不冲突则共存 · 同名则项目级覆盖包级 · 互不感知
570
603
  ```
571
604
 
572
- 技能靠 `description` 字段自描述触发词 · 不需要注册表 · 不需要改全局配置 · 上线即生效
605
+ 技能靠 `description` 字段自描述触发词 · 不需要注册表 · 不需要改全局配置 · 上线即生效
573
606
 
574
607
  #### 4. 路由词表可插拔:分片隔离,局部更新
575
608
 
576
609
  词条变更只改对应 `matchers/m-xxx.json`,其他路由 diff 为零;结构见「[一、路由与上下文加载 → matchers 分片](#matchers-分片不嵌入-manifest)」。
577
610
 
578
- 词条变更局部化 · 合并冲突最小化 · 新增路由不影响存量
611
+ 词条变更局部化 · 合并冲突最小化 · 新增路由不影响存量
579
612
 
580
613
  #### 5. 执行模型可插拔:config 按项目切换
581
614
 
@@ -588,13 +621,14 @@ flow2spec.config.json
588
621
  switchAgentVerification: false → 落盘侧自验,日常使用
589
622
  switchAgentVerification: true → 交叉校验,高置信度关键场景
590
623
 
591
- changeTracking.feat/fix/implement: false 不创建任务清单
592
- changeTracking.feat/fix/implement: true 对应技能执行时自动创建任务清单,支持跨会话续作
624
+ changeTracking.feat: true f2s-kb-feat 默认创建任务清单
625
+ changeTracking.fix: false f2s-kb-fix 默认不创建任务清单
626
+ changeTracking.implement: true → implement-tech-design 默认创建任务清单
593
627
 
594
628
  三个维度正交 · 各技能可进一步细化覆盖全局配置
595
629
  ```
596
630
 
597
- 改一行配置切换执行策略 · 不修改任何技能文件 · 新项目开箱即用,老项目按需升级
631
+ 改一行配置切换执行策略 · 不修改任何技能文件 · 新项目开箱即用,老项目按需升级
598
632
 
599
633
  ---
600
634
 
@@ -641,4 +675,3 @@ flow2spec.config.json
641
675
  - [体系与原理](./体系与原理.md)
642
676
  - [使用案例-模拟对话](./使用案例-模拟对话.md)
643
677
  - [Flow2Spec-演讲稿](./Flow2Spec-演讲稿.md)
644
-
@@ -6,19 +6,19 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
 
9
- const HOOK_COMMAND = 'node .claude/hooks/f2s-config-inject.js';
9
+ const HOOK_COMMAND_CONFIG_INJECT = 'node .claude/hooks/f2s-config-inject.js';
10
+ const HOOK_COMMAND_CONFIG_SESSION = 'node .claude/hooks/f2s-config-session.js';
11
+ const HOOK_COMMAND_UPDATE_CHECK = 'node .claude/hooks/f2s-update-check.js';
10
12
 
11
- /**
12
- * 判断 PreToolUse 数组里是否已存在 f2s-config-inject hook。
13
- * @param {Array} preToolUseArr
14
- * @returns {boolean}
15
- */
16
- function hasF2sHook(preToolUseArr) {
17
- if (!Array.isArray(preToolUseArr)) return false;
18
- for (const group of preToolUseArr) {
13
+ // 向下兼容
14
+ const HOOK_COMMAND = HOOK_COMMAND_CONFIG_INJECT;
15
+
16
+ function hasHookCommand(arr, fragment) {
17
+ if (!Array.isArray(arr)) return false;
18
+ for (const group of arr) {
19
19
  if (!group || !Array.isArray(group.hooks)) continue;
20
20
  for (const h of group.hooks) {
21
- if (h && h.type === 'command' && String(h.command || '').includes('f2s-config-inject')) {
21
+ if (h && h.type === 'command' && String(h.command || '').includes(fragment)) {
22
22
  return true;
23
23
  }
24
24
  }
@@ -26,8 +26,29 @@ function hasF2sHook(preToolUseArr) {
26
26
  return false;
27
27
  }
28
28
 
29
+ function removeHookCommand(arr, fragment) {
30
+ if (!Array.isArray(arr)) return [];
31
+ const next = [];
32
+ for (const group of arr) {
33
+ if (!group || !Array.isArray(group.hooks)) {
34
+ next.push(group);
35
+ continue;
36
+ }
37
+ const hooks = group.hooks.filter((h) => {
38
+ return !(h && h.type === 'command' && String(h.command || '').includes(fragment));
39
+ });
40
+ if (hooks.length) next.push(Object.assign({}, group, { hooks }));
41
+ }
42
+ return next;
43
+ }
44
+
45
+ // 旧函数名保持兼容
46
+ function hasF2sHook(preToolUseArr) {
47
+ return hasHookCommand(preToolUseArr, 'f2s-config-inject');
48
+ }
49
+
29
50
  /**
30
- * 将 f2s PreToolUse hook 合并进现有 settings,返回新对象(不修改原对象)。
51
+ * 将 f2s PreToolUse 守门 hook 合并进现有 settings,返回新对象(不修改原对象)。
31
52
  * @param {object} existing 现有 settings(可为 {})
32
53
  * @returns {object}
33
54
  */
@@ -48,6 +69,61 @@ function mergeF2sHook(existing) {
48
69
  return { settings: next, changed: true };
49
70
  }
50
71
 
72
+ /**
73
+ * 将 f2s SessionStart 配置摘要 hook 合并进 settings。
74
+ * @param {object} existing
75
+ * @returns {{ settings, changed }}
76
+ */
77
+ function mergeConfigSessionHook(existing) {
78
+ const next = JSON.parse(JSON.stringify(existing || {}));
79
+ if (!next.hooks) next.hooks = {};
80
+ if (!next.hooks.SessionStart) next.hooks.SessionStart = [];
81
+
82
+ if (hasHookCommand(next.hooks.SessionStart, 'f2s-config-session')) {
83
+ return { settings: next, changed: false };
84
+ }
85
+
86
+ next.hooks.SessionStart.push({
87
+ hooks: [{ type: 'command', command: HOOK_COMMAND_CONFIG_SESSION }],
88
+ });
89
+
90
+ return { settings: next, changed: true };
91
+ }
92
+
93
+ /**
94
+ * 将 f2s 更新检查 hook 合并进 settings:
95
+ * - SessionStart:执行完整检测,写入 .Knowledge/update-check.json,并直接 emit 提示
96
+ * - 同时清理旧版 UserPromptSubmit 中的 f2s-update-check / f2s-update-notice
97
+ * @param {object} existing
98
+ * @returns {{ settings, changed }}
99
+ */
100
+ function mergeUpdateCheckHook(existing) {
101
+ const next = JSON.parse(JSON.stringify(existing || {}));
102
+ if (!next.hooks) next.hooks = {};
103
+ if (!next.hooks.SessionStart) next.hooks.SessionStart = [];
104
+
105
+ let changed = false;
106
+
107
+ if (Array.isArray(next.hooks.UserPromptSubmit)) {
108
+ const before = JSON.stringify(next.hooks.UserPromptSubmit);
109
+ next.hooks.UserPromptSubmit = removeHookCommand(next.hooks.UserPromptSubmit, 'f2s-update-check');
110
+ next.hooks.UserPromptSubmit = removeHookCommand(next.hooks.UserPromptSubmit, 'f2s-update-notice');
111
+ if (JSON.stringify(next.hooks.UserPromptSubmit) !== before) changed = true;
112
+ if (next.hooks.UserPromptSubmit.length === 0) {
113
+ delete next.hooks.UserPromptSubmit;
114
+ }
115
+ }
116
+
117
+ if (!hasHookCommand(next.hooks.SessionStart, 'f2s-update-check')) {
118
+ next.hooks.SessionStart.push({
119
+ hooks: [{ type: 'command', command: HOOK_COMMAND_UPDATE_CHECK }],
120
+ });
121
+ changed = true;
122
+ }
123
+
124
+ return { settings: next, changed };
125
+ }
126
+
51
127
  /**
52
128
  * 读取 .claude/settings.json(不存在则返回 {})。
53
129
  * @param {string} claudeRoot .claude 目录绝对路径
@@ -74,41 +150,69 @@ function writeSettings(claudeRoot, settings) {
74
150
  }
75
151
 
76
152
  /**
77
- * templates/hooks/f2s-config-inject.js 复制到 .claude/hooks/。
78
- * @param {string} claudeRoot
79
- * @param {string} templatesDir
153
+ * 复制 hook 脚本到 .claude/hooks/。
80
154
  */
81
- function copyHookScript(claudeRoot, templatesDir) {
82
- const src = path.join(templatesDir, 'hooks', 'f2s-config-inject.js');
155
+ function copyHookScript(claudeRoot, templatesDir, scriptName) {
156
+ const src = path.join(templatesDir, 'hooks', scriptName);
83
157
  if (!fs.existsSync(src)) return { written: false, reason: 'missing-template' };
84
158
 
85
159
  const hooksDir = path.join(claudeRoot, 'hooks');
86
160
  if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir, { recursive: true });
87
161
 
88
- const dest = path.join(hooksDir, 'f2s-config-inject.js');
89
- fs.copyFileSync(src, dest);
162
+ let body = fs.readFileSync(src, 'utf8');
163
+ if (body.includes('__FLOW2SPEC_PACKAGE_NAME__')) {
164
+ let packageName = '@double-codeing/flow2spec';
165
+ try {
166
+ packageName = JSON.parse(
167
+ fs.readFileSync(path.join(templatesDir, '..', 'package.json'), 'utf8'),
168
+ ).name || packageName;
169
+ } catch (_) {}
170
+ body = body.replace(/__FLOW2SPEC_PACKAGE_NAME__/g, packageName);
171
+ }
172
+ fs.writeFileSync(path.join(hooksDir, scriptName), body, 'utf8');
90
173
  return { written: true };
91
174
  }
92
175
 
93
176
  /**
94
- * 主入口:为 claude agent 配置 f2s PreToolUse hook。
95
- * @param {string} cwd 项目根目录
96
- * @param {string} templatesDir flow2spec 包 templates 目录
97
- * @returns {{ hookScriptResult, settingsChanged }}
177
+ * 主入口:为 claude agent 配置 f2s hooks(SessionStart 配置摘要 + PreToolUse 守门 + 更新检测/提示)。
178
+ * @param {string} cwd
179
+ * @param {string} templatesDir
180
+ * @returns {{ hookScriptResult, updateCheckResult, settingsChanged }}
98
181
  */
99
182
  function writeClaudeAgentHooks(cwd, templatesDir) {
100
183
  const claudeRoot = path.join(cwd, '.claude');
101
184
  if (!fs.existsSync(claudeRoot)) fs.mkdirSync(claudeRoot, { recursive: true });
102
185
 
103
- const hookScriptResult = copyHookScript(claudeRoot, templatesDir);
186
+ const hookScriptResult = copyHookScript(claudeRoot, templatesDir, 'f2s-config-inject.js');
187
+ const configSessionResult = copyHookScript(claudeRoot, templatesDir, 'f2s-config-session.js');
188
+ const updateCheckResult = copyHookScript(claudeRoot, templatesDir, 'f2s-update-check.js');
104
189
 
105
- const existing = readSettings(claudeRoot);
106
- const { settings, changed } = mergeF2sHook(existing);
107
- if (changed) {
108
- writeSettings(claudeRoot, settings);
190
+ // 清理旧版残留的 f2s-update-notice.js
191
+ const noticeStale = path.join(claudeRoot, 'hooks', 'f2s-update-notice.js');
192
+ if (fs.existsSync(noticeStale)) {
193
+ try { fs.unlinkSync(noticeStale); } catch (_) {}
109
194
  }
110
195
 
111
- return { hookScriptResult, settingsChanged: changed };
196
+ let settings = readSettings(claudeRoot);
197
+ let changed = false;
198
+
199
+ const r1 = mergeF2sHook(settings);
200
+ if (r1.changed) { settings = r1.settings; changed = true; }
201
+
202
+ const r2 = mergeConfigSessionHook(settings);
203
+ if (r2.changed) { settings = r2.settings; changed = true; }
204
+
205
+ const r3 = mergeUpdateCheckHook(settings);
206
+ if (r3.changed) { settings = r3.settings; changed = true; }
207
+
208
+ if (changed) writeSettings(claudeRoot, settings);
209
+
210
+ return { hookScriptResult, configSessionResult, updateCheckResult, settingsChanged: changed };
112
211
  }
113
212
 
114
- module.exports = { writeClaudeAgentHooks, mergeF2sHook };
213
+ module.exports = {
214
+ writeClaudeAgentHooks,
215
+ mergeF2sHook,
216
+ mergeConfigSessionHook,
217
+ mergeUpdateCheckHook,
218
+ };
@@ -8,9 +8,12 @@ const DEFAULTS = {
8
8
  // switchAgentVerification:false=落盘侧同会话内验;true+技能绑定=交叉验(子落盘主验/主落盘子验)
9
9
  switchAgentVerification: false,
10
10
  changeTracking: {
11
- feat: false,
11
+ feat: true,
12
12
  fix: false,
13
- implement: false,
13
+ implement: true,
14
+ },
15
+ updateCheck: {
16
+ enabled: true,
14
17
  },
15
18
  };
16
19
 
@@ -35,7 +38,7 @@ const CONFIG_FIELDS = [
35
38
  {
36
39
  key: "changeTracking.feat",
37
40
  type: "boolean",
38
- default: false,
41
+ default: true,
39
42
  question: "启用变更追踪 - f2s-kb-feat(新增能力时创建可续作的任务清单)?",
40
43
  },
41
44
  {
@@ -47,9 +50,15 @@ const CONFIG_FIELDS = [
47
50
  {
48
51
  key: "changeTracking.implement",
49
52
  type: "boolean",
50
- default: false,
53
+ default: true,
51
54
  question: "启用变更追踪 - f2s-implement-tech-design(实现技术方案时创建可续作的任务清单)?",
52
55
  },
56
+ {
57
+ key: "updateCheck.enabled",
58
+ type: "boolean",
59
+ default: true,
60
+ question: "启用每日版本更新提示(每天第一次 Agent 对话时检查是否有新版 flow2spec)?",
61
+ },
53
62
  ];
54
63
 
55
64
  function normalizeBool(value, fallback) {
@@ -83,6 +92,7 @@ function loadFlow2specConfig(cwd) {
83
92
  const out = {
84
93
  ...DEFAULTS,
85
94
  changeTracking: { ...DEFAULTS.changeTracking },
95
+ updateCheck: { ...DEFAULTS.updateCheck },
86
96
  };
87
97
  if (!fs.existsSync(abs)) {
88
98
  return out;
@@ -126,6 +136,14 @@ function loadFlow2specConfig(cwd) {
126
136
  };
127
137
  }
128
138
  }
139
+ if (Object.prototype.hasOwnProperty.call(raw, "updateCheck")) {
140
+ const uc = raw.updateCheck;
141
+ if (uc && typeof uc === "object" && !Array.isArray(uc)) {
142
+ out.updateCheck = {
143
+ enabled: normalizeBool(uc.enabled, DEFAULTS.updateCheck.enabled),
144
+ };
145
+ }
146
+ }
129
147
  return out;
130
148
  }
131
149
 
@@ -209,10 +227,18 @@ function ensureFlow2specProjectConfig(cwd, templatesDir, options = {}) {
209
227
  try {
210
228
  base = JSON.parse(fs.readFileSync(src, "utf8"));
211
229
  } catch {
212
- base = { ...DEFAULTS, changeTracking: { ...DEFAULTS.changeTracking } };
230
+ base = {
231
+ ...DEFAULTS,
232
+ changeTracking: { ...DEFAULTS.changeTracking },
233
+ updateCheck: { ...DEFAULTS.updateCheck },
234
+ };
213
235
  }
214
236
  } else {
215
- base = { ...DEFAULTS, changeTracking: { ...DEFAULTS.changeTracking } };
237
+ base = {
238
+ ...DEFAULTS,
239
+ changeTracking: { ...DEFAULTS.changeTracking },
240
+ updateCheck: { ...DEFAULTS.updateCheck },
241
+ };
216
242
  }
217
243
  const merged = values && typeof values === "object" ? mergeValues(base, values) : base;
218
244
  fs.writeFileSync(dest, `${JSON.stringify(merged, null, 2)}\n`, "utf8");