cowork-flow 0.0.20 → 0.0.21
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 +65 -20
- package/package.json +1 -1
- package/src/cli.js +109 -2
- package/src/commands/init.js +61 -4
- package/src/commands/sync.js +10 -1
- package/src/lib/copy-template.js +38 -7
- package/src/lib/platforms.js +73 -0
- package/template/.agent/skills/brainstorming/SKILL.md +13 -9
- package/template/.agent/skills/finish-work/SKILL.md +3 -3
- package/template/.agent/skills/meta/SKILL.md +1 -1
- package/template/.agent/skills/start/SKILL.md +35 -16
- package/template/.agent/skills/writing-plans/SKILL.md +1 -1
- package/template/.claude/agents/cowork-check.md +41 -0
- package/template/.claude/agents/cowork-implement.md +40 -0
- package/template/.claude/agents/cowork-research.md +40 -0
- package/template/.claude/commands/cowork-check.md +27 -0
- package/template/.claude/commands/cowork-implement.md +27 -0
- package/template/.claude/commands/cowork-research.md +27 -0
- package/template/.claude/hooks/inject-workflow-state.py +298 -0
- package/template/.claude/settings.json +24 -0
- package/template/.claude/skills/before-dev/SKILL.md +31 -0
- package/template/.claude/skills/brainstorming/SKILL.md +32 -0
- package/template/.claude/skills/break-loop/SKILL.md +26 -0
- package/template/.claude/skills/check/SKILL.md +29 -0
- package/template/.claude/skills/continue/SKILL.md +29 -0
- package/template/.claude/skills/finish-work/SKILL.md +32 -0
- package/template/.claude/skills/meta/SKILL.md +30 -0
- package/template/.claude/skills/python-design/SKILL.md +27 -0
- package/template/.claude/skills/start/SKILL.md +78 -0
- package/template/.claude/skills/update-spec/SKILL.md +39 -0
- package/template/.claude/skills/writing-plans/SKILL.md +70 -0
- package/template/.codex/agents/cowork-check.toml +17 -22
- package/template/.codex/agents/cowork-implement.toml +18 -23
- package/template/.codex/agents/cowork-research.toml +14 -19
- package/template/.codex/agents/default.toml +27 -0
- package/template/.codex/agents/explorer.toml +28 -0
- package/template/.codex/agents/worker.toml +29 -0
- package/template/.codex/hooks/inject-workflow-state.py +161 -108
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/claude-code/adapter.yaml +35 -0
- package/template/.cowork-flow/adapters/codex/adapter.yaml +35 -0
- package/template/.cowork-flow/adapters/opencode/adapter.yaml +33 -0
- package/template/.cowork-flow/config.yaml +4 -6
- package/template/.cowork-flow/scripts/add_session.py +20 -7
- package/template/.cowork-flow/scripts/common/active_task.py +212 -1
- package/template/.cowork-flow/scripts/common/config.py +0 -17
- package/template/.cowork-flow/scripts/common/entry_classifier.py +122 -0
- package/template/.cowork-flow/scripts/common/execution_context.py +10 -17
- package/template/.cowork-flow/scripts/common/readiness.py +249 -0
- package/template/.cowork-flow/scripts/doctor.py +418 -40
- package/template/.cowork-flow/scripts/project_context.py +230 -0
- package/template/.cowork-flow/scripts/run.py +3 -0
- package/template/.cowork-flow/scripts/subagent.py +152 -238
- package/template/.cowork-flow/scripts/task.py +330 -17
- package/template/.cowork-flow/spec/adapter.schema.json +132 -0
- package/template/.cowork-flow/spec/capabilities.md +34 -0
- package/template/.cowork-flow/spec/entry-contract.md +45 -0
- package/template/.cowork-flow/spec/registry.json +54 -0
- package/template/.cowork-flow/spec/subagent-dispatch.md +77 -0
- package/template/.cowork-flow/spec/workflow-state-templates.md +66 -0
- package/template/.cowork-flow/workflow.md +158 -203
- package/template/.opencode/agents/cowork-check.md +46 -0
- package/template/.opencode/agents/cowork-implement.md +45 -0
- package/template/.opencode/agents/cowork-research.md +46 -0
- package/template/.opencode/commands/cowork-check.md +26 -0
- package/template/.opencode/commands/cowork-implement.md +26 -0
- package/template/.opencode/commands/cowork-research.md +26 -0
- package/template/.opencode/plugins/cowork-flow.js +317 -0
- package/template/CLAUDE.md +5 -0
- package/template/.agent/skills/entry-boundary/SKILL.md +0 -81
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate and refresh .cowork-flow/project-context.md."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from common.paths import DIR_WORKFLOW, get_repo_root
|
|
13
|
+
|
|
14
|
+
PROJECT_CONTEXT_FILE = "project-context.md"
|
|
15
|
+
GENERATED_START = "<!-- COWORK-FLOW:PROJECT-CONTEXT:START -->"
|
|
16
|
+
GENERATED_END = "<!-- COWORK-FLOW:PROJECT-CONTEXT:END -->"
|
|
17
|
+
MANUAL_HEADING = "## Manual Notes"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _read_text(path: Path) -> str:
|
|
21
|
+
try:
|
|
22
|
+
return path.read_text(encoding="utf-8")
|
|
23
|
+
except (OSError, UnicodeDecodeError):
|
|
24
|
+
return ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _read_json(path: Path) -> dict:
|
|
28
|
+
try:
|
|
29
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
30
|
+
except (OSError, json.JSONDecodeError):
|
|
31
|
+
return {}
|
|
32
|
+
return data if isinstance(data, dict) else {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _display(path: Path, repo_root: Path) -> str:
|
|
36
|
+
try:
|
|
37
|
+
return path.resolve().relative_to(repo_root.resolve()).as_posix()
|
|
38
|
+
except ValueError:
|
|
39
|
+
return str(path)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _status_line(repo_root: Path, rel_path: str) -> str:
|
|
43
|
+
path = repo_root / rel_path
|
|
44
|
+
return f"- `{rel_path}`: {'present' if path.exists() else 'missing'}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _extract_backtick_value(text: str, label: str) -> str | None:
|
|
48
|
+
pattern = re.compile(rf"-\s*{re.escape(label)}\s*[::]\s*`([^`]+)`")
|
|
49
|
+
match = pattern.search(text)
|
|
50
|
+
if match:
|
|
51
|
+
return match.group(1).strip()
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _project_identity(repo_root: Path) -> list[str]:
|
|
56
|
+
agents = _read_text(repo_root / "AGENTS.md")
|
|
57
|
+
package = _read_json(repo_root / "package.json")
|
|
58
|
+
name = _extract_backtick_value(agents, "项目名称") or str(package.get("name") or "unknown")
|
|
59
|
+
stack = _extract_backtick_value(agents, "主要技术栈") or "unspecified"
|
|
60
|
+
commit_policy = _extract_backtick_value(agents, "提交策略") or "unspecified"
|
|
61
|
+
doc_language = _extract_backtick_value(agents, "文档语言") or "unspecified"
|
|
62
|
+
return [
|
|
63
|
+
"## Project Identity",
|
|
64
|
+
"",
|
|
65
|
+
f"- Name: `{name}`",
|
|
66
|
+
f"- Stack: {stack}",
|
|
67
|
+
f"- Commit policy: {commit_policy}",
|
|
68
|
+
f"- Documentation language: {doc_language}",
|
|
69
|
+
"",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _package_scripts(repo_root: Path) -> list[str]:
|
|
74
|
+
package_path = repo_root / "package.json"
|
|
75
|
+
package = _read_json(package_path)
|
|
76
|
+
lines = ["## Package Scripts", ""]
|
|
77
|
+
if not package_path.is_file():
|
|
78
|
+
lines.extend(["- package.json: missing", ""])
|
|
79
|
+
return lines
|
|
80
|
+
|
|
81
|
+
scripts = package.get("scripts")
|
|
82
|
+
if not isinstance(scripts, dict) or not scripts:
|
|
83
|
+
lines.extend(["- No package scripts declared.", ""])
|
|
84
|
+
return lines
|
|
85
|
+
|
|
86
|
+
for name in sorted(str(key) for key in scripts.keys()):
|
|
87
|
+
value = scripts.get(name)
|
|
88
|
+
if isinstance(value, str):
|
|
89
|
+
lines.append(f"- `npm run {name}`: `{value}`")
|
|
90
|
+
lines.append("")
|
|
91
|
+
return lines
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _workflow_commands() -> list[str]:
|
|
95
|
+
return [
|
|
96
|
+
"## Workflow Commands",
|
|
97
|
+
"",
|
|
98
|
+
"- `./.cowork-flow/run task next`: show next safe workflow action.",
|
|
99
|
+
"- `./.cowork-flow/run task start <task-dir>`: activate task and run readiness gates.",
|
|
100
|
+
"- `./.cowork-flow/run task review [task-dir]`: move task to check stage.",
|
|
101
|
+
"- `./.cowork-flow/run task complete [task-dir]`: mark task completed.",
|
|
102
|
+
"- `./.cowork-flow/run change validate <slug>`: validate change metadata and required docs.",
|
|
103
|
+
"- `./.cowork-flow/run project-context refresh`: refresh this file.",
|
|
104
|
+
"",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _host_adapters(repo_root: Path) -> list[str]:
|
|
109
|
+
lines = ["## Host Adapters", ""]
|
|
110
|
+
for rel_path in (
|
|
111
|
+
".codex",
|
|
112
|
+
".opencode",
|
|
113
|
+
".claude",
|
|
114
|
+
".cowork-flow/adapters/codex/adapter.yaml",
|
|
115
|
+
".cowork-flow/adapters/opencode/adapter.yaml",
|
|
116
|
+
".cowork-flow/adapters/claude-code/adapter.yaml",
|
|
117
|
+
):
|
|
118
|
+
lines.append(_status_line(repo_root, rel_path))
|
|
119
|
+
lines.append("")
|
|
120
|
+
return lines
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _important_specs(repo_root: Path) -> list[str]:
|
|
124
|
+
lines = ["## Important Specs", ""]
|
|
125
|
+
spec_dir = repo_root / DIR_WORKFLOW / "spec"
|
|
126
|
+
if not spec_dir.is_dir():
|
|
127
|
+
lines.extend(["- `.cowork-flow/spec`: missing", ""])
|
|
128
|
+
return lines
|
|
129
|
+
|
|
130
|
+
for path in sorted(spec_dir.rglob("*.md")):
|
|
131
|
+
lines.append(f"- `{_display(path, repo_root)}`")
|
|
132
|
+
lines.append("")
|
|
133
|
+
return lines
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _local_constraints(repo_root: Path) -> list[str]:
|
|
137
|
+
lines = ["## Local Constraints", ""]
|
|
138
|
+
for rel_path in (
|
|
139
|
+
"AGENTS.md",
|
|
140
|
+
".cowork-flow/workflow.md",
|
|
141
|
+
".cowork-flow/config.yaml",
|
|
142
|
+
".cowork-flow/.version",
|
|
143
|
+
):
|
|
144
|
+
lines.append(_status_line(repo_root, rel_path))
|
|
145
|
+
lines.extend(
|
|
146
|
+
[
|
|
147
|
+
"- Generated context is an index. Authoritative rules stay in `AGENTS.md`, `.cowork-flow/workflow.md`, and `.cowork-flow/spec/`.",
|
|
148
|
+
"- Do not hand-edit generated sections; add durable project notes under Manual Notes.",
|
|
149
|
+
"",
|
|
150
|
+
]
|
|
151
|
+
)
|
|
152
|
+
return lines
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _render_generated(repo_root: Path) -> str:
|
|
156
|
+
lines: list[str] = [
|
|
157
|
+
GENERATED_START,
|
|
158
|
+
"## Generated Context",
|
|
159
|
+
"",
|
|
160
|
+
"Generated deterministically from local project files.",
|
|
161
|
+
"",
|
|
162
|
+
]
|
|
163
|
+
for section in (
|
|
164
|
+
_project_identity(repo_root),
|
|
165
|
+
_package_scripts(repo_root),
|
|
166
|
+
_workflow_commands(),
|
|
167
|
+
_host_adapters(repo_root),
|
|
168
|
+
_important_specs(repo_root),
|
|
169
|
+
_local_constraints(repo_root),
|
|
170
|
+
):
|
|
171
|
+
lines.extend(section)
|
|
172
|
+
lines.append(GENERATED_END)
|
|
173
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _extract_manual_notes(existing: str) -> str:
|
|
177
|
+
marker = f"\n{MANUAL_HEADING}"
|
|
178
|
+
index = existing.find(marker)
|
|
179
|
+
if index >= 0:
|
|
180
|
+
manual = existing[index + 1 :].rstrip()
|
|
181
|
+
if manual:
|
|
182
|
+
return manual + "\n"
|
|
183
|
+
if existing.startswith(MANUAL_HEADING):
|
|
184
|
+
return existing.rstrip() + "\n"
|
|
185
|
+
return f"{MANUAL_HEADING}\n\n- Add project-specific notes here.\n"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def render_project_context(repo_root: Path, existing: str = "") -> str:
|
|
189
|
+
manual_notes = _extract_manual_notes(existing)
|
|
190
|
+
return (
|
|
191
|
+
"# Project Context\n\n"
|
|
192
|
+
f"{_render_generated(repo_root)}\n"
|
|
193
|
+
f"{manual_notes}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def refresh_project_context(repo_root: Path) -> Path:
|
|
198
|
+
path = repo_root / DIR_WORKFLOW / PROJECT_CONTEXT_FILE
|
|
199
|
+
existing = _read_text(path)
|
|
200
|
+
path.write_text(render_project_context(repo_root, existing), encoding="utf-8")
|
|
201
|
+
return path
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def cmd_refresh(args: argparse.Namespace) -> int:
|
|
205
|
+
repo_root = get_repo_root()
|
|
206
|
+
path = refresh_project_context(repo_root)
|
|
207
|
+
print(f"refreshed {_display(path, repo_root)}")
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
212
|
+
parser = argparse.ArgumentParser(description="Generate cowork-flow project context")
|
|
213
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
214
|
+
|
|
215
|
+
refresh = subparsers.add_parser("refresh", help="Refresh .cowork-flow/project-context.md")
|
|
216
|
+
refresh.set_defaults(func=cmd_refresh)
|
|
217
|
+
|
|
218
|
+
return parser
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def main(argv: list[str] | None = None) -> int:
|
|
222
|
+
parser = build_parser()
|
|
223
|
+
args = parser.parse_args(argv)
|
|
224
|
+
if not args.command:
|
|
225
|
+
args = parser.parse_args(["refresh"])
|
|
226
|
+
return args.func(args)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
if __name__ == "__main__":
|
|
230
|
+
sys.exit(main())
|
|
@@ -28,6 +28,8 @@ COMMAND_SCRIPTS = {
|
|
|
28
28
|
"add_session": "add_session.py",
|
|
29
29
|
"subagent": "subagent.py",
|
|
30
30
|
"doctor": "doctor.py",
|
|
31
|
+
"project-context": "project_context.py",
|
|
32
|
+
"project_context": "project_context.py",
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
CONTEXT_AWARE_COMMANDS = {"resume", "task", "subagent"}
|
|
@@ -51,6 +53,7 @@ Common commands:
|
|
|
51
53
|
add-session
|
|
52
54
|
subagent
|
|
53
55
|
doctor
|
|
56
|
+
project-context
|
|
54
57
|
""".rstrip()
|
|
55
58
|
)
|
|
56
59
|
|