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.
Files changed (70) hide show
  1. package/README.md +65 -20
  2. package/package.json +1 -1
  3. package/src/cli.js +109 -2
  4. package/src/commands/init.js +61 -4
  5. package/src/commands/sync.js +10 -1
  6. package/src/lib/copy-template.js +38 -7
  7. package/src/lib/platforms.js +73 -0
  8. package/template/.agent/skills/brainstorming/SKILL.md +13 -9
  9. package/template/.agent/skills/finish-work/SKILL.md +3 -3
  10. package/template/.agent/skills/meta/SKILL.md +1 -1
  11. package/template/.agent/skills/start/SKILL.md +35 -16
  12. package/template/.agent/skills/writing-plans/SKILL.md +1 -1
  13. package/template/.claude/agents/cowork-check.md +41 -0
  14. package/template/.claude/agents/cowork-implement.md +40 -0
  15. package/template/.claude/agents/cowork-research.md +40 -0
  16. package/template/.claude/commands/cowork-check.md +27 -0
  17. package/template/.claude/commands/cowork-implement.md +27 -0
  18. package/template/.claude/commands/cowork-research.md +27 -0
  19. package/template/.claude/hooks/inject-workflow-state.py +298 -0
  20. package/template/.claude/settings.json +24 -0
  21. package/template/.claude/skills/before-dev/SKILL.md +31 -0
  22. package/template/.claude/skills/brainstorming/SKILL.md +32 -0
  23. package/template/.claude/skills/break-loop/SKILL.md +26 -0
  24. package/template/.claude/skills/check/SKILL.md +29 -0
  25. package/template/.claude/skills/continue/SKILL.md +29 -0
  26. package/template/.claude/skills/finish-work/SKILL.md +32 -0
  27. package/template/.claude/skills/meta/SKILL.md +30 -0
  28. package/template/.claude/skills/python-design/SKILL.md +27 -0
  29. package/template/.claude/skills/start/SKILL.md +78 -0
  30. package/template/.claude/skills/update-spec/SKILL.md +39 -0
  31. package/template/.claude/skills/writing-plans/SKILL.md +70 -0
  32. package/template/.codex/agents/cowork-check.toml +17 -22
  33. package/template/.codex/agents/cowork-implement.toml +18 -23
  34. package/template/.codex/agents/cowork-research.toml +14 -19
  35. package/template/.codex/agents/default.toml +27 -0
  36. package/template/.codex/agents/explorer.toml +28 -0
  37. package/template/.codex/agents/worker.toml +29 -0
  38. package/template/.codex/hooks/inject-workflow-state.py +161 -108
  39. package/template/.cowork-flow/.version +1 -1
  40. package/template/.cowork-flow/adapters/claude-code/adapter.yaml +35 -0
  41. package/template/.cowork-flow/adapters/codex/adapter.yaml +35 -0
  42. package/template/.cowork-flow/adapters/opencode/adapter.yaml +33 -0
  43. package/template/.cowork-flow/config.yaml +4 -6
  44. package/template/.cowork-flow/scripts/add_session.py +20 -7
  45. package/template/.cowork-flow/scripts/common/active_task.py +212 -1
  46. package/template/.cowork-flow/scripts/common/config.py +0 -17
  47. package/template/.cowork-flow/scripts/common/entry_classifier.py +122 -0
  48. package/template/.cowork-flow/scripts/common/execution_context.py +10 -17
  49. package/template/.cowork-flow/scripts/common/readiness.py +249 -0
  50. package/template/.cowork-flow/scripts/doctor.py +418 -40
  51. package/template/.cowork-flow/scripts/project_context.py +230 -0
  52. package/template/.cowork-flow/scripts/run.py +3 -0
  53. package/template/.cowork-flow/scripts/subagent.py +152 -238
  54. package/template/.cowork-flow/scripts/task.py +330 -17
  55. package/template/.cowork-flow/spec/adapter.schema.json +132 -0
  56. package/template/.cowork-flow/spec/capabilities.md +34 -0
  57. package/template/.cowork-flow/spec/entry-contract.md +45 -0
  58. package/template/.cowork-flow/spec/registry.json +54 -0
  59. package/template/.cowork-flow/spec/subagent-dispatch.md +77 -0
  60. package/template/.cowork-flow/spec/workflow-state-templates.md +66 -0
  61. package/template/.cowork-flow/workflow.md +158 -203
  62. package/template/.opencode/agents/cowork-check.md +46 -0
  63. package/template/.opencode/agents/cowork-implement.md +45 -0
  64. package/template/.opencode/agents/cowork-research.md +46 -0
  65. package/template/.opencode/commands/cowork-check.md +26 -0
  66. package/template/.opencode/commands/cowork-implement.md +26 -0
  67. package/template/.opencode/commands/cowork-research.md +26 -0
  68. package/template/.opencode/plugins/cowork-flow.js +317 -0
  69. package/template/CLAUDE.md +5 -0
  70. 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