@michaelxwb/code-flow 0.5.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/README.md +157 -0
- package/package.json +16 -0
- package/src/adapters/claude/CLAUDE.md +51 -0
- package/src/adapters/claude/commands/cf-init.md +262 -0
- package/src/adapters/claude/commands/cf-inject.md +55 -0
- package/src/adapters/claude/commands/cf-learn.md +291 -0
- package/src/adapters/claude/commands/cf-stats.md +86 -0
- package/src/adapters/claude/commands/cf-task/align.md +283 -0
- package/src/adapters/claude/commands/cf-task/archive.md +131 -0
- package/src/adapters/claude/commands/cf-task/block.md +30 -0
- package/src/adapters/claude/commands/cf-task/graph.md +73 -0
- package/src/adapters/claude/commands/cf-task/note.md +239 -0
- package/src/adapters/claude/commands/cf-task/plan.md +270 -0
- package/src/adapters/claude/commands/cf-task/prd.md +138 -0
- package/src/adapters/claude/commands/cf-task/start.md +173 -0
- package/src/adapters/claude/commands/cf-task/status.md +59 -0
- package/src/adapters/claude/commands/cf-validate.md +73 -0
- package/src/adapters/claude/settings.local.json +60 -0
- package/src/adapters/codex/AGENTS.md +51 -0
- package/src/adapters/codex/config.toml +3 -0
- package/src/adapters/codex/hooks.json +48 -0
- package/src/adapters/codex/skills/cf-init/SKILL.md +271 -0
- package/src/adapters/codex/skills/cf-inject/SKILL.md +56 -0
- package/src/adapters/codex/skills/cf-learn/SKILL.md +296 -0
- package/src/adapters/codex/skills/cf-stats/SKILL.md +87 -0
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +284 -0
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +132 -0
- package/src/adapters/codex/skills/cf-task-block/SKILL.md +31 -0
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +74 -0
- package/src/adapters/codex/skills/cf-task-note/SKILL.md +236 -0
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +271 -0
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +139 -0
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +174 -0
- package/src/adapters/codex/skills/cf-task-status/SKILL.md +60 -0
- package/src/adapters/codex/skills/cf-validate/SKILL.md +74 -0
- package/src/adapters/costrict/CLAUDE.md +51 -0
- package/src/adapters/costrict/commands/cf-init.md +262 -0
- package/src/adapters/costrict/commands/cf-inject.md +55 -0
- package/src/adapters/costrict/commands/cf-learn.md +291 -0
- package/src/adapters/costrict/commands/cf-stats.md +86 -0
- package/src/adapters/costrict/commands/cf-task/align.md +283 -0
- package/src/adapters/costrict/commands/cf-task/archive.md +131 -0
- package/src/adapters/costrict/commands/cf-task/block.md +30 -0
- package/src/adapters/costrict/commands/cf-task/graph.md +73 -0
- package/src/adapters/costrict/commands/cf-task/note.md +239 -0
- package/src/adapters/costrict/commands/cf-task/plan.md +270 -0
- package/src/adapters/costrict/commands/cf-task/prd.md +138 -0
- package/src/adapters/costrict/commands/cf-task/start.md +173 -0
- package/src/adapters/costrict/commands/cf-task/status.md +59 -0
- package/src/adapters/costrict/commands/cf-validate.md +73 -0
- package/src/adapters/costrict/settings.local.json +60 -0
- package/src/adapters/opencode/AGENTS.md +51 -0
- package/src/adapters/opencode/commands/cf-init.md +275 -0
- package/src/adapters/opencode/commands/cf-inject.md +59 -0
- package/src/adapters/opencode/commands/cf-learn.md +295 -0
- package/src/adapters/opencode/commands/cf-stats.md +90 -0
- package/src/adapters/opencode/commands/cf-task/align.md +287 -0
- package/src/adapters/opencode/commands/cf-task/archive.md +135 -0
- package/src/adapters/opencode/commands/cf-task/block.md +34 -0
- package/src/adapters/opencode/commands/cf-task/graph.md +77 -0
- package/src/adapters/opencode/commands/cf-task/note.md +243 -0
- package/src/adapters/opencode/commands/cf-task/plan.md +274 -0
- package/src/adapters/opencode/commands/cf-task/prd.md +142 -0
- package/src/adapters/opencode/commands/cf-task/start.md +177 -0
- package/src/adapters/opencode/commands/cf-task/status.md +63 -0
- package/src/adapters/opencode/commands/cf-validate.md +77 -0
- package/src/adapters/opencode/opencode.json +4 -0
- package/src/adapters/opencode/plugins/code-flow/index.js +132 -0
- package/src/adapters/opencode/plugins/code-flow/package.json +6 -0
- package/src/cli.js +810 -0
- package/src/core/code-flow/config.yml +159 -0
- package/src/core/code-flow/scripts/cf_checks.py +306 -0
- package/src/core/code-flow/scripts/cf_core.py +954 -0
- package/src/core/code-flow/scripts/cf_feedback.py +56 -0
- package/src/core/code-flow/scripts/cf_inject_hook.py +178 -0
- package/src/core/code-flow/scripts/cf_log.py +146 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +196 -0
- package/src/core/code-flow/scripts/cf_scan.py +293 -0
- package/src/core/code-flow/scripts/cf_session_hook.py +42 -0
- package/src/core/code-flow/scripts/cf_stats.py +439 -0
- package/src/core/code-flow/scripts/cf_stop_hook.py +191 -0
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +402 -0
- package/src/core/code-flow/specs/backend/_map.md +56 -0
- package/src/core/code-flow/specs/backend/code-quality-performance.md +47 -0
- package/src/core/code-flow/specs/backend/database.md +39 -0
- package/src/core/code-flow/specs/backend/directory-structure.md +42 -0
- package/src/core/code-flow/specs/backend/logging.md +38 -0
- package/src/core/code-flow/specs/backend/platform-rules.md +43 -0
- package/src/core/code-flow/specs/frontend/_map.md +52 -0
- package/src/core/code-flow/specs/frontend/component-specs.md +56 -0
- package/src/core/code-flow/specs/frontend/directory-structure.md +41 -0
- package/src/core/code-flow/specs/frontend/quality-standards.md +46 -0
- package/src/core/code-flow/specs/shared/_map.md +57 -0
- package/src/core/code-flow/specs/shared/design/design-frontend.md +247 -0
- package/src/core/code-flow/specs/shared/design/design-full.md +494 -0
- package/src/core/code-flow/specs/shared/design/design-lite.md +293 -0
- package/src/core/code-flow/specs/shared/prd-template.md +187 -0
- package/src/core/code-flow/tasks/.gitkeep +0 -0
- package/src/core/code-flow/validation.yml +48 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import cf_log
|
|
8
|
+
from cf_checks import load_check_state, parse_spec_checks
|
|
9
|
+
from cf_core import (
|
|
10
|
+
build_effective_mapping,
|
|
11
|
+
build_spec_catalog,
|
|
12
|
+
estimate_tokens,
|
|
13
|
+
load_config,
|
|
14
|
+
non_injectable_specs,
|
|
15
|
+
parse_spec_frontmatter,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
REVIEW_MIN_COVERAGE_DAYS = 7
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _log_coverage_sufficient(events: list) -> bool:
|
|
22
|
+
""""未命中"信号需要足够的日志覆盖期——刚部署一天的日志没资格断言
|
|
23
|
+
"30 天未命中"(实测误伤:单条事件就把全部 spec 标为待复审)。"""
|
|
24
|
+
from datetime import datetime, timedelta
|
|
25
|
+
earliest = min((str(e.get("ts", "")) for e in events), default="")
|
|
26
|
+
try:
|
|
27
|
+
first = datetime.fromisoformat(earliest)
|
|
28
|
+
except ValueError:
|
|
29
|
+
return False
|
|
30
|
+
return first <= datetime.now() - timedelta(days=REVIEW_MIN_COVERAGE_DAYS)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_review_list(project_root: str, specs: list) -> list:
|
|
34
|
+
"""待复审清单(FEAT-06):未命中注入 / 已停用 / 反复误报。
|
|
35
|
+
|
|
36
|
+
豁免:check-state `_review_exempt` 列表中的条目不再提示(S-09 处置闭环)。
|
|
37
|
+
日志覆盖不足 REVIEW_MIN_COVERAGE_DAYS 天时跳过"未命中"信号,
|
|
38
|
+
避免误伤全新安装与刚部署项目。
|
|
39
|
+
"""
|
|
40
|
+
state = load_check_state(project_root)
|
|
41
|
+
exempt = set(state.get("_review_exempt") or [])
|
|
42
|
+
review: list = []
|
|
43
|
+
|
|
44
|
+
events = cf_log.read_events(project_root, days=30)
|
|
45
|
+
if events and _log_coverage_sufficient(events):
|
|
46
|
+
engaged: set = set()
|
|
47
|
+
for event in events:
|
|
48
|
+
data = event.get("data") or {}
|
|
49
|
+
if event.get("event") == "inject":
|
|
50
|
+
engaged.update(data.get("specs") or [])
|
|
51
|
+
elif event.get("event") == "violation" and data.get("spec"):
|
|
52
|
+
engaged.add(data["spec"])
|
|
53
|
+
for spec in specs:
|
|
54
|
+
rel = spec.get("rel", "")
|
|
55
|
+
if rel.endswith("_map.md") or rel.startswith("shared/"):
|
|
56
|
+
continue
|
|
57
|
+
if rel not in engaged and rel not in exempt:
|
|
58
|
+
review.append({"item": rel, "reason": "30 天未命中注入/未触发检查"})
|
|
59
|
+
|
|
60
|
+
for cid, entry in state.items():
|
|
61
|
+
if cid.startswith("_") or not isinstance(entry, dict) or cid in exempt:
|
|
62
|
+
continue
|
|
63
|
+
if entry.get("disabled"):
|
|
64
|
+
review.append({
|
|
65
|
+
"item": cid,
|
|
66
|
+
"reason": f"已自动停用({entry.get('disabled_reason', '')}),待改写或删除",
|
|
67
|
+
})
|
|
68
|
+
elif int(entry.get("fp_count", 0)) >= 2:
|
|
69
|
+
review.append({"item": cid, "reason": f"误报 {entry['fp_count']} 次"})
|
|
70
|
+
return review
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
PATH_PATTERN = re.compile(r"(?:[\w./-]+/)+[\w.-]+\.[A-Za-z0-9]+")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def read_text(path: str) -> str:
|
|
77
|
+
try:
|
|
78
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
79
|
+
return file.read().strip()
|
|
80
|
+
except Exception:
|
|
81
|
+
return ""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def normalize_line(line: str) -> str:
|
|
85
|
+
return " ".join(line.strip().split())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def find_redundant_lines(specs: list) -> dict:
|
|
89
|
+
line_map: dict = {}
|
|
90
|
+
for spec in specs:
|
|
91
|
+
for raw in spec["content"].splitlines():
|
|
92
|
+
line = normalize_line(raw)
|
|
93
|
+
if not line:
|
|
94
|
+
continue
|
|
95
|
+
if line.startswith("#") or line == "---":
|
|
96
|
+
continue
|
|
97
|
+
if len(line) < 6:
|
|
98
|
+
continue
|
|
99
|
+
line_map.setdefault(line, set()).add(spec["path"])
|
|
100
|
+
redundant = {line: paths for line, paths in line_map.items() if len(paths) >= 3}
|
|
101
|
+
return redundant
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def find_missing_paths(text: str, project_root: str, base_dir: str = "") -> list:
|
|
105
|
+
"""路径引用检查:项目根解析失败时,再按 spec 自身目录二次解析——
|
|
106
|
+
_map.md 常用相对本域目录的引用(如 design/design-lite.md)。"""
|
|
107
|
+
missing: list = []
|
|
108
|
+
for token in set(PATH_PATTERN.findall(text)):
|
|
109
|
+
if token.startswith("http://") or token.startswith("https://"):
|
|
110
|
+
continue
|
|
111
|
+
if os.path.isabs(token):
|
|
112
|
+
if not os.path.exists(token):
|
|
113
|
+
missing.append(token)
|
|
114
|
+
continue
|
|
115
|
+
if os.path.exists(os.path.join(project_root, token)):
|
|
116
|
+
continue
|
|
117
|
+
if base_dir and os.path.exists(os.path.join(base_dir, token)):
|
|
118
|
+
continue
|
|
119
|
+
missing.append(token)
|
|
120
|
+
return sorted(missing)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def build_report(project_root: str) -> dict:
|
|
124
|
+
"""规范质量审计数据(cf-scan 引擎,亦供 cf-stats --audit 复用)。
|
|
125
|
+
|
|
126
|
+
files 含 issues / template 标记,review 为待复审清单;展示层负责过滤与渲染。"""
|
|
127
|
+
files = []
|
|
128
|
+
total_tokens = 0
|
|
129
|
+
|
|
130
|
+
claude_path = os.path.join(project_root, "CLAUDE.md")
|
|
131
|
+
if os.path.exists(claude_path):
|
|
132
|
+
content = read_text(claude_path)
|
|
133
|
+
tokens = estimate_tokens(content)
|
|
134
|
+
total_tokens += tokens
|
|
135
|
+
files.append({"path": "CLAUDE.md", "tokens": tokens, "issues": []})
|
|
136
|
+
|
|
137
|
+
specs_root = os.path.join(project_root, ".code-flow", "specs")
|
|
138
|
+
specs = []
|
|
139
|
+
if os.path.isdir(specs_root):
|
|
140
|
+
for root, _, filenames in os.walk(specs_root):
|
|
141
|
+
for name in filenames:
|
|
142
|
+
if not name.endswith(".md"):
|
|
143
|
+
continue
|
|
144
|
+
full_path = os.path.join(root, name)
|
|
145
|
+
content = read_text(full_path)
|
|
146
|
+
if not content:
|
|
147
|
+
continue
|
|
148
|
+
tokens = estimate_tokens(content)
|
|
149
|
+
total_tokens += tokens
|
|
150
|
+
rel = os.path.relpath(full_path, specs_root)
|
|
151
|
+
if rel.replace(os.sep, "/").startswith("_session/"):
|
|
152
|
+
continue # 会话级临时约束(FEAT-08)不参与审计与预算
|
|
153
|
+
rel_path = os.path.join("specs", rel).replace(os.sep, "/")
|
|
154
|
+
spec_entry = {
|
|
155
|
+
"path": rel_path,
|
|
156
|
+
"rel": rel.replace(os.sep, "/"),
|
|
157
|
+
"tokens": tokens,
|
|
158
|
+
"issues": [],
|
|
159
|
+
"content": content,
|
|
160
|
+
}
|
|
161
|
+
specs.append(spec_entry)
|
|
162
|
+
|
|
163
|
+
# 模板标记前置:模板(tags:[])不参与冗长/冗余审计,也不把
|
|
164
|
+
# 模板间共享的文档头计入其他文件的冗余计数
|
|
165
|
+
config = load_config(project_root)
|
|
166
|
+
effective_mapping = build_effective_mapping(
|
|
167
|
+
project_root, config.get("path_mapping") or {}
|
|
168
|
+
)
|
|
169
|
+
excluded_specs = non_injectable_specs(effective_mapping)
|
|
170
|
+
for spec in specs:
|
|
171
|
+
spec["template"] = spec.get("rel", "") in excluded_specs
|
|
172
|
+
|
|
173
|
+
redundant_map = find_redundant_lines([s for s in specs if not s["template"]])
|
|
174
|
+
|
|
175
|
+
for spec in specs:
|
|
176
|
+
issues = []
|
|
177
|
+
if not spec["template"] and spec["tokens"] > 500:
|
|
178
|
+
issues.append(f"冗长: {spec['tokens']} tokens")
|
|
179
|
+
|
|
180
|
+
spec_dir = os.path.join(specs_root, os.path.dirname(spec.get("rel", "")))
|
|
181
|
+
missing_paths = find_missing_paths(spec["content"], project_root, spec_dir)
|
|
182
|
+
for path in missing_paths[:3]:
|
|
183
|
+
issues.append(f"过时: 路径不存在 {path}")
|
|
184
|
+
|
|
185
|
+
redundant_lines = [line for line, paths in redundant_map.items() if spec["path"] in paths]
|
|
186
|
+
for line in redundant_lines[:3]:
|
|
187
|
+
issues.append(f"冗余: '{line}' 出现于 {len(redundant_map[line])} 个文件")
|
|
188
|
+
|
|
189
|
+
# Constraint specs feed the catalog one line each — flag missing
|
|
190
|
+
# frontmatter description (catalog falls back to blockquote/H1).
|
|
191
|
+
rel = spec.get("rel", "")
|
|
192
|
+
is_constraint = not rel.endswith("_map.md") and not rel.startswith("shared/")
|
|
193
|
+
if is_constraint:
|
|
194
|
+
meta, _ = parse_spec_frontmatter(spec["content"])
|
|
195
|
+
if not (meta.get("description") or "").strip():
|
|
196
|
+
issues.append("缺描述: 建议加 frontmatter description(catalog 适用场景一句话)")
|
|
197
|
+
# checks 标注校验(E-01 / B-04 的离线出口)
|
|
198
|
+
_, check_errors = parse_spec_checks(spec["content"])
|
|
199
|
+
for error in check_errors[:3]:
|
|
200
|
+
issues.append(f"checks: {error}")
|
|
201
|
+
|
|
202
|
+
files.append({
|
|
203
|
+
"path": spec["path"],
|
|
204
|
+
"tokens": spec["tokens"],
|
|
205
|
+
"issues": issues,
|
|
206
|
+
"template": spec["template"],
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
for entry in files:
|
|
210
|
+
if total_tokens <= 0:
|
|
211
|
+
entry["percent"] = "0%"
|
|
212
|
+
else:
|
|
213
|
+
entry["percent"] = f"{round(entry['tokens'] * 100 / total_tokens)}%"
|
|
214
|
+
|
|
215
|
+
budget = (config.get("budget") or {}).get("total", 2500)
|
|
216
|
+
try:
|
|
217
|
+
budget = int(budget)
|
|
218
|
+
except Exception:
|
|
219
|
+
budget = 2500
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
catalog_max = int((config.get("budget") or {}).get("catalog_max", 200))
|
|
223
|
+
except Exception:
|
|
224
|
+
catalog_max = 200
|
|
225
|
+
catalog = build_spec_catalog(project_root, effective_mapping, catalog_max)
|
|
226
|
+
catalog_info = {
|
|
227
|
+
"tokens": estimate_tokens(catalog),
|
|
228
|
+
"max": catalog_max,
|
|
229
|
+
"entries": catalog.count("\n- `"),
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
review = build_review_list(project_root, specs)
|
|
233
|
+
|
|
234
|
+
# 预算口径与 cf-stats 一致:tags:[] 命令模板不计入注入预算
|
|
235
|
+
templates_tokens = sum(e["tokens"] for e in files if e.get("template"))
|
|
236
|
+
injectable_tokens = total_tokens - templates_tokens
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
"files": files,
|
|
240
|
+
"total_tokens": injectable_tokens,
|
|
241
|
+
"templates_tokens": templates_tokens,
|
|
242
|
+
"budget": budget,
|
|
243
|
+
"catalog": catalog_info,
|
|
244
|
+
"review": review,
|
|
245
|
+
"warnings": [],
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def main() -> None:
|
|
250
|
+
json_output = "--json" in sys.argv
|
|
251
|
+
only_issues = "--only-issues" in sys.argv
|
|
252
|
+
limit = None
|
|
253
|
+
for arg in sys.argv[1:]:
|
|
254
|
+
if arg.startswith("--limit="):
|
|
255
|
+
try:
|
|
256
|
+
limit = int(arg.split("=", 1)[1])
|
|
257
|
+
except Exception:
|
|
258
|
+
limit = None
|
|
259
|
+
|
|
260
|
+
report = build_report(os.getcwd())
|
|
261
|
+
files = report["files"]
|
|
262
|
+
if limit is not None:
|
|
263
|
+
files = files[:limit]
|
|
264
|
+
if only_issues:
|
|
265
|
+
files = [entry for entry in files if entry.get("issues")]
|
|
266
|
+
|
|
267
|
+
if json_output:
|
|
268
|
+
print(json.dumps(dict(report, files=files), ensure_ascii=False))
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
catalog_info = report["catalog"]
|
|
272
|
+
print("FILE | TOKENS | PERCENT | ISSUES")
|
|
273
|
+
for entry in files:
|
|
274
|
+
issues = entry.get("issues") or []
|
|
275
|
+
issue_text = " / ".join(issues) if issues else "-"
|
|
276
|
+
marker = " [模板]" if entry.get("template") else ""
|
|
277
|
+
print(f"{entry['path']}{marker} | {entry['tokens']} | {entry['percent']} | {issue_text}")
|
|
278
|
+
print("TOTAL:", f"{report['total_tokens']} / {report['budget']}")
|
|
279
|
+
if report["templates_tokens"]:
|
|
280
|
+
print("TEMPLATES (非注入):", f"{report['templates_tokens']} tokens,不计预算")
|
|
281
|
+
print(
|
|
282
|
+
"CATALOG:",
|
|
283
|
+
f"{catalog_info['tokens']} / {catalog_info['max']} tokens,",
|
|
284
|
+
f"{catalog_info['entries']} entries",
|
|
285
|
+
)
|
|
286
|
+
if report["review"]:
|
|
287
|
+
print("REVIEW (待复审):")
|
|
288
|
+
for item in report["review"]:
|
|
289
|
+
print(f" - {item['item']}: {item['reason']}")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
main()
|
|
@@ -0,0 +1,42 @@
|
|
|
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()
|