@michaelxwb/code-flow 0.5.2 → 0.6.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.
- package/docs/migrations/spec-workflow-0.6.0.md +42 -0
- package/package.json +4 -2
- package/src/adapters/claude/CLAUDE.md +11 -17
- package/src/adapters/claude/commands/cf-init.md +5 -4
- package/src/adapters/claude/commands/cf-spec.md +63 -0
- package/src/adapters/claude/commands/cf-sync.md +25 -0
- package/src/adapters/claude/commands/cf-task/align.md +23 -1
- package/src/adapters/claude/commands/cf-task/archive.md +7 -0
- package/src/adapters/claude/commands/cf-task/plan.md +16 -0
- package/src/adapters/claude/commands/cf-task/prd.md +24 -0
- package/src/adapters/claude/commands/cf-task/start.md +13 -10
- package/src/adapters/claude/settings.local.json +1 -11
- package/src/adapters/codex/AGENTS.md +11 -17
- package/src/adapters/codex/hooks.json +6 -4
- package/src/adapters/codex/skills/cf-init/SKILL.md +5 -4
- package/src/adapters/codex/skills/cf-spec/SKILL.md +56 -0
- package/src/adapters/codex/skills/cf-sync/SKILL.md +30 -0
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +23 -1
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +7 -0
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +16 -0
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +24 -0
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +13 -10
- package/src/adapters/costrict/CLAUDE.md +11 -17
- package/src/adapters/costrict/commands/cf-init.md +5 -4
- package/src/adapters/costrict/commands/cf-spec.md +63 -0
- package/src/adapters/costrict/commands/cf-sync.md +25 -0
- package/src/adapters/costrict/commands/cf-task/align.md +23 -1
- package/src/adapters/costrict/commands/cf-task/archive.md +7 -0
- package/src/adapters/costrict/commands/cf-task/plan.md +16 -0
- package/src/adapters/costrict/commands/cf-task/prd.md +24 -0
- package/src/adapters/costrict/commands/cf-task/start.md +13 -10
- package/src/adapters/costrict/settings.local.json +1 -11
- package/src/adapters/opencode/AGENTS.md +11 -17
- package/src/adapters/opencode/commands/cf-init.md +4 -3
- package/src/adapters/opencode/commands/cf-spec.md +55 -0
- package/src/adapters/opencode/commands/cf-sync.md +29 -0
- package/src/adapters/opencode/commands/cf-task/align.md +24 -1
- package/src/adapters/opencode/commands/cf-task/archive.md +8 -0
- package/src/adapters/opencode/commands/cf-task/plan.md +17 -0
- package/src/adapters/opencode/commands/cf-task/prd.md +25 -0
- package/src/adapters/opencode/commands/cf-task/start.md +14 -10
- package/src/adapters/opencode/plugins/code-flow/index.js +3 -4
- package/src/cli.js +51 -2
- package/src/core/code-flow/config.yml +14 -16
- package/src/core/code-flow/scripts/cf_checks.py +30 -0
- package/src/core/code-flow/scripts/cf_core.py +36 -256
- package/src/core/code-flow/scripts/cf_log.py +8 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +6 -9
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +88 -0
- package/src/core/code-flow/scripts/cf_session_state.py +37 -0
- package/src/core/code-flow/scripts/cf_spec_context.py +1470 -0
- package/src/core/code-flow/scripts/cf_spec_gate.py +230 -0
- package/src/core/code-flow/scripts/cf_spec_metadata.py +304 -0
- package/src/core/code-flow/scripts/cf_spec_migrate.py +512 -0
- package/src/core/code-flow/scripts/cf_spec_resolver.py +271 -0
- package/src/core/code-flow/scripts/cf_spec_router.py +149 -0
- package/src/core/code-flow/scripts/cf_spec_session.py +118 -0
- package/src/core/code-flow/scripts/cf_spec_verify.py +340 -0
- package/src/core/code-flow/scripts/cf_stats.py +40 -5
- package/src/core/code-flow/scripts/cf_stop_hook.py +43 -2
- package/src/core/code-flow/scripts/cf_sync.py +140 -0
- package/src/core/code-flow/scripts/cf_task_runtime.py +184 -0
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +115 -345
- package/src/core/code-flow/specs/backend/code-quality-performance.md +13 -1
- package/src/core/code-flow/specs/backend/database.md +13 -1
- package/src/core/code-flow/specs/backend/directory-structure.md +13 -1
- package/src/core/code-flow/specs/backend/logging.md +13 -1
- package/src/core/code-flow/specs/backend/platform-rules.md +13 -1
- package/src/core/code-flow/specs/frontend/component-specs.md +13 -1
- package/src/core/code-flow/specs/frontend/directory-structure.md +13 -1
- package/src/core/code-flow/specs/frontend/quality-standards.md +13 -1
- package/src/core/code-flow/specs/shared/design/design-frontend.md +10 -0
- package/src/core/code-flow/specs/shared/design/design-full.md +10 -0
- package/src/core/code-flow/specs/shared/design/design-lite.md +10 -0
- package/src/core/code-flow/specs/shared/prd-template.md +14 -3
- package/src/migrate/spec-workflow.js +238 -0
- package/src/adapters/claude/commands/cf-inject.md +0 -55
- package/src/adapters/codex/skills/cf-inject/SKILL.md +0 -56
- package/src/adapters/costrict/commands/cf-inject.md +0 -55
- package/src/adapters/opencode/commands/cf-inject.md +0 -59
- package/src/core/code-flow/scripts/cf_inject_hook.py +0 -178
- package/src/core/code-flow/scripts/cf_session_hook.py +0 -42
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 强制重新注入指定领域的编码规范
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# cf-inject
|
|
6
|
-
|
|
7
|
-
强制重新注入指定领域的编码规范(通常不需要手动调用)。
|
|
8
|
-
|
|
9
|
-
## 自动注入机制
|
|
10
|
-
|
|
11
|
-
本项目的规范注入是**全自动**的,通过两层机制保障:
|
|
12
|
-
|
|
13
|
-
1. **AGENTS.md 指令**:AI 根据问题自动判断领域,读取 `_map.md` 导航地图理解项目结构
|
|
14
|
-
2. **code-flow 插件**:在你发送消息时根据引用的文件自动注入匹配的约束 specs
|
|
15
|
-
|
|
16
|
-
两层职责分离:AGENTS.md 负责导航(Tier 0),插件负责约束注入(Tier 1)。
|
|
17
|
-
|
|
18
|
-
正常情况下**无需手动调用**此命令。
|
|
19
|
-
|
|
20
|
-
## 何时使用
|
|
21
|
-
|
|
22
|
-
- 需要强制刷新已注入的规范(如 spec 文件刚被修改)
|
|
23
|
-
- 想要预览某个领域的完整规范内容
|
|
24
|
-
- 自动注入未生效时的排查手段
|
|
25
|
-
- 插件标签匹配遗漏了某些 spec,需要手动补充
|
|
26
|
-
|
|
27
|
-
## 输入
|
|
28
|
-
|
|
29
|
-
- `/project:cf-inject frontend` — 强制加载前端全部 specs
|
|
30
|
-
- `/project:cf-inject backend` — 强制加载后端全部 specs
|
|
31
|
-
|
|
32
|
-
## 执行步骤
|
|
33
|
-
|
|
34
|
-
1. 读取 `.code-flow/config.yml`,获取指定领域的 `specs` 列表
|
|
35
|
-
2. 提取所有 spec 的 `path` 字段(兼容新旧格式)
|
|
36
|
-
3. 按 tier 排序:tier 0(`_map.md`)在前,tier 1 按列表顺序
|
|
37
|
-
4. 逐个读取 `.code-flow/specs/` 下的匹配文件
|
|
38
|
-
5. 将规范内容直接输出到对话中,格式:
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
## Active Specs (manual inject)
|
|
42
|
-
|
|
43
|
-
### Navigation (Retrieval Map)
|
|
44
|
-
|
|
45
|
-
#### [domain]/_map.md
|
|
46
|
-
[导航地图内容]
|
|
47
|
-
|
|
48
|
-
### Constraints (all specs for domain)
|
|
49
|
-
|
|
50
|
-
#### [spec-path]
|
|
51
|
-
[spec 内容]
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
以上规范是本次开发的约束条件,生成代码必须遵循。
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
6. 更新 `.code-flow/.inject-state`,将该域所有 spec 路径标记为已注入,防止插件重复注入
|
|
58
|
-
|
|
59
|
-
> 注:手动 inject 会加载该域的**全部** spec,不做标签过滤。这是与插件自动注入的区别——插件按标签精准匹配,手动注入用于全量预览或强制刷新。
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
import cf_log
|
|
7
|
-
from cf_core import (
|
|
8
|
-
_log,
|
|
9
|
-
assemble_context,
|
|
10
|
-
build_effective_mapping,
|
|
11
|
-
debug_log,
|
|
12
|
-
ensure_utf8_io,
|
|
13
|
-
extract_context_tags,
|
|
14
|
-
fallback_domains_for_context,
|
|
15
|
-
is_code_file,
|
|
16
|
-
load_config,
|
|
17
|
-
load_inject_state,
|
|
18
|
-
match_domains,
|
|
19
|
-
match_specs_by_tags,
|
|
20
|
-
read_matched_specs,
|
|
21
|
-
resolve_compress,
|
|
22
|
-
resolve_quality_loop,
|
|
23
|
-
resolve_session_id,
|
|
24
|
-
save_inject_state,
|
|
25
|
-
select_specs_tiered,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def main() -> None:
|
|
33
|
-
try:
|
|
34
|
-
ensure_utf8_io()
|
|
35
|
-
raw = sys.stdin.read()
|
|
36
|
-
if not raw.strip():
|
|
37
|
-
return
|
|
38
|
-
data = json.loads(raw)
|
|
39
|
-
tool_name = data.get("tool_name", "")
|
|
40
|
-
tool_input = data.get("tool_input") or {}
|
|
41
|
-
file_path = tool_input.get("file_path", "")
|
|
42
|
-
if tool_name not in {"Edit", "Write", "MultiEdit"}:
|
|
43
|
-
return
|
|
44
|
-
if not isinstance(file_path, str) or not file_path:
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
project_root = os.getcwd()
|
|
48
|
-
abs_path = file_path
|
|
49
|
-
if not os.path.isabs(abs_path):
|
|
50
|
-
abs_path = os.path.join(project_root, file_path)
|
|
51
|
-
rel_path = os.path.relpath(abs_path, project_root)
|
|
52
|
-
|
|
53
|
-
config = load_config(project_root)
|
|
54
|
-
if not config:
|
|
55
|
-
return
|
|
56
|
-
inject_config = config.get("inject") or {}
|
|
57
|
-
if inject_config.get("auto") is False:
|
|
58
|
-
return
|
|
59
|
-
if not is_code_file(rel_path, inject_config):
|
|
60
|
-
return
|
|
61
|
-
compress_enabled = resolve_compress(inject_config)
|
|
62
|
-
|
|
63
|
-
mapping = config.get("path_mapping") or {}
|
|
64
|
-
effective_mapping = build_effective_mapping(project_root, mapping)
|
|
65
|
-
domains = match_domains(rel_path, effective_mapping)
|
|
66
|
-
|
|
67
|
-
sid = resolve_session_id(data)
|
|
68
|
-
|
|
69
|
-
# Quality-loop instrumentation (FEAT-00): every code-file edit is an
|
|
70
|
-
# event — correction pairing and stop-check need them even when no
|
|
71
|
-
# spec matches below. append_event never raises (hot path safe).
|
|
72
|
-
quality_loop = resolve_quality_loop(config)
|
|
73
|
-
if quality_loop["enabled"]:
|
|
74
|
-
cf_log.append_event(
|
|
75
|
-
project_root, "edit",
|
|
76
|
-
{"file": rel_path.replace(os.sep, "/"), "tool": tool_name},
|
|
77
|
-
sid,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# Extract context tags from file path
|
|
81
|
-
context_tags = extract_context_tags(rel_path)
|
|
82
|
-
if not domains:
|
|
83
|
-
domains = sorted(fallback_domains_for_context(effective_mapping, context_tags))
|
|
84
|
-
if not domains:
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
# Budget config
|
|
88
|
-
budget_cfg = config.get("budget") or {}
|
|
89
|
-
l1_budget = 1700
|
|
90
|
-
map_max = 400
|
|
91
|
-
try:
|
|
92
|
-
l1_budget = int(budget_cfg.get("l1_max", 1700))
|
|
93
|
-
except (ValueError, TypeError):
|
|
94
|
-
pass
|
|
95
|
-
try:
|
|
96
|
-
map_max = int(budget_cfg.get("map_max", 400))
|
|
97
|
-
except (ValueError, TypeError):
|
|
98
|
-
pass
|
|
99
|
-
|
|
100
|
-
# Strict tag-based matching per domain. No bulk-load fallback: when a
|
|
101
|
-
# Tier 1 spec's tags don't intersect context_tags, the spec is NOT
|
|
102
|
-
# injected. Tier 0 (_map.md) uses the "*" wildcard so it still reaches
|
|
103
|
-
# the model as navigation. Re-inject every call (session dedup removed).
|
|
104
|
-
all_matched = []
|
|
105
|
-
for domain in domains:
|
|
106
|
-
domain_cfg = effective_mapping.get(domain) or {}
|
|
107
|
-
specs_config = domain_cfg.get("specs") or []
|
|
108
|
-
matched, _ = match_specs_by_tags(specs_config, context_tags)
|
|
109
|
-
if matched:
|
|
110
|
-
specs = read_matched_specs(
|
|
111
|
-
project_root, domain, matched, compress=compress_enabled
|
|
112
|
-
)
|
|
113
|
-
all_matched.extend(specs)
|
|
114
|
-
|
|
115
|
-
if not all_matched:
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
selected = select_specs_tiered(all_matched, l1_budget, map_max)
|
|
119
|
-
if not selected:
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
# Load state with session isolation (deferred until after match success)
|
|
123
|
-
state = load_inject_state(project_root)
|
|
124
|
-
same_session = state.get("session_id") == sid
|
|
125
|
-
if same_session:
|
|
126
|
-
injected_specs = set(state.get("injected_specs") or [])
|
|
127
|
-
else:
|
|
128
|
-
injected_specs = set()
|
|
129
|
-
|
|
130
|
-
# Update state with newly injected spec paths. Preserve
|
|
131
|
-
# UserPromptSubmit-owned fields (prompt_count / prompt_inject_window)
|
|
132
|
-
# verbatim so cross-hook writes don't reset the dedup window.
|
|
133
|
-
new_injected = injected_specs | {s["path"] for s in selected}
|
|
134
|
-
debug_log(f"inject_hook injected={[s['path'] for s in selected]} path={rel_path}", project_root)
|
|
135
|
-
new_state = {
|
|
136
|
-
"session_id": sid,
|
|
137
|
-
"injected_specs": sorted(new_injected),
|
|
138
|
-
"last_file": abs_path,
|
|
139
|
-
}
|
|
140
|
-
if same_session:
|
|
141
|
-
if "prompt_count" in state:
|
|
142
|
-
new_state["prompt_count"] = state["prompt_count"]
|
|
143
|
-
if "prompt_inject_window" in state:
|
|
144
|
-
new_state["prompt_inject_window"] = state["prompt_inject_window"]
|
|
145
|
-
save_inject_state(project_root, new_state)
|
|
146
|
-
|
|
147
|
-
if quality_loop["enabled"]:
|
|
148
|
-
cf_log.append_event(
|
|
149
|
-
project_root, "inject",
|
|
150
|
-
{"specs": [s["path"] for s in selected],
|
|
151
|
-
"mode": "full", "source": "pretooluse"},
|
|
152
|
-
sid,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
payload = {
|
|
156
|
-
"hookSpecificOutput": {
|
|
157
|
-
"hookEventName": "PreToolUse",
|
|
158
|
-
"additionalContext": assemble_context(
|
|
159
|
-
selected, "## Active Specs (auto-injected)"
|
|
160
|
-
),
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if os.environ.get("CF_DEBUG") == "1":
|
|
164
|
-
payload["debug"] = {
|
|
165
|
-
"target": abs_path,
|
|
166
|
-
"domains": sorted(domains),
|
|
167
|
-
"context_tags": sorted(context_tags),
|
|
168
|
-
"matched_specs": [s["path"] for s in selected],
|
|
169
|
-
}
|
|
170
|
-
sys.stdout.write(json.dumps(payload, ensure_ascii=False))
|
|
171
|
-
except Exception as exc:
|
|
172
|
-
# Fix #9: log errors to stderr instead of silently swallowing
|
|
173
|
-
_log(f"cf_inject_hook error: {exc}")
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if __name__ == "__main__":
|
|
178
|
-
main()
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""SessionStart hook: reset .inject-state for the new session.
|
|
3
|
-
|
|
4
|
-
Reads the session_id from the hook payload (stdin JSON) so the value matches
|
|
5
|
-
what PreToolUse / UserPromptSubmit will write later — keeping the
|
|
6
|
-
"hook id 优先、回退 PID" contract in cf_core.resolve_session_id consistent
|
|
7
|
-
across the session. If stdin is empty (older runtime, unit test) we fall back
|
|
8
|
-
to the current PID; the next hook with a real session_id will then trigger
|
|
9
|
-
the normal session-mismatch reset path, so behavior degrades gracefully.
|
|
10
|
-
"""
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
import sys
|
|
14
|
-
|
|
15
|
-
from cf_core import ensure_utf8_io, resolve_session_id
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def main() -> None:
|
|
19
|
-
try:
|
|
20
|
-
ensure_utf8_io()
|
|
21
|
-
raw = sys.stdin.read()
|
|
22
|
-
data = json.loads(raw) if raw and raw.strip() else {}
|
|
23
|
-
if not isinstance(data, dict):
|
|
24
|
-
data = {}
|
|
25
|
-
sid = resolve_session_id(data)
|
|
26
|
-
|
|
27
|
-
project_root = os.getcwd()
|
|
28
|
-
state_path = os.path.join(project_root, ".code-flow", ".inject-state")
|
|
29
|
-
os.makedirs(os.path.dirname(state_path), exist_ok=True)
|
|
30
|
-
payload = {
|
|
31
|
-
"session_id": sid,
|
|
32
|
-
"injected_specs": [],
|
|
33
|
-
"last_file": "",
|
|
34
|
-
}
|
|
35
|
-
with open(state_path, "w", encoding="utf-8") as f:
|
|
36
|
-
json.dump(payload, f)
|
|
37
|
-
except Exception:
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if __name__ == "__main__":
|
|
42
|
-
main()
|