@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
package/lib/init.js
CHANGED
|
@@ -106,6 +106,137 @@ function writeJson(filePath, data) {
|
|
|
106
106
|
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
function readPackageName(templatesDir) {
|
|
110
|
+
try {
|
|
111
|
+
return readJson(path.join(templatesDir, "..", "package.json")).name;
|
|
112
|
+
} catch (_) {
|
|
113
|
+
return "@double-codeing/flow2spec";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function writeHookScriptWithPackageName(destDir, templatesDir, scriptName) {
|
|
118
|
+
const src = path.join(templatesDir, "hooks", scriptName);
|
|
119
|
+
if (!fs.existsSync(src)) return { written: false, reason: "missing-template" };
|
|
120
|
+
ensureDir(destDir);
|
|
121
|
+
let body = fs.readFileSync(src, "utf8");
|
|
122
|
+
body = body.replace(/__FLOW2SPEC_PACKAGE_NAME__/g, readPackageName(templatesDir));
|
|
123
|
+
fs.writeFileSync(path.join(destDir, scriptName), body, "utf8");
|
|
124
|
+
return { written: true };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function hasHookCommand(groups, fragment) {
|
|
128
|
+
if (!Array.isArray(groups)) return false;
|
|
129
|
+
return groups.some((group) =>
|
|
130
|
+
Array.isArray(group?.hooks) &&
|
|
131
|
+
group.hooks.some(
|
|
132
|
+
(hook) =>
|
|
133
|
+
hook &&
|
|
134
|
+
hook.type === "command" &&
|
|
135
|
+
String(hook.command || "").includes(fragment),
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function mergeCodexUpdateCheckHook(existing) {
|
|
141
|
+
const next =
|
|
142
|
+
existing && typeof existing === "object" && !Array.isArray(existing)
|
|
143
|
+
? JSON.parse(JSON.stringify(existing))
|
|
144
|
+
: {};
|
|
145
|
+
if (!next.hooks || typeof next.hooks !== "object" || Array.isArray(next.hooks)) {
|
|
146
|
+
next.hooks = {};
|
|
147
|
+
}
|
|
148
|
+
if (!Array.isArray(next.hooks.SessionStart)) {
|
|
149
|
+
next.hooks.SessionStart = [];
|
|
150
|
+
}
|
|
151
|
+
if (hasHookCommand(next.hooks.SessionStart, "f2s-update-check")) {
|
|
152
|
+
return { config: next, changed: false };
|
|
153
|
+
}
|
|
154
|
+
next.hooks.SessionStart.push({
|
|
155
|
+
matcher: "startup|resume",
|
|
156
|
+
hooks: [
|
|
157
|
+
{
|
|
158
|
+
type: "command",
|
|
159
|
+
command: "node .codex/hooks/f2s-update-check.js",
|
|
160
|
+
statusMessage: "Checking Flow2Spec knowledge version",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
return { config: next, changed: true };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function writeCodexUpdateCheckHook(cwd, templatesDir) {
|
|
168
|
+
const codexRoot = path.join(cwd, ".codex");
|
|
169
|
+
const hooksDir = path.join(codexRoot, "hooks");
|
|
170
|
+
const scriptResult = writeHookScriptWithPackageName(
|
|
171
|
+
hooksDir,
|
|
172
|
+
templatesDir,
|
|
173
|
+
"f2s-update-check.js",
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const hooksJsonPath = path.join(codexRoot, "hooks.json");
|
|
177
|
+
let existing = {};
|
|
178
|
+
if (fs.existsSync(hooksJsonPath)) {
|
|
179
|
+
try {
|
|
180
|
+
existing = readJson(hooksJsonPath);
|
|
181
|
+
} catch (_) {
|
|
182
|
+
existing = {};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const { config, changed } = mergeCodexUpdateCheckHook(existing);
|
|
186
|
+
if (changed || !fs.existsSync(hooksJsonPath)) {
|
|
187
|
+
writeJson(hooksJsonPath, config);
|
|
188
|
+
}
|
|
189
|
+
return { scriptResult, hooksJsonChanged: changed };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function mergeCursorUpdateCheckHook(existing) {
|
|
193
|
+
const next =
|
|
194
|
+
existing && typeof existing === "object" && !Array.isArray(existing)
|
|
195
|
+
? JSON.parse(JSON.stringify(existing))
|
|
196
|
+
: {};
|
|
197
|
+
next.version = Number.isFinite(Number(next.version)) ? Number(next.version) : 1;
|
|
198
|
+
if (!next.hooks || typeof next.hooks !== "object" || Array.isArray(next.hooks)) {
|
|
199
|
+
next.hooks = {};
|
|
200
|
+
}
|
|
201
|
+
if (!Array.isArray(next.hooks.sessionStart)) {
|
|
202
|
+
next.hooks.sessionStart = [];
|
|
203
|
+
}
|
|
204
|
+
const exists = next.hooks.sessionStart.some((hook) =>
|
|
205
|
+
hook && String(hook.command || "").includes("f2s-update-check"),
|
|
206
|
+
);
|
|
207
|
+
if (exists) return { config: next, changed: false };
|
|
208
|
+
next.hooks.sessionStart.push({
|
|
209
|
+
command: "node .cursor/hooks/f2s-update-check.js",
|
|
210
|
+
timeout: 10,
|
|
211
|
+
});
|
|
212
|
+
return { config: next, changed: true };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function writeCursorUpdateCheckHook(cwd, templatesDir) {
|
|
216
|
+
const cursorRoot = path.join(cwd, ".cursor");
|
|
217
|
+
const hooksDir = path.join(cursorRoot, "hooks");
|
|
218
|
+
const scriptResult = writeHookScriptWithPackageName(
|
|
219
|
+
hooksDir,
|
|
220
|
+
templatesDir,
|
|
221
|
+
"f2s-update-check.js",
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const hooksJsonPath = path.join(cursorRoot, "hooks.json");
|
|
225
|
+
let existing = {};
|
|
226
|
+
if (fs.existsSync(hooksJsonPath)) {
|
|
227
|
+
try {
|
|
228
|
+
existing = readJson(hooksJsonPath);
|
|
229
|
+
} catch (_) {
|
|
230
|
+
existing = {};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const { config, changed } = mergeCursorUpdateCheckHook(existing);
|
|
234
|
+
if (changed || !fs.existsSync(hooksJsonPath)) {
|
|
235
|
+
writeJson(hooksJsonPath, config);
|
|
236
|
+
}
|
|
237
|
+
return { scriptResult, hooksJsonChanged: changed };
|
|
238
|
+
}
|
|
239
|
+
|
|
109
240
|
function buildDefaultMatcherPath(matcherId) {
|
|
110
241
|
return `${KNOWLEDGE_ROOT}/matchers/${matcherId}.json`;
|
|
111
242
|
}
|
|
@@ -217,7 +348,7 @@ function mergeTopicMetadata(templateMetadata, existingMetadata, topicPaths) {
|
|
|
217
348
|
return out;
|
|
218
349
|
}
|
|
219
350
|
|
|
220
|
-
function buildMergedRouting(templateRouting, existingRouting) {
|
|
351
|
+
function buildMergedRouting(templateRouting, existingRouting, pkgVersion) {
|
|
221
352
|
const mergedTaskRules = unionByKey(
|
|
222
353
|
templateRouting.taskToTopicRules,
|
|
223
354
|
existingRouting.taskToTopicRules,
|
|
@@ -242,7 +373,7 @@ function buildMergedRouting(templateRouting, existingRouting) {
|
|
|
242
373
|
);
|
|
243
374
|
|
|
244
375
|
const knownMerged = {
|
|
245
|
-
version: templateRouting.version || existingRouting.version,
|
|
376
|
+
version: pkgVersion || templateRouting.version || existingRouting.version,
|
|
246
377
|
knowledgeRoot:
|
|
247
378
|
existingRouting.knowledgeRoot || templateRouting.knowledgeRoot,
|
|
248
379
|
generatedFrom:
|
|
@@ -466,7 +597,13 @@ function upgradeKnowledgeRoutingAndMatchers(cwd, templatesDir, options = {}) {
|
|
|
466
597
|
const existingRouting = hadRouting ? readJson(routingPath) : {};
|
|
467
598
|
const existingMatchers = hadMatchers ? readJson(matchersPath) : {};
|
|
468
599
|
|
|
469
|
-
|
|
600
|
+
// 读包版本号,用于写入 manifest-routing.json 的 version 字段
|
|
601
|
+
let pkgVersion;
|
|
602
|
+
try {
|
|
603
|
+
pkgVersion = readJson(path.join(templatesDir, "..", "package.json")).version;
|
|
604
|
+
} catch (_) {}
|
|
605
|
+
|
|
606
|
+
const mergedRouting = buildMergedRouting(templateRouting, existingRouting, pkgVersion);
|
|
470
607
|
const mergedMatchers = buildMergedMatchers(
|
|
471
608
|
templateMatchers,
|
|
472
609
|
existingMatchers,
|
|
@@ -879,6 +1016,14 @@ async function run(cwd, agentIds, options = {}) {
|
|
|
879
1016
|
claudeHooksResult.hookScriptWritten = result.hookScriptResult?.written ?? false;
|
|
880
1017
|
claudeHooksResult.settingsChanged = result.settingsChanged;
|
|
881
1018
|
}
|
|
1019
|
+
// Cursor:写入官方 hooks.json,使 sessionStart 自动运行更新检测。
|
|
1020
|
+
if (id === "cursor") {
|
|
1021
|
+
writeCursorUpdateCheckHook(cwd, templatesDir);
|
|
1022
|
+
}
|
|
1023
|
+
// Codex:写入官方 hooks.json,使 SessionStart 自动运行更新检测。
|
|
1024
|
+
if (id === "codex") {
|
|
1025
|
+
writeCodexUpdateCheckHook(cwd, templatesDir);
|
|
1026
|
+
}
|
|
882
1027
|
}
|
|
883
1028
|
return {
|
|
884
1029
|
ids,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@double-codeing/flow2spec",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "在业务仓库初始化「文档驱动、可写回知识库」的 AI 协作骨架:项目根 .Knowledge 承载 stock-docs/req-docs 与机读路由,.cursor/.claude/.codex 写入 f2s-* 规则与技能(含 Karpathy 式编码行为准则,init 同步 rules / Codex topics / skills);init 只落结构与模板,业务内容由各 f2s-* 技能在对话中维护。",
|
|
5
5
|
"homepage": "https://github.com/Lands-1203/Flow2Spec#readme",
|
|
6
6
|
"repository": {
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| `skills/` | Flow2Spec 技能(`f2s-*`) |
|
|
16
16
|
| `topics/` | 规则长文镜像(与 Cursor/Claude `rules` 同源) |
|
|
17
|
+
| `hooks.json` | Codex SessionStart hook 配置,用于启动时检测 Flow2Spec 知识库版本 |
|
|
18
|
+
| `hooks/` | hook 脚本目录 |
|
|
17
19
|
| `config.toml` | 项目级 Codex 配置(若已创建) |
|
|
18
20
|
|
|
19
21
|
配置真值:仓库根 **`flow2spec.config.json`**(须 Read);init 快照表见根 **`AGENTS.md`**。
|
package/templates/AGENTS.md
CHANGED
|
@@ -93,6 +93,19 @@
|
|
|
93
93
|
|
|
94
94
|
执行 Flow2Spec 相关任务时,先读本文件(**`./AGENTS.md`**)与 **`./.Knowledge/manifest-routing.json`**,再按需打开上列 **`./.codex/topics/*.md`** 文件。
|
|
95
95
|
|
|
96
|
+
## 知识库版本自检(SessionStart 自动触发;每日首次,仅 updateCheck.enabled=true 时)
|
|
97
|
+
|
|
98
|
+
Codex 由 `flow2spec init codex` 写入 **`.codex/hooks.json`**,在 `SessionStart` 的 `startup|resume` 事件自动执行 `node .codex/hooks/f2s-update-check.js`;首次生成或 hook 内容变化后,Codex 可能要求通过 `/hooks` 审核并信任该项目 hook。
|
|
99
|
+
|
|
100
|
+
**规则层双保险**(若 hook 未运行,则按本节补检;与脚本缓存互为备份):
|
|
101
|
+
|
|
102
|
+
1. 读 `flow2spec.config.json` → 若 `updateCheck.enabled` 不为 `true`,跳过,不做任何提示。
|
|
103
|
+
2. 读 `.Knowledge/update-check.json` → 若文件存在且 `checkedAt` 与今日为同一自然日(`new Date(checkedAt).toDateString() === new Date().toDateString()`),不重复查 npm;但若 `needsUpgrade=true` 或 `latestNpm > manifestVersion`,本会话首次回复用户时仍须提醒执行 `f2s-kb-upgrade`;若当前 `.Knowledge/manifest-routing.json.version` 已不低于 `latestNpm`,删除该缓存并不再提示。
|
|
104
|
+
3. 上述两步均未跳过时:执行 `node .codex/hooks/f2s-update-check.js`,解析标准输出的 JSON:
|
|
105
|
+
- 若含 `hookSpecificOutput.additionalContext`:**告知用户**该内容(建议执行 `/f2s-kb-upgrade`)。
|
|
106
|
+
- 无输出或解析失败:静默,不提示。
|
|
107
|
+
4. 以上步骤出现任何错误,静默跳过,不影响正常对话。
|
|
108
|
+
|
|
96
109
|
## 可用 Flow2Spec 技能(自动生成)
|
|
97
110
|
|
|
98
111
|
{{FLOW2SPEC_CODEX_SKILLS_SUMMARY}}
|
|
@@ -1,26 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
/**
|
|
4
|
-
* flow2spec PreToolUse
|
|
4
|
+
* flow2spec PreToolUse guard — 仅在调用 f2s-* Skill 前提示必须先 Read flow2spec.config.json。
|
|
5
|
+
* 不在 PreToolUse 中反复注入完整配置;配置摘要由 SessionStart hook 一次性提供。
|
|
5
6
|
* 由 flow2spec init --claude 写入 .claude/hooks/f2s-config-inject.js。
|
|
6
7
|
*/
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
|
|
10
|
-
const DEFAULT_CFG = {
|
|
11
|
-
subAgent: false,
|
|
12
|
-
switchAgentVerification: false,
|
|
13
|
-
changeTracking: { feat: false, fix: false, implement: false },
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/** 与 lib/flow2specConfig.js 一致,避免 hook 依赖包内路径 */
|
|
17
|
-
function normalizeBool(value, fallback) {
|
|
18
|
-
if (value === true || value === 'true' || value === 1 || value === '1')
|
|
19
|
-
return true;
|
|
20
|
-
if (value === false || value === 'false' || value === 0 || value === '0')
|
|
21
|
-
return false;
|
|
22
|
-
return fallback;
|
|
23
|
-
}
|
|
24
8
|
|
|
25
9
|
function emitAdditionalContext(lines) {
|
|
26
10
|
process.stdout.write(
|
|
@@ -29,85 +13,8 @@ function emitAdditionalContext(lines) {
|
|
|
29
13
|
hookEventName: 'PreToolUse',
|
|
30
14
|
additionalContext: lines.join('\n'),
|
|
31
15
|
},
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function normalizeCfg(raw) {
|
|
37
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
38
|
-
return { ...DEFAULT_CFG, changeTracking: { ...DEFAULT_CFG.changeTracking } };
|
|
39
|
-
}
|
|
40
|
-
const ct = raw.changeTracking;
|
|
41
|
-
let changeTracking = { ...DEFAULT_CFG.changeTracking };
|
|
42
|
-
if (typeof ct === 'boolean') {
|
|
43
|
-
changeTracking = {
|
|
44
|
-
feat: normalizeBool(ct, DEFAULT_CFG.changeTracking.feat),
|
|
45
|
-
fix: normalizeBool(ct, DEFAULT_CFG.changeTracking.fix),
|
|
46
|
-
implement: normalizeBool(ct, DEFAULT_CFG.changeTracking.implement),
|
|
47
|
-
};
|
|
48
|
-
} else if (ct && typeof ct === 'object' && !Array.isArray(ct)) {
|
|
49
|
-
changeTracking = {
|
|
50
|
-
feat: normalizeBool(ct.feat, DEFAULT_CFG.changeTracking.feat),
|
|
51
|
-
fix: normalizeBool(ct.fix, DEFAULT_CFG.changeTracking.fix),
|
|
52
|
-
implement: normalizeBool(ct.implement, DEFAULT_CFG.changeTracking.implement),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
const switchRaw = Object.prototype.hasOwnProperty.call(raw, 'switchAgentVerification')
|
|
56
|
-
? raw.switchAgentVerification
|
|
57
|
-
: raw.subAgentVerification;
|
|
58
|
-
return {
|
|
59
|
-
subAgent: normalizeBool(raw.subAgent, DEFAULT_CFG.subAgent),
|
|
60
|
-
switchAgentVerification: normalizeBool(
|
|
61
|
-
switchRaw,
|
|
62
|
-
DEFAULT_CFG.switchAgentVerification,
|
|
63
|
-
),
|
|
64
|
-
changeTracking,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function buildChangeTrackingLines(skillName, cfg) {
|
|
69
|
-
const lines = [];
|
|
70
|
-
const ctKeyMap = {
|
|
71
|
-
'f2s-kb-feat': 'feat',
|
|
72
|
-
'f2s-kb-fix': 'fix',
|
|
73
|
-
'f2s-implement-tech-design': 'implement',
|
|
74
|
-
};
|
|
75
|
-
const ctKey = ctKeyMap[skillName];
|
|
76
|
-
if (ctKey === undefined) return lines;
|
|
77
|
-
const ctValue = normalizeBool(
|
|
78
|
-
(cfg.changeTracking ?? {})[ctKey],
|
|
79
|
-
DEFAULT_CFG.changeTracking[ctKey],
|
|
16
|
+
}) + '\n',
|
|
80
17
|
);
|
|
81
|
-
lines.push('');
|
|
82
|
-
if (ctValue) {
|
|
83
|
-
const stepMap = {
|
|
84
|
-
feat: '步骤 0',
|
|
85
|
-
fix: '步骤 0',
|
|
86
|
-
implement: '步骤 2.5(写入任务清单)、2.6(随步打钩 task.md)、步骤 5(归档门禁后归档)',
|
|
87
|
-
};
|
|
88
|
-
lines.push(
|
|
89
|
-
`changeTracking.${ctKey}=true → 本技能变更追踪【必须执行】:在 ${stepMap[ctKey]} 创建或续作 .task/active/ 任务,禁止跳过。`,
|
|
90
|
-
);
|
|
91
|
-
} else {
|
|
92
|
-
lines.push(
|
|
93
|
-
`changeTracking.${ctKey}=false → 本技能变更追踪步骤跳过,不得创建 .task/ 目录。`,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
return lines;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function buildCoreLines(cfg, sourceLabel) {
|
|
100
|
-
const lines = [
|
|
101
|
-
`[flow2spec] ${sourceLabel}`,
|
|
102
|
-
` subAgent = ${cfg.subAgent}`,
|
|
103
|
-
` switchAgentVerification = ${cfg.switchAgentVerification}`,
|
|
104
|
-
` changeTracking = ${JSON.stringify(cfg.changeTracking)}`,
|
|
105
|
-
'',
|
|
106
|
-
'subAgent=true → 按技能 SKILL.md 中 B/C 模式派子 agent 并行扫描,主 agent 合并落盘。',
|
|
107
|
-
'subAgent=false → 全部在主 agent 内完成,不派子 agent。',
|
|
108
|
-
'switchAgentVerification=true → 子 agent 落盘的由主 agent 校验;主 agent 落盘的由子 agent 校验(须 subAgent=true 且实际拆出子任务)。',
|
|
109
|
-
];
|
|
110
|
-
return lines;
|
|
111
18
|
}
|
|
112
19
|
|
|
113
20
|
const chunks = [];
|
|
@@ -116,7 +23,7 @@ process.stdin.on('end', () => {
|
|
|
116
23
|
let skillName = '';
|
|
117
24
|
try {
|
|
118
25
|
const input = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
119
|
-
skillName = String(input?.tool_input?.skill || '');
|
|
26
|
+
skillName = String(input?.tool_input?.skill || input?.tool_input?.name || '');
|
|
120
27
|
} catch (_err) {
|
|
121
28
|
process.exit(0);
|
|
122
29
|
return;
|
|
@@ -127,55 +34,10 @@ process.stdin.on('end', () => {
|
|
|
127
34
|
return;
|
|
128
35
|
}
|
|
129
36
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const lines = [
|
|
136
|
-
'[flow2spec] 项目根不存在 flow2spec.config.json;按统一入口约定,缺失字段均视为 false。',
|
|
137
|
-
'',
|
|
138
|
-
...buildCoreLines(cfg, '等价配置预览(建议随后 Read 或 flow2spec init 补齐该文件):'),
|
|
139
|
-
'',
|
|
140
|
-
'可执行:flow2spec init(或从包模板复制 flow2spec.config.json 到项目根)。',
|
|
141
|
-
...buildChangeTrackingLines(skillName, cfg),
|
|
142
|
-
];
|
|
143
|
-
emitAdditionalContext(lines);
|
|
144
|
-
process.exit(0);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let raw;
|
|
149
|
-
try {
|
|
150
|
-
raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
151
|
-
} catch (parseErr) {
|
|
152
|
-
const cfg = { ...DEFAULT_CFG, changeTracking: { ...DEFAULT_CFG.changeTracking } };
|
|
153
|
-
const lines = [
|
|
154
|
-
`[flow2spec] flow2spec.config.json 存在但 JSON 解析失败:${parseErr.message || String(parseErr)}`,
|
|
155
|
-
'在修复文件前,请按以下默认安全语义执行本技能(与「文件不存在」一致):',
|
|
156
|
-
'',
|
|
157
|
-
...buildCoreLines(cfg, '等价配置(解析失败回退)'),
|
|
158
|
-
...buildChangeTrackingLines(skillName, cfg),
|
|
159
|
-
];
|
|
160
|
-
emitAdditionalContext(lines);
|
|
161
|
-
process.exit(0);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const cfg = normalizeCfg(raw);
|
|
166
|
-
const lines = [
|
|
167
|
-
...buildCoreLines(cfg, 'flow2spec.config.json 已自动注入,执行本 f2s-* 技能前必须遵守以下配置:'),
|
|
168
|
-
'',
|
|
169
|
-
'仍建议用 Read(flow2spec.config.json) 与磁盘核对,尤其在刚编辑过该文件时。',
|
|
170
|
-
...buildChangeTrackingLines(skillName, cfg),
|
|
171
|
-
];
|
|
172
|
-
emitAdditionalContext(lines);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
const msg = err && err.message ? err.message : String(err);
|
|
175
|
-
emitAdditionalContext([
|
|
176
|
-
`[flow2spec] f2s-config-inject hook 未预期异常:${msg}`,
|
|
177
|
-
'请手动 Read("flow2spec.config.json") 后再执行本技能;若持续失败请检查 hook 脚本与项目根路径。',
|
|
178
|
-
]);
|
|
179
|
-
}
|
|
37
|
+
emitAdditionalContext([
|
|
38
|
+
`[flow2spec] 即将调用 ${skillName}。进入该 Skill 正文前,首个动作必须 Read("flow2spec.config.json")。`,
|
|
39
|
+
'SessionStart 中的配置摘要仅作提醒;若摘要与磁盘不一致,以本次 Read 结果为准。',
|
|
40
|
+
'读取后再按 subAgent / switchAgentVerification / changeTracking 的实际值执行后续步骤。',
|
|
41
|
+
]);
|
|
180
42
|
process.exit(0);
|
|
181
43
|
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* flow2spec SessionStart hook — 会话开始时一次性注入 flow2spec.config.json 摘要。
|
|
5
|
+
* 该摘要不替代 f2s-* Skill 正文前的 Read("flow2spec.config.json")。
|
|
6
|
+
* 由 flow2spec init --claude 写入 .claude/hooks/f2s-config-session.js。
|
|
7
|
+
*/
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CFG = {
|
|
12
|
+
subAgent: false,
|
|
13
|
+
switchAgentVerification: false,
|
|
14
|
+
changeTracking: { feat: true, fix: false, implement: true },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function normalizeBool(value, fallback) {
|
|
18
|
+
if (value === true || value === 'true' || value === 1 || value === '1')
|
|
19
|
+
return true;
|
|
20
|
+
if (value === false || value === 'false' || value === 0 || value === '0')
|
|
21
|
+
return false;
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeCfg(raw) {
|
|
26
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
27
|
+
return { ...DEFAULT_CFG, changeTracking: { ...DEFAULT_CFG.changeTracking } };
|
|
28
|
+
}
|
|
29
|
+
const ct = raw.changeTracking;
|
|
30
|
+
let changeTracking = { ...DEFAULT_CFG.changeTracking };
|
|
31
|
+
if (typeof ct === 'boolean') {
|
|
32
|
+
changeTracking = {
|
|
33
|
+
feat: normalizeBool(ct, DEFAULT_CFG.changeTracking.feat),
|
|
34
|
+
fix: normalizeBool(ct, DEFAULT_CFG.changeTracking.fix),
|
|
35
|
+
implement: normalizeBool(ct, DEFAULT_CFG.changeTracking.implement),
|
|
36
|
+
};
|
|
37
|
+
} else if (ct && typeof ct === 'object' && !Array.isArray(ct)) {
|
|
38
|
+
changeTracking = {
|
|
39
|
+
feat: normalizeBool(ct.feat, DEFAULT_CFG.changeTracking.feat),
|
|
40
|
+
fix: normalizeBool(ct.fix, DEFAULT_CFG.changeTracking.fix),
|
|
41
|
+
implement: normalizeBool(ct.implement, DEFAULT_CFG.changeTracking.implement),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const switchRaw = Object.prototype.hasOwnProperty.call(raw, 'switchAgentVerification')
|
|
45
|
+
? raw.switchAgentVerification
|
|
46
|
+
: raw.subAgentVerification;
|
|
47
|
+
return {
|
|
48
|
+
subAgent: normalizeBool(raw.subAgent, DEFAULT_CFG.subAgent),
|
|
49
|
+
switchAgentVerification: normalizeBool(
|
|
50
|
+
switchRaw,
|
|
51
|
+
DEFAULT_CFG.switchAgentVerification,
|
|
52
|
+
),
|
|
53
|
+
changeTracking,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function emit(lines) {
|
|
58
|
+
process.stdout.write(
|
|
59
|
+
JSON.stringify({
|
|
60
|
+
hookSpecificOutput: {
|
|
61
|
+
hookEventName: 'SessionStart',
|
|
62
|
+
additionalContext: lines.join('\n'),
|
|
63
|
+
},
|
|
64
|
+
}) + '\n',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function main() {
|
|
69
|
+
const configPath = path.resolve(process.cwd(), 'flow2spec.config.json');
|
|
70
|
+
if (!fs.existsSync(configPath)) {
|
|
71
|
+
const cfg = { ...DEFAULT_CFG, changeTracking: { ...DEFAULT_CFG.changeTracking } };
|
|
72
|
+
emit([
|
|
73
|
+
'[flow2spec] 本会话未找到 flow2spec.config.json;f2s-* Skill 前仍必须尝试 Read("flow2spec.config.json"),缺失字段按默认值处理。',
|
|
74
|
+
`配置摘要:subAgent=${cfg.subAgent}, switchAgentVerification=${cfg.switchAgentVerification}, changeTracking=${JSON.stringify(cfg.changeTracking)}`,
|
|
75
|
+
]);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const cfg = normalizeCfg(JSON.parse(fs.readFileSync(configPath, 'utf8')));
|
|
81
|
+
emit([
|
|
82
|
+
'[flow2spec] SessionStart 配置摘要(仅作提醒,执行 f2s-* Skill 前仍必须 Read 磁盘文件):',
|
|
83
|
+
`subAgent=${cfg.subAgent}`,
|
|
84
|
+
`switchAgentVerification=${cfg.switchAgentVerification}`,
|
|
85
|
+
`changeTracking=${JSON.stringify(cfg.changeTracking)}`,
|
|
86
|
+
]);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
emit([
|
|
89
|
+
`[flow2spec] flow2spec.config.json 解析失败:${err.message || String(err)}`,
|
|
90
|
+
'执行 f2s-* Skill 前必须先修复或 Read 该文件;无法读取时按缺失字段默认值处理。',
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main();
|