cowork-flow 0.0.28 → 0.0.29

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 (63) hide show
  1. package/README.md +40 -12
  2. package/package.json +1 -1
  3. package/src/commands/init.js +1 -1
  4. package/src/commands/update.js +2 -4
  5. package/src/lib/copy-template.js +50 -3
  6. package/src/lib/platforms.js +0 -3
  7. package/template/.claude/agents/cowork-check.md +3 -3
  8. package/template/.claude/agents/cowork-implement.md +3 -3
  9. package/template/.claude/agents/cowork-research.md +3 -3
  10. package/template/.claude/hooks/inject-workflow-state.py +2 -11
  11. package/template/.codex/hooks/inject-workflow-state.py +3 -12
  12. package/template/.cowork-flow/.version +1 -1
  13. package/template/.cowork-flow/adapters/claude-code/adapter.yaml +0 -1
  14. package/template/.cowork-flow/adapters/codex/adapter.yaml +0 -1
  15. package/template/.cowork-flow/adapters/opencode/adapter.yaml +0 -1
  16. package/template/.cowork-flow/scripts/commands/__init__.py +1 -0
  17. package/template/.cowork-flow/scripts/commands/_bootstrap.py +10 -0
  18. package/template/.cowork-flow/scripts/{add_session.py → commands/add_session.py} +8 -10
  19. package/template/.cowork-flow/scripts/{change.py → commands/change.py} +6 -2
  20. package/template/.cowork-flow/scripts/{doctor.py → commands/doctor.py} +11 -42
  21. package/template/.cowork-flow/scripts/{get_context.py → commands/get_context.py} +5 -1
  22. package/template/.cowork-flow/scripts/{get_developer.py → commands/get_developer.py} +6 -2
  23. package/template/.cowork-flow/scripts/{init_developer.py → commands/init_developer.py} +6 -2
  24. package/template/.cowork-flow/scripts/{party_mode_v2.py → commands/party_mode_v2.py} +6 -2
  25. package/template/.cowork-flow/scripts/{resume.py → commands/resume.py} +6 -2
  26. package/template/.cowork-flow/scripts/{subagent.py → commands/subagent.py} +7 -3
  27. package/template/.cowork-flow/scripts/{task.py → commands/task.py} +34 -29
  28. package/template/.cowork-flow/scripts/common/__init__.py +1 -1
  29. package/template/.cowork-flow/scripts/common/core/__init__.py +1 -0
  30. package/template/.cowork-flow/scripts/common/{developer.py → core/developer.py} +1 -1
  31. package/template/.cowork-flow/scripts/common/gates/__init__.py +1 -0
  32. package/template/.cowork-flow/scripts/common/{coding_standards.py → gates/coding_standards.py} +3 -2
  33. package/template/.cowork-flow/scripts/common/{tdd_evidence.py → gates/tdd_evidence.py} +75 -11
  34. package/template/.cowork-flow/scripts/common/{test_intent.py → gates/test_intent.py} +2 -1
  35. package/template/.cowork-flow/scripts/common/{validate_coding_standards.py → gates/validate_coding_standards.py} +1 -1
  36. package/template/.cowork-flow/scripts/common/{validate_rules.py → gates/validate_rules.py} +1 -1
  37. package/template/.cowork-flow/scripts/common/git/__init__.py +1 -0
  38. package/template/.cowork-flow/scripts/common/{git_context.py → git/git_context.py} +3 -3
  39. package/template/.cowork-flow/scripts/common/task/__init__.py +1 -0
  40. package/template/.cowork-flow/scripts/common/{active_task.py → task/active_task.py} +1 -1
  41. package/template/.cowork-flow/scripts/common/{readiness.py → task/readiness.py} +1 -1
  42. package/template/.cowork-flow/scripts/common/{task_utils.py → task/task_utils.py} +2 -2
  43. package/template/.cowork-flow/scripts/run.py +32 -25
  44. package/template/.cowork-flow/spec/contracts/index.md +0 -1
  45. package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -12
  46. package/template/.cowork-flow/spec/schemas/adapter.schema.json +1 -5
  47. package/template/.cowork-flow/workflow.md +7 -7
  48. package/template/.opencode/agents/cowork-check.md +3 -3
  49. package/template/.opencode/agents/cowork-implement.md +3 -3
  50. package/template/.opencode/agents/cowork-research.md +3 -3
  51. package/template/.opencode/plugins/cowork-flow.js +0 -9
  52. package/template/AGENTS.md +4 -3
  53. package/template/.cowork-flow/scripts/project_context.py +0 -230
  54. package/template/.cowork-flow/spec/contracts/entry-contract.md +0 -45
  55. /package/template/.cowork-flow/scripts/common/{config.py → core/config.py} +0 -0
  56. /package/template/.cowork-flow/scripts/common/{execution_context.py → core/execution_context.py} +0 -0
  57. /package/template/.cowork-flow/scripts/common/{files.py → core/files.py} +0 -0
  58. /package/template/.cowork-flow/scripts/common/{paths.py → core/paths.py} +0 -0
  59. /package/template/.cowork-flow/scripts/common/{gates.py → gates/gates.py} +0 -0
  60. /package/template/.cowork-flow/scripts/common/{validate_implementation.py → gates/validate_implementation.py} +0 -0
  61. /package/template/.cowork-flow/scripts/common/{git_snapshot.py → git/git_snapshot.py} +0 -0
  62. /package/template/.cowork-flow/scripts/common/{archive_utils.py → task/archive_utils.py} +0 -0
  63. /package/template/.cowork-flow/scripts/common/{state_machine.py → task/state_machine.py} +0 -0
package/README.md CHANGED
@@ -99,7 +99,7 @@ OpenCode 项目级固定 agent、slash command 和系统上下文注入插件。
99
99
  npx cowork-flow init ./my-project --platform codex
100
100
  ```
101
101
 
102
- `init` 会显式要求平台和开发者身份。平台可选 `codex`、`opencode`、`claude-code`、`all`,其中 `both` 仍作为兼容别名表示 `codex,opencode`。交互式终端会提示输入;脚本或 CI 请传入:
102
+ `init` 会显式要求平台和开发者身份。平台可选 `codex`、`opencode`、`claude-code`、`all`。交互式终端会提示输入;脚本或 CI 请传入:
103
103
 
104
104
  ```bash
105
105
  npx cowork-flow init ./my-project --platform codex --developer <your-name>
@@ -134,7 +134,7 @@ npx cowork-flow --help
134
134
  cowork-flow init ./my-project --platform codex
135
135
  ```
136
136
 
137
- `init` 会直接复制模板中的通用 `.cowork-flow/`,并按 `--platform` 只复制对应 host 资产:`codex` 复制 `.codex/`、`.agents/skills/` 与 `.cowork-flow/adapters/codex/`,`opencode` 复制 `.opencode/`、`.agents/skills/` 与 `.cowork-flow/adapters/opencode/`,`claude-code` 复制 `CLAUDE.md`、`.claude/`(含 `.claude/settings.json`、`.claude/hooks/`、`.claude/skills/`)与 `.cowork-flow/adapters/claude-code/`,不复制 `.agents/skills/`。`all` 或 `--platform codex,opencode,claude-code` 同时复制三者和 `.agents/skills/`。`both` 保持兼容,只复制 Codex 和 OpenCode。它还会初始化 `.cowork-flow/.developer` 与 `.cowork-flow/workspace/<developer>/`。交互式终端会先显示 checkbox 多选界面选择平台,再提示开发者名称;非交互式环境必须传入 `--platform <codex|opencode|claude-code|all>` 与 `--developer <name>`。`init` 不会复制额外技能包。
137
+ `init` 会直接复制模板中的通用 `.cowork-flow/`,并按 `--platform` 只复制对应 host 资产:`codex` 复制 `.codex/`、`.agents/skills/` 与 `.cowork-flow/adapters/codex/`,`opencode` 复制 `.opencode/`、`.agents/skills/` 与 `.cowork-flow/adapters/opencode/`,`claude-code` 复制 `CLAUDE.md`、`.claude/`(含 `.claude/settings.json`、`.claude/hooks/`、`.claude/skills/`)与 `.cowork-flow/adapters/claude-code/`,不复制 `.agents/skills/`。`all` 或 `--platform codex,opencode,claude-code` 同时复制三者和 `.agents/skills/`。它还会初始化 `.cowork-flow/.developer` 与 `.cowork-flow/workspace/<developer>/`。交互式终端会先显示 checkbox 多选界面选择平台,再提示开发者名称;非交互式环境必须传入 `--platform <codex|opencode|claude-code|all>` 与 `--developer <name>`。`init` 不会复制额外技能包。
138
138
 
139
139
  初始化到当前项目:
140
140
 
@@ -185,6 +185,16 @@ cowork-flow sync . --dry-run
185
185
  下面示例使用 macOS / Linux 写法;Windows 原生命令行中把
186
186
  `./.cowork-flow/run` 替换为 `.\.cowork-flow\run.cmd`。
187
187
 
188
+ 当前主流程以 `.cowork-flow/workflow.md` 为准。默认路径是:
189
+
190
+ ```text
191
+ changes -> brainstorming -> read spec -> plan -> tasks -> implement -> check -> complete
192
+ ```
193
+
194
+ L0 文档、格式、小范围重构或测试补充可简化为
195
+ `brainstorming -> read spec -> implement -> check -> complete`;L1/L2 在完成后继续
196
+ `archive -> add session`。
197
+
188
198
  初始化或查看开发者身份:
189
199
 
190
200
  ```bash
@@ -196,7 +206,6 @@ cowork-flow sync . --dry-run
196
206
 
197
207
  ```bash
198
208
  ./.cowork-flow/run get-context
199
- ./.cowork-flow/run project-context refresh
200
209
  ./.cowork-flow/run task list
201
210
  ./.cowork-flow/run task next
202
211
  ```
@@ -216,18 +225,35 @@ cowork-flow sync . --dry-run
216
225
  ./.cowork-flow/run task start <task-dir>
217
226
  ```
218
227
 
219
- `task next` is read-only. It reports the active task, status, blockers, and the
220
- next safe command before the main session starts work, dispatches fixed agents,
221
- checks, archives, or records the session.
222
-
223
- `project-context refresh` 会创建或刷新 `.cowork-flow/project-context.md`。生成块由
224
- 本地项目文件确定,`Manual Notes` 区保留人工补充;该文件是项目索引,不替代
225
- `AGENTS.md`、`.cowork-flow/workflow.md` 或 `.cowork-flow/spec/`。
228
+ `task next` 只读,不推进状态。它根据当前会话任务和 `task.json.status`
229
+ 报告当前任务、阻塞项和下一条安全命令;主会话在启动工作、派发固定代理、
230
+ 进入检查、归档或记录 session 前都应先看它。
226
231
 
227
232
  L2 任务的 readiness gate 会在 `task start` 前阻塞缺失的 proposal/spec/design、
228
233
  计划、任务链接、边界、假设、验收标准或 verification commands;同一 blocker
229
234
  会出现在 `task next` 输出中。
230
235
 
236
+ 任务状态流转:
237
+
238
+ ```mermaid
239
+ stateDiagram-v2
240
+ [*] --> NoTask
241
+ state "无当前任务" as NoTask
242
+ state "planning" as Planning
243
+ state "in_progress" as InProgress
244
+ state "review" as Review
245
+ state "completed" as Completed
246
+ state "已归档" as Archived
247
+
248
+ NoTask --> Planning: task create
249
+ Planning --> InProgress: task start
250
+ InProgress --> Review: task review
251
+ Review --> Completed: task complete
252
+ Completed --> Archived: task archive
253
+ Completed --> NoTask: task finish
254
+ Archived --> [*]
255
+ ```
256
+
231
257
  任务状态由阶段命令推进,`task next` 只读取状态:
232
258
 
233
259
  | 阶段 | 命令 | `task.json.status` |
@@ -236,11 +262,13 @@ L2 任务的 readiness gate 会在 `task start` 前阻塞缺失的 proposal/spec
236
262
  | 开始执行 | `task start <task-dir>` | `in_progress` |
237
263
  | 进入检查 | `task review [task-dir]` | `review` |
238
264
  | 检查完成 | `task complete [task-dir]` | `completed` |
265
+ | 归档任务 | `task archive <task-name>` | 归档副本保持 `completed` |
239
266
  | 清会话指针 | `task finish` | 不改变状态 |
240
267
 
241
268
  执行 plan 时使用固定 cowork agents:
242
269
 
243
- 主会话负责创建/启动任务、维护计划与收口验证;可并行的实现或检查工作通过 Codex subagent 交给固定角色 agent。
270
+ 主会话负责创建/启动任务、维护计划与收口验证;实现和检查默认通过当前 Host Adapter
271
+ 派发给固定角色 agent。固定 agent 是叶子执行者,不能再派发其他 agent,也不能替代主会话完成归档、记录 session 或提交。
244
272
 
245
273
  ```text
246
274
  ./.cowork-flow/run subagent init --role implement --agent-type cowork-implement --execution-task-dir <task-dir> --title "<title>"
@@ -255,7 +283,7 @@ cowork_host_context_key: <host_context_key>
255
283
 
256
284
  子代理首步执行 `./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>`;主会话验收前必须确认 runtime context 中 `status=bound` 且 `bound_context_key` 匹配。默认角色为 `cowork-research`、`cowork-implement`、`cowork-check`,任务上下文来自已绑定 runtime context,而不是 prompt 形状。
257
285
 
258
- Codex hook 启用后,每轮会自动注入 `<workflow-state>`,其中包含入口分类、当前任务、状态来源和下一步流程提示。状态文本来自 `.cowork-flow/spec/contracts/workflow-state-templates.md`;`workflow.md` 只描述流程,不内联状态片段。hook 不替代主会话验收;它只负责把 task 状态和 workflow gate 放进上下文。
286
+ Host hook/plugin 启用后,每轮会自动注入 workflow state,其中包含入口分类、当前任务、状态来源和下一步流程提示。状态文本来自 `.cowork-flow/spec/contracts/workflow-state-templates.md`;`workflow.md` 只描述流程,不内联状态片段。hook 不替代主会话验收;它只负责把 task 状态和 workflow gate 放进上下文。
259
287
 
260
288
  记录 session:
261
289
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -195,7 +195,7 @@ function initialIndexContent(name) {
195
195
  - Sessions are appended to the journal file
196
196
  - A new journal file is created automatically after the current file exceeds 2000 lines
197
197
  - Use \`add_session.py\` to record sessions
198
- - New records use English text; legacy records can remain as they are
198
+ - New records use English text; existing records can remain as they are
199
199
  `;
200
200
  }
201
201
 
@@ -6,10 +6,8 @@ import {
6
6
  } from '../lib/package-info.js';
7
7
 
8
8
  function validateUpdateArgs(args) {
9
- for (const arg of args) {
10
- if (arg !== '--global' && arg !== '--yes') {
11
- throw new Error(`Unknown update option: ${arg}`);
12
- }
9
+ if (args.length > 0) {
10
+ throw new Error(`Unknown update option: ${args[0]}`);
13
11
  }
14
12
  }
15
13
 
@@ -1,5 +1,5 @@
1
1
  import { constants } from 'node:fs';
2
- import { access, chmod, copyFile, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
2
+ import { access, chmod, copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join, relative } from 'node:path';
4
4
 
5
5
  import { templateRoot } from './paths.js';
@@ -99,6 +99,41 @@ const SAFE_SYNC_FILES = new Set([
99
99
  '.cowork-flow/spec/contracts/workflow-state-templates.md'
100
100
  ]);
101
101
 
102
+ const OBSOLETE_SYNC_FILES = new Set([
103
+ '.cowork-flow/project-context.md',
104
+ '.cowork-flow/scripts/add_session.py',
105
+ '.cowork-flow/scripts/change.py',
106
+ '.cowork-flow/scripts/doctor.py',
107
+ '.cowork-flow/scripts/get_context.py',
108
+ '.cowork-flow/scripts/get_developer.py',
109
+ '.cowork-flow/scripts/init_developer.py',
110
+ '.cowork-flow/scripts/party_mode_v2.py',
111
+ '.cowork-flow/scripts/project_context.py',
112
+ '.cowork-flow/scripts/resume.py',
113
+ '.cowork-flow/scripts/subagent.py',
114
+ '.cowork-flow/scripts/task.py',
115
+ '.cowork-flow/scripts/common/active_task.py',
116
+ '.cowork-flow/scripts/common/archive_utils.py',
117
+ '.cowork-flow/scripts/common/coding_standards.py',
118
+ '.cowork-flow/scripts/common/config.py',
119
+ '.cowork-flow/scripts/common/developer.py',
120
+ '.cowork-flow/scripts/common/execution_context.py',
121
+ '.cowork-flow/scripts/common/files.py',
122
+ '.cowork-flow/scripts/common/gates.py',
123
+ '.cowork-flow/scripts/common/git_context.py',
124
+ '.cowork-flow/scripts/common/git_snapshot.py',
125
+ '.cowork-flow/scripts/common/paths.py',
126
+ '.cowork-flow/scripts/common/readiness.py',
127
+ '.cowork-flow/scripts/common/state_machine.py',
128
+ '.cowork-flow/scripts/common/task_utils.py',
129
+ '.cowork-flow/scripts/common/tdd_evidence.py',
130
+ '.cowork-flow/scripts/common/test_intent.py',
131
+ '.cowork-flow/scripts/common/validate_coding_standards.py',
132
+ '.cowork-flow/scripts/common/validate_implementation.py',
133
+ '.cowork-flow/scripts/common/validate_rules.py',
134
+ '.cowork-flow/spec/contracts/entry-contract.md'
135
+ ]);
136
+
102
137
  const COWORK_FLOW_START = '<!-- COWORK-FLOW:START -->';
103
138
  const COWORK_FLOW_END = '<!-- COWORK-FLOW:END -->';
104
139
 
@@ -200,6 +235,13 @@ export async function buildSyncPlan(targetDir, options = {}) {
200
235
  }
201
236
  }
202
237
 
238
+ for (const file of OBSOLETE_SYNC_FILES) {
239
+ const destination = join(targetDir, file);
240
+ if (await pathExists(destination)) {
241
+ actions.push({ action: 'delete', source: null, destination, relativePath: file });
242
+ }
243
+ }
244
+
203
245
  actions.push({
204
246
  action: 'update',
205
247
  source: null,
@@ -226,7 +268,7 @@ export async function detectInstalledPlatforms(targetDir) {
226
268
  }
227
269
 
228
270
  export function summarizePlan(actions, dryRun = false) {
229
- const counts = { create: 0, update: 0, skip: 0, protected: 0 };
271
+ const counts = { create: 0, update: 0, skip: 0, protected: 0, delete: 0 };
230
272
  for (const action of actions) {
231
273
  counts[action.action] += 1;
232
274
  }
@@ -234,7 +276,8 @@ export function summarizePlan(actions, dryRun = false) {
234
276
  const prefix = dryRun ? 'dry-run ' : '';
235
277
  const createLabel = dryRun ? 'would-create' : 'created';
236
278
  const updateLabel = dryRun ? 'would-update' : 'updated';
237
- return `${prefix}${createLabel}=${counts.create} ${updateLabel}=${counts.update} skipped=${counts.skip} protected=${counts.protected}\n`;
279
+ const deleteLabel = dryRun ? 'would-delete' : 'deleted';
280
+ return `${prefix}${createLabel}=${counts.create} ${updateLabel}=${counts.update} ${deleteLabel}=${counts.delete} skipped=${counts.skip} protected=${counts.protected}\n`;
238
281
  }
239
282
 
240
283
  export async function applyPlan(actions, options = {}) {
@@ -246,6 +289,10 @@ export async function applyPlan(actions, options = {}) {
246
289
  if (item.action === 'skip' || item.action === 'protected') {
247
290
  continue;
248
291
  }
292
+ if (item.action === 'delete') {
293
+ await rm(item.destination, { force: true });
294
+ continue;
295
+ }
249
296
 
250
297
  await mkdir(dirname(item.destination), { recursive: true });
251
298
  if (item.content !== undefined) {
@@ -1,10 +1,7 @@
1
1
  export const SUPPORTED_PLATFORMS = ['codex', 'opencode', 'claude-code'];
2
2
 
3
- const LEGACY_BOTH_PLATFORMS = ['codex', 'opencode'];
4
-
5
3
  const PLATFORM_ALIASES = new Map([
6
4
  ['all', SUPPORTED_PLATFORMS],
7
- ['both', LEGACY_BOTH_PLATFORMS],
8
5
  ['codex', ['codex']],
9
6
  ['opencode', ['opencode']],
10
7
  ['claude', ['claude-code']],
@@ -24,9 +24,9 @@ state is injected. The first child step must still run:
24
24
  ```
25
25
 
26
26
  If the explicit bind fails, or if the bound context is missing, closed, invalid,
27
- or names another agent type, report `needs_context` and stop. Do not use
28
- `COWORK_ENTRY_CONTRACT_V1` to infer subagent identity; that contract classifies
29
- main-session prompts only.
27
+ or names another agent type, report `needs_context` and stop. Do not infer
28
+ subagent identity from prompt shape; runtime context binding is the only formal
29
+ signal.
30
30
 
31
31
  Rules:
32
32
 
@@ -24,9 +24,9 @@ state is injected. The first child step must still run:
24
24
  ```
25
25
 
26
26
  If the explicit bind fails, or if the bound context is missing, closed, invalid,
27
- or names another agent type, report `needs_context` and stop. Do not use
28
- `COWORK_ENTRY_CONTRACT_V1` to infer subagent identity; that contract classifies
29
- main-session prompts only.
27
+ or names another agent type, report `needs_context` and stop. Do not infer
28
+ subagent identity from prompt shape; runtime context binding is the only formal
29
+ signal.
30
30
 
31
31
  ---
32
32
 
@@ -24,9 +24,9 @@ state is injected. The first child step must still run:
24
24
  ```
25
25
 
26
26
  If the explicit bind fails, or if the bound context is missing, closed, invalid,
27
- or names another agent type, report `needs_context` and stop. Do not use
28
- `COWORK_ENTRY_CONTRACT_V1` to infer subagent identity; that contract classifies
29
- main-session prompts only.
27
+ or names another agent type, report `needs_context` and stop. Do not infer
28
+ subagent identity from prompt shape; runtime context binding is the only formal
29
+ signal.
30
30
 
31
31
  Rules:
32
32
 
@@ -17,15 +17,6 @@ TAG_RE = re.compile(
17
17
  )
18
18
  DEFAULT_CONTRACT_REGISTRY = {
19
19
  "contracts": [
20
- {
21
- "id": "COWORK_ENTRY_CONTRACT_V1",
22
- "path": ".cowork-flow/spec/contracts/entry-contract.md",
23
- "digest": [
24
- "Classify main-session requests before task start, resume, archive, or commit.",
25
- "Runtime context, not prompt labels, identifies formal subagent sessions.",
26
- ],
27
- "readWhen": ["before task start/resume/archive", "when prompt and bootstrap text conflict"],
28
- },
29
20
  {
30
21
  "id": "RUNTIME_CONTEXT_DISPATCH_V2",
31
22
  "path": ".cowork-flow/spec/contracts/subagent-dispatch.md",
@@ -170,7 +161,7 @@ def _load_common(root: Path) -> None:
170
161
  def _get_active_task(root: Path, hook_input: dict[str, Any]) -> tuple[str | None, str, str]:
171
162
  _load_common(root)
172
163
  try:
173
- from common.active_task import get_active_task # type: ignore[import-not-found]
164
+ from common.task.active_task import get_active_task # type: ignore[import-not-found]
174
165
  except Exception:
175
166
  return None, "no_task", "unavailable"
176
167
 
@@ -194,7 +185,7 @@ def _get_active_task(root: Path, hook_input: dict[str, Any]) -> tuple[str | None
194
185
  def _resolve_runtime_context(root: Path, hook_input: dict[str, Any]) -> tuple[dict[str, Any] | None, str | None]:
195
186
  _load_common(root)
196
187
  try:
197
- from common.active_task import ( # type: ignore[import-not-found]
188
+ from common.task.active_task import ( # type: ignore[import-not-found]
198
189
  bind_runtime_context,
199
190
  read_runtime_context,
200
191
  resolve_runtime_context_id,
@@ -18,15 +18,6 @@ TAG_RE = re.compile(
18
18
  )
19
19
  DEFAULT_CONTRACT_REGISTRY = {
20
20
  "contracts": [
21
- {
22
- "id": "COWORK_ENTRY_CONTRACT_V1",
23
- "path": ".cowork-flow/spec/contracts/entry-contract.md",
24
- "digest": [
25
- "Classify main-session requests before task start, resume, archive, or commit.",
26
- "Runtime context, not prompt labels, identifies formal subagent sessions.",
27
- ],
28
- "readWhen": ["before task start/resume/archive", "when prompt and bootstrap text conflict"],
29
- },
30
21
  {
31
22
  "id": "RUNTIME_CONTEXT_DISPATCH_V2",
32
23
  "path": ".cowork-flow/spec/contracts/subagent-dispatch.md",
@@ -154,7 +145,7 @@ def _load_common(root: Path) -> None:
154
145
  def _get_dispatch_mode(root: Path) -> str:
155
146
  _load_common(root)
156
147
  try:
157
- from common.config import get_codex_dispatch_mode # type: ignore[import-not-found]
148
+ from common.core.config import get_codex_dispatch_mode # type: ignore[import-not-found]
158
149
  except Exception:
159
150
  return "sub-agent"
160
151
  try:
@@ -166,7 +157,7 @@ def _get_dispatch_mode(root: Path) -> str:
166
157
  def _get_active_task(root: Path, hook_input: dict[str, Any]) -> tuple[str | None, str, str]:
167
158
  _load_common(root)
168
159
  try:
169
- from common.active_task import get_active_task # type: ignore[import-not-found]
160
+ from common.task.active_task import get_active_task # type: ignore[import-not-found]
170
161
  except Exception:
171
162
  return None, "no_task", "unavailable"
172
163
 
@@ -190,7 +181,7 @@ def _get_active_task(root: Path, hook_input: dict[str, Any]) -> tuple[str | None
190
181
  def _resolve_runtime_context(root: Path, hook_input: dict[str, Any]) -> tuple[dict[str, Any] | None, str | None]:
191
182
  _load_common(root)
192
183
  try:
193
- from common.active_task import ( # type: ignore[import-not-found]
184
+ from common.task.active_task import ( # type: ignore[import-not-found]
194
185
  bind_runtime_context,
195
186
  read_runtime_context,
196
187
  resolve_runtime_context_id,
@@ -1 +1 @@
1
- 0.0.28
1
+ 0.0.29
@@ -27,7 +27,6 @@ runtimeContext:
27
27
  envKey: COWORK_FLOW_RUNTIME_CONTEXT_ID
28
28
  metadataKey: cowork_runtime_context_id
29
29
  contracts:
30
- entry: COWORK_ENTRY_CONTRACT_V1
31
30
  dispatch: RUNTIME_CONTEXT_DISPATCH_V2
32
31
  leafExecutor: true
33
32
  fallback:
@@ -27,7 +27,6 @@ runtimeContext:
27
27
  envKey: COWORK_FLOW_RUNTIME_CONTEXT_ID
28
28
  metadataKey: cowork_runtime_context_id
29
29
  contracts:
30
- entry: COWORK_ENTRY_CONTRACT_V1
31
30
  dispatch: RUNTIME_CONTEXT_DISPATCH_V2
32
31
  leafExecutor: true
33
32
  fallback:
@@ -25,7 +25,6 @@ runtimeContext:
25
25
  envKey: COWORK_FLOW_RUNTIME_CONTEXT_ID
26
26
  metadataKey: cowork_runtime_context_id
27
27
  contracts:
28
- entry: COWORK_ENTRY_CONTRACT_V1
29
28
  dispatch: RUNTIME_CONTEXT_DISPATCH_V2
30
29
  leafExecutor: true
31
30
  fallback:
@@ -0,0 +1 @@
1
+ """Command entrypoints for cowork-flow."""
@@ -0,0 +1,10 @@
1
+ """Allow command scripts to import the sibling common package when run directly."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ SCRIPTS_DIR = Path(__file__).resolve().parents[1]
9
+ if str(SCRIPTS_DIR) not in sys.path:
10
+ sys.path.insert(0, str(SCRIPTS_DIR))
@@ -17,7 +17,11 @@ import sys
17
17
  from datetime import datetime
18
18
  from pathlib import Path
19
19
 
20
- from common.paths import (
20
+ if __package__:
21
+ from . import _bootstrap as _bootstrap # noqa: F401
22
+ else:
23
+ import _bootstrap # noqa: F401
24
+ from common.core.paths import (
21
25
  DIR_TASKS,
22
26
  DIR_WORKFLOW,
23
27
  DIR_WORKSPACE,
@@ -27,8 +31,8 @@ from common.paths import (
27
31
  get_developer,
28
32
  get_workspace_dir,
29
33
  )
30
- from common.developer import ensure_developer
31
- from common.config import get_session_commit_message, get_max_journal_lines
34
+ from common.core.developer import ensure_developer
35
+ from common.core.config import get_session_commit_message, get_max_journal_lines
32
36
 
33
37
  MARKER_CURRENT_STATUS_START = "@@@auto:current-status"
34
38
  MARKER_CURRENT_STATUS_END = "@@@/auto:current-status"
@@ -428,12 +432,6 @@ def main() -> int:
428
432
  action="store_true",
429
433
  help="Auto git commit after recording workspace metadata",
430
434
  )
431
- parser.add_argument(
432
- "--no-commit",
433
- action="store_true",
434
- help="Deprecated no-op; add-session no longer commits by default",
435
- )
436
-
437
435
  args = parser.parse_args()
438
436
 
439
437
  extra_content = "(add details)"
@@ -446,7 +444,7 @@ def main() -> int:
446
444
 
447
445
  return add_session(
448
446
  args.title, args.commit, args.summary, extra_content,
449
- auto_commit=args.auto_commit and not args.no_commit,
447
+ auto_commit=args.auto_commit,
450
448
  )
451
449
 
452
450
 
@@ -11,8 +11,12 @@ from datetime import datetime
11
11
  from pathlib import Path
12
12
  from typing import Iterable
13
13
 
14
- from common.archive_utils import archive_directory_resumable
15
- from common.paths import (
14
+ if __package__:
15
+ from . import _bootstrap as _bootstrap # noqa: F401
16
+ else:
17
+ import _bootstrap # noqa: F401
18
+ from common.task.archive_utils import archive_directory_resumable
19
+ from common.core.paths import (
16
20
  DIR_ARCHIVE,
17
21
  DIR_CHANGES,
18
22
  DIR_WORKFLOW,
@@ -13,7 +13,11 @@ try:
13
13
  except ModuleNotFoundError: # pragma: no cover - Python < 3.11
14
14
  tomllib = None # type: ignore[assignment]
15
15
 
16
- from common.paths import get_repo_root
16
+ if __package__:
17
+ from . import _bootstrap as _bootstrap # noqa: F401
18
+ else:
19
+ import _bootstrap # noqa: F401
20
+ from common.core.paths import get_repo_root
17
21
 
18
22
 
19
23
  ENTRY_BOUNDARY_DIR = "entry" + "-boundary"
@@ -101,22 +105,12 @@ REQUIRED_WORKFLOW_STATE_TEMPLATE_SNIPPETS = [
101
105
  "UNKNOWN is not a delegated",
102
106
  ]
103
107
 
104
- REQUIRED_ENTRY_CONTRACT_SNIPPETS = [
105
- "COWORK_ENTRY_CONTRACT_V1",
106
- "main-session prompts",
107
- "resolved by runtime context binding",
108
- "`UNKNOWN` is not subagent evidence",
109
- "Runtime context binding overrides project bootstrap text",
110
- ]
111
-
112
108
  REQUIRED_CONTRACT_REGISTRY_SNIPPETS = [
113
109
  '"schemaVersion": 1',
114
- '"COWORK_ENTRY_CONTRACT_V1"',
115
110
  '"RUNTIME_CONTEXT_DISPATCH_V2"',
116
111
  '"HOST_ADAPTER_CAPABILITIES_V1"',
117
112
  '"HOST_ADAPTER_SCHEMA_V1"',
118
113
  '"readWhen"',
119
- '".cowork-flow/spec/contracts/entry-contract.md"',
120
114
  '".cowork-flow/spec/contracts/subagent-dispatch.md"',
121
115
  '".cowork-flow/spec/contracts/capabilities.md"',
122
116
  '".cowork-flow/spec/schemas/adapter.schema.json"',
@@ -216,11 +210,6 @@ def _check_toml_parseable(path: Path, errors: list[str]) -> dict | None:
216
210
 
217
211
 
218
212
  def _check_common_contracts(repo_root: Path, errors: list[str]) -> None:
219
- for rel in (
220
- ".cowork-flow/spec/contracts/entry-contract.md",
221
- "template/.cowork-flow/spec/contracts/entry-contract.md",
222
- ):
223
- _check_file_contains(repo_root / rel, REQUIRED_ENTRY_CONTRACT_SNIPPETS, errors)
224
213
  for rel in (
225
214
  ".cowork-flow/spec/runtime/contract-registry.json",
226
215
  "template/.cowork-flow/spec/runtime/contract-registry.json",
@@ -273,23 +262,6 @@ def _check_host_adapters(repo_root: Path, errors: list[str]) -> None:
273
262
  )
274
263
 
275
264
 
276
- def cmd_entry_contract(_: argparse.Namespace) -> int:
277
- repo_root = get_repo_root()
278
- errors: list[str] = []
279
- _check_common_contracts(repo_root, errors)
280
- for rel in (
281
- f".agents/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
282
- f"template/.agents/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
283
- ):
284
- _check_file_absent(repo_root / rel, errors)
285
- if errors:
286
- for error in errors:
287
- print(f"ERROR: {error}", file=sys.stderr)
288
- return 1
289
- print("entry contract checks passed")
290
- return 0
291
-
292
-
293
265
  def cmd_host_adapters(_: argparse.Namespace) -> int:
294
266
  repo_root = get_repo_root()
295
267
  errors: list[str] = []
@@ -459,12 +431,12 @@ def cmd_subagent_safety(_: argparse.Namespace) -> int:
459
431
  ):
460
432
  _check_file_contains(repo_root / rel, REQUIRED_RUNTIME_HOOK_SNIPPETS, errors)
461
433
  for rel in (
462
- ".cowork-flow/scripts/subagent.py",
463
- "template/.cowork-flow/scripts/subagent.py",
464
- ".cowork-flow/scripts/common/execution_context.py",
465
- "template/.cowork-flow/scripts/common/execution_context.py",
466
- ".cowork-flow/scripts/common/active_task.py",
467
- "template/.cowork-flow/scripts/common/active_task.py",
434
+ ".cowork-flow/scripts/commands/subagent.py",
435
+ "template/.cowork-flow/scripts/commands/subagent.py",
436
+ ".cowork-flow/scripts/common/core/execution_context.py",
437
+ "template/.cowork-flow/scripts/common/core/execution_context.py",
438
+ ".cowork-flow/scripts/common/task/active_task.py",
439
+ "template/.cowork-flow/scripts/common/task/active_task.py",
468
440
  ):
469
441
  if not (repo_root / rel).is_file():
470
442
  errors.append(f"missing file: {rel}")
@@ -479,7 +451,6 @@ def cmd_subagent_safety(_: argparse.Namespace) -> int:
479
451
  def build_parser() -> argparse.ArgumentParser:
480
452
  parser = argparse.ArgumentParser(description="cowork-flow diagnostics")
481
453
  parser.add_argument("--subagent-safety", action="store_true", help="Check subagent safety wiring")
482
- parser.add_argument("--entry-contract", action="store_true", help="Check entry classification contract")
483
454
  parser.add_argument("--host-adapters", action="store_true", help="Check host adapter declarations")
484
455
  return parser
485
456
 
@@ -489,8 +460,6 @@ def main() -> int:
489
460
  args = parser.parse_args()
490
461
  if args.subagent_safety:
491
462
  return cmd_subagent_safety(args)
492
- if args.entry_contract:
493
- return cmd_entry_contract(args)
494
463
  if args.host_adapters:
495
464
  return cmd_host_adapters(args)
496
465
  parser.print_help()
@@ -9,7 +9,11 @@ Usage:
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from common.git_context import main
12
+ if __package__:
13
+ from . import _bootstrap as _bootstrap # noqa: F401
14
+ else:
15
+ import _bootstrap # noqa: F401
16
+ from common.git.git_context import main
13
17
 
14
18
 
15
19
  if __name__ == "__main__":
@@ -2,14 +2,18 @@
2
2
  """
3
3
  Get current developer name.
4
4
 
5
- This is a wrapper that uses common/paths.py
5
+ This is a wrapper that uses common/core/paths.py
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
10
  import sys
11
11
 
12
- from common.paths import get_developer
12
+ if __package__:
13
+ from . import _bootstrap as _bootstrap # noqa: F401
14
+ else:
15
+ import _bootstrap # noqa: F401
16
+ from common.core.paths import get_developer
13
17
 
14
18
 
15
19
  def main() -> None:
@@ -14,12 +14,16 @@ from __future__ import annotations
14
14
 
15
15
  import sys
16
16
 
17
- from common.paths import (
17
+ if __package__:
18
+ from . import _bootstrap as _bootstrap # noqa: F401
19
+ else:
20
+ import _bootstrap # noqa: F401
21
+ from common.core.paths import (
18
22
  DIR_WORKFLOW,
19
23
  FILE_DEVELOPER,
20
24
  get_developer,
21
25
  )
22
- from common.developer import init_developer
26
+ from common.core.developer import init_developer
23
27
 
24
28
 
25
29
  def main() -> None:
@@ -18,8 +18,12 @@ from datetime import datetime, timezone
18
18
  from pathlib import Path
19
19
  from typing import Any
20
20
 
21
- from common.config import get_party_mode_v2_config
22
- from common.paths import DIR_WORKFLOW, get_repo_root
21
+ if __package__:
22
+ from . import _bootstrap as _bootstrap # noqa: F401
23
+ else:
24
+ import _bootstrap # noqa: F401
25
+ from common.core.config import get_party_mode_v2_config
26
+ from common.core.paths import DIR_WORKFLOW, get_repo_root
23
27
 
24
28
  RUNTIME_DIR = ".runtime"
25
29
  MODE_DIR = "party-mode-v2"