@jahanxu/code-flow 0.1.0

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 (30) hide show
  1. package/package.json +13 -0
  2. package/src/adapters/claude/settings.local.json +26 -0
  3. package/src/adapters/claude/skills/cf-init.md +13 -0
  4. package/src/adapters/claude/skills/cf-inject.md +12 -0
  5. package/src/adapters/claude/skills/cf-learn.md +11 -0
  6. package/src/adapters/claude/skills/cf-scan.md +12 -0
  7. package/src/adapters/claude/skills/cf-stats.md +11 -0
  8. package/src/adapters/claude/skills/cf-validate.md +12 -0
  9. package/src/adapters/codex/AGENTS.md +3 -0
  10. package/src/adapters/cursor/cursorrules +1 -0
  11. package/src/cli.js +105 -0
  12. package/src/core/code-flow/config.yml +97 -0
  13. package/src/core/code-flow/scripts/cf_core.py +129 -0
  14. package/src/core/code-flow/scripts/cf_init.py +829 -0
  15. package/src/core/code-flow/scripts/cf_inject.py +150 -0
  16. package/src/core/code-flow/scripts/cf_inject_hook.py +107 -0
  17. package/src/core/code-flow/scripts/cf_learn.py +202 -0
  18. package/src/core/code-flow/scripts/cf_scan.py +157 -0
  19. package/src/core/code-flow/scripts/cf_session_hook.py +16 -0
  20. package/src/core/code-flow/scripts/cf_stats.py +108 -0
  21. package/src/core/code-flow/scripts/cf_validate.py +340 -0
  22. package/src/core/code-flow/specs/backend/code-quality-performance.md +13 -0
  23. package/src/core/code-flow/specs/backend/database.md +13 -0
  24. package/src/core/code-flow/specs/backend/directory-structure.md +13 -0
  25. package/src/core/code-flow/specs/backend/logging.md +13 -0
  26. package/src/core/code-flow/specs/backend/platform-rules.md +13 -0
  27. package/src/core/code-flow/specs/frontend/component-specs.md +14 -0
  28. package/src/core/code-flow/specs/frontend/directory-structure.md +14 -0
  29. package/src/core/code-flow/specs/frontend/quality-standards.md +15 -0
  30. package/src/core/code-flow/validation.yml +30 -0
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@jahanxu/code-flow",
3
+ "version": "0.1.0",
4
+ "license": "UNLICENSED",
5
+ "bin": {
6
+ "code-flow": "src/cli.js"
7
+ },
8
+ "files": [
9
+ "src/cli.js",
10
+ "src/core/**",
11
+ "src/adapters/**"
12
+ ]
13
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Edit|Write|MultiEdit",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "python3 .code-flow/scripts/cf_inject_hook.py",
10
+ "timeout": 5
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "SessionStart": [
16
+ {
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "python3 .code-flow/scripts/cf_session_hook.py"
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,13 @@
1
+ # cf-init
2
+
3
+ 初始化 code-flow 规范体系(目录、配置、spec、skill、hooks)。
4
+
5
+ ## Usage
6
+ - `/cf-init`
7
+ - `/cf-init frontend|backend|fullstack`
8
+
9
+ ## Command
10
+ - `python3 .code-flow/scripts/cf_init.py [frontend|backend|fullstack]`
11
+
12
+ ## Notes
13
+ - 依赖 Python 3.9+ 与 pyyaml(脚本会尝试自动安装)。
@@ -0,0 +1,12 @@
1
+ # cf-inject
2
+
3
+ 手动注入指定领域或文件路径对应的规范(Hook 失败时回退)。
4
+
5
+ ## Usage
6
+ - `/cf-inject frontend|backend`
7
+ - `/cf-inject path/to/file.ext`
8
+ - `/cf-inject --list-specs --domain=frontend`
9
+
10
+ ## Command
11
+ - `python3 .code-flow/scripts/cf_inject.py [domain|file_path]`
12
+ - `python3 .code-flow/scripts/cf_inject.py --list-specs --domain=frontend`
@@ -0,0 +1,11 @@
1
+ # cf-learn
2
+
3
+ 将经验沉淀到对应规范的 Learnings 段落。
4
+
5
+ ## Usage
6
+ - `/cf-learn --scope=global --content="..." `
7
+ - `/cf-learn --scope=frontend --content="..." --file=frontend/component-specs.md`
8
+ - `/cf-learn --scope=backend --content="..." --file=backend/logging.md`
9
+
10
+ ## Command
11
+ - `python3 .code-flow/scripts/cf_learn.py --scope=global|frontend|backend --content=\"...\" [--file=spec] [--dry-run]`
@@ -0,0 +1,12 @@
1
+ # cf-scan
2
+
3
+ 审计规范文件的 token 分布与问题提示。
4
+
5
+ ## Usage
6
+ - `/cf-scan`
7
+ - `/cf-scan --json`
8
+ - `/cf-scan --only-issues`
9
+ - `/cf-scan --limit=10`
10
+
11
+ ## Command
12
+ - `python3 .code-flow/scripts/cf_scan.py [--json] [--only-issues] [--limit=N]`
@@ -0,0 +1,11 @@
1
+ # cf-stats
2
+
3
+ 统计 L0/L1 规范 token 使用情况。
4
+
5
+ ## Usage
6
+ - `/cf-stats`
7
+ - `/cf-stats --human`
8
+ - `/cf-stats --domain=frontend`
9
+
10
+ ## Command
11
+ - `python3 .code-flow/scripts/cf_stats.py [--human] [--domain=frontend]`
@@ -0,0 +1,12 @@
1
+ # cf-validate
2
+
3
+ 基于变更文件执行验证规则(类型检查/测试/lint)。
4
+
5
+ ## Usage
6
+ - `/cf-validate`
7
+ - `/cf-validate path/to/file.py`
8
+ - `/cf-validate --files=src/a.ts,src/b.ts`
9
+ - `/cf-validate --only-failed`
10
+
11
+ ## Command
12
+ - `python3 .code-flow/scripts/cf_validate.py [file_path] [--files=...] [--only-failed] [--json-short] [--output=table]`
@@ -0,0 +1,3 @@
1
+ # AGENTS
2
+
3
+ Placeholder for Codex adapter.
@@ -0,0 +1 @@
1
+ # Placeholder for Cursor adapter rules.
package/src/cli.js ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const usage = [
10
+ 'Usage: code-flow init',
11
+ ' code-flow --help'
12
+ ].join('\n');
13
+
14
+ function printUsage(stream) {
15
+ stream.write(`${usage}\n`);
16
+ }
17
+
18
+ function fail(message) {
19
+ if (message) {
20
+ process.stderr.write(`${message}\n`);
21
+ }
22
+ printUsage(process.stderr);
23
+ process.exit(1);
24
+ }
25
+
26
+ function ensurePython3() {
27
+ const probe = spawnSync('python3', ['--version'], { stdio: 'ignore' });
28
+ if (probe.error || probe.status !== 0) {
29
+ process.stderr.write('Error: python3 is required but was not found in PATH.\n');
30
+ process.exit(1);
31
+ }
32
+ }
33
+
34
+ function copyFileIfMissing(srcFile, destFile) {
35
+ if (fs.existsSync(destFile)) {
36
+ return;
37
+ }
38
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
39
+ fs.copyFileSync(srcFile, destFile);
40
+ }
41
+
42
+ function copyDirRecursive(srcDir, destDir) {
43
+ fs.mkdirSync(destDir, { recursive: true });
44
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
45
+ for (const entry of entries) {
46
+ const srcPath = path.join(srcDir, entry.name);
47
+ const destPath = path.join(destDir, entry.name);
48
+ if (entry.isDirectory()) {
49
+ copyDirRecursive(srcPath, destPath);
50
+ continue;
51
+ }
52
+ if (entry.isFile()) {
53
+ if (!fs.existsSync(destPath)) {
54
+ fs.copyFileSync(srcPath, destPath);
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ function runInit() {
61
+ ensurePython3();
62
+
63
+ const cwd = process.cwd();
64
+ const baseDir = __dirname;
65
+ const coreDir = path.join(baseDir, 'core');
66
+ const adaptersDir = path.join(baseDir, 'adapters');
67
+
68
+ copyDirRecursive(path.join(coreDir, 'code-flow'), path.join(cwd, '.code-flow'));
69
+ copyFileIfMissing(path.join(coreDir, 'claude.md'), path.join(cwd, 'CLAUDE.md'));
70
+ copyDirRecursive(path.join(adaptersDir, 'claude'), path.join(cwd, '.claude'));
71
+
72
+ const result = spawnSync(
73
+ 'python3',
74
+ ['.code-flow/scripts/cf_init.py'],
75
+ { stdio: 'inherit', cwd }
76
+ );
77
+
78
+ if (result.error) {
79
+ process.stderr.write(`Error: ${result.error.message}\n`);
80
+ process.exit(1);
81
+ }
82
+
83
+ process.exit(result.status ?? 0);
84
+ }
85
+
86
+ const args = process.argv.slice(2);
87
+
88
+ if (args.length === 1 && args[0] === '--help') {
89
+ printUsage(process.stdout);
90
+ process.exit(0);
91
+ }
92
+
93
+ if (args.length === 1 && args[0] === 'init') {
94
+ runInit();
95
+ }
96
+
97
+ if (args.length === 0) {
98
+ fail('Error: missing command.');
99
+ }
100
+
101
+ if (args.includes('--path')) {
102
+ fail('Error: --path is not supported; run init in the current directory.');
103
+ }
104
+
105
+ fail(`Error: unknown command "${args.join(' ')}".`);
@@ -0,0 +1,97 @@
1
+ version: 1
2
+
3
+ budget:
4
+ total: 2500
5
+ l0_max: 800
6
+ l1_max: 1700
7
+
8
+ inject:
9
+ auto: true
10
+ code_extensions:
11
+ - ".py"
12
+ - ".ts"
13
+ - ".tsx"
14
+ - ".js"
15
+ - ".jsx"
16
+ - ".go"
17
+ - ".java"
18
+ - ".rs"
19
+ - ".rb"
20
+ - ".vue"
21
+ - ".svelte"
22
+ skip_extensions:
23
+ - ".md"
24
+ - ".txt"
25
+ - ".json"
26
+ - ".yml"
27
+ - ".yaml"
28
+ - ".toml"
29
+ - ".lock"
30
+ - ".csv"
31
+ - ".xml"
32
+ - ".svg"
33
+ - ".png"
34
+ - ".jpg"
35
+ - ".jpeg"
36
+ - ".gif"
37
+ - ".ico"
38
+ - ".pdf"
39
+ - ".zip"
40
+ - ".gz"
41
+ - ".tar"
42
+ skip_paths:
43
+ - "docs/**"
44
+ - "*.config.*"
45
+ - ".code-flow/**"
46
+ - ".claude/**"
47
+ - "node_modules/**"
48
+ - "dist/**"
49
+ - "build/**"
50
+ - "out/**"
51
+ - "coverage/**"
52
+ - ".next/**"
53
+ - ".cache/**"
54
+ - ".venv/**"
55
+ - "venv/**"
56
+ - "__pycache__/**"
57
+ - ".git/**"
58
+
59
+ path_mapping:
60
+ frontend:
61
+ patterns:
62
+ - "src/components/**"
63
+ - "src/pages/**"
64
+ - "src/hooks/**"
65
+ - "src/styles/**"
66
+ - "**/*.tsx"
67
+ - "**/*.jsx"
68
+ - "**/*.css"
69
+ - "**/*.scss"
70
+ specs:
71
+ - "frontend/directory-structure.md"
72
+ - "frontend/quality-standards.md"
73
+ - "frontend/component-specs.md"
74
+ spec_priority:
75
+ "frontend/directory-structure.md": 1
76
+ "frontend/quality-standards.md": 2
77
+ "frontend/component-specs.md": 3
78
+ backend:
79
+ patterns:
80
+ - "services/**"
81
+ - "api/**"
82
+ - "models/**"
83
+ - "*.py"
84
+ - "**/*.py"
85
+ - "**/*.go"
86
+ specs:
87
+ - "backend/directory-structure.md"
88
+ - "backend/logging.md"
89
+ - "backend/database.md"
90
+ - "backend/platform-rules.md"
91
+ - "backend/code-quality-performance.md"
92
+ spec_priority:
93
+ "backend/directory-structure.md": 1
94
+ "backend/database.md": 2
95
+ "backend/logging.md": 3
96
+ "backend/code-quality-performance.md": 4
97
+ "backend/platform-rules.md": 5
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+ import fnmatch
3
+ import json
4
+ import os
5
+
6
+
7
+ def load_config(project_root: str) -> dict:
8
+ config_path = os.path.join(project_root, ".code-flow", "config.yml")
9
+ if not os.path.exists(config_path):
10
+ return {}
11
+ try:
12
+ import yaml
13
+ except Exception:
14
+ return {}
15
+ try:
16
+ with open(config_path, "r", encoding="utf-8") as file:
17
+ data = yaml.safe_load(file)
18
+ return data or {}
19
+ except Exception:
20
+ return {}
21
+
22
+
23
+ def estimate_tokens(text: str) -> int:
24
+ return len(text) // 4
25
+
26
+
27
+ def normalize_path(path: str) -> str:
28
+ return path.replace(os.sep, "/")
29
+
30
+
31
+ def is_code_file(rel_path: str, inject_config: dict) -> bool:
32
+ rel_path = normalize_path(rel_path)
33
+ for pattern in inject_config.get("skip_paths") or []:
34
+ if fnmatch.fnmatch(rel_path, pattern):
35
+ return False
36
+ _, ext = os.path.splitext(rel_path)
37
+ if ext in (inject_config.get("skip_extensions") or []):
38
+ return False
39
+ code_exts = inject_config.get("code_extensions") or []
40
+ return ext in code_exts
41
+
42
+
43
+ def match_domains(rel_path: str, mapping: dict) -> list:
44
+ rel_path = normalize_path(rel_path)
45
+ domains = []
46
+ for domain, cfg in (mapping or {}).items():
47
+ patterns = cfg.get("patterns") or []
48
+ for pattern in patterns:
49
+ if fnmatch.fnmatch(rel_path, pattern):
50
+ domains.append(domain)
51
+ break
52
+ return domains
53
+
54
+
55
+ def read_specs(project_root: str, domain: str, domain_cfg: dict) -> list:
56
+ specs_root = os.path.join(project_root, ".code-flow", "specs")
57
+ specs = []
58
+ for rel in domain_cfg.get("specs") or []:
59
+ spec_path = os.path.join(specs_root, rel)
60
+ try:
61
+ with open(spec_path, "r", encoding="utf-8") as file:
62
+ content = file.read().strip()
63
+ if not content:
64
+ continue
65
+ specs.append(
66
+ {
67
+ "path": rel,
68
+ "content": content,
69
+ "tokens": estimate_tokens(content),
70
+ "domain": domain,
71
+ }
72
+ )
73
+ except Exception:
74
+ continue
75
+ return specs
76
+
77
+
78
+ def select_specs(specs: list, budget: int, priorities: dict) -> list:
79
+ if budget <= 0:
80
+ return []
81
+
82
+ def priority(spec: dict) -> int:
83
+ value = priorities.get(spec.get("path"))
84
+ if isinstance(value, int):
85
+ return value
86
+ try:
87
+ return int(value)
88
+ except Exception:
89
+ return 1000
90
+
91
+ ordered = sorted(specs, key=lambda spec: (priority(spec), spec.get("path", "")))
92
+ selected = []
93
+ total = 0
94
+ for spec in ordered:
95
+ if total + spec.get("tokens", 0) <= budget:
96
+ selected.append(spec)
97
+ total += spec.get("tokens", 0)
98
+ return selected
99
+
100
+
101
+ def assemble_context(specs: list, heading: str) -> str:
102
+ parts = [heading]
103
+ for spec in specs:
104
+ parts.append(f"### {spec['path']}")
105
+ parts.append(spec["content"])
106
+ parts.append("---")
107
+ parts.append("以上规范是本次开发的约束条件,生成代码必须遵循。")
108
+ return "\n\n".join(parts)
109
+
110
+
111
+ def load_inject_state(project_root: str) -> dict:
112
+ state_path = os.path.join(project_root, ".code-flow", ".inject-state")
113
+ try:
114
+ with open(state_path, "r", encoding="utf-8") as file:
115
+ data = json.load(file)
116
+ if isinstance(data, dict):
117
+ return data
118
+ except Exception:
119
+ return {}
120
+ return {}
121
+
122
+
123
+ def save_inject_state(project_root: str, payload: dict) -> None:
124
+ state_path = os.path.join(project_root, ".code-flow", ".inject-state")
125
+ try:
126
+ with open(state_path, "w", encoding="utf-8") as file:
127
+ json.dump(payload, file)
128
+ except Exception:
129
+ return