@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.
- package/package.json +13 -0
- package/src/adapters/claude/settings.local.json +26 -0
- package/src/adapters/claude/skills/cf-init.md +13 -0
- package/src/adapters/claude/skills/cf-inject.md +12 -0
- package/src/adapters/claude/skills/cf-learn.md +11 -0
- package/src/adapters/claude/skills/cf-scan.md +12 -0
- package/src/adapters/claude/skills/cf-stats.md +11 -0
- package/src/adapters/claude/skills/cf-validate.md +12 -0
- package/src/adapters/codex/AGENTS.md +3 -0
- package/src/adapters/cursor/cursorrules +1 -0
- package/src/cli.js +105 -0
- package/src/core/code-flow/config.yml +97 -0
- package/src/core/code-flow/scripts/cf_core.py +129 -0
- package/src/core/code-flow/scripts/cf_init.py +829 -0
- package/src/core/code-flow/scripts/cf_inject.py +150 -0
- package/src/core/code-flow/scripts/cf_inject_hook.py +107 -0
- package/src/core/code-flow/scripts/cf_learn.py +202 -0
- package/src/core/code-flow/scripts/cf_scan.py +157 -0
- package/src/core/code-flow/scripts/cf_session_hook.py +16 -0
- package/src/core/code-flow/scripts/cf_stats.py +108 -0
- package/src/core/code-flow/scripts/cf_validate.py +340 -0
- package/src/core/code-flow/specs/backend/code-quality-performance.md +13 -0
- package/src/core/code-flow/specs/backend/database.md +13 -0
- package/src/core/code-flow/specs/backend/directory-structure.md +13 -0
- package/src/core/code-flow/specs/backend/logging.md +13 -0
- package/src/core/code-flow/specs/backend/platform-rules.md +13 -0
- package/src/core/code-flow/specs/frontend/component-specs.md +14 -0
- package/src/core/code-flow/specs/frontend/directory-structure.md +14 -0
- package/src/core/code-flow/specs/frontend/quality-standards.md +15 -0
- package/src/core/code-flow/validation.yml +30 -0
package/package.json
ADDED
|
@@ -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-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 @@
|
|
|
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
|