@michaelxwb/code-flow 0.8.1 → 1.0.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/README.md +73 -152
- package/bin/code-flow.js +12 -0
- package/docs/README.md +19 -0
- package/docs/USAGE.md +156 -1417
- package/docs/v1-design.md +1858 -0
- package/docs/v1-implementation.md +186 -0
- package/lib/adapters.js +126 -0
- package/lib/cli.js +258 -0
- package/lib/config.js +85 -0
- package/lib/doctor.js +283 -0
- package/lib/frontmatter.js +42 -0
- package/lib/fs.js +141 -0
- package/lib/glob.js +23 -0
- package/lib/hook.js +99 -0
- package/lib/init.js +132 -0
- package/lib/learn.js +170 -0
- package/lib/locks.js +52 -0
- package/lib/markdown.js +13 -0
- package/lib/process.js +116 -0
- package/lib/render.js +115 -0
- package/lib/repo.js +48 -0
- package/lib/runs.js +155 -0
- package/lib/session.js +40 -0
- package/lib/spec.js +80 -0
- package/lib/task.js +258 -0
- package/lib/taskdoc.js +308 -0
- package/lib/toml.js +140 -0
- package/lib/verify.js +168 -0
- package/lib/worktree.js +930 -0
- package/package.json +18 -21
- package/templates/adapters/opencode.mjs +65 -0
- package/templates/config.json +39 -0
- package/templates/instructions.md +13 -0
- package/templates/launcher/bridge.cjs +295 -0
- package/templates/launcher/hook.cmd +15 -0
- package/templates/launcher/hook.ps1 +10 -0
- package/templates/launcher/hook.sh +20 -0
- package/templates/skills/cf-check.md +19 -0
- package/templates/skills/cf-init.md +16 -0
- package/templates/skills/cf-spec.md +20 -0
- package/templates/skills/cf-task-align.md +19 -0
- package/templates/skills/cf-task-archive.md +20 -0
- package/templates/skills/cf-task-plan.md +31 -0
- package/templates/skills/cf-task-prd.md +14 -0
- package/templates/skills/cf-task-start.md +41 -0
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-frontend.md +12 -12
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-full.md +17 -19
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-lite.md +12 -12
- package/{src/core/code-flow/specs/shared/prd-template.md → templates/workflow/prd.md} +1 -1
- package/templates/workflow/project-guidelines.md +48 -0
- package/templates/workflow/scenarios.md +67 -0
- package/templates/workflow/task.md +56 -0
- package/docs/acceptance-contract.md +0 -217
- package/docs/ci/acceptance-evidence.md +0 -77
- package/docs/migrations/runtime-0.7.0.md +0 -41
- package/docs/migrations/runtime-0.8.0.md +0 -154
- package/docs/migrations/runtime-0.9.0.md +0 -40
- package/docs/migrations/spec-workflow-0.6.0.md +0 -42
- package/docs/spec-injection-hardening.md +0 -54
- package/docs/spec-injection-performance.json +0 -73
- package/docs/spec-optimization.md +0 -12
- package/src/adapters/claude/CLAUDE.md +0 -45
- package/src/adapters/claude/commands/cf-init.md +0 -262
- package/src/adapters/claude/commands/cf-learn.md +0 -291
- package/src/adapters/claude/commands/cf-spec.md +0 -87
- package/src/adapters/claude/commands/cf-stats.md +0 -96
- package/src/adapters/claude/commands/cf-sync.md +0 -39
- package/src/adapters/claude/commands/cf-task/align.md +0 -331
- package/src/adapters/claude/commands/cf-task/archive.md +0 -155
- package/src/adapters/claude/commands/cf-task/block.md +0 -49
- package/src/adapters/claude/commands/cf-task/graph.md +0 -92
- package/src/adapters/claude/commands/cf-task/note.md +0 -248
- package/src/adapters/claude/commands/cf-task/plan.md +0 -477
- package/src/adapters/claude/commands/cf-task/prd.md +0 -173
- package/src/adapters/claude/commands/cf-task/start.md +0 -317
- package/src/adapters/claude/commands/cf-task/status.md +0 -71
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +0 -148
- package/src/adapters/claude/commands/cf-validate.md +0 -100
- package/src/adapters/claude/settings.local.json +0 -50
- package/src/adapters/codex/AGENTS.md +0 -45
- package/src/adapters/codex/config.toml +0 -3
- package/src/adapters/codex/hooks.json +0 -54
- package/src/adapters/codex/skills/cf-init/SKILL.md +0 -273
- package/src/adapters/codex/skills/cf-learn/SKILL.md +0 -296
- package/src/adapters/codex/skills/cf-spec/SKILL.md +0 -88
- package/src/adapters/codex/skills/cf-stats/SKILL.md +0 -97
- package/src/adapters/codex/skills/cf-sync/SKILL.md +0 -45
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +0 -332
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +0 -156
- package/src/adapters/codex/skills/cf-task-block/SKILL.md +0 -50
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +0 -93
- package/src/adapters/codex/skills/cf-task-note/SKILL.md +0 -245
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +0 -478
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +0 -174
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +0 -318
- package/src/adapters/codex/skills/cf-task-status/SKILL.md +0 -72
- package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +0 -145
- package/src/adapters/codex/skills/cf-validate/SKILL.md +0 -102
- package/src/adapters/commands.json +0 -115
- package/src/adapters/costrict/CLAUDE.md +0 -45
- package/src/adapters/costrict/commands/cf-init.md +0 -262
- package/src/adapters/costrict/commands/cf-learn.md +0 -291
- package/src/adapters/costrict/commands/cf-spec.md +0 -87
- package/src/adapters/costrict/commands/cf-stats.md +0 -96
- package/src/adapters/costrict/commands/cf-sync.md +0 -39
- package/src/adapters/costrict/commands/cf-task/align.md +0 -331
- package/src/adapters/costrict/commands/cf-task/archive.md +0 -155
- package/src/adapters/costrict/commands/cf-task/block.md +0 -49
- package/src/adapters/costrict/commands/cf-task/graph.md +0 -92
- package/src/adapters/costrict/commands/cf-task/note.md +0 -248
- package/src/adapters/costrict/commands/cf-task/plan.md +0 -477
- package/src/adapters/costrict/commands/cf-task/prd.md +0 -173
- package/src/adapters/costrict/commands/cf-task/start.md +0 -317
- package/src/adapters/costrict/commands/cf-task/status.md +0 -71
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +0 -148
- package/src/adapters/costrict/commands/cf-validate.md +0 -100
- package/src/adapters/costrict/settings.local.json +0 -50
- package/src/adapters/install-plan.json +0 -100
- package/src/adapters/opencode/AGENTS.md +0 -45
- package/src/adapters/opencode/commands/cf-init.md +0 -281
- package/src/adapters/opencode/commands/cf-learn.md +0 -295
- package/src/adapters/opencode/commands/cf-spec.md +0 -91
- package/src/adapters/opencode/commands/cf-stats.md +0 -100
- package/src/adapters/opencode/commands/cf-sync.md +0 -44
- package/src/adapters/opencode/commands/cf-task/align.md +0 -331
- package/src/adapters/opencode/commands/cf-task/archive.md +0 -160
- package/src/adapters/opencode/commands/cf-task/block.md +0 -54
- package/src/adapters/opencode/commands/cf-task/graph.md +0 -97
- package/src/adapters/opencode/commands/cf-task/note.md +0 -253
- package/src/adapters/opencode/commands/cf-task/plan.md +0 -477
- package/src/adapters/opencode/commands/cf-task/prd.md +0 -178
- package/src/adapters/opencode/commands/cf-task/start.md +0 -317
- package/src/adapters/opencode/commands/cf-task/status.md +0 -76
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +0 -148
- package/src/adapters/opencode/commands/cf-validate.md +0 -105
- package/src/adapters/opencode/opencode.json +0 -3
- package/src/adapters/opencode/plugins/code-flow/index.js +0 -162
- package/src/adapters/opencode/plugins/code-flow/package.json +0 -6
- package/src/adapters/platform-bindings.json +0 -370
- package/src/cli.js +0 -1076
- package/src/core/code-flow/config.yml +0 -161
- package/src/core/code-flow/runtime-commands.json +0 -259
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +0 -10
- package/src/core/code-flow/scripts/cf_acceptance_runner.py +0 -10
- package/src/core/code-flow/scripts/cf_audit.py +0 -10
- package/src/core/code-flow/scripts/cf_codex_hook.py +0 -10
- package/src/core/code-flow/scripts/cf_design_lint.py +0 -10
- package/src/core/code-flow/scripts/cf_evidence_import.py +0 -10
- package/src/core/code-flow/scripts/cf_feedback.py +0 -10
- package/src/core/code-flow/scripts/cf_opencode_event.py +0 -10
- package/src/core/code-flow/scripts/cf_post_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_scenarios.py +0 -10
- package/src/core/code-flow/scripts/cf_spec_context.py +0 -10
- package/src/core/code-flow/scripts/cf_spec_gate.py +0 -10
- package/src/core/code-flow/scripts/cf_spec_session.py +0 -10
- package/src/core/code-flow/scripts/cf_stats.py +0 -10
- package/src/core/code-flow/scripts/cf_stop_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_sync.py +0 -10
- package/src/core/code-flow/scripts/cf_task_index.py +0 -10
- package/src/core/code-flow/scripts/cf_task_parallel.py +0 -10
- package/src/core/code-flow/scripts/cf_task_workflow.py +0 -10
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_validation.py +0 -10
- 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 +0 -347
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_manifest_core.py +0 -660
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_plan.py +0 -202
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_registration.py +0 -60
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_risk.py +0 -73
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_schema.py +0 -282
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_selection.py +0 -266
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_inputs.py +0 -172
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_registration.py +0 -326
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_checks.py +0 -333
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_plan_quality.py +0 -197
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_quality.py +0 -174
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_source.py +0 -300
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_traceability.py +0 -502
- package/src/core/code-flow/scripts/codeflow/cli/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_manifest.py +0 -347
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_runner.py +0 -1490
- package/src/core/code-flow/scripts/codeflow/cli/cf_audit.py +0 -533
- package/src/core/code-flow/scripts/codeflow/cli/cf_design_lint.py +0 -172
- package/src/core/code-flow/scripts/codeflow/cli/cf_evidence_import.py +0 -607
- package/src/core/code-flow/scripts/codeflow/cli/cf_feedback.py +0 -53
- package/src/core/code-flow/scripts/codeflow/cli/cf_scenarios.py +0 -58
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_context.py +0 -1175
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_gate.py +0 -60
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_session.py +0 -46
- package/src/core/code-flow/scripts/codeflow/cli/cf_stats.py +0 -540
- package/src/core/code-flow/scripts/codeflow/cli/cf_sync.py +0 -144
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_index.py +0 -177
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_parallel.py +0 -1142
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_workflow.py +0 -742
- package/src/core/code-flow/scripts/codeflow/cli/cf_validation.py +0 -31
- package/src/core/code-flow/scripts/codeflow/core/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_context_cache.py +0 -67
- package/src/core/code-flow/scripts/codeflow/core/cf_core.py +0 -609
- package/src/core/code-flow/scripts/codeflow/core/cf_exec_base.py +0 -325
- package/src/core/code-flow/scripts/codeflow/core/cf_fs.py +0 -57
- package/src/core/code-flow/scripts/codeflow/core/cf_git.py +0 -92
- package/src/core/code-flow/scripts/codeflow/core/cf_log.py +0 -160
- package/src/core/code-flow/scripts/codeflow/core/cf_markdown.py +0 -77
- package/src/core/code-flow/scripts/codeflow/core/cf_native_paths.py +0 -26
- package/src/core/code-flow/scripts/codeflow/core/cf_patch.py +0 -33
- package/src/core/code-flow/scripts/codeflow/core/cf_runtime_install.py +0 -32
- package/src/core/code-flow/scripts/codeflow/core/cf_session_state.py +0 -37
- package/src/core/code-flow/scripts/codeflow/core/cf_stop_guard.py +0 -53
- package/src/core/code-flow/scripts/codeflow/core/cf_task_state.py +0 -9
- package/src/core/code-flow/scripts/codeflow/core/cf_validators.py +0 -408
- package/src/core/code-flow/scripts/codeflow/core/cf_worktree_env.py +0 -253
- package/src/core/code-flow/scripts/codeflow/evidence/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/evidence/cf_evidence_store.py +0 -527
- package/src/core/code-flow/scripts/codeflow/hooks/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_codex_hook.py +0 -95
- package/src/core/code-flow/scripts/codeflow/hooks/cf_opencode_event.py +0 -98
- package/src/core/code-flow/scripts/codeflow/hooks/cf_post_hook.py +0 -30
- package/src/core/code-flow/scripts/codeflow/hooks/cf_pre_tool_hook.py +0 -41
- package/src/core/code-flow/scripts/codeflow/hooks/cf_stop_hook.py +0 -292
- package/src/core/code-flow/scripts/codeflow/hooks/cf_user_prompt_hook.py +0 -161
- package/src/core/code-flow/scripts/codeflow/spec/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_active_task.py +0 -412
- package/src/core/code-flow/scripts/codeflow/spec/cf_context_refresh.py +0 -301
- package/src/core/code-flow/scripts/codeflow/spec/cf_edit_service.py +0 -149
- package/src/core/code-flow/scripts/codeflow/spec/cf_injection.py +0 -111
- package/src/core/code-flow/scripts/codeflow/spec/cf_rule_readiness.py +0 -114
- package/src/core/code-flow/scripts/codeflow/spec/cf_scan.py +0 -367
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_budget.py +0 -55
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_context_model.py +0 -477
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_impact.py +0 -119
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_metadata.py +0 -387
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_migrate.py +0 -529
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize.py +0 -193
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize_apply.py +0 -187
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_resolver.py +0 -369
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_router.py +0 -304
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_verify.py +0 -687
- package/src/core/code-flow/scripts/codeflow/spec/cf_stage_gate.py +0 -330
- package/src/core/code-flow/scripts/codeflow/spec/cf_task_projection.py +0 -83
- package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_service.py +0 -755
- package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_transaction.py +0 -110
- package/src/core/code-flow/scripts/codeflow/task/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_requirement_verification.py +0 -867
- package/src/core/code-flow/scripts/codeflow/task/cf_task_doc.py +0 -178
- package/src/core/code-flow/scripts/codeflow/task/cf_task_merge.py +0 -635
- package/src/core/code-flow/scripts/codeflow/task/cf_task_plan.py +0 -251
- package/src/core/code-flow/scripts/codeflow/task/cf_task_runtime.py +0 -570
- package/src/core/code-flow/scripts/codeflow/task/cf_validation_service.py +0 -73
- package/src/core/code-flow/scripts/runtime-resources.json +0 -29
- package/src/core/code-flow/specs/backend/_map.md +0 -56
- package/src/core/code-flow/specs/backend/code-quality-performance.md +0 -59
- package/src/core/code-flow/specs/backend/database.md +0 -51
- package/src/core/code-flow/specs/backend/directory-structure.md +0 -54
- package/src/core/code-flow/specs/backend/logging.md +0 -50
- package/src/core/code-flow/specs/backend/platform-rules.md +0 -55
- package/src/core/code-flow/specs/frontend/_map.md +0 -52
- package/src/core/code-flow/specs/frontend/component-specs.md +0 -68
- package/src/core/code-flow/specs/frontend/directory-structure.md +0 -53
- package/src/core/code-flow/specs/frontend/quality-standards.md +0 -58
- package/src/core/code-flow/specs/shared/_map.md +0 -57
- package/src/core/code-flow/specs/shared/scenarios-template.md +0 -55
- package/src/core/code-flow/tasks/.gitkeep +0 -0
- package/src/core/code-flow/validation.yml +0 -59
- package/src/migrate/runtime.js +0 -213
- package/src/migrate/spec-workflow.js +0 -253
- package/src/runtime.js +0 -219
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Path-frozen entry shim: implementation lives in codeflow.cli.cf_validation."""
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
from codeflow.cli import cf_validation as _impl
|
|
6
|
-
|
|
7
|
-
if __name__ == "__main__":
|
|
8
|
-
raise SystemExit(_impl.main())
|
|
9
|
-
else:
|
|
10
|
-
sys.modules[__name__] = _impl
|
|
File without changes
|
|
File without changes
|
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
"""Append execution history without erasing the user's acceptance contract."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import re
|
|
6
|
-
from datetime import datetime, timezone
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Mapping, Optional, Sequence
|
|
9
|
-
from uuid import uuid4
|
|
10
|
-
|
|
11
|
-
from codeflow.evidence.cf_evidence_store import EvidenceStore, discover_project_root, git_commit
|
|
12
|
-
from codeflow.acceptance.cf_acceptance_schema import check_outcome, load_manifest, recorded_check_hash, scenario_check_ids
|
|
13
|
-
|
|
14
|
-
STATES = frozenset(("planned", "pending", "tbd", "verified", "unverified", "stale", "failed", "incomplete", "not_configured", "e2e_deferred", "env_blocked", "manual_pending", "accepted_risk"))
|
|
15
|
-
_COVERAGE_STATES = STATES | {"designed"}
|
|
16
|
-
_EVIDENCE_STATUS = {
|
|
17
|
-
"passed": "passed",
|
|
18
|
-
"failed": "failed",
|
|
19
|
-
"incomplete": "incomplete",
|
|
20
|
-
"not_configured": "not_configured",
|
|
21
|
-
"e2e_deferred": "deferred",
|
|
22
|
-
"env_blocked": "env_blocked",
|
|
23
|
-
"accepted_risk": "accepted_risk",
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def scenario_line(line: str, scenario_id: str) -> bool:
|
|
28
|
-
return bool(re.match(rf"^\s*(?:\|\s*|[-*]\s+){re.escape(scenario_id)}(?=\s*[:|])", line))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def line_status(line: str, scenario_id: str) -> str:
|
|
32
|
-
if not scenario_line(line, scenario_id):
|
|
33
|
-
return ""
|
|
34
|
-
if line.lstrip().startswith("|"):
|
|
35
|
-
cells = [cell.strip().lower() for cell in line.strip().strip("|").split("|")]
|
|
36
|
-
return next((cell for cell in reversed(cells) if cell in _COVERAGE_STATES), "")
|
|
37
|
-
match = re.match(r"^\s*[-*]\s+[^:]+:\s*([a-z0-9_]+)\b", line, re.I)
|
|
38
|
-
return match.group(1).lower() if match and match.group(1).lower() in _COVERAGE_STATES else ""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _status_index(header: str) -> int:
|
|
42
|
-
cells = [cell.strip().lower() for cell in header.strip().strip("|").split("|")]
|
|
43
|
-
for index, cell in enumerate(cells):
|
|
44
|
-
if cell in ("状态", "status"):
|
|
45
|
-
return index + 1
|
|
46
|
-
return -1
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _status_line(line: str, scenario_id: str, status: str, status_index: int = -1) -> str:
|
|
50
|
-
if not scenario_line(line, scenario_id):
|
|
51
|
-
return line
|
|
52
|
-
if line.lstrip().startswith("|"):
|
|
53
|
-
cells = line.split("|")
|
|
54
|
-
if 0 < status_index < len(cells) - 1:
|
|
55
|
-
cells[status_index] = f" {status} "
|
|
56
|
-
return "|".join(cells)
|
|
57
|
-
for index in range(len(cells) - 1, 0, -1):
|
|
58
|
-
if cells[index].strip().lower() in _COVERAGE_STATES:
|
|
59
|
-
cells[index] = f" {status} "
|
|
60
|
-
return "|".join(cells)
|
|
61
|
-
return line.rstrip().rstrip("|") + f"| {status} |"
|
|
62
|
-
prefix = r"(^\s*[-*]\s+[^:]+:\s*)"
|
|
63
|
-
if line_status(line, scenario_id):
|
|
64
|
-
return re.sub(prefix + r"(?:" + "|".join(sorted(_COVERAGE_STATES)) + r")\b", rf"\g<1>{status}", line, count=1, flags=re.I)
|
|
65
|
-
return re.sub(prefix, rf"\g<1>{status} — ", line, count=1)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _patch_section(section: str, scenario_id: str, status: str, record: str) -> str:
|
|
69
|
-
contract = re.search(r"(?ms)^### Acceptance Contract[^\n]*\n(.*?)(?=^### |^## |\Z)", section)
|
|
70
|
-
if contract:
|
|
71
|
-
lines = contract.group(1).splitlines()
|
|
72
|
-
header = next((line for line in lines if line.lstrip().startswith("|")
|
|
73
|
-
and ("状态" in line or "status" in line.lower())), "")
|
|
74
|
-
index = _status_index(header)
|
|
75
|
-
body = "\n".join(_status_line(line, scenario_id, status, index) for line in lines) + "\n"
|
|
76
|
-
section = section[:contract.start(1)] + body + section[contract.end(1):]
|
|
77
|
-
evidence = re.search(r"(?ms)^### Acceptance Evidence[^\n]*\n(.*?)(?=^### |^## |\Z)", section)
|
|
78
|
-
if evidence:
|
|
79
|
-
body = evidence.group(1)
|
|
80
|
-
if record not in body.splitlines():
|
|
81
|
-
body = body.rstrip() + "\n" + record + "\n\n"
|
|
82
|
-
section = section[:evidence.start(1)] + body + section[evidence.end(1):]
|
|
83
|
-
else:
|
|
84
|
-
section = section.rstrip() + "\n\n### Acceptance Evidence\n" + record + "\n"
|
|
85
|
-
return section
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def sync_task_evidence(task: Path, scenario_id: str, confirmed_by: str, evidence: str, owner: str = "", status: str = "verified") -> str:
|
|
89
|
-
"""Sync one scenario's status into the task file; returns what was written.
|
|
90
|
-
|
|
91
|
-
- owner + TASK section → contract/evidence + coverage
|
|
92
|
-
- ownerless legacy scenario → coverage only (a scenario with neither owner
|
|
93
|
-
nor coverage has no truthful place to attribute evidence; first-TASK
|
|
94
|
-
attribution corrupted unrelated contracts, so it is skipped)
|
|
95
|
-
"""
|
|
96
|
-
text = task.read_text(encoding="utf-8")
|
|
97
|
-
record = f"- {scenario_id}: {status} — {evidence} (confirmed_by: {confirmed_by})"
|
|
98
|
-
if owner:
|
|
99
|
-
task_pattern = re.escape(owner)
|
|
100
|
-
match = re.search(rf"(?ms)^##\s+{task_pattern}:.*?(?=^## |\Z)", text)
|
|
101
|
-
if match is None:
|
|
102
|
-
raise ValueError(f"acceptance owner missing: {owner}")
|
|
103
|
-
section = _patch_section(match.group(0), scenario_id, status, record)
|
|
104
|
-
text = text[:match.start()] + section + text[match.end():]
|
|
105
|
-
coverage = re.search(r"(?ms)^## Acceptance Coverage[^\n]*\n(.*?)(?=^## |\Z)", text)
|
|
106
|
-
if coverage:
|
|
107
|
-
lines = coverage.group(1).splitlines()
|
|
108
|
-
header = next((line for line in lines if line.lstrip().startswith("|")
|
|
109
|
-
and ("状态" in line or "status" in line.lower())), "")
|
|
110
|
-
index = _status_index(header)
|
|
111
|
-
body = "\n".join(_status_line(line, scenario_id, status, index) for line in lines) + "\n"
|
|
112
|
-
text = text[:coverage.start(1)] + body + text[coverage.end(1):]
|
|
113
|
-
elif not owner:
|
|
114
|
-
return "skipped_no_owner"
|
|
115
|
-
from codeflow.core.cf_fs import atomic_write_text
|
|
116
|
-
atomic_write_text(task, text)
|
|
117
|
-
return "synced"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _run_record(row: Mapping[str, object], result: Mapping[str, object], run_id: str,
|
|
121
|
-
executed_at: str, fingerprint: str, commit: str,
|
|
122
|
-
check_config: Optional[Mapping[str, object]] = None) -> dict[str, object]:
|
|
123
|
-
source = check_config if isinstance(check_config, Mapping) else row
|
|
124
|
-
command = source.get("command")
|
|
125
|
-
if not isinstance(command, list):
|
|
126
|
-
command = []
|
|
127
|
-
timeout = source.get("timeout", 60)
|
|
128
|
-
cwd = source.get("cwd", ".")
|
|
129
|
-
state = str(result.get("status"))
|
|
130
|
-
exit_code = result.get("exit_code")
|
|
131
|
-
return {
|
|
132
|
-
"run_id": run_id,
|
|
133
|
-
"check_id": str(row.get("check") or row.get("id")),
|
|
134
|
-
"scenario_id": str(row.get("id")),
|
|
135
|
-
"command": command,
|
|
136
|
-
"cwd": cwd if isinstance(cwd, str) else ".",
|
|
137
|
-
"timeout": timeout if isinstance(timeout, (int, float)) else 60,
|
|
138
|
-
"input_hash": fingerprint if isinstance(fingerprint, str) else "",
|
|
139
|
-
"exit_code": exit_code if isinstance(exit_code, int) else (0 if state == "passed" else 1),
|
|
140
|
-
"status": _EVIDENCE_STATUS[state],
|
|
141
|
-
"executed_at": executed_at,
|
|
142
|
-
"source": str(result.get("source") or "local"),
|
|
143
|
-
"environment_hash": str(result.get("environment_hash") or ""),
|
|
144
|
-
"commit": commit,
|
|
145
|
-
"run_url": str(result.get("run_url") or ""),
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _outcome_mapping(result: Mapping[str, object], field: str) -> Mapping[str, object]:
|
|
150
|
-
value = result.get(field)
|
|
151
|
-
return value if isinstance(value, Mapping) else {}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _store_evidence_runs(path: Path, rows: Mapping[str, Mapping[str, object]],
|
|
155
|
-
results: Sequence[Mapping[str, object]], run_id: str,
|
|
156
|
-
executed_at: str, fingerprint: str) -> dict[str, int]:
|
|
157
|
-
from codeflow.evidence.cf_evidence_store import requirement_key
|
|
158
|
-
project = discover_project_root(path.parent)
|
|
159
|
-
store = EvidenceStore(project, requirement_key(path.parent))
|
|
160
|
-
commit = git_commit(project)
|
|
161
|
-
created = duplicates = 0
|
|
162
|
-
for result in results:
|
|
163
|
-
state = str(result.get("status"))
|
|
164
|
-
if state not in _EVIDENCE_STATUS:
|
|
165
|
-
continue
|
|
166
|
-
if result.get("cache_reused") is True and result.get("checkpoint_reused") is not True:
|
|
167
|
-
continue
|
|
168
|
-
row = rows.get(str(result.get("id")))
|
|
169
|
-
if row is None:
|
|
170
|
-
continue
|
|
171
|
-
result_fingerprint = str(result.get("input_hash") or fingerprint)
|
|
172
|
-
raw_ids = result.get("check_ids")
|
|
173
|
-
check_ids = [str(value) for value in raw_ids] if isinstance(raw_ids, list) else []
|
|
174
|
-
check_id = str(row.get("check") or row.get("id"))
|
|
175
|
-
if not check_ids:
|
|
176
|
-
check_ids = [check_id]
|
|
177
|
-
check_hashes = _outcome_mapping(result, "check_hashes")
|
|
178
|
-
check_reused = _outcome_mapping(result, "check_reused")
|
|
179
|
-
check_checkpoint = _outcome_mapping(result, "checkpoint_reused_checks")
|
|
180
|
-
configs = _outcome_mapping(result, "check_configs")
|
|
181
|
-
for item_id in check_ids:
|
|
182
|
-
checkpointed = check_checkpoint.get(item_id, result.get("checkpoint_reused"))
|
|
183
|
-
item_hash = str(check_hashes.get(item_id) or result_fingerprint)
|
|
184
|
-
if check_reused.get(item_id) is True:
|
|
185
|
-
if checkpointed is not True:
|
|
186
|
-
continue
|
|
187
|
-
if any(
|
|
188
|
-
run.get("input_hash") == item_hash
|
|
189
|
-
and run.get("status") in ("passed", "verified")
|
|
190
|
-
for run in store.list_runs(item_id)
|
|
191
|
-
):
|
|
192
|
-
continue
|
|
193
|
-
config = configs.get(item_id)
|
|
194
|
-
record = _run_record(
|
|
195
|
-
row, check_outcome(result, item_id), run_id, executed_at, item_hash, commit,
|
|
196
|
-
config if isinstance(config, Mapping) else None,
|
|
197
|
-
)
|
|
198
|
-
record["check_id"] = item_id
|
|
199
|
-
outcome = store.write_run(item_id, record)
|
|
200
|
-
created += outcome["status"] == "created"
|
|
201
|
-
duplicates += outcome["status"] == "duplicate"
|
|
202
|
-
return {"created": created, "duplicates": duplicates}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def _restore_checkpoint(row: dict[str, object], result: Mapping[str, object], run_key: str,
|
|
206
|
-
executed_at: str, fingerprint: str) -> Optional[tuple[dict[str, object], str]]:
|
|
207
|
-
if result["status"] != "passed" or row.get("status") == "verified":
|
|
208
|
-
return None
|
|
209
|
-
row["revision"] = int(row.get("revision", 0)) + 1
|
|
210
|
-
record: dict[str, object] = {
|
|
211
|
-
"id": result["id"],
|
|
212
|
-
"kind": str(result.get("kind", "functional")),
|
|
213
|
-
"status": "passed",
|
|
214
|
-
"exit_code": 0,
|
|
215
|
-
"cache_reused": True,
|
|
216
|
-
"checkpoint_reused": True,
|
|
217
|
-
"revision": row["revision"],
|
|
218
|
-
"run_id": run_key,
|
|
219
|
-
"executed_at": executed_at,
|
|
220
|
-
"command": row.get("command"),
|
|
221
|
-
"cwd": row.get("cwd", "."),
|
|
222
|
-
"timeout": row.get("timeout", 60),
|
|
223
|
-
}
|
|
224
|
-
if fingerprint:
|
|
225
|
-
record["fingerprint"] = fingerprint
|
|
226
|
-
for key in ("check_ids", "check_hashes", "check_results", "check_reused", "checkpoint_reused_checks", "check_configs", "input_hash", "risk"):
|
|
227
|
-
if key in result:
|
|
228
|
-
record[key] = result[key]
|
|
229
|
-
row["status"] = "verified"
|
|
230
|
-
row.pop("stale_checks", None)
|
|
231
|
-
row.pop("stale_reason", None)
|
|
232
|
-
return record, "verified"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def _record_result(row: dict[str, object], result: Mapping[str, object], run_key: str,
|
|
236
|
-
executed_at: str, fingerprint: str) -> Optional[tuple[dict[str, object], str]]:
|
|
237
|
-
"""Apply one result to a manifest row; None means keep the row unchanged."""
|
|
238
|
-
state = str(result["status"])
|
|
239
|
-
if result.get("cache_reused") is True:
|
|
240
|
-
return _restore_checkpoint(row, result, run_key, executed_at, fingerprint)
|
|
241
|
-
# A deferral placeholder (e2e skipped in a non-E2E run) must never
|
|
242
|
-
# downgrade a verified terminal scenario: verify-e2e runs first, then
|
|
243
|
-
# later Done/finish passes would otherwise silently un-verify it.
|
|
244
|
-
if state == "e2e_deferred" and row.get("status") == "verified":
|
|
245
|
-
return None
|
|
246
|
-
# An unavailable environment is not new product evidence: keep a prior
|
|
247
|
-
# verified conclusion, but never promote an unverified row.
|
|
248
|
-
if state == "env_blocked" and row.get("status") == "verified":
|
|
249
|
-
return None
|
|
250
|
-
row["revision"] = int(row.get("revision", 0)) + 1
|
|
251
|
-
status = "verified" if state == "passed" else state
|
|
252
|
-
row["status"] = status
|
|
253
|
-
row.pop("stale_checks", None)
|
|
254
|
-
row.pop("stale_reason", None)
|
|
255
|
-
record = {**result, "revision": row["revision"], "run_id": run_key, "executed_at": executed_at}
|
|
256
|
-
# Identity for cross-run reuse checks: command/cwd/timeout + content
|
|
257
|
-
# fingerprint of the worktree the command actually ran against.
|
|
258
|
-
record["command"] = row.get("command")
|
|
259
|
-
record["cwd"] = row.get("cwd", ".")
|
|
260
|
-
record["timeout"] = row.get("timeout", 60)
|
|
261
|
-
if fingerprint and status == "verified":
|
|
262
|
-
record["fingerprint"] = fingerprint
|
|
263
|
-
return record, status
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def stale_scenario_checks(row: Mapping[str, object], hashes: Mapping[str, Mapping[str, object]],
|
|
267
|
-
fingerprint: str) -> list[str]:
|
|
268
|
-
"""Compare every declared check; a missing or unavailable hash is stale."""
|
|
269
|
-
if row.get("status") not in ("verified", "accepted_risk", "stale"):
|
|
270
|
-
return []
|
|
271
|
-
if row.get("kind") == "manual" or row.get("exec") in ("manual", "aspirational"):
|
|
272
|
-
return []
|
|
273
|
-
check_ids = scenario_check_ids(row)
|
|
274
|
-
evidence = row.get("evidence")
|
|
275
|
-
evidence = evidence if isinstance(evidence, Mapping) else {}
|
|
276
|
-
if not check_ids:
|
|
277
|
-
return [] if fingerprint and evidence.get("fingerprint") == fingerprint else [str(row["id"])]
|
|
278
|
-
stale = []
|
|
279
|
-
for check_id in check_ids:
|
|
280
|
-
expected = hashes.get(check_id, {}).get("input_hash")
|
|
281
|
-
actual = recorded_check_hash(row, check_id)
|
|
282
|
-
if not expected or expected != actual:
|
|
283
|
-
stale.append(check_id)
|
|
284
|
-
return stale
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def invalidate_manifest_evidence(root: Path, manifest: Path,
|
|
288
|
-
fingerprint: str) -> list[dict[str, object]]:
|
|
289
|
-
"""Mark outdated conclusions without changing historical execution evidence."""
|
|
290
|
-
from codeflow.acceptance.cf_check_inputs import check_input_hashes
|
|
291
|
-
from codeflow.core.cf_fs import atomic_write_text
|
|
292
|
-
data = load_manifest(manifest)
|
|
293
|
-
checks = {str(check["id"]): check for check in data.get("checks", [])}
|
|
294
|
-
hashes = check_input_hashes(str(root), checks, fingerprint)
|
|
295
|
-
changed: list[dict[str, object]] = []
|
|
296
|
-
for row in data["scenarios"]:
|
|
297
|
-
stale = stale_scenario_checks(row, hashes, fingerprint)
|
|
298
|
-
if not stale:
|
|
299
|
-
continue
|
|
300
|
-
row.update(status="stale", stale_checks=stale)
|
|
301
|
-
changed.append({"scenario_id": row["id"], "checks": stale})
|
|
302
|
-
if not changed:
|
|
303
|
-
return []
|
|
304
|
-
atomic_write_text(manifest, json.dumps(data, ensure_ascii=False, indent=2) + "\n")
|
|
305
|
-
task_name = data.get("task_file")
|
|
306
|
-
if isinstance(task_name, str) and task_name:
|
|
307
|
-
task = (manifest.parent / task_name).resolve()
|
|
308
|
-
rows = {row["id"]: row for row in data["scenarios"]}
|
|
309
|
-
for item in changed:
|
|
310
|
-
row = rows[item["scenario_id"]]
|
|
311
|
-
sync_task_evidence(task, str(row["id"]), "runtime",
|
|
312
|
-
"input changed: " + ", ".join(item["checks"]),
|
|
313
|
-
str(row.get("owner", "")), "stale")
|
|
314
|
-
return changed
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def persist_results(path: Path, data: dict[str, object], results: Sequence[Mapping[str, object]],
|
|
318
|
-
fingerprint: str = "", run_id: str = "") -> None:
|
|
319
|
-
rows = {row["id"]: row for row in data["scenarios"]}
|
|
320
|
-
run_key, executed_at = run_id or uuid4().hex, datetime.now(timezone.utc).isoformat()
|
|
321
|
-
updates = []
|
|
322
|
-
for result in results:
|
|
323
|
-
state = str(result["status"])
|
|
324
|
-
if state in ("not_included", "manual_pending"):
|
|
325
|
-
continue
|
|
326
|
-
row = rows[result["id"]]
|
|
327
|
-
result_fingerprint = str(result.get("input_hash") or fingerprint)
|
|
328
|
-
applied = _record_result(row, result, run_key, executed_at, result_fingerprint)
|
|
329
|
-
if applied is None:
|
|
330
|
-
continue
|
|
331
|
-
record, status = applied
|
|
332
|
-
row["evidence"] = record
|
|
333
|
-
if result.get("cache_reused") is not True:
|
|
334
|
-
runs = row.setdefault("runs", [])
|
|
335
|
-
runs.append(record)
|
|
336
|
-
if len(runs) > 50:
|
|
337
|
-
del runs[:-50]
|
|
338
|
-
updates.append((row, record, status))
|
|
339
|
-
from codeflow.core.cf_fs import atomic_write_text
|
|
340
|
-
atomic_write_text(path, json.dumps(data, ensure_ascii=False, indent=2) + "\n")
|
|
341
|
-
_store_evidence_runs(path, rows, results, run_key, executed_at, fingerprint)
|
|
342
|
-
task_name = data.get("task_file")
|
|
343
|
-
if isinstance(task_name, str) and task_name:
|
|
344
|
-
task = Path(task_name) if Path(task_name).is_absolute() else path.parent / task_name
|
|
345
|
-
for row, record, status in updates:
|
|
346
|
-
detail = f"automated command {record['status']}; run_id={run_key}"
|
|
347
|
-
sync_task_evidence(task, str(row["id"]), "runner", detail, str(row.get("owner", "")), status)
|