@devcxl/opencode-cabbage 1.2.0 → 2.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 (104) hide show
  1. package/.codex-plugin/plugin.json +36 -0
  2. package/README.md +67 -21
  3. package/README.zh.md +26 -13
  4. package/assets/agents/dev-lifecycle.md +6 -0
  5. package/assets/prompts/bootstrap.md +4 -0
  6. package/assets/skills/flow-change/SKILL.md +109 -0
  7. package/assets/skills/flow-design/SKILL.md +15 -0
  8. package/assets/skills/flow-docs/SKILL.md +112 -0
  9. package/assets/skills/flow-incident/SKILL.md +98 -0
  10. package/assets/skills/flow-research/SKILL.md +9 -0
  11. package/assets/skills/flow-setup/SKILL.md +13 -0
  12. package/codex-skills/agent-architect/SKILL.md +79 -0
  13. package/codex-skills/agent-dev-lifecycle/SKILL.md +311 -0
  14. package/codex-skills/agent-developer/SKILL.md +51 -0
  15. package/codex-skills/agent-goal-verify/SKILL.md +45 -0
  16. package/codex-skills/agent-researcher/SKILL.md +32 -0
  17. package/codex-skills/agent-reviewer/SKILL.md +109 -0
  18. package/codex-skills/flow-change/SKILL.md +109 -0
  19. package/codex-skills/flow-code/SKILL.md +71 -0
  20. package/codex-skills/flow-design/SKILL.md +161 -0
  21. package/codex-skills/flow-docs/SKILL.md +112 -0
  22. package/codex-skills/flow-incident/SKILL.md +98 -0
  23. package/codex-skills/flow-release/SKILL.md +90 -0
  24. package/codex-skills/flow-requirements/SKILL.md +90 -0
  25. package/codex-skills/flow-research/SKILL.md +86 -0
  26. package/codex-skills/flow-review/SKILL.md +110 -0
  27. package/codex-skills/flow-setup/SKILL.md +88 -0
  28. package/codex-skills/flow-tasks/SKILL.md +220 -0
  29. package/codex-skills/flow-tdd/SKILL.md +235 -0
  30. package/dist/hooks/session-start.d.ts +1 -0
  31. package/dist/hooks/session-start.js +72 -0
  32. package/dist/hooks/session-start.js.map +1 -0
  33. package/dist/index.d.ts +2 -5
  34. package/dist/index.js +1 -4
  35. package/dist/index.js.map +1 -1
  36. package/dist/plugin/agents.d.ts +20 -0
  37. package/dist/plugin/agents.js +79 -0
  38. package/dist/plugin/agents.js.map +1 -1
  39. package/dist/plugin/cabbage-cli.d.ts +28 -0
  40. package/dist/plugin/cabbage-cli.js +92 -0
  41. package/dist/plugin/cabbage-cli.js.map +1 -0
  42. package/dist/plugin/cabbage-tool.d.ts +103 -0
  43. package/dist/plugin/cabbage-tool.js +169 -0
  44. package/dist/plugin/cabbage-tool.js.map +1 -0
  45. package/dist/plugin/commands.d.ts +10 -0
  46. package/dist/plugin/commands.js +1 -1
  47. package/dist/plugin/commands.js.map +1 -1
  48. package/dist/plugin/continuation.d.ts +22 -0
  49. package/dist/plugin/continuation.js +233 -0
  50. package/dist/plugin/continuation.js.map +1 -0
  51. package/dist/plugin/goal-store.d.ts +64 -0
  52. package/dist/plugin/goal-store.js +57 -0
  53. package/dist/plugin/goal-store.js.map +1 -0
  54. package/dist/plugin/goal.d.ts +95 -38
  55. package/dist/plugin/goal.js +208 -199
  56. package/dist/plugin/goal.js.map +1 -1
  57. package/dist/plugin/install-dir.d.ts +13 -0
  58. package/dist/plugin/install-dir.js +46 -0
  59. package/dist/plugin/install-dir.js.map +1 -0
  60. package/dist/plugin/server.d.ts +4 -12
  61. package/dist/plugin/server.js +279 -423
  62. package/dist/plugin/server.js.map +1 -1
  63. package/dist/plugin/session-abort.d.ts +5 -0
  64. package/dist/plugin/session-abort.js +26 -0
  65. package/dist/plugin/session-abort.js.map +1 -0
  66. package/dist/plugin/skills.d.ts +15 -0
  67. package/dist/plugin/skills.js +47 -50
  68. package/dist/plugin/skills.js.map +1 -1
  69. package/dist/plugin.d.ts +2 -2
  70. package/dist/plugin.js +6 -2
  71. package/dist/plugin.js.map +1 -1
  72. package/dist/util/paths.d.ts +0 -6
  73. package/dist/util/paths.js +0 -15
  74. package/dist/util/paths.js.map +1 -1
  75. package/hooks/hooks.json +17 -0
  76. package/package.json +13 -8
  77. package/vendor/cabbage/MANIFEST.json +8 -0
  78. package/vendor/cabbage/cabbage_cli/__init__.py +1 -0
  79. package/vendor/cabbage/cabbage_cli/__main__.py +2 -0
  80. package/vendor/cabbage/cabbage_cli/assets/docs-site/.vitepress/config.ts +78 -0
  81. package/vendor/cabbage/cabbage_cli/assets/docs-site/README.md +31 -0
  82. package/vendor/cabbage/cabbage_cli/assets/docs-site/package.json +16 -0
  83. package/vendor/cabbage/cabbage_cli/assets/docs-site/pnpm-lock.yaml +2504 -0
  84. package/vendor/cabbage/cabbage_cli/assets/integrations/cabbage.yml +127 -0
  85. package/vendor/cabbage/cabbage_cli/assets/templates/adr.md +47 -0
  86. package/vendor/cabbage/cabbage_cli/assets/templates/api-design.md +45 -0
  87. package/vendor/cabbage/cabbage_cli/assets/templates/change-record.md +21 -0
  88. package/vendor/cabbage/cabbage_cli/assets/templates/database-design.md +37 -0
  89. package/vendor/cabbage/cabbage_cli/assets/templates/generic.md +20 -0
  90. package/vendor/cabbage/cabbage_cli/assets/templates/impact.md +41 -0
  91. package/vendor/cabbage/cabbage_cli/assets/templates/incident.md +48 -0
  92. package/vendor/cabbage/cabbage_cli/assets/templates/postmortem.md +43 -0
  93. package/vendor/cabbage/cabbage_cli/assets/templates/prd.md +71 -0
  94. package/vendor/cabbage/cabbage_cli/assets/templates/release-plan.md +45 -0
  95. package/vendor/cabbage/cabbage_cli/assets/templates/rfc.md +44 -0
  96. package/vendor/cabbage/cabbage_cli/assets/templates/security-review.md +35 -0
  97. package/vendor/cabbage/cabbage_cli/assets/templates/tasks.md +62 -0
  98. package/vendor/cabbage/cabbage_cli/assets/templates/tech-spec.md +87 -0
  99. package/vendor/cabbage/cabbage_cli/assets/templates/test-plan.md +44 -0
  100. package/vendor/cabbage/cabbage_cli/assets/workflows/change.yaml +11 -0
  101. package/vendor/cabbage/cabbage_cli/assets/workflows/incident.yaml +4 -0
  102. package/vendor/cabbage/cabbage_cli/cli.py +283 -0
  103. package/vendor/cabbage/cabbage_cli/core.py +606 -0
  104. package/vendor/cabbage/cabbage_cli/scaffold.py +313 -0
@@ -0,0 +1,62 @@
1
+ ---
2
+ change: {{CHANGE_ID}}
3
+ cabbage_stage: {{STAGE_ID}}
4
+ change_type: {{CHANGE_TYPE}}
5
+ ---
6
+
7
+ <!-- Replace every marked prompt before verifying this stage. Split work into vertical tracer-bullet tasks with an explicit DAG dependency graph and complete every checkbox. -->
8
+
9
+ # Preparation
10
+
11
+ ```mermaid
12
+ flowchart TD
13
+ Pre["Prerequisites / Pre-Refactor"] --> Task1["Task 1: Core Behavior Slice"]
14
+ Task1 --> Task2["Task 2: Parallel Behavior Slice A"]
15
+ Task1 --> Task3["Task 3: Parallel Behavior Slice B"]
16
+ Task2 --> Final["Convergence & Sync"]
17
+ Task3 --> Final
18
+ ```
19
+
20
+ ## Parallel Execution Strategy
21
+
22
+ - **Sequential Baseline**: Prerequisites, data models, or behavior-preserving pre-refactors.
23
+ - **Parallel Slices**: Independent vertical feature slices executing concurrently in isolated contexts.
24
+ - **Convergence & Verification**: Integration tests, docs synchronization, and release checks.
25
+
26
+ - [ ] <!-- CABBAGE: Record prerequisite verification, baseline fixtures, or behavior-preserving pre-refactors. -->
27
+
28
+ # Tasks
29
+
30
+ ## Task 1: <!-- CABBAGE: Primary Feature Slice Slug -->
31
+ - **Builds**: <!-- CABBAGE: Observable end-to-end user/system capability delivered by this task. -->
32
+ - **Blocked By**: None (or Preparation)
33
+ - **Parallel Group**: Group 1
34
+ - **Verification**: `<!-- CABBAGE: targeted test command -->`
35
+ - **Standard Operating Procedure (Task SOP)**:
36
+ 1. *[RED]* Define public test seam and write failing test.
37
+ 2. *[GREEN]* Implement minimal code to pass the test.
38
+ 3. *[REFACTOR]* Clean code, verify lint/types, maintain behavior.
39
+ 4. *[VERIFY]* Run verification command and confirm passing.
40
+ - [ ] <!-- CABBAGE: Implement behavior slice 1 through public test seam. -->
41
+ - [ ] <!-- CABBAGE: Add failing test (RED) then minimal implementation (GREEN). -->
42
+
43
+ ## Task 2: <!-- CABBAGE: Secondary Feature / Extension Slice Slug -->
44
+ - **Builds**: <!-- CABBAGE: Observable end-to-end capability delivered by this task. -->
45
+ - **Blocked By**: Task 1
46
+ - **Parallel Group**: Group 2
47
+ - **Verification**: `<!-- CABBAGE: targeted test command -->`
48
+ - **Standard Operating Procedure (Task SOP)**:
49
+ 1. *[RED]* Define public test seam and write failing test.
50
+ 2. *[GREEN]* Implement minimal code to pass the test.
51
+ 3. *[REFACTOR]* Clean code, verify lint/types, maintain behavior.
52
+ 4. *[VERIFY]* Run verification command and confirm passing.
53
+ - [ ] <!-- CABBAGE: Implement behavior slice 2 through public test seam. -->
54
+ - [ ] <!-- CABBAGE: Add failing test (RED) then minimal implementation (GREEN). -->
55
+
56
+ - [ ] Add or update automated regression tests
57
+ - [ ] Update affected current-state documentation under docs/
58
+
59
+ # Verification
60
+
61
+ - [ ] <!-- CABBAGE: Record the exact test, validation, and build commands to run. -->
62
+ - [ ] Verify rollout and rollback readiness when applicable
@@ -0,0 +1,87 @@
1
+ ---
2
+ change: {{CHANGE_ID}}
3
+ cabbage_stage: {{STAGE_ID}}
4
+ change_type: {{CHANGE_TYPE}}
5
+ ---
6
+
7
+ <!-- Replace every marked prompt before verifying this stage. Use N/A with a reason when a section does not apply. -->
8
+
9
+ # Context
10
+
11
+ ## Current State
12
+
13
+ <!-- CABBAGE: Describe the current implementation, constraints, and the reason for change. -->
14
+
15
+ ## Goals and Non-goals
16
+
17
+ - Goal: <!-- CABBAGE: State the technical outcome. -->
18
+ - Non-goal: <!-- CABBAGE: State what this design intentionally excludes. -->
19
+
20
+ # Requirements
21
+
22
+ | ID | Technical requirement | Source |
23
+ |---|---|---|
24
+ | TR-1 | <!-- CABBAGE: Record a functional or non-functional requirement. --> | <!-- CABBAGE: Link it to a requirement, constraint, or risk. --> |
25
+
26
+ # Design
27
+
28
+ ## Overview
29
+
30
+ <!-- CABBAGE: Explain the proposed design and the responsibilities of each affected component. -->
31
+
32
+ <!-- CABBAGE: Add a Mermaid component, sequence, or state diagram when it improves reviewability; otherwise state N/A. -->
33
+
34
+ ## Interfaces and Data
35
+
36
+ <!-- CABBAGE: Describe interface changes, data flow, state transitions, invariants, and compatibility constraints. -->
37
+
38
+ ## Decision Boundaries
39
+
40
+ ### AI Autonomous Decisions
41
+
42
+ - <!-- CABBAGE: List internal implementation decisions AI can independently make (e.g. private helpers, internal data structures, local refactoring). -->
43
+
44
+ ### Human Gate Decisions
45
+
46
+ - <!-- CABBAGE: List critical decisions requiring explicit human review/approval (e.g. breaking API changes, storage migration, security policies). -->
47
+
48
+ # Alternatives
49
+
50
+ ## Architecture Options Comparison
51
+
52
+ | Option | Architecture Approach | Benefits | Costs & Risks | Recommendation |
53
+ |---|---|---|---|---|
54
+ | Option A (Recommended) | <!-- CABBAGE: Describe primary approach. --> | <!-- CABBAGE: List advantages and trade-offs. --> | <!-- CABBAGE: List costs and risks. --> | Chosen |
55
+ | Option B (Alternative) | <!-- CABBAGE: Describe alternative approach (e.g. minimal vs decoupled). --> | <!-- CABBAGE: List advantages. --> | <!-- CABBAGE: List trade-offs/drawbacks. --> | Rejected |
56
+
57
+ ## Selected Decision & Trade-off Rationale
58
+
59
+ <!-- CABBAGE: Explain why the chosen option won over alternatives and state accepted trade-offs. -->
60
+
61
+ # Security and Privacy
62
+
63
+ <!-- CABBAGE: Describe trust boundaries, authorization, sensitive data, validation, and privacy impact, or state N/A. -->
64
+
65
+ # Observability
66
+
67
+ | Signal | Purpose | Alert or dashboard |
68
+ |---|---|---|
69
+ | <!-- CABBAGE: Name a log, metric, trace, or audit event. --> | <!-- CABBAGE: Explain what it proves. --> | <!-- CABBAGE: Describe how operators use it. --> |
70
+
71
+ # Failure Modes
72
+
73
+ | Failure mode | Detection | Handling | Recovery |
74
+ |---|---|---|---|
75
+ | <!-- CABBAGE: Describe a credible failure. --> | <!-- CABBAGE: Describe the signal. --> | <!-- CABBAGE: Describe runtime behavior. --> | <!-- CABBAGE: Describe recovery steps. --> |
76
+
77
+ # Rollout
78
+
79
+ <!-- CABBAGE: Describe sequencing, feature flags, compatibility windows, migration, and success criteria. -->
80
+
81
+ # Rollback
82
+
83
+ <!-- CABBAGE: Define rollback triggers, steps, and data-safety constraints. -->
84
+
85
+ # Open Questions
86
+
87
+ - <!-- CABBAGE: Record unresolved technical decisions and an owner, or state N/A. -->
@@ -0,0 +1,44 @@
1
+ ---
2
+ change: {{CHANGE_ID}}
3
+ cabbage_stage: {{STAGE_ID}}
4
+ change_type: {{CHANGE_TYPE}}
5
+ ---
6
+
7
+ <!-- Replace every marked prompt before verifying this stage. Use N/A with a reason when a section does not apply. -->
8
+
9
+ # Strategy
10
+
11
+ <!-- CABBAGE: Define the scope, test levels, public test seams, and key quality risks. -->
12
+
13
+ | Level | Scope | Test seam | Owner |
14
+ |---|---|---|---|
15
+ | <!-- CABBAGE: Unit, integration, contract, E2E, or manual. --> | <!-- CABBAGE: State what is covered. --> | <!-- CABBAGE: Name the public interface or observable result. --> | <!-- CABBAGE: Name the owner. --> |
16
+
17
+ # Test Environment and Data
18
+
19
+ <!-- CABBAGE: Describe required environments, fixtures, accounts, data setup, isolation, and cleanup. -->
20
+
21
+ # Cases
22
+
23
+ | ID | Scenario | Level | Expected result | Priority |
24
+ |---|---|---|---|---|
25
+ | T-1 | <!-- CABBAGE: Describe a critical happy path or failure case. --> | <!-- CABBAGE: Name the test level. --> | <!-- CABBAGE: State the observable outcome. --> | High |
26
+
27
+ # Regression Coverage
28
+
29
+ - <!-- CABBAGE: Identify existing behavior and tests that protect it from regression. -->
30
+
31
+ # Non-functional Testing
32
+
33
+ | Quality attribute | Method | Threshold |
34
+ |---|---|---|
35
+ | <!-- CABBAGE: Performance, security, resilience, accessibility, or another attribute. --> | <!-- CABBAGE: Describe the verification method. --> | <!-- CABBAGE: Define the pass threshold. --> |
36
+
37
+ # Entry and Exit Criteria
38
+
39
+ - Entry: <!-- CABBAGE: Define prerequisites for starting verification. -->
40
+ - Exit: <!-- CABBAGE: Define evidence required to finish verification. -->
41
+
42
+ # Risks
43
+
44
+ - <!-- CABBAGE: Record coverage gaps, flaky dependencies, or untestable assumptions and their mitigations. -->
@@ -0,0 +1,11 @@
1
+ # One change record; specialist documents open only for declared risks.
2
+ record_covers: [product, testing]
3
+ stages:
4
+ - {id: design, artifact: tech-spec.md, template: tech-spec.md, when: impact.design, required_headings: [Context, Design, Failure Modes, Rollout]}
5
+ - {id: rfc, artifact: rfc.md, template: rfc.md, when: impact.rfc, required_headings: [Problem, Proposal, Alternatives]}
6
+ - {id: adr, artifact: adr.md, template: adr.md, when: impact.architecture, required_headings: [Context, Decision, Consequences]}
7
+ - {id: api, artifact: api-design.md, template: api-design.md, when: impact.api, required_headings: [Contract, Compatibility]}
8
+ - {id: database, artifact: database-design.md, template: database-design.md, when: impact.database, required_headings: [Schema, Migration, Rollback]}
9
+ - {id: security, artifact: security-review.md, template: security-review.md, when: impact.security, required_headings: [Threats, Controls]}
10
+ - {id: release, artifact: release-plan.md, template: release-plan.md, when: impact.deployment, required_headings: [Deployment, Rollback, Verification]}
11
+ - {id: tasks, artifact: tasks.md, template: change-record.md, depends_on: [design, rfc, adr, api, database, security, release], validator: checklist, required_headings: [Goal, Design, Tasks, Verification]}
@@ -0,0 +1,4 @@
1
+ stages:
2
+ - {id: incident, artifact: incident.md, template: incident.md, required_headings: [Impact, Timeline, Mitigation]}
3
+ - {id: postmortem, artifact: postmortem.md, template: postmortem.md, depends_on: [incident], required_headings: [Root Cause, Contributing Factors, Corrective Actions]}
4
+ - {id: tasks, artifact: tasks.md, template: tasks.md, depends_on: [postmortem], validator: checklist, required_headings: [Tasks]}
@@ -0,0 +1,283 @@
1
+ from __future__ import annotations
2
+ import argparse, json, shutil, subprocess, sys
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+ from . import __version__
6
+ from .core import (CabbageError, archive_root, change_dir, change_spec, changes_root,
7
+ ci_check, condition_enabled, dump_yaml, gate, get_change_tasks_dag,
8
+ load_config, load_yaml, project_root, ready_to_archive, stage_map, stage_statuses,
9
+ sync_change_to_docs, validate_change, verify_stage, workflow)
10
+ from .scaffold import init_project, new_change, ensure_artifacts, adopt_project, discard_change
11
+
12
+ def emit(data):
13
+ # Every caller only wants JSON here; human-readable text is printed explicitly.
14
+ print(json.dumps(data,ensure_ascii=False,indent=2))
15
+
16
+ def cmd_init(a):
17
+ root=Path.cwd().resolve(); init_project(root,a.force,not a.no_vendor_cli)
18
+ print("initialized cabbage")
19
+ print("next: cabbage new change <change-id>")
20
+ print("existing docs to onboard? run: cabbage adopt")
21
+
22
+ def cmd_adopt(a):
23
+ root=project_root(); data=adopt_project(root, apply=a.apply)
24
+ if a.json: emit(data); return 0
25
+ if data.get("applied"):
26
+ print(f"migrated {len(data['applied'])} document(s):")
27
+ for item in data["applied"]:
28
+ print(f" {item['from']} -> {item['to']}")
29
+ for w in data.get("warnings", []):
30
+ print(f"warning: {w}")
31
+ counts=data["counts"]
32
+ print(f"adoption report written to {data['report']}")
33
+ print(f"documents: {sum(counts.values())} total; keep={counts['keep']} migrate={counts['migrate']} import={counts['import']} review={counts['review']}")
34
+ for r in data["documents"]:
35
+ if r["action"] in {"migrate","import","review"}:
36
+ target=r["target"] or "-"
37
+ print(f" {r['action']:8} {r['path']} -> {target}")
38
+ print("next: resolve review rows, then follow the cabbage-adopt skill")
39
+ return 0
40
+
41
+ def cmd_discard(a):
42
+ root=project_root(); discard_change(root, a.change)
43
+ print(f"discarded change `{a.change}`")
44
+ return 0
45
+
46
+ def cmd_doctor(a):
47
+ results = {}
48
+ # 1. Python
49
+ py_ok = sys.version_info >= (3, 10)
50
+ results["python"] = {
51
+ "ok": py_ok,
52
+ "version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
53
+ "message": "Python >= 3.10 required" if not py_ok else "OK",
54
+ }
55
+ # 2. PyYAML
56
+ try:
57
+ import yaml
58
+ results["pyyaml"] = {"ok": True, "version": getattr(yaml, "__version__", "unknown")}
59
+ except ImportError:
60
+ results["pyyaml"] = {"ok": False, "message": "PyYAML is not installed"}
61
+
62
+ # 3. Git
63
+ git_which = shutil.which("git")
64
+ if git_which:
65
+ try:
66
+ p = subprocess.run(["git", "--version"], capture_output=True, text=True)
67
+ results["git"] = {"ok": True, "version": p.stdout.strip()}
68
+ except Exception as e:
69
+ results["git"] = {"ok": False, "message": str(e)}
70
+ else:
71
+ results["git"] = {"ok": False, "message": "git executable not found in PATH"}
72
+
73
+ # 4. pnpm
74
+ pnpm_which = shutil.which("pnpm")
75
+ if pnpm_which:
76
+ try:
77
+ p = subprocess.run(["pnpm", "--version"], capture_output=True, text=True)
78
+ results["pnpm"] = {"ok": True, "version": p.stdout.strip()}
79
+ except Exception as e:
80
+ results["pnpm"] = {"ok": False, "message": str(e)}
81
+ else:
82
+ results["pnpm"] = {"ok": True, "version": "not installed (optional for docs dev/build)"}
83
+
84
+ # 5. Cabbage Project
85
+ try:
86
+ r = project_root()
87
+ cfg = load_config(r)
88
+ results["project"] = {"ok": True, "path": str(r), "version": cfg.get("version", 1)}
89
+ except Exception as e:
90
+ results["project"] = {"ok": False, "message": str(e)}
91
+
92
+ all_ok = results["python"]["ok"] and results["pyyaml"]["ok"] and results["git"]["ok"]
93
+ if a.json:
94
+ emit({"ok": all_ok, "checks": results})
95
+ else:
96
+ print("Cabbage Doctor Diagnosis:")
97
+ for name, info in results.items():
98
+ status = "✓" if info["ok"] else "✗"
99
+ ver = f"({info.get('version', '')})" if info.get('version') else ""
100
+ msg = f"- {info.get('message')}" if not info["ok"] or 'message' in info else ""
101
+ print(f" {status} {name:10} {ver} {msg}".rstrip())
102
+ if all_ok:
103
+ print("\nAll essential dependencies and environment checks passed.")
104
+ else:
105
+ print("\nSome environment checks failed.")
106
+ return 0 if all_ok else 1
107
+
108
+ def cmd_new(a):
109
+ root=project_root(); new_change(root,a.type,a.change); print(f"created {a.change}"); cmd_status(argparse.Namespace(change=a.change,json=False))
110
+
111
+ def cmd_status(a):
112
+ root=project_root()
113
+ if a.change:
114
+ items=stage_statuses(root,a.change)
115
+ if a.json: emit({"change":a.change,"stages":items})
116
+ else:
117
+ for x in items: print(f"{x['status']:7} {x['id']:16} {x.get('artifact') or ''}")
118
+ else:
119
+ dirs=changes_root(root); rows=[]
120
+ for p in sorted(dirs.iterdir() if dirs.exists() else []):
121
+ if p.is_dir() and (p/"change.yaml").exists():
122
+ st=stage_statuses(root,p.name)
123
+ done=sum(x['status']=='done' for x in st)
124
+ total=sum(x['status']!='skipped' for x in st)
125
+ stale=sum(x['status']=='stale' for x in st)
126
+ rows.append({"change":p.name,"done":done,"total":total,"stale":stale,
127
+ "ready_to_archive":ready_to_archive(st)})
128
+ if a.json: emit(rows)
129
+ elif not rows: print("no active changes")
130
+ else:
131
+ for r in rows: print(f"{r['change']:<20} {r['done']}/{r['total']} done {r['stale']} stale")
132
+ ready=[r["change"] for r in rows if r["ready_to_archive"]]
133
+ if ready:
134
+ print(f"\n{len(ready)} change(s) fully verified and ready to archive.")
135
+ print("After confirming the merge, run: cabbage archive <change-id>")
136
+
137
+ def cmd_next(a):
138
+ root=project_root(); sts=stage_statuses(root,a.change); status={x['id']:x['status'] for x in sts}; spec=change_spec(root,a.change); wf,_=workflow(root,spec['type']); sm=stage_map(wf)
139
+ ready=[]
140
+ for x in sts:
141
+ if x['status'] not in {'pending','stale'}: continue
142
+ deps=[d for d in sm[x['id']].get('depends_on',[]) if d in sm and condition_enabled(sm[d],spec)]
143
+ if all(status.get(d)=='done' for d in deps): ready.append(x)
144
+ data={"change":a.change,"ready":ready,"blocked":[x for x in sts if x['status'] in {'pending','stale'} and x not in ready]}
145
+ if a.json: emit(data)
146
+ else:
147
+ if data["ready"]:
148
+ print("ready:"); [print(f" {x['id']:<16} {x.get('artifact') or ''}") for x in data["ready"]]
149
+ else: print("no ready stages")
150
+ if data["blocked"]:
151
+ print("blocked:"); [print(f" {x['id']:<16} {x.get('artifact') or ''}") for x in data["blocked"]]
152
+ return 0 if ready or all(x['status'] in {'done','skipped'} for x in sts) else 2
153
+
154
+ def cmd_impact(a):
155
+ root=project_root(); p=change_dir(root,a.change)/"change.yaml"; spec=load_yaml(p); cfg=load_config(root)
156
+ for pair in a.set or []:
157
+ if '=' not in pair: raise CabbageError("--set expects field=true|false")
158
+ k,v=pair.split('=',1)
159
+ if k not in cfg.get('impact_fields',[]): raise CabbageError(f"unknown impact field: {k}")
160
+ if v.lower() not in {'true','false'}: raise CabbageError("impact value must be true or false")
161
+ spec.setdefault('impact',{})[k]=v.lower()=='true'
162
+ if a.set:
163
+ dump_yaml(p,spec); ensure_artifacts(root,a.change)
164
+ if a.json: emit({"change":a.change,"impact":spec.get('impact',{})})
165
+ else:
166
+ for k,v in spec.get('impact',{}).items(): print(f" {k:<12} {'Yes' if v else 'No'}")
167
+
168
+ def cmd_validate(a):
169
+ root=project_root(); errors=[]
170
+ ids=[]
171
+ if a.all:
172
+ ids=[p.name for p in changes_root(root).iterdir() if p.is_dir() and (p/'change.yaml').exists()]
173
+ elif a.change: ids=[a.change]
174
+ else: raise CabbageError("provide a change id or --all")
175
+ for cid in ids: errors.extend(f"{cid}: {e}" for e in validate_change(root,cid))
176
+ if a.json: emit({"ok":not errors,"errors":errors})
177
+ elif errors:
178
+ print("INVALID"); [print(f"- {e}") for e in errors]
179
+ else: print("VALID")
180
+ return 1 if errors else 0
181
+
182
+ def cmd_verify(a):
183
+ root=project_root(); verify_stage(root,a.change,a.stage); print(f"verified {a.change}:{a.stage}")
184
+
185
+ def cmd_sync(a):
186
+ root=project_root(); synced=sync_change_to_docs(root,a.change)
187
+ if a.json: emit({"change":a.change,"synced":synced})
188
+ else:
189
+ if synced:
190
+ print(f"synced {len(synced)} document(s) to current-state docs:")
191
+ for path in synced: print(f" {path}")
192
+ else:
193
+ print("no documents to sync")
194
+
195
+ def cmd_gate(a):
196
+ root=project_root(); errors=gate(root,a.change,a.target)
197
+ if a.json: emit({"ok":not errors,"errors":errors})
198
+ elif errors:
199
+ print("BLOCKED"); [print(f"- {e}") for e in errors]
200
+ else: print("ALLOWED")
201
+ return 1 if errors else 0
202
+
203
+ def cmd_archive(a):
204
+ root=project_root(); errors=gate(root,a.change,'archive')
205
+ if errors: raise CabbageError("archive gate blocked:\n"+'\n'.join(errors))
206
+ synced=sync_change_to_docs(root,a.change)
207
+ d=change_dir(root,a.change); spec=load_yaml(d/'change.yaml'); spec['status']='archived'; dump_yaml(d/'change.yaml',spec)
208
+ dest=archive_root(root)/datetime.now().strftime('%Y')/a.change; dest.parent.mkdir(parents=True,exist_ok=True)
209
+ if dest.exists(): raise CabbageError(f"archive destination exists: {dest}")
210
+ shutil.move(str(d),str(dest))
211
+ if synced:
212
+ print(f"synced {len(synced)} document(s) to current-state docs")
213
+ print(f"archived to {dest.relative_to(root)}")
214
+
215
+ def cmd_ci(a):
216
+ root=project_root(); errors=ci_check(root,a.base)
217
+ if errors:
218
+ print("CABBAGE CI FAILED"); [print(f"- {e}") for e in errors]; return 1
219
+ print("CABBAGE CI PASSED"); return 0
220
+
221
+ def cmd_docs(a):
222
+ root=project_root(); docs=root/load_config(root).get('docs',{}).get('dir','docs')
223
+ if a.action=='install': cmd=['pnpm','install']
224
+ elif a.action=='dev': cmd=['pnpm','run','dev']
225
+ else: cmd=['pnpm','run','build']
226
+ if shutil.which('pnpm') is None: raise CabbageError('pnpm not found')
227
+ return subprocess.call(cmd,cwd=docs)
228
+
229
+ def cmd_tasks(a):
230
+ root=project_root(); data=get_change_tasks_dag(root, a.change)
231
+ if a.json: emit(data); return 0
232
+
233
+ print(f"Task DAG for `{data['change']}`:")
234
+ print(f" Summary: {data['total_tasks']} total | {data['completed_tasks']} completed | {data['ready_tasks']} ready | {data['blocked_tasks']} blocked\n")
235
+ for grp, grp_tasks in data["parallel_groups"].items():
236
+ print(f" [{grp}]")
237
+ for t in grp_tasks:
238
+ status_tag = "[DONE] " if t["is_completed"] else ("[READY] " if t["is_ready"] else "[BLOCKED]")
239
+ blocked_info = f" (Blocked by: {', '.join(t['blocked_by'])})" if t["blocked_by"] and not t["is_completed"] else ""
240
+ print(f" {status_tag} {t['task_id']}: {t['title']}{blocked_info}")
241
+ if t["verification"] and t["verification"] != "N/A":
242
+ print(f" Verification: {t['verification']}")
243
+ print()
244
+
245
+ if not data.get("structured"):
246
+ print("Plain checklist: no declared dependencies or verification commands.")
247
+ print("Verification results belong in the record's Verification section;")
248
+ print("use `## Task <id>: <title>` sections only when parallelism or blocking matters.")
249
+ elif data["completed_tasks"] == data["total_tasks"] and data["total_tasks"] > 0:
250
+ print("All tasks in DAG completed. Ready for verification & gate.")
251
+ elif data["ready_tasks"] == 0:
252
+ print("No tasks currently ready (check prerequisites or dependencies).")
253
+ return 0
254
+
255
+ def parser():
256
+ p=argparse.ArgumentParser(prog='cabbage'); p.add_argument('--version',action='version',version=__version__); sp=p.add_subparsers(dest='cmd',required=True)
257
+ x=sp.add_parser('init'); x.add_argument('--force',action='store_true'); x.add_argument('--no-vendor-cli',action='store_true'); x.set_defaults(func=cmd_init)
258
+ x=sp.add_parser('adopt'); x.add_argument('--apply',action='store_true'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_adopt)
259
+ x=sp.add_parser('doctor'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_doctor)
260
+ x=sp.add_parser('new'); x.add_argument('type'); x.add_argument('change'); x.set_defaults(func=cmd_new)
261
+ x=sp.add_parser('discard'); x.add_argument('change'); x.set_defaults(func=cmd_discard)
262
+ x=sp.add_parser('status'); x.add_argument('change',nargs='?'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_status)
263
+ x=sp.add_parser('next'); x.add_argument('change'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_next)
264
+ x=sp.add_parser('impact'); x.add_argument('change'); x.add_argument('--set',action='append'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_impact)
265
+ x=sp.add_parser('validate'); x.add_argument('change',nargs='?'); x.add_argument('--all',action='store_true'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_validate)
266
+ x=sp.add_parser('verify'); x.add_argument('change'); x.add_argument('stage'); x.set_defaults(func=cmd_verify)
267
+ x=sp.add_parser('sync'); x.add_argument('change'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_sync)
268
+ x=sp.add_parser('gate'); x.add_argument('change'); x.add_argument('target'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_gate)
269
+ x=sp.add_parser('archive'); x.add_argument('change'); x.set_defaults(func=cmd_archive)
270
+ x=sp.add_parser('ci'); x.add_argument('--base',required=True); x.set_defaults(func=cmd_ci)
271
+ x=sp.add_parser('tasks'); x.add_argument('change'); x.add_argument('--json',action='store_true'); x.set_defaults(func=cmd_tasks)
272
+ x=sp.add_parser('docs'); x.add_argument('action',choices=['install','dev','build']); x.set_defaults(func=cmd_docs)
273
+ return p
274
+
275
+ def main(argv=None):
276
+ try:
277
+ a=parser().parse_args(argv); return a.func(a) or 0
278
+ except CabbageError as e:
279
+ print(f"cabbage: {e}",file=sys.stderr); return 2
280
+ except KeyboardInterrupt:
281
+ print("\naborted by user",file=sys.stderr); return 130
282
+
283
+ if __name__=='__main__': raise SystemExit(main())