@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.
Files changed (268) hide show
  1. package/README.md +73 -152
  2. package/bin/code-flow.js +12 -0
  3. package/docs/README.md +19 -0
  4. package/docs/USAGE.md +156 -1417
  5. package/docs/v1-design.md +1858 -0
  6. package/docs/v1-implementation.md +186 -0
  7. package/lib/adapters.js +126 -0
  8. package/lib/cli.js +258 -0
  9. package/lib/config.js +85 -0
  10. package/lib/doctor.js +283 -0
  11. package/lib/frontmatter.js +42 -0
  12. package/lib/fs.js +141 -0
  13. package/lib/glob.js +23 -0
  14. package/lib/hook.js +99 -0
  15. package/lib/init.js +132 -0
  16. package/lib/learn.js +170 -0
  17. package/lib/locks.js +52 -0
  18. package/lib/markdown.js +13 -0
  19. package/lib/process.js +116 -0
  20. package/lib/render.js +115 -0
  21. package/lib/repo.js +48 -0
  22. package/lib/runs.js +155 -0
  23. package/lib/session.js +40 -0
  24. package/lib/spec.js +80 -0
  25. package/lib/task.js +258 -0
  26. package/lib/taskdoc.js +308 -0
  27. package/lib/toml.js +140 -0
  28. package/lib/verify.js +168 -0
  29. package/lib/worktree.js +930 -0
  30. package/package.json +18 -21
  31. package/templates/adapters/opencode.mjs +65 -0
  32. package/templates/config.json +39 -0
  33. package/templates/instructions.md +13 -0
  34. package/templates/launcher/bridge.cjs +295 -0
  35. package/templates/launcher/hook.cmd +15 -0
  36. package/templates/launcher/hook.ps1 +10 -0
  37. package/templates/launcher/hook.sh +20 -0
  38. package/templates/skills/cf-check.md +19 -0
  39. package/templates/skills/cf-init.md +16 -0
  40. package/templates/skills/cf-spec.md +20 -0
  41. package/templates/skills/cf-task-align.md +19 -0
  42. package/templates/skills/cf-task-archive.md +20 -0
  43. package/templates/skills/cf-task-plan.md +31 -0
  44. package/templates/skills/cf-task-prd.md +14 -0
  45. package/templates/skills/cf-task-start.md +41 -0
  46. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-frontend.md +12 -12
  47. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-full.md +17 -19
  48. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-lite.md +12 -12
  49. package/{src/core/code-flow/specs/shared/prd-template.md → templates/workflow/prd.md} +1 -1
  50. package/templates/workflow/project-guidelines.md +48 -0
  51. package/templates/workflow/scenarios.md +67 -0
  52. package/templates/workflow/task.md +56 -0
  53. package/docs/acceptance-contract.md +0 -217
  54. package/docs/ci/acceptance-evidence.md +0 -77
  55. package/docs/migrations/runtime-0.7.0.md +0 -41
  56. package/docs/migrations/runtime-0.8.0.md +0 -154
  57. package/docs/migrations/runtime-0.9.0.md +0 -40
  58. package/docs/migrations/spec-workflow-0.6.0.md +0 -42
  59. package/docs/spec-injection-hardening.md +0 -54
  60. package/docs/spec-injection-performance.json +0 -73
  61. package/docs/spec-optimization.md +0 -12
  62. package/src/adapters/claude/CLAUDE.md +0 -45
  63. package/src/adapters/claude/commands/cf-init.md +0 -262
  64. package/src/adapters/claude/commands/cf-learn.md +0 -291
  65. package/src/adapters/claude/commands/cf-spec.md +0 -87
  66. package/src/adapters/claude/commands/cf-stats.md +0 -96
  67. package/src/adapters/claude/commands/cf-sync.md +0 -39
  68. package/src/adapters/claude/commands/cf-task/align.md +0 -331
  69. package/src/adapters/claude/commands/cf-task/archive.md +0 -155
  70. package/src/adapters/claude/commands/cf-task/block.md +0 -49
  71. package/src/adapters/claude/commands/cf-task/graph.md +0 -92
  72. package/src/adapters/claude/commands/cf-task/note.md +0 -248
  73. package/src/adapters/claude/commands/cf-task/plan.md +0 -477
  74. package/src/adapters/claude/commands/cf-task/prd.md +0 -173
  75. package/src/adapters/claude/commands/cf-task/start.md +0 -317
  76. package/src/adapters/claude/commands/cf-task/status.md +0 -71
  77. package/src/adapters/claude/commands/cf-task/verify-e2e.md +0 -148
  78. package/src/adapters/claude/commands/cf-validate.md +0 -100
  79. package/src/adapters/claude/settings.local.json +0 -50
  80. package/src/adapters/codex/AGENTS.md +0 -45
  81. package/src/adapters/codex/config.toml +0 -3
  82. package/src/adapters/codex/hooks.json +0 -54
  83. package/src/adapters/codex/skills/cf-init/SKILL.md +0 -273
  84. package/src/adapters/codex/skills/cf-learn/SKILL.md +0 -296
  85. package/src/adapters/codex/skills/cf-spec/SKILL.md +0 -88
  86. package/src/adapters/codex/skills/cf-stats/SKILL.md +0 -97
  87. package/src/adapters/codex/skills/cf-sync/SKILL.md +0 -45
  88. package/src/adapters/codex/skills/cf-task-align/SKILL.md +0 -332
  89. package/src/adapters/codex/skills/cf-task-archive/SKILL.md +0 -156
  90. package/src/adapters/codex/skills/cf-task-block/SKILL.md +0 -50
  91. package/src/adapters/codex/skills/cf-task-graph/SKILL.md +0 -93
  92. package/src/adapters/codex/skills/cf-task-note/SKILL.md +0 -245
  93. package/src/adapters/codex/skills/cf-task-plan/SKILL.md +0 -478
  94. package/src/adapters/codex/skills/cf-task-prd/SKILL.md +0 -174
  95. package/src/adapters/codex/skills/cf-task-start/SKILL.md +0 -318
  96. package/src/adapters/codex/skills/cf-task-status/SKILL.md +0 -72
  97. package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +0 -145
  98. package/src/adapters/codex/skills/cf-validate/SKILL.md +0 -102
  99. package/src/adapters/commands.json +0 -115
  100. package/src/adapters/costrict/CLAUDE.md +0 -45
  101. package/src/adapters/costrict/commands/cf-init.md +0 -262
  102. package/src/adapters/costrict/commands/cf-learn.md +0 -291
  103. package/src/adapters/costrict/commands/cf-spec.md +0 -87
  104. package/src/adapters/costrict/commands/cf-stats.md +0 -96
  105. package/src/adapters/costrict/commands/cf-sync.md +0 -39
  106. package/src/adapters/costrict/commands/cf-task/align.md +0 -331
  107. package/src/adapters/costrict/commands/cf-task/archive.md +0 -155
  108. package/src/adapters/costrict/commands/cf-task/block.md +0 -49
  109. package/src/adapters/costrict/commands/cf-task/graph.md +0 -92
  110. package/src/adapters/costrict/commands/cf-task/note.md +0 -248
  111. package/src/adapters/costrict/commands/cf-task/plan.md +0 -477
  112. package/src/adapters/costrict/commands/cf-task/prd.md +0 -173
  113. package/src/adapters/costrict/commands/cf-task/start.md +0 -317
  114. package/src/adapters/costrict/commands/cf-task/status.md +0 -71
  115. package/src/adapters/costrict/commands/cf-task/verify-e2e.md +0 -148
  116. package/src/adapters/costrict/commands/cf-validate.md +0 -100
  117. package/src/adapters/costrict/settings.local.json +0 -50
  118. package/src/adapters/install-plan.json +0 -100
  119. package/src/adapters/opencode/AGENTS.md +0 -45
  120. package/src/adapters/opencode/commands/cf-init.md +0 -281
  121. package/src/adapters/opencode/commands/cf-learn.md +0 -295
  122. package/src/adapters/opencode/commands/cf-spec.md +0 -91
  123. package/src/adapters/opencode/commands/cf-stats.md +0 -100
  124. package/src/adapters/opencode/commands/cf-sync.md +0 -44
  125. package/src/adapters/opencode/commands/cf-task/align.md +0 -331
  126. package/src/adapters/opencode/commands/cf-task/archive.md +0 -160
  127. package/src/adapters/opencode/commands/cf-task/block.md +0 -54
  128. package/src/adapters/opencode/commands/cf-task/graph.md +0 -97
  129. package/src/adapters/opencode/commands/cf-task/note.md +0 -253
  130. package/src/adapters/opencode/commands/cf-task/plan.md +0 -477
  131. package/src/adapters/opencode/commands/cf-task/prd.md +0 -178
  132. package/src/adapters/opencode/commands/cf-task/start.md +0 -317
  133. package/src/adapters/opencode/commands/cf-task/status.md +0 -76
  134. package/src/adapters/opencode/commands/cf-task/verify-e2e.md +0 -148
  135. package/src/adapters/opencode/commands/cf-validate.md +0 -105
  136. package/src/adapters/opencode/opencode.json +0 -3
  137. package/src/adapters/opencode/plugins/code-flow/index.js +0 -162
  138. package/src/adapters/opencode/plugins/code-flow/package.json +0 -6
  139. package/src/adapters/platform-bindings.json +0 -370
  140. package/src/cli.js +0 -1076
  141. package/src/core/code-flow/config.yml +0 -161
  142. package/src/core/code-flow/runtime-commands.json +0 -259
  143. package/src/core/code-flow/scripts/cf_acceptance_manifest.py +0 -10
  144. package/src/core/code-flow/scripts/cf_acceptance_runner.py +0 -10
  145. package/src/core/code-flow/scripts/cf_audit.py +0 -10
  146. package/src/core/code-flow/scripts/cf_codex_hook.py +0 -10
  147. package/src/core/code-flow/scripts/cf_design_lint.py +0 -10
  148. package/src/core/code-flow/scripts/cf_evidence_import.py +0 -10
  149. package/src/core/code-flow/scripts/cf_feedback.py +0 -10
  150. package/src/core/code-flow/scripts/cf_opencode_event.py +0 -10
  151. package/src/core/code-flow/scripts/cf_post_hook.py +0 -11
  152. package/src/core/code-flow/scripts/cf_pre_tool_hook.py +0 -11
  153. package/src/core/code-flow/scripts/cf_scenarios.py +0 -10
  154. package/src/core/code-flow/scripts/cf_spec_context.py +0 -10
  155. package/src/core/code-flow/scripts/cf_spec_gate.py +0 -10
  156. package/src/core/code-flow/scripts/cf_spec_session.py +0 -10
  157. package/src/core/code-flow/scripts/cf_stats.py +0 -10
  158. package/src/core/code-flow/scripts/cf_stop_hook.py +0 -11
  159. package/src/core/code-flow/scripts/cf_sync.py +0 -10
  160. package/src/core/code-flow/scripts/cf_task_index.py +0 -10
  161. package/src/core/code-flow/scripts/cf_task_parallel.py +0 -10
  162. package/src/core/code-flow/scripts/cf_task_workflow.py +0 -10
  163. package/src/core/code-flow/scripts/cf_user_prompt_hook.py +0 -11
  164. package/src/core/code-flow/scripts/cf_validation.py +0 -10
  165. package/src/core/code-flow/scripts/codeflow/__init__.py +0 -0
  166. package/src/core/code-flow/scripts/codeflow/acceptance/__init__.py +0 -0
  167. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_evidence.py +0 -347
  168. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_manifest_core.py +0 -660
  169. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_plan.py +0 -202
  170. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_registration.py +0 -60
  171. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_risk.py +0 -73
  172. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_schema.py +0 -282
  173. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_selection.py +0 -266
  174. package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_inputs.py +0 -172
  175. package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_registration.py +0 -326
  176. package/src/core/code-flow/scripts/codeflow/acceptance/cf_checks.py +0 -333
  177. package/src/core/code-flow/scripts/codeflow/acceptance/cf_plan_quality.py +0 -197
  178. package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_quality.py +0 -174
  179. package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_source.py +0 -300
  180. package/src/core/code-flow/scripts/codeflow/acceptance/cf_traceability.py +0 -502
  181. package/src/core/code-flow/scripts/codeflow/cli/__init__.py +0 -0
  182. package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_manifest.py +0 -347
  183. package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_runner.py +0 -1490
  184. package/src/core/code-flow/scripts/codeflow/cli/cf_audit.py +0 -533
  185. package/src/core/code-flow/scripts/codeflow/cli/cf_design_lint.py +0 -172
  186. package/src/core/code-flow/scripts/codeflow/cli/cf_evidence_import.py +0 -607
  187. package/src/core/code-flow/scripts/codeflow/cli/cf_feedback.py +0 -53
  188. package/src/core/code-flow/scripts/codeflow/cli/cf_scenarios.py +0 -58
  189. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_context.py +0 -1175
  190. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_gate.py +0 -60
  191. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_session.py +0 -46
  192. package/src/core/code-flow/scripts/codeflow/cli/cf_stats.py +0 -540
  193. package/src/core/code-flow/scripts/codeflow/cli/cf_sync.py +0 -144
  194. package/src/core/code-flow/scripts/codeflow/cli/cf_task_index.py +0 -177
  195. package/src/core/code-flow/scripts/codeflow/cli/cf_task_parallel.py +0 -1142
  196. package/src/core/code-flow/scripts/codeflow/cli/cf_task_workflow.py +0 -742
  197. package/src/core/code-flow/scripts/codeflow/cli/cf_validation.py +0 -31
  198. package/src/core/code-flow/scripts/codeflow/core/__init__.py +0 -0
  199. package/src/core/code-flow/scripts/codeflow/core/cf_context_cache.py +0 -67
  200. package/src/core/code-flow/scripts/codeflow/core/cf_core.py +0 -609
  201. package/src/core/code-flow/scripts/codeflow/core/cf_exec_base.py +0 -325
  202. package/src/core/code-flow/scripts/codeflow/core/cf_fs.py +0 -57
  203. package/src/core/code-flow/scripts/codeflow/core/cf_git.py +0 -92
  204. package/src/core/code-flow/scripts/codeflow/core/cf_log.py +0 -160
  205. package/src/core/code-flow/scripts/codeflow/core/cf_markdown.py +0 -77
  206. package/src/core/code-flow/scripts/codeflow/core/cf_native_paths.py +0 -26
  207. package/src/core/code-flow/scripts/codeflow/core/cf_patch.py +0 -33
  208. package/src/core/code-flow/scripts/codeflow/core/cf_runtime_install.py +0 -32
  209. package/src/core/code-flow/scripts/codeflow/core/cf_session_state.py +0 -37
  210. package/src/core/code-flow/scripts/codeflow/core/cf_stop_guard.py +0 -53
  211. package/src/core/code-flow/scripts/codeflow/core/cf_task_state.py +0 -9
  212. package/src/core/code-flow/scripts/codeflow/core/cf_validators.py +0 -408
  213. package/src/core/code-flow/scripts/codeflow/core/cf_worktree_env.py +0 -253
  214. package/src/core/code-flow/scripts/codeflow/evidence/__init__.py +0 -0
  215. package/src/core/code-flow/scripts/codeflow/evidence/cf_evidence_store.py +0 -527
  216. package/src/core/code-flow/scripts/codeflow/hooks/__init__.py +0 -0
  217. package/src/core/code-flow/scripts/codeflow/hooks/cf_codex_hook.py +0 -95
  218. package/src/core/code-flow/scripts/codeflow/hooks/cf_opencode_event.py +0 -98
  219. package/src/core/code-flow/scripts/codeflow/hooks/cf_post_hook.py +0 -30
  220. package/src/core/code-flow/scripts/codeflow/hooks/cf_pre_tool_hook.py +0 -41
  221. package/src/core/code-flow/scripts/codeflow/hooks/cf_stop_hook.py +0 -292
  222. package/src/core/code-flow/scripts/codeflow/hooks/cf_user_prompt_hook.py +0 -161
  223. package/src/core/code-flow/scripts/codeflow/spec/__init__.py +0 -0
  224. package/src/core/code-flow/scripts/codeflow/spec/cf_active_task.py +0 -412
  225. package/src/core/code-flow/scripts/codeflow/spec/cf_context_refresh.py +0 -301
  226. package/src/core/code-flow/scripts/codeflow/spec/cf_edit_service.py +0 -149
  227. package/src/core/code-flow/scripts/codeflow/spec/cf_injection.py +0 -111
  228. package/src/core/code-flow/scripts/codeflow/spec/cf_rule_readiness.py +0 -114
  229. package/src/core/code-flow/scripts/codeflow/spec/cf_scan.py +0 -367
  230. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_budget.py +0 -55
  231. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_context_model.py +0 -477
  232. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_impact.py +0 -119
  233. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_metadata.py +0 -387
  234. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_migrate.py +0 -529
  235. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize.py +0 -193
  236. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize_apply.py +0 -187
  237. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_resolver.py +0 -369
  238. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_router.py +0 -304
  239. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_verify.py +0 -687
  240. package/src/core/code-flow/scripts/codeflow/spec/cf_stage_gate.py +0 -330
  241. package/src/core/code-flow/scripts/codeflow/spec/cf_task_projection.py +0 -83
  242. package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_service.py +0 -755
  243. package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_transaction.py +0 -110
  244. package/src/core/code-flow/scripts/codeflow/task/__init__.py +0 -0
  245. package/src/core/code-flow/scripts/codeflow/task/cf_requirement_verification.py +0 -867
  246. package/src/core/code-flow/scripts/codeflow/task/cf_task_doc.py +0 -178
  247. package/src/core/code-flow/scripts/codeflow/task/cf_task_merge.py +0 -635
  248. package/src/core/code-flow/scripts/codeflow/task/cf_task_plan.py +0 -251
  249. package/src/core/code-flow/scripts/codeflow/task/cf_task_runtime.py +0 -570
  250. package/src/core/code-flow/scripts/codeflow/task/cf_validation_service.py +0 -73
  251. package/src/core/code-flow/scripts/runtime-resources.json +0 -29
  252. package/src/core/code-flow/specs/backend/_map.md +0 -56
  253. package/src/core/code-flow/specs/backend/code-quality-performance.md +0 -59
  254. package/src/core/code-flow/specs/backend/database.md +0 -51
  255. package/src/core/code-flow/specs/backend/directory-structure.md +0 -54
  256. package/src/core/code-flow/specs/backend/logging.md +0 -50
  257. package/src/core/code-flow/specs/backend/platform-rules.md +0 -55
  258. package/src/core/code-flow/specs/frontend/_map.md +0 -52
  259. package/src/core/code-flow/specs/frontend/component-specs.md +0 -68
  260. package/src/core/code-flow/specs/frontend/directory-structure.md +0 -53
  261. package/src/core/code-flow/specs/frontend/quality-standards.md +0 -58
  262. package/src/core/code-flow/specs/shared/_map.md +0 -57
  263. package/src/core/code-flow/specs/shared/scenarios-template.md +0 -55
  264. package/src/core/code-flow/tasks/.gitkeep +0 -0
  265. package/src/core/code-flow/validation.yml +0 -59
  266. package/src/migrate/runtime.js +0 -213
  267. package/src/migrate/spec-workflow.js +0 -253
  268. 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