@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,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""cf-sync: one-command canonical → deploy sync for dual-copy artifacts.
|
|
3
|
-
|
|
4
|
-
Pairs (canonical source → deployed live copy), each with an explicit include
|
|
5
|
-
prefix list. Project-owned trees (specs/, tasks/) and merge-managed files
|
|
6
|
-
(CLAUDE.md, AGENTS.md, settings.local.json, hooks.json, config.toml,
|
|
7
|
-
opencode.json, config.yml, validation.yml) are upgraded by `code-flow init`
|
|
8
|
-
with additive merges, not by byte-sync:
|
|
9
|
-
|
|
10
|
-
src/core/code-flow → .code-flow (scripts/, runtime-commands.json, .version, .gitignore)
|
|
11
|
-
src/adapters/claude → .claude (commands/)
|
|
12
|
-
src/adapters/codex/skills → .agents/skills (everything)
|
|
13
|
-
src/adapters/costrict → .costrict (commands/)
|
|
14
|
-
src/adapters/opencode → .opencode (commands/, plugins/)
|
|
15
|
-
|
|
16
|
-
`check` (default) reports drift; `sync` copies canonical → deploy. Deploy-only
|
|
17
|
-
files are never deleted (use --prune to remove files whose source side no
|
|
18
|
-
longer contains them within the include prefixes).
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
import argparse
|
|
22
|
-
import json
|
|
23
|
-
import os
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
import sys
|
|
26
|
-
from typing import IO, Optional, Sequence
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
PAIRS = (
|
|
30
|
-
("src/core/code-flow", ".code-flow", ("scripts/", "runtime-commands.json", ".version", ".gitignore")),
|
|
31
|
-
("src/adapters/claude", ".claude", ("commands/",)),
|
|
32
|
-
("src/adapters/codex/skills", ".agents/skills", ("*",)),
|
|
33
|
-
("src/adapters/costrict", ".costrict", ("commands/",)),
|
|
34
|
-
("src/adapters/opencode", ".opencode", ("commands/", "plugins/")),
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
_IGNORED_DIRS = {"__pycache__", ".git", "node_modules", "dist", "build"}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _included(relative: str, prefixes: Sequence[str]) -> bool:
|
|
41
|
-
return any(prefix == "*" or relative.startswith(prefix) for prefix in prefixes)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _walk(source: Path) -> list[str]:
|
|
45
|
-
files = []
|
|
46
|
-
for current, dirs, names in os.walk(source):
|
|
47
|
-
dirs[:] = sorted(item for item in dirs if item not in _IGNORED_DIRS)
|
|
48
|
-
base = Path(current)
|
|
49
|
-
for name in names:
|
|
50
|
-
files.append(str((base / name).relative_to(source)))
|
|
51
|
-
return sorted(files)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _relative(relative: str) -> str:
|
|
55
|
-
return relative.replace("\\", "/")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _canonical_bytes(root: Path, source: Path) -> bytes:
|
|
59
|
-
if source == root / "src/adapters/opencode/plugins/code-flow/package.json":
|
|
60
|
-
data = json.loads(source.read_text(encoding="utf-8"))
|
|
61
|
-
data["version"] = json.loads((root / "package.json").read_text(encoding="utf-8"))["version"]
|
|
62
|
-
return (json.dumps(data, indent=2, ensure_ascii=False) + "\n").encode("utf-8")
|
|
63
|
-
return source.read_bytes()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _pair_diffs(root: Path, source_rel: str, deploy_rel: str, prefixes: Sequence[str]) -> tuple[list[str], list[str], list[str]]:
|
|
67
|
-
"""Return (differ, missing_in_deploy, deploy_only_within_prefixes)."""
|
|
68
|
-
source = root / source_rel
|
|
69
|
-
deploy = root / deploy_rel
|
|
70
|
-
differ: list[str] = []
|
|
71
|
-
missing: list[str] = []
|
|
72
|
-
for relative in _walk(source):
|
|
73
|
-
if not _included(_relative(relative), prefixes):
|
|
74
|
-
continue
|
|
75
|
-
source_file = source / relative
|
|
76
|
-
deploy_file = deploy / relative
|
|
77
|
-
if not deploy_file.exists():
|
|
78
|
-
missing.append(relative)
|
|
79
|
-
elif _canonical_bytes(root, source_file) != deploy_file.read_bytes():
|
|
80
|
-
differ.append(relative)
|
|
81
|
-
deploy_only: list[str] = []
|
|
82
|
-
if deploy.is_dir():
|
|
83
|
-
deploy_relative = set(_walk(deploy))
|
|
84
|
-
source_relative = {_relative(item) for item in _walk(source) if _included(_relative(item), prefixes)}
|
|
85
|
-
deploy_only = sorted(item for item in deploy_relative if item not in source_relative and _included(item, prefixes))
|
|
86
|
-
return differ, missing, deploy_only
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _report(stdout: IO[str], root: Path, verbose: bool) -> int:
|
|
90
|
-
drift = 0
|
|
91
|
-
for source_rel, deploy_rel, prefixes in PAIRS:
|
|
92
|
-
differ, missing, deploy_only = _pair_diffs(root, source_rel, deploy_rel, prefixes)
|
|
93
|
-
status = "OK " if not (differ or missing) else "DRIFT"
|
|
94
|
-
stdout.write(f"{status} {source_rel} -> {deploy_rel}\n")
|
|
95
|
-
for relative in differ:
|
|
96
|
-
stdout.write(f" diff {deploy_rel}/{relative}\n")
|
|
97
|
-
drift += 1
|
|
98
|
-
for relative in missing:
|
|
99
|
-
stdout.write(f" miss {deploy_rel}/{relative}\n")
|
|
100
|
-
drift += 1
|
|
101
|
-
if verbose and deploy_only:
|
|
102
|
-
stdout.write(f" only-in-deploy ({len(deploy_only)}):\n")
|
|
103
|
-
for relative in deploy_only[:10]:
|
|
104
|
-
stdout.write(f" {deploy_rel}/{relative}\n")
|
|
105
|
-
return drift
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _sync(root: Path, stdout: IO[str]) -> int:
|
|
109
|
-
copied = 0
|
|
110
|
-
for source_rel, deploy_rel, prefixes in PAIRS:
|
|
111
|
-
source = root / source_rel
|
|
112
|
-
deploy = root / deploy_rel
|
|
113
|
-
for relative in _walk(source):
|
|
114
|
-
if not _included(_relative(relative), prefixes):
|
|
115
|
-
continue
|
|
116
|
-
source_file = source / relative
|
|
117
|
-
deploy_file = deploy / relative
|
|
118
|
-
content = _canonical_bytes(root, source_file)
|
|
119
|
-
if deploy_file.exists() and content == deploy_file.read_bytes():
|
|
120
|
-
continue
|
|
121
|
-
deploy_file.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
-
deploy_file.write_bytes(content)
|
|
123
|
-
stdout.write(f"sync {deploy_rel}/{relative}\n")
|
|
124
|
-
copied += 1
|
|
125
|
-
return copied
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def main(argv: Optional[Sequence[str]] = None, stdout: IO[str] = sys.stdout) -> int:
|
|
129
|
-
parser = argparse.ArgumentParser(prog=os.environ.get("CF_RUNTIME_COMMAND", "cf_sync.py"))
|
|
130
|
-
parser.add_argument("action", choices=("check", "sync"), nargs="?", default="check")
|
|
131
|
-
parser.add_argument("--root", default=".")
|
|
132
|
-
parser.add_argument("--verbose", action="store_true")
|
|
133
|
-
args = parser.parse_args(argv)
|
|
134
|
-
root = Path(args.root).resolve()
|
|
135
|
-
if args.action == "check":
|
|
136
|
-
drift = _report(stdout, root, args.verbose)
|
|
137
|
-
if drift:
|
|
138
|
-
stdout.write(f"{drift} file(s) drifted; run `code-flow sync apply` to deploy.\n")
|
|
139
|
-
return 1
|
|
140
|
-
stdout.write("all pairs in sync.\n")
|
|
141
|
-
return 0
|
|
142
|
-
copied = _sync(root, stdout)
|
|
143
|
-
stdout.write(f"{copied} file(s) deployed.\n")
|
|
144
|
-
return 0
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Programmatic task-file index: parse TASK statuses/depends, compute DAG batches.
|
|
3
|
-
|
|
4
|
-
Single worktrees allow one active TASK at a time; batches list *independently
|
|
5
|
-
developable* tasks (no dependency path between them). `cf-task:start` may
|
|
6
|
-
dispatch batch tasks in their own worktrees for parallel subagents — the
|
|
7
|
-
single-active constraint stays per worktree.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import argparse
|
|
13
|
-
import os
|
|
14
|
-
import json
|
|
15
|
-
import sys
|
|
16
|
-
from dataclasses import dataclass
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from typing import IO, Mapping, Optional, Sequence
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
from codeflow.task.cf_task_doc import TaskDocument, TaskDocumentError, parse_task_document
|
|
22
|
-
from codeflow.task.cf_task_plan import (
|
|
23
|
-
DEFAULT_MAX_TASKS,
|
|
24
|
-
TASK_SIZES,
|
|
25
|
-
evaluate_plan,
|
|
26
|
-
)
|
|
27
|
-
from codeflow.core.cf_task_state import FINISHED_STATUSES as _DONE_STATUSES
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass(frozen=True)
|
|
31
|
-
class TaskNode:
|
|
32
|
-
task_id: str
|
|
33
|
-
status: str
|
|
34
|
-
depends: tuple[str, ...]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def project_task_nodes(document: TaskDocument) -> tuple[TaskNode, ...]:
|
|
38
|
-
"""Project a parsed TaskDocument into the index node shape."""
|
|
39
|
-
return tuple(TaskNode(task.id, task.status, task.depends) for task in document.tasks)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def parse_task_file(task_file: str) -> tuple[TaskNode, ...]:
|
|
43
|
-
"""Parse every `## TASK-xxx` section into status/depends nodes.
|
|
44
|
-
|
|
45
|
-
Unknown statuses are preserved verbatim (e.g. `verified` from the E2E
|
|
46
|
-
flow) — the parser never rejects them, completion checks treat done and
|
|
47
|
-
verified as finished.
|
|
48
|
-
"""
|
|
49
|
-
try:
|
|
50
|
-
text = Path(task_file).read_text(encoding="utf-8")
|
|
51
|
-
except (OSError, UnicodeError) as exc:
|
|
52
|
-
raise ValueError(f"task_read_error: {exc}") from exc
|
|
53
|
-
try:
|
|
54
|
-
document = parse_task_document(text, source=str(task_file))
|
|
55
|
-
except TaskDocumentError as exc:
|
|
56
|
-
raise ValueError(str(exc)) from exc
|
|
57
|
-
return project_task_nodes(document)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def task_batches(nodes: Sequence[TaskNode]) -> list[list[str]]:
|
|
61
|
-
"""Topological batches over unfinished TASKs; unknown deps and cycles raise."""
|
|
62
|
-
ids = {node.task_id for node in nodes}
|
|
63
|
-
for node in nodes:
|
|
64
|
-
unknown = [dep for dep in node.depends if dep not in ids]
|
|
65
|
-
if unknown:
|
|
66
|
-
raise ValueError(f"unknown depends: {node.task_id} -> {', '.join(unknown)}")
|
|
67
|
-
pending = {node.task_id for node in nodes if node.status not in _DONE_STATUSES}
|
|
68
|
-
finished = {node.task_id for node in nodes if node.status in _DONE_STATUSES}
|
|
69
|
-
deps = {node.task_id: set(node.depends) for node in nodes}
|
|
70
|
-
batches: list[list[str]] = []
|
|
71
|
-
while pending:
|
|
72
|
-
ready = sorted(task for task in pending if deps[task] <= finished)
|
|
73
|
-
if not ready:
|
|
74
|
-
raise ValueError(f"dependency cycle among: {', '.join(sorted(pending))}")
|
|
75
|
-
batches.append(ready)
|
|
76
|
-
finished.update(ready)
|
|
77
|
-
pending.difference_update(ready)
|
|
78
|
-
return batches
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def dependency_components(nodes: Sequence[TaskNode]) -> list[list[str]]:
|
|
82
|
-
"""Connected dependency components; different components are independent."""
|
|
83
|
-
parent = {node.task_id: node.task_id for node in nodes}
|
|
84
|
-
|
|
85
|
-
def find(task: str) -> str:
|
|
86
|
-
while parent[task] != task:
|
|
87
|
-
parent[task] = parent[parent[task]]
|
|
88
|
-
task = parent[task]
|
|
89
|
-
return task
|
|
90
|
-
|
|
91
|
-
for node in nodes:
|
|
92
|
-
for dep in node.depends:
|
|
93
|
-
left, right = find(node.task_id), find(dep)
|
|
94
|
-
if left != right:
|
|
95
|
-
parent[left] = right
|
|
96
|
-
groups: dict[str, list[str]] = {}
|
|
97
|
-
for node in nodes:
|
|
98
|
-
groups.setdefault(find(node.task_id), []).append(node.task_id)
|
|
99
|
-
return [sorted(members) for members in groups.values()]
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def independent_groups(nodes: Sequence[TaskNode]) -> list[list[str]]:
|
|
103
|
-
"""Compatibility alias: groups are independent of each other, not within."""
|
|
104
|
-
return dependency_components(nodes)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def index_data(
|
|
108
|
-
task_file: str,
|
|
109
|
-
*,
|
|
110
|
-
budget: bool = False,
|
|
111
|
-
task_size: str = "capability",
|
|
112
|
-
max_tasks: int = DEFAULT_MAX_TASKS,
|
|
113
|
-
min_average_hours: Optional[float] = None,
|
|
114
|
-
) -> dict[str, object]:
|
|
115
|
-
nodes = parse_task_file(task_file)
|
|
116
|
-
data: dict[str, object] = {
|
|
117
|
-
"tasks": [
|
|
118
|
-
{"id": node.task_id, "status": node.status, "depends": list(node.depends)}
|
|
119
|
-
for node in nodes
|
|
120
|
-
],
|
|
121
|
-
"batches": task_batches(nodes),
|
|
122
|
-
"independent_groups": independent_groups(nodes),
|
|
123
|
-
"dependency_components": dependency_components(nodes),
|
|
124
|
-
"note": "batches 组内无依赖;dependency_components 仅组间独立(independent_groups 为兼容别名);单 worktree 一次仅激活一个 TASK,批次内独立任务可由 cf-task:start 在各 worktree 并行派发",
|
|
125
|
-
}
|
|
126
|
-
if budget:
|
|
127
|
-
data["budget"] = evaluate_plan(
|
|
128
|
-
task_file,
|
|
129
|
-
task_size=task_size,
|
|
130
|
-
max_tasks=max_tasks,
|
|
131
|
-
min_average_hours=min_average_hours,
|
|
132
|
-
)
|
|
133
|
-
return data
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def main(argv: Optional[Sequence[str]] = None, stdout: IO[str] = sys.stdout) -> int:
|
|
137
|
-
parser = argparse.ArgumentParser(prog=os.environ.get("CF_RUNTIME_COMMAND", "cf_task_index.py"))
|
|
138
|
-
parser.add_argument("--task-file", required=True)
|
|
139
|
-
parser.add_argument("--dag", action="store_true", help="输出拓扑批次与独立分组")
|
|
140
|
-
parser.add_argument("--budget", action="store_true", help="附加能力级计划预算评估")
|
|
141
|
-
parser.add_argument("--task-size", choices=TASK_SIZES, default="capability")
|
|
142
|
-
parser.add_argument("--max-tasks", type=int, default=DEFAULT_MAX_TASKS)
|
|
143
|
-
parser.add_argument("--min-average-hours", type=float, default=None)
|
|
144
|
-
parser.add_argument("--json", action="store_true")
|
|
145
|
-
args = parser.parse_args(argv)
|
|
146
|
-
try:
|
|
147
|
-
data = index_data(
|
|
148
|
-
args.task_file,
|
|
149
|
-
budget=args.budget,
|
|
150
|
-
task_size=args.task_size,
|
|
151
|
-
max_tasks=args.max_tasks,
|
|
152
|
-
min_average_hours=args.min_average_hours,
|
|
153
|
-
)
|
|
154
|
-
except (OSError, ValueError) as exc:
|
|
155
|
-
stdout.write(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
|
|
156
|
-
return 2
|
|
157
|
-
if args.json or args.dag:
|
|
158
|
-
stdout.write(json.dumps(data, ensure_ascii=False))
|
|
159
|
-
else:
|
|
160
|
-
lines = ["TASK 索引:"]
|
|
161
|
-
for item in data["tasks"]:
|
|
162
|
-
lines.append(f" - {item['id']}: {item['status']} (依赖: {', '.join(item['depends']) or '无'})")
|
|
163
|
-
for index, batch in enumerate(data["batches"], 1):
|
|
164
|
-
lines.append(f"批次 {index}(可独立开发): {', '.join(batch)}")
|
|
165
|
-
if args.budget:
|
|
166
|
-
budget_data = data["budget"]
|
|
167
|
-
lines.append(
|
|
168
|
-
f"计划预算: {budget_data['task_count']} 任务 / 总计 {budget_data['total_hours']}h"
|
|
169
|
-
f" / 平均 {budget_data['average_hours']}h({budget_data['task_size']})"
|
|
170
|
-
)
|
|
171
|
-
for warning in budget_data["warnings"]:
|
|
172
|
-
lines.append(f" ! {warning['message']}")
|
|
173
|
-
lines.append(f"decision: {budget_data['decision']}")
|
|
174
|
-
stdout.write("\n".join(lines) + "\n")
|
|
175
|
-
if args.budget and data["budget"]["requires_merge"]:
|
|
176
|
-
return 1
|
|
177
|
-
return 0
|