@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.
Files changed (99) hide show
  1. package/README.md +157 -0
  2. package/package.json +16 -0
  3. package/src/adapters/claude/CLAUDE.md +51 -0
  4. package/src/adapters/claude/commands/cf-init.md +262 -0
  5. package/src/adapters/claude/commands/cf-inject.md +55 -0
  6. package/src/adapters/claude/commands/cf-learn.md +291 -0
  7. package/src/adapters/claude/commands/cf-stats.md +86 -0
  8. package/src/adapters/claude/commands/cf-task/align.md +283 -0
  9. package/src/adapters/claude/commands/cf-task/archive.md +131 -0
  10. package/src/adapters/claude/commands/cf-task/block.md +30 -0
  11. package/src/adapters/claude/commands/cf-task/graph.md +73 -0
  12. package/src/adapters/claude/commands/cf-task/note.md +239 -0
  13. package/src/adapters/claude/commands/cf-task/plan.md +270 -0
  14. package/src/adapters/claude/commands/cf-task/prd.md +138 -0
  15. package/src/adapters/claude/commands/cf-task/start.md +173 -0
  16. package/src/adapters/claude/commands/cf-task/status.md +59 -0
  17. package/src/adapters/claude/commands/cf-validate.md +73 -0
  18. package/src/adapters/claude/settings.local.json +60 -0
  19. package/src/adapters/codex/AGENTS.md +51 -0
  20. package/src/adapters/codex/config.toml +3 -0
  21. package/src/adapters/codex/hooks.json +48 -0
  22. package/src/adapters/codex/skills/cf-init/SKILL.md +271 -0
  23. package/src/adapters/codex/skills/cf-inject/SKILL.md +56 -0
  24. package/src/adapters/codex/skills/cf-learn/SKILL.md +296 -0
  25. package/src/adapters/codex/skills/cf-stats/SKILL.md +87 -0
  26. package/src/adapters/codex/skills/cf-task-align/SKILL.md +284 -0
  27. package/src/adapters/codex/skills/cf-task-archive/SKILL.md +132 -0
  28. package/src/adapters/codex/skills/cf-task-block/SKILL.md +31 -0
  29. package/src/adapters/codex/skills/cf-task-graph/SKILL.md +74 -0
  30. package/src/adapters/codex/skills/cf-task-note/SKILL.md +236 -0
  31. package/src/adapters/codex/skills/cf-task-plan/SKILL.md +271 -0
  32. package/src/adapters/codex/skills/cf-task-prd/SKILL.md +139 -0
  33. package/src/adapters/codex/skills/cf-task-start/SKILL.md +174 -0
  34. package/src/adapters/codex/skills/cf-task-status/SKILL.md +60 -0
  35. package/src/adapters/codex/skills/cf-validate/SKILL.md +74 -0
  36. package/src/adapters/costrict/CLAUDE.md +51 -0
  37. package/src/adapters/costrict/commands/cf-init.md +262 -0
  38. package/src/adapters/costrict/commands/cf-inject.md +55 -0
  39. package/src/adapters/costrict/commands/cf-learn.md +291 -0
  40. package/src/adapters/costrict/commands/cf-stats.md +86 -0
  41. package/src/adapters/costrict/commands/cf-task/align.md +283 -0
  42. package/src/adapters/costrict/commands/cf-task/archive.md +131 -0
  43. package/src/adapters/costrict/commands/cf-task/block.md +30 -0
  44. package/src/adapters/costrict/commands/cf-task/graph.md +73 -0
  45. package/src/adapters/costrict/commands/cf-task/note.md +239 -0
  46. package/src/adapters/costrict/commands/cf-task/plan.md +270 -0
  47. package/src/adapters/costrict/commands/cf-task/prd.md +138 -0
  48. package/src/adapters/costrict/commands/cf-task/start.md +173 -0
  49. package/src/adapters/costrict/commands/cf-task/status.md +59 -0
  50. package/src/adapters/costrict/commands/cf-validate.md +73 -0
  51. package/src/adapters/costrict/settings.local.json +60 -0
  52. package/src/adapters/opencode/AGENTS.md +51 -0
  53. package/src/adapters/opencode/commands/cf-init.md +275 -0
  54. package/src/adapters/opencode/commands/cf-inject.md +59 -0
  55. package/src/adapters/opencode/commands/cf-learn.md +295 -0
  56. package/src/adapters/opencode/commands/cf-stats.md +90 -0
  57. package/src/adapters/opencode/commands/cf-task/align.md +287 -0
  58. package/src/adapters/opencode/commands/cf-task/archive.md +135 -0
  59. package/src/adapters/opencode/commands/cf-task/block.md +34 -0
  60. package/src/adapters/opencode/commands/cf-task/graph.md +77 -0
  61. package/src/adapters/opencode/commands/cf-task/note.md +243 -0
  62. package/src/adapters/opencode/commands/cf-task/plan.md +274 -0
  63. package/src/adapters/opencode/commands/cf-task/prd.md +142 -0
  64. package/src/adapters/opencode/commands/cf-task/start.md +177 -0
  65. package/src/adapters/opencode/commands/cf-task/status.md +63 -0
  66. package/src/adapters/opencode/commands/cf-validate.md +77 -0
  67. package/src/adapters/opencode/opencode.json +4 -0
  68. package/src/adapters/opencode/plugins/code-flow/index.js +132 -0
  69. package/src/adapters/opencode/plugins/code-flow/package.json +6 -0
  70. package/src/cli.js +810 -0
  71. package/src/core/code-flow/config.yml +159 -0
  72. package/src/core/code-flow/scripts/cf_checks.py +306 -0
  73. package/src/core/code-flow/scripts/cf_core.py +954 -0
  74. package/src/core/code-flow/scripts/cf_feedback.py +56 -0
  75. package/src/core/code-flow/scripts/cf_inject_hook.py +178 -0
  76. package/src/core/code-flow/scripts/cf_log.py +146 -0
  77. package/src/core/code-flow/scripts/cf_post_hook.py +196 -0
  78. package/src/core/code-flow/scripts/cf_scan.py +293 -0
  79. package/src/core/code-flow/scripts/cf_session_hook.py +42 -0
  80. package/src/core/code-flow/scripts/cf_stats.py +439 -0
  81. package/src/core/code-flow/scripts/cf_stop_hook.py +191 -0
  82. package/src/core/code-flow/scripts/cf_user_prompt_hook.py +402 -0
  83. package/src/core/code-flow/specs/backend/_map.md +56 -0
  84. package/src/core/code-flow/specs/backend/code-quality-performance.md +47 -0
  85. package/src/core/code-flow/specs/backend/database.md +39 -0
  86. package/src/core/code-flow/specs/backend/directory-structure.md +42 -0
  87. package/src/core/code-flow/specs/backend/logging.md +38 -0
  88. package/src/core/code-flow/specs/backend/platform-rules.md +43 -0
  89. package/src/core/code-flow/specs/frontend/_map.md +52 -0
  90. package/src/core/code-flow/specs/frontend/component-specs.md +56 -0
  91. package/src/core/code-flow/specs/frontend/directory-structure.md +41 -0
  92. package/src/core/code-flow/specs/frontend/quality-standards.md +46 -0
  93. package/src/core/code-flow/specs/shared/_map.md +57 -0
  94. package/src/core/code-flow/specs/shared/design/design-frontend.md +247 -0
  95. package/src/core/code-flow/specs/shared/design/design-full.md +494 -0
  96. package/src/core/code-flow/specs/shared/design/design-lite.md +293 -0
  97. package/src/core/code-flow/specs/shared/prd-template.md +187 -0
  98. package/src/core/code-flow/tasks/.gitkeep +0 -0
  99. package/src/core/code-flow/validation.yml +48 -0
@@ -0,0 +1,77 @@
1
+ ---
2
+ description: 根据变更文件自动匹配并执行验证规则
3
+ ---
4
+
5
+ # cf-validate
6
+
7
+ 根据变更文件自动匹配并执行验证规则(测试、类型检查、lint),失败时自动尝试修复。
8
+
9
+ ## 输入
10
+
11
+ - `/project:cf-validate` — 基于 git diff 自动获取变更文件
12
+ - `/project:cf-validate src/Foo.tsx` — 验证指定文件
13
+ - `/project:cf-validate --files=src/a.ts,src/b.ts` — 验证多个文件
14
+
15
+ ## 执行步骤
16
+
17
+ ### 1. 获取变更文件列表
18
+
19
+ 用 Bash 执行:
20
+
21
+ ```bash
22
+ git diff --name-only HEAD
23
+ ```
24
+
25
+ 如果用户指定了文件路径,使用用户指定的文件。如果无变更文件,输出"无变更需要验证"。
26
+
27
+ ### 2. 读取验证规则
28
+
29
+ 用 Read 读取 `.code-flow/validation.yml`。如果不存在,尝试读取 `package.json` 中的 `scripts.test` 和 `scripts.lint` 作为回退。
30
+
31
+ validation.yml 格式:
32
+
33
+ ```yaml
34
+ validators:
35
+ - name: "验证器名称"
36
+ trigger: "**/*.{ts,tsx}"
37
+ command: "npx tsc --noEmit"
38
+ timeout: 30000
39
+ on_fail: "修复建议"
40
+ ```
41
+
42
+ ### 3. 匹配并执行验证
43
+
44
+ 对每条验证规则:
45
+ - 将 `trigger` glob 与变更文件列表做匹配
46
+ - 匹配到 → 用 Bash 执行 `command`
47
+ - 如果 command 包含 `{files}` → 替换为匹配到的文件路径列表,**每个路径用单引号包裹**(如 `'src/foo.ts' 'src/bar.ts'`)
48
+ - 如果 command 不含 `{files}` → 直接执行原命令(如 `npx tsc --noEmit` 本身就检查全局)
49
+ - 未匹配 → 跳过该规则
50
+
51
+ 每条命令使用对应的 `timeout` 值(毫秒)。
52
+
53
+ ### 4. 汇总结果
54
+
55
+ - **全部通过** → 输出确认信息
56
+ - **有失败** → 展示每个失败项的:
57
+ - 验证器名称
58
+ - 执行的命令
59
+ - 错误输出(截取关键部分)
60
+ - `on_fail` 修复建议
61
+
62
+ ### 5. 自动修复
63
+
64
+ 对于失败的验证项,根据错误输出分析问题根因,自动尝试修复代码。修复后提示用户再次运行 `/project:cf-validate` 确认。
65
+
66
+ ## 安全设计
67
+
68
+ - 对 `{files}` 中的每个文件路径用单引号包裹,防止 shell 注入
69
+ - 仅接受 `git diff` 输出的文件路径或用户显式指定的路径
70
+ - 用户指定的路径必须位于项目根目录内
71
+
72
+ ## 异常处理
73
+
74
+ - validation.yml 不存在 → 尝试检测 package.json scripts 中的 test/lint 命令
75
+ - 命令执行超时 → 输出超时提示,建议增大 timeout 或缩小验证范围
76
+ - 命令不存在 → 提示安装依赖(如 `pip install mypy`)
77
+ - 无变更文件 → 输出"无变更需要验证"
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "plugin": [".opencode/plugins/code-flow"]
4
+ }
@@ -0,0 +1,132 @@
1
+ import { spawnSync } from "child_process";
2
+ import { join } from "path";
3
+ import { appendFileSync, mkdirSync } from "fs";
4
+
5
+ const SCRIPT_DIR = ".code-flow/scripts";
6
+
7
+ // Per-session cache of spec context to inject into system prompt
8
+ const sessionContext = new Map();
9
+
10
+ function debugLog(projectRoot, msg) {
11
+ if (process.env.CF_DEBUG !== "1") return;
12
+ try {
13
+ const dir = join(projectRoot, ".code-flow");
14
+ mkdirSync(dir, { recursive: true });
15
+ const ts = new Date().toISOString().replace("T", " ").slice(0, 19);
16
+ appendFileSync(join(dir, ".debug.log"), `${ts} [opencode] ${msg}\n`);
17
+ } catch {}
18
+ }
19
+
20
+ function pythonPath(projectRoot, script) {
21
+ return join(projectRoot, SCRIPT_DIR, script);
22
+ }
23
+
24
+ function callHook(projectRoot, script, input) {
25
+ try {
26
+ const proc = spawnSync("python3", [pythonPath(projectRoot, script)], {
27
+ cwd: projectRoot,
28
+ input: JSON.stringify(input),
29
+ encoding: "utf-8",
30
+ timeout: 5000,
31
+ maxBuffer: 1024 * 1024,
32
+ });
33
+ if (proc.error || proc.status !== 0) {
34
+ debugLog(projectRoot, `callHook ${script} failed: ${proc.error || proc.stderr}`);
35
+ return null;
36
+ }
37
+ const stdout = (proc.stdout || "").trim();
38
+ if (!stdout) return null;
39
+ return JSON.parse(stdout);
40
+ } catch (e) {
41
+ debugLog(projectRoot, `callHook ${script} exception: ${e.message}`);
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function extractPromptText(output) {
47
+ if (!output.parts) return "";
48
+ return output.parts
49
+ .filter((p) => p.type === "text" && !p.synthetic && !p.ignored)
50
+ .map((p) => p.text)
51
+ .join("\n");
52
+ }
53
+
54
+ export const CodeFlow = async (ctx) => {
55
+ const projectRoot = ctx.directory;
56
+
57
+ return {
58
+ event: async (input) => {
59
+ if (input.event?.type === "session.created") {
60
+ const sid = input.event?.properties?.info?.id || "";
61
+ debugLog(projectRoot, `session.created sid=${sid}`);
62
+ if (sid) sessionContext.delete(sid);
63
+ callHook(projectRoot, "cf_session_hook.py", { session_id: sid });
64
+ }
65
+ if (input.event?.type === "session.idle") {
66
+ const sid =
67
+ input.event?.properties?.sessionID ||
68
+ input.event?.properties?.info?.id || "";
69
+ const result = callHook(projectRoot, "cf_stop_hook.py", { session_id: sid });
70
+ if (result?.reason) {
71
+ // idle 无法阻断,校验失败排队到下一轮 system prompt
72
+ const pending = sessionContext.get(sid);
73
+ sessionContext.set(sid, pending ? pending + "\n\n" + result.reason : result.reason);
74
+ debugLog(projectRoot, `stop-check feedback queued`);
75
+ }
76
+ }
77
+ },
78
+
79
+ "chat.message": async (input, output) => {
80
+ const promptText = extractPromptText(output);
81
+ if (!promptText) return;
82
+
83
+ debugLog(projectRoot, `chat.message sid=${input.sessionID} prompt_len=${promptText.length}`);
84
+
85
+ const result = callHook(projectRoot, "cf_user_prompt_hook.py", {
86
+ prompt: promptText,
87
+ session_id: input.sessionID,
88
+ });
89
+
90
+ if (result?.hookSpecificOutput?.additionalContext) {
91
+ const ctxLen = result.hookSpecificOutput.additionalContext.length;
92
+ debugLog(projectRoot, `hook matched — context ${ctxLen} chars cached`);
93
+ sessionContext.set(
94
+ input.sessionID,
95
+ result.hookSpecificOutput.additionalContext
96
+ );
97
+ } else {
98
+ debugLog(projectRoot, `hook returned no context`);
99
+ }
100
+ },
101
+
102
+ "tool.execute.after": async (input, output) => {
103
+ const tool = String(input?.tool || "").toLowerCase();
104
+ if (!["edit", "write", "multiedit", "patch"].includes(tool)) return;
105
+ const args = output?.args || input?.args || {};
106
+ const filePath = args.filePath || args.file_path || args.path;
107
+ if (!filePath) return;
108
+ const result = callHook(projectRoot, "cf_post_hook.py", {
109
+ tool_name: tool === "write" ? "Write" : "Edit",
110
+ tool_input: { file_path: filePath },
111
+ session_id: input?.sessionID || "",
112
+ });
113
+ const ctx = result?.hookSpecificOutput?.additionalContext;
114
+ if (ctx) {
115
+ // 反馈排队,下一轮 system.transform 注入(opencode 无法当轮插话)
116
+ const sid = input?.sessionID || "";
117
+ const pending = sessionContext.get(sid);
118
+ sessionContext.set(sid, pending ? pending + "\n\n" + ctx : ctx);
119
+ debugLog(projectRoot, `post-check feedback queued ${ctx.length} chars`);
120
+ }
121
+ },
122
+
123
+ "experimental.chat.system.transform": async (input, output) => {
124
+ const ctx = sessionContext.get(input.sessionID);
125
+ if (ctx) {
126
+ output.system.push(ctx);
127
+ debugLog(projectRoot, `system.transform — injected ${ctx.length} chars, system now ${output.system.length} parts`);
128
+ sessionContext.delete(input.sessionID);
129
+ }
130
+ },
131
+ };
132
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "code-flow-opencode",
3
+ "type": "module",
4
+ "main": "index.js",
5
+ "description": "code-flow spec injection plugin for OpenCode"
6
+ }