@michaelxwb/code-flow 0.6.4 → 0.8.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 +47 -3
- package/docs/USAGE.md +1529 -0
- package/docs/acceptance-contract.md +217 -0
- package/docs/ci/acceptance-evidence.md +77 -0
- package/docs/migrations/runtime-0.7.0.md +41 -0
- package/docs/migrations/runtime-0.8.0.md +154 -0
- package/docs/migrations/runtime-0.9.0.md +40 -0
- package/docs/spec-injection-hardening.md +54 -0
- package/docs/spec-injection-performance.json +73 -0
- package/docs/spec-optimization.md +12 -0
- package/package.json +18 -3
- package/src/adapters/claude/CLAUDE.md +1 -1
- package/src/adapters/claude/commands/cf-init.md +1 -3
- package/src/adapters/claude/commands/cf-spec.md +23 -3
- package/src/adapters/claude/commands/cf-stats.md +11 -1
- package/src/adapters/claude/commands/cf-sync.md +20 -6
- package/src/adapters/claude/commands/cf-task/align.md +25 -3
- package/src/adapters/claude/commands/cf-task/archive.md +36 -40
- package/src/adapters/claude/commands/cf-task/block.md +15 -2
- package/src/adapters/claude/commands/cf-task/graph.md +13 -1
- package/src/adapters/claude/commands/cf-task/note.md +13 -1
- package/src/adapters/claude/commands/cf-task/plan.md +137 -23
- package/src/adapters/claude/commands/cf-task/prd.md +13 -2
- package/src/adapters/claude/commands/cf-task/start.md +67 -35
- package/src/adapters/claude/commands/cf-task/status.md +12 -0
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +72 -34
- package/src/adapters/claude/commands/cf-validate.md +30 -5
- package/src/adapters/codex/AGENTS.md +1 -1
- package/src/adapters/codex/hooks.json +14 -10
- package/src/adapters/codex/skills/cf-init/SKILL.md +4 -4
- package/src/adapters/codex/skills/cf-spec/SKILL.md +34 -6
- package/src/adapters/codex/skills/cf-stats/SKILL.md +11 -1
- package/src/adapters/codex/skills/cf-sync/SKILL.md +21 -6
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +30 -8
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +36 -40
- package/src/adapters/codex/skills/cf-task-block/SKILL.md +15 -2
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +13 -1
- package/src/adapters/codex/skills/cf-task-note/SKILL.md +13 -1
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +175 -21
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +13 -2
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +67 -35
- package/src/adapters/codex/skills/cf-task-status/SKILL.md +12 -0
- package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +73 -34
- package/src/adapters/codex/skills/cf-validate/SKILL.md +31 -5
- package/src/adapters/commands.json +115 -0
- package/src/adapters/costrict/CLAUDE.md +1 -1
- package/src/adapters/costrict/commands/cf-init.md +1 -3
- package/src/adapters/costrict/commands/cf-spec.md +23 -3
- package/src/adapters/costrict/commands/cf-stats.md +11 -1
- package/src/adapters/costrict/commands/cf-sync.md +20 -6
- package/src/adapters/costrict/commands/cf-task/align.md +25 -3
- package/src/adapters/costrict/commands/cf-task/archive.md +36 -40
- package/src/adapters/costrict/commands/cf-task/block.md +15 -2
- package/src/adapters/costrict/commands/cf-task/graph.md +13 -1
- package/src/adapters/costrict/commands/cf-task/note.md +13 -1
- package/src/adapters/costrict/commands/cf-task/plan.md +137 -23
- package/src/adapters/costrict/commands/cf-task/prd.md +13 -2
- package/src/adapters/costrict/commands/cf-task/start.md +67 -35
- package/src/adapters/costrict/commands/cf-task/status.md +12 -0
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +72 -34
- package/src/adapters/costrict/commands/cf-validate.md +30 -5
- package/src/adapters/install-plan.json +100 -0
- package/src/adapters/opencode/AGENTS.md +1 -1
- package/src/adapters/opencode/commands/cf-init.md +7 -7
- package/src/adapters/opencode/commands/cf-learn.md +6 -6
- package/src/adapters/opencode/commands/cf-spec.md +34 -2
- package/src/adapters/opencode/commands/cf-stats.md +17 -7
- package/src/adapters/opencode/commands/cf-sync.md +21 -6
- package/src/adapters/opencode/commands/cf-task/align.md +25 -8
- package/src/adapters/opencode/commands/cf-task/archive.md +36 -40
- package/src/adapters/opencode/commands/cf-task/block.md +16 -2
- package/src/adapters/opencode/commands/cf-task/graph.md +14 -1
- package/src/adapters/opencode/commands/cf-task/note.md +14 -1
- package/src/adapters/opencode/commands/cf-task/plan.md +137 -23
- package/src/adapters/opencode/commands/cf-task/prd.md +13 -2
- package/src/adapters/opencode/commands/cf-task/start.md +68 -41
- package/src/adapters/opencode/commands/cf-task/status.md +13 -0
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +72 -34
- package/src/adapters/opencode/commands/cf-validate.md +35 -9
- package/src/adapters/opencode/plugins/code-flow/index.js +128 -179
- package/src/adapters/platform-bindings.json +370 -0
- package/src/cli.js +369 -316
- package/src/core/code-flow/config.yml +4 -1
- package/src/core/code-flow/runtime-commands.json +259 -0
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +5 -289
- package/src/core/code-flow/scripts/cf_acceptance_runner.py +5 -187
- package/src/core/code-flow/scripts/cf_audit.py +10 -0
- package/src/core/code-flow/scripts/cf_codex_hook.py +10 -0
- package/src/core/code-flow/scripts/cf_design_lint.py +10 -0
- package/src/core/code-flow/scripts/cf_evidence_import.py +10 -0
- package/src/core/code-flow/scripts/cf_feedback.py +5 -51
- package/src/core/code-flow/scripts/cf_opencode_event.py +10 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +6 -191
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +6 -99
- package/src/core/code-flow/scripts/cf_scenarios.py +10 -0
- package/src/core/code-flow/scripts/cf_spec_context.py +5 -1714
- package/src/core/code-flow/scripts/cf_spec_gate.py +5 -257
- package/src/core/code-flow/scripts/cf_spec_session.py +5 -114
- package/src/core/code-flow/scripts/cf_stats.py +5 -501
- package/src/core/code-flow/scripts/cf_stop_hook.py +6 -376
- package/src/core/code-flow/scripts/cf_sync.py +5 -135
- package/src/core/code-flow/scripts/cf_task_index.py +5 -143
- package/src/core/code-flow/scripts/cf_task_parallel.py +5 -380
- package/src/core/code-flow/scripts/cf_task_workflow.py +6 -103
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +6 -173
- package/src/core/code-flow/scripts/cf_validation.py +6 -77
- package/src/core/code-flow/scripts/codeflow/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_evidence.py +347 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_manifest_core.py +660 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_plan.py +202 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_registration.py +60 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_risk.py +73 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_schema.py +282 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_selection.py +266 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_inputs.py +172 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_registration.py +326 -0
- package/src/core/code-flow/scripts/{cf_checks.py → codeflow/acceptance/cf_checks.py} +8 -11
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_plan_quality.py +197 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_quality.py +174 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_source.py +300 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_traceability.py +502 -0
- package/src/core/code-flow/scripts/codeflow/cli/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_manifest.py +347 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_runner.py +1490 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_audit.py +533 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_design_lint.py +172 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_evidence_import.py +607 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_feedback.py +53 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_scenarios.py +58 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_context.py +1175 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_gate.py +60 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_session.py +46 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_stats.py +540 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_sync.py +144 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_index.py +177 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_parallel.py +1142 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_workflow.py +742 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_validation.py +31 -0
- package/src/core/code-flow/scripts/codeflow/core/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_context_cache.py +67 -0
- package/src/core/code-flow/scripts/{cf_core.py → codeflow/core/cf_core.py} +119 -297
- package/src/core/code-flow/scripts/codeflow/core/cf_exec_base.py +325 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_fs.py +57 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_git.py +92 -0
- package/src/core/code-flow/scripts/{cf_log.py → codeflow/core/cf_log.py} +13 -7
- package/src/core/code-flow/scripts/codeflow/core/cf_markdown.py +77 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_native_paths.py +26 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_patch.py +33 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_runtime_install.py +32 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_stop_guard.py +53 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_task_state.py +9 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_validators.py +408 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_worktree_env.py +253 -0
- package/src/core/code-flow/scripts/codeflow/evidence/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/evidence/cf_evidence_store.py +527 -0
- package/src/core/code-flow/scripts/codeflow/hooks/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_codex_hook.py +95 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_opencode_event.py +98 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_post_hook.py +30 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_pre_tool_hook.py +41 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_stop_hook.py +292 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_user_prompt_hook.py +161 -0
- package/src/core/code-flow/scripts/codeflow/spec/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_active_task.py +412 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_context_refresh.py +301 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_edit_service.py +149 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_injection.py +111 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_rule_readiness.py +114 -0
- package/src/core/code-flow/scripts/{cf_scan.py → codeflow/spec/cf_scan.py} +93 -21
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_budget.py +55 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_context_model.py +477 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_impact.py +119 -0
- package/src/core/code-flow/scripts/{cf_spec_metadata.py → codeflow/spec/cf_spec_metadata.py} +37 -3
- package/src/core/code-flow/scripts/{cf_spec_migrate.py → codeflow/spec/cf_spec_migrate.py} +27 -10
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize.py +193 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize_apply.py +187 -0
- package/src/core/code-flow/scripts/{cf_spec_resolver.py → codeflow/spec/cf_spec_resolver.py} +39 -17
- package/src/core/code-flow/scripts/{cf_spec_router.py → codeflow/spec/cf_spec_router.py} +96 -36
- package/src/core/code-flow/scripts/{cf_spec_verify.py → codeflow/spec/cf_spec_verify.py} +252 -26
- package/src/core/code-flow/scripts/codeflow/spec/cf_stage_gate.py +330 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_task_projection.py +83 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_service.py +755 -0
- package/src/core/code-flow/scripts/{cf_workflow_transaction.py → codeflow/spec/cf_workflow_transaction.py} +29 -15
- package/src/core/code-flow/scripts/codeflow/task/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_requirement_verification.py +867 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_doc.py +178 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_merge.py +635 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_plan.py +251 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_runtime.py +570 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_validation_service.py +73 -0
- package/src/core/code-flow/scripts/runtime-resources.json +29 -0
- package/src/core/code-flow/specs/shared/design/design-frontend.md +18 -15
- package/src/core/code-flow/specs/shared/design/design-full.md +33 -23
- package/src/core/code-flow/specs/shared/design/design-lite.md +23 -13
- package/src/core/code-flow/specs/shared/scenarios-template.md +55 -0
- package/src/core/code-flow/validation.yml +9 -3
- package/src/migrate/runtime.js +213 -0
- package/src/migrate/spec-workflow.js +16 -1
- package/src/runtime.js +219 -0
- package/src/core/code-flow/scripts/cf_acceptance_evidence.py +0 -100
- package/src/core/code-flow/scripts/cf_acceptance_schema.py +0 -83
- package/src/core/code-flow/scripts/cf_exec_base.py +0 -112
- package/src/core/code-flow/scripts/cf_task_runtime.py +0 -299
- package/src/core/code-flow/scripts/cf_task_state.py +0 -3
- package/src/core/code-flow/scripts/cf_workflow_service.py +0 -366
- /package/src/core/code-flow/scripts/{cf_session_state.py → codeflow/core/cf_session_state.py} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import json
|
|
3
|
+
import glob
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
5
6
|
import sys
|
|
7
|
+
from pathlib import Path
|
|
6
8
|
|
|
7
|
-
import cf_log
|
|
8
|
-
from cf_checks import load_check_state, parse_spec_checks
|
|
9
|
-
from cf_core import (
|
|
9
|
+
from codeflow.core import cf_log
|
|
10
|
+
from codeflow.acceptance.cf_checks import load_check_state, parse_spec_checks
|
|
11
|
+
from codeflow.core.cf_core import (
|
|
10
12
|
build_effective_mapping,
|
|
11
13
|
build_spec_catalog,
|
|
12
14
|
estimate_tokens,
|
|
@@ -86,14 +88,30 @@ def normalize_line(line: str) -> str:
|
|
|
86
88
|
return " ".join(line.strip().split())
|
|
87
89
|
|
|
88
90
|
|
|
91
|
+
_YAML_STRUCTURE = re.compile(
|
|
92
|
+
r"^(?:stages:.*|enforcement:.*|verifiers:.*|tags:.*|tier:.*|"
|
|
93
|
+
r"- (?:design|plan|code|review)|-?\s*type:\s*(?:command|test))$"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _audit_body(content: str) -> str:
|
|
98
|
+
"""Remove only a complete frontmatter block at the start of the file."""
|
|
99
|
+
lines = content.splitlines()
|
|
100
|
+
if lines and lines[0].strip() == "---":
|
|
101
|
+
end = next((i for i in range(1, len(lines)) if lines[i].strip() == "---"), None)
|
|
102
|
+
if end is not None:
|
|
103
|
+
return "\n".join(lines[end + 1:])
|
|
104
|
+
return content
|
|
105
|
+
|
|
106
|
+
|
|
89
107
|
def find_redundant_lines(specs: list) -> dict:
|
|
90
108
|
line_map: dict = {}
|
|
91
109
|
for spec in specs:
|
|
92
|
-
for raw in spec["content"].splitlines():
|
|
110
|
+
for raw in _audit_body(spec["content"]).splitlines():
|
|
93
111
|
line = normalize_line(raw)
|
|
94
112
|
if not line:
|
|
95
113
|
continue
|
|
96
|
-
if line.startswith("#") or line == "---":
|
|
114
|
+
if line.startswith("#") or line == "---" or _YAML_STRUCTURE.fullmatch(line):
|
|
97
115
|
continue
|
|
98
116
|
if len(line) < 6:
|
|
99
117
|
continue
|
|
@@ -102,23 +120,73 @@ def find_redundant_lines(specs: list) -> dict:
|
|
|
102
120
|
return redundant
|
|
103
121
|
|
|
104
122
|
|
|
105
|
-
def
|
|
106
|
-
""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
def _audit_options(config: dict) -> tuple[list[str], int]:
|
|
124
|
+
audit = config.get("audit", {})
|
|
125
|
+
if not isinstance(audit, dict):
|
|
126
|
+
raise ValueError("audit must be a mapping")
|
|
127
|
+
roots = audit.get("path_roots", [])
|
|
128
|
+
if not isinstance(roots, list) or any(not isinstance(root, str) or not root for root in roots):
|
|
129
|
+
raise ValueError("audit.path_roots must be a list of paths")
|
|
130
|
+
maximum = audit.get("long_spec_tokens", 500)
|
|
131
|
+
if type(maximum) is not int or maximum <= 0:
|
|
132
|
+
raise ValueError("audit.long_spec_tokens must be a positive integer")
|
|
133
|
+
return roots, maximum
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _audit_roots(project_root: str, configured: list[str]) -> tuple[list[str], list[dict[str, str]]]:
|
|
137
|
+
project = Path(project_root).resolve()
|
|
138
|
+
roots: list[str] = []
|
|
139
|
+
warnings: list[dict[str, str]] = []
|
|
140
|
+
discovered = [path for pattern in ("apps/*/src/*", "packages/*/src/*", "lib/*/src/*")
|
|
141
|
+
for path in sorted(glob.glob(str(project / pattern)))]
|
|
142
|
+
for value in [*configured, *discovered]:
|
|
143
|
+
candidate = (project / value).resolve()
|
|
144
|
+
if (candidate != project and project not in candidate.parents) or not candidate.is_dir() or not os.access(candidate, os.R_OK | os.X_OK):
|
|
145
|
+
if value in configured:
|
|
146
|
+
warnings.append({"code": "invalid_path_root", "path": value})
|
|
111
147
|
continue
|
|
148
|
+
resolved = str(candidate)
|
|
149
|
+
if resolved not in roots:
|
|
150
|
+
roots.append(resolved)
|
|
151
|
+
return roots, warnings
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _path_resolution(text: str, project_root: str, base_dir: str,
|
|
155
|
+
roots: list[str]) -> tuple[list[str], dict[str, str]]:
|
|
156
|
+
missing: list[str] = []
|
|
157
|
+
resolved: dict[str, str] = {}
|
|
158
|
+
project = Path(project_root).resolve()
|
|
159
|
+
for token in _path_tokens(text):
|
|
160
|
+
candidates = [str(project), base_dir, *roots]
|
|
112
161
|
if os.path.isabs(token):
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
162
|
+
candidates = [""]
|
|
163
|
+
for root in candidates:
|
|
164
|
+
if root and not Path(root).is_dir():
|
|
165
|
+
continue
|
|
166
|
+
target = Path(os.path.join(root, token)).resolve()
|
|
167
|
+
if not os.path.isabs(token) and target != project and project not in target.parents:
|
|
168
|
+
continue
|
|
169
|
+
if target.exists():
|
|
170
|
+
resolved[token] = os.path.relpath(root, project) if root else "/"
|
|
171
|
+
break
|
|
172
|
+
else:
|
|
173
|
+
missing.append(token)
|
|
174
|
+
return missing, resolved
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _path_tokens(text: str) -> list[str]:
|
|
178
|
+
tokens: set[str] = set()
|
|
179
|
+
for word in re.findall(r"[^\s`<>()\"']+", text):
|
|
180
|
+
if "://" in word or any(char in word for char in "*?{}[]"):
|
|
119
181
|
continue
|
|
120
|
-
|
|
121
|
-
return sorted(
|
|
182
|
+
tokens.update(PATH_PATTERN.findall(word))
|
|
183
|
+
return sorted(tokens)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def find_missing_paths(text: str, project_root: str, base_dir: str = "") -> list:
|
|
187
|
+
"""路径引用检查:项目根解析失败时,再按 spec 自身目录二次解析——
|
|
188
|
+
_map.md 常用相对本域目录的引用(如 design/design-lite.md)。"""
|
|
189
|
+
return _path_resolution(text, project_root, base_dir, [])[0]
|
|
122
190
|
|
|
123
191
|
|
|
124
192
|
def build_report(project_root: str, platform: str = "") -> dict:
|
|
@@ -165,6 +233,8 @@ def build_report(project_root: str, platform: str = "") -> dict:
|
|
|
165
233
|
# 模板标记前置:模板(tags:[])不参与冗长/冗余审计,也不把
|
|
166
234
|
# 模板间共享的文档头计入其他文件的冗余计数
|
|
167
235
|
config = load_config(project_root)
|
|
236
|
+
configured_roots, long_spec_tokens = _audit_options(config)
|
|
237
|
+
roots, audit_warnings = _audit_roots(project_root, configured_roots)
|
|
168
238
|
effective_mapping = build_effective_mapping(
|
|
169
239
|
project_root, config.get("path_mapping") or {}
|
|
170
240
|
)
|
|
@@ -176,11 +246,11 @@ def build_report(project_root: str, platform: str = "") -> dict:
|
|
|
176
246
|
|
|
177
247
|
for spec in specs:
|
|
178
248
|
issues = []
|
|
179
|
-
if not spec["template"] and spec["tokens"] >
|
|
249
|
+
if not spec["template"] and spec["tokens"] > long_spec_tokens:
|
|
180
250
|
issues.append(f"冗长: {spec['tokens']} tokens")
|
|
181
251
|
|
|
182
252
|
spec_dir = os.path.join(specs_root, os.path.dirname(spec.get("rel", "")))
|
|
183
|
-
missing_paths =
|
|
253
|
+
missing_paths, resolved_paths = _path_resolution(spec["content"], project_root, spec_dir, roots)
|
|
184
254
|
for path in missing_paths[:3]:
|
|
185
255
|
issues.append(f"过时: 路径不存在 {path}")
|
|
186
256
|
|
|
@@ -206,6 +276,7 @@ def build_report(project_root: str, platform: str = "") -> dict:
|
|
|
206
276
|
"tokens": spec["tokens"],
|
|
207
277
|
"issues": issues,
|
|
208
278
|
"template": spec["template"],
|
|
279
|
+
"resolved_paths": resolved_paths,
|
|
209
280
|
})
|
|
210
281
|
|
|
211
282
|
for entry in files:
|
|
@@ -245,6 +316,7 @@ def build_report(project_root: str, platform: str = "") -> dict:
|
|
|
245
316
|
"catalog": catalog_info,
|
|
246
317
|
"review": review,
|
|
247
318
|
"warnings": [],
|
|
319
|
+
"audit_warnings": audit_warnings,
|
|
248
320
|
}
|
|
249
321
|
|
|
250
322
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Shared path-projection inputs and budget accounting."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
from codeflow.core.cf_core import assemble_context, compress_content, estimate_tokens, load_config, parse_spec_frontmatter, resolve_compress
|
|
8
|
+
from codeflow.spec.cf_spec_resolver import resolve_candidates
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SpecBudgetError(ValueError):
|
|
12
|
+
def __init__(self, code: str, message: str) -> None:
|
|
13
|
+
self.code = code
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def path_specs(root: str, paths: Sequence[str], compress: bool = True,
|
|
18
|
+
matched_only: bool = True) -> list[dict[str, object]]:
|
|
19
|
+
try:
|
|
20
|
+
candidates = resolve_candidates(root, 'code', tuple(paths), matched_only=matched_only)
|
|
21
|
+
except ValueError as exc:
|
|
22
|
+
raise SpecBudgetError('invalid_spec_metadata', str(exc)) from exc
|
|
23
|
+
specs: list[dict[str, object]] = []
|
|
24
|
+
for candidate in candidates:
|
|
25
|
+
if matched_only and candidate.scope == 'unmatched':
|
|
26
|
+
continue
|
|
27
|
+
target = Path(root) / '.code-flow/specs' / candidate.path
|
|
28
|
+
try:
|
|
29
|
+
raw = target.read_text(encoding='utf-8')
|
|
30
|
+
except OSError as exc:
|
|
31
|
+
raise SpecBudgetError('spec_read_failed', f'{candidate.path}: {exc}') from exc
|
|
32
|
+
_, content = parse_spec_frontmatter(raw)
|
|
33
|
+
final = compress_content(content.strip()) if compress else content.strip()
|
|
34
|
+
specs.append({'path': candidate.path, 'content': final, 'tier': 1})
|
|
35
|
+
return specs
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def measure_path_budget(root: str, paths: Sequence[str]) -> dict[str, object]:
|
|
39
|
+
config = load_config(root)
|
|
40
|
+
quality = config.get('quality_loop')
|
|
41
|
+
compress = resolve_compress(quality if isinstance(quality, dict) else {})
|
|
42
|
+
matched = path_specs(root, paths, compress) if paths else []
|
|
43
|
+
aggregate = path_specs(root, (), compress, matched_only=False)
|
|
44
|
+
text = assemble_context(matched, '## Active Specs (path-mapped)') if matched else ''
|
|
45
|
+
budget = config.get('budget', {})
|
|
46
|
+
if not isinstance(budget, dict):
|
|
47
|
+
raise SpecBudgetError('invalid_budget', 'budget must be a mapping')
|
|
48
|
+
limit = min(int(budget.get('total', 2500)), int(budget.get('l1_max', 1700)))
|
|
49
|
+
if limit <= 0:
|
|
50
|
+
raise SpecBudgetError('invalid_budget', 'budget limit must be positive')
|
|
51
|
+
total = estimate_tokens(text)
|
|
52
|
+
return {'matched_specs': [{'path': item['path'], 'tokens': estimate_tokens(item['content'])}
|
|
53
|
+
for item in matched], 'matched_total': total,
|
|
54
|
+
'aggregate_total': sum(estimate_tokens(item['content']) for item in aggregate),
|
|
55
|
+
'limit': limit, 'over_limit': total > limit}
|