@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.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/cli.js +122 -11
- package/docs/en/architecture.md +2 -2
- package/docs/en/commands-reference.md +14 -10
- package/docs/en/design-principles.md +8 -5
- package/docs/en/directory-conventions.md +4 -0
- package/docs/en/usage-guide.md +12 -4
- package/docs/en/usage-scenarios.md +2 -2
- package/docs//344/275/223/347/263/273/344/270/216/345/216/237/347/220/206.md +2 -2
- 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
- package/docs//344/275/277/347/224/250/350/257/264/346/230/216.md +11 -4
- package/docs//345/221/275/344/273/244/350/257/264/346/230/216.md +13 -10
- 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
- package/docs//350/256/276/350/256/241/350/257/264/346/230/216.md +86 -53
- package/lib/claudeSettingsAdapter.js +133 -29
- package/lib/flow2specConfig.js +32 -6
- package/lib/init.js +148 -3
- package/package.json +1 -1
- package/templates/AGENTS.codex-stub.md +2 -0
- package/templates/AGENTS.md +13 -0
- package/templates/flow2spec.config.json +5 -2
- package/templates/hooks/f2s-config-inject.js +9 -147
- package/templates/hooks/f2s-config-session.js +95 -0
- package/templates/hooks/f2s-update-check.js +187 -0
- package/templates/knowledge/topics/f2s-config-precheck.md +2 -2
- package/templates/rules/f2s-config-check.mdc +3 -1
- package/templates/rules/f2s-flow2spec-unified-entry.mdc +13 -0
- package/templates/skills/f2s-git-commit/SKILL.md +21 -5
- 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/`
|
|
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
|
|
78
|
-
| L1
|
|
79
|
-
| L2
|
|
80
|
-
| L3
|
|
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
|
-
七条入口
|
|
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
|
-
知识有版本
|
|
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
|
-
生命周期由技能驱动
|
|
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
|
-
全局配置是允许拆的上限
|
|
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`**
|
|
508
|
-
| **Claude `f2s-config-
|
|
509
|
-
| **
|
|
510
|
-
|
|
|
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
|
|
592
|
-
changeTracking.
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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(
|
|
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
|
-
*
|
|
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',
|
|
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
|
-
|
|
89
|
-
|
|
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
|
|
95
|
-
* @param {string} cwd
|
|
96
|
-
* @param {string} templatesDir
|
|
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
|
-
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
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
|
-
|
|
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 = {
|
|
213
|
+
module.exports = {
|
|
214
|
+
writeClaudeAgentHooks,
|
|
215
|
+
mergeF2sHook,
|
|
216
|
+
mergeConfigSessionHook,
|
|
217
|
+
mergeUpdateCheckHook,
|
|
218
|
+
};
|
package/lib/flow2specConfig.js
CHANGED
|
@@ -8,9 +8,12 @@ const DEFAULTS = {
|
|
|
8
8
|
// switchAgentVerification:false=落盘侧同会话内验;true+技能绑定=交叉验(子落盘主验/主落盘子验)
|
|
9
9
|
switchAgentVerification: false,
|
|
10
10
|
changeTracking: {
|
|
11
|
-
feat:
|
|
11
|
+
feat: true,
|
|
12
12
|
fix: false,
|
|
13
|
-
implement:
|
|
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:
|
|
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:
|
|
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 = {
|
|
230
|
+
base = {
|
|
231
|
+
...DEFAULTS,
|
|
232
|
+
changeTracking: { ...DEFAULTS.changeTracking },
|
|
233
|
+
updateCheck: { ...DEFAULTS.updateCheck },
|
|
234
|
+
};
|
|
213
235
|
}
|
|
214
236
|
} else {
|
|
215
|
-
base = {
|
|
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");
|