cowork-flow 1.9.2 → 2.1.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 (91) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.en.md +1 -1
  3. package/README.md +1 -1
  4. package/docs/architecture.md +1 -34
  5. package/docs/direction.md +4 -3
  6. package/docs/hosts.md +14 -10
  7. package/docs/index.md +1 -1
  8. package/docs/release.md +1 -1
  9. package/package.json +6 -6
  10. package/presets/claude-code/.claude-plugin/plugin.json +1 -1
  11. package/presets/codex/.codex-plugin/plugin.json +1 -1
  12. package/presets/dsh/plugins/workflow-state.js +72 -24
  13. package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
  14. package/presets/kimi-code/hooks/cowork-flow-inject.mjs +28 -2
  15. package/presets/opencode/cowork-flow/plugin-core.js +70 -88
  16. package/presets/opencode/plugins/cowork-flow.js +7 -9
  17. package/presets/qoder/.qoder-plugin/plugin.json +1 -1
  18. package/presets/qoder/hooks/hooks.json +1 -1
  19. package/presets/qoder/hooks/inject-context.mjs +28 -2
  20. package/presets/zcode/.zcode-plugin/plugin.json +1 -1
  21. package/presets/zcode/hooks/inject-context.mjs +28 -3
  22. package/src/commands/host.js +23 -2
  23. package/src/commands/install-dsh-hook.js +1 -6
  24. package/src/commands/install-dsh-preset.js +1 -6
  25. package/src/commands/install-zcode-plugin.js +2 -3
  26. package/src/commands/registry.js +4 -2
  27. package/src/lib/asset-plan.js +1 -1
  28. package/src/lib/dsh-home.js +6 -0
  29. package/src/lib/plan-applier.js +1 -6
  30. package/template/.claude/agents/cowork-check.md +1 -1
  31. package/template/.claude/agents/cowork-implement.md +1 -1
  32. package/template/.claude/agents/cowork-research.md +1 -1
  33. package/template/.codex/agents/cowork-check.toml +1 -1
  34. package/template/.codex/agents/cowork-implement.toml +1 -1
  35. package/template/.codex/agents/cowork-research.toml +1 -1
  36. package/template/.codex/agents/default.toml +1 -1
  37. package/template/.codex/agents/explorer.toml +1 -1
  38. package/template/.codex/agents/worker.toml +1 -1
  39. package/template/.cowork-flow/.version +1 -1
  40. package/template/.cowork-flow/adapters/claude-code/adapter.yaml +1 -1
  41. package/template/.cowork-flow/adapters/codex/adapter.yaml +1 -1
  42. package/template/.cowork-flow/adapters/dsh/adapter.yaml +1 -1
  43. package/template/.cowork-flow/adapters/opencode/adapter.yaml +1 -1
  44. package/template/.cowork-flow/adapters/qoder/adapter.yaml +1 -1
  45. package/template/.cowork-flow/run +2 -2
  46. package/template/.cowork-flow/run.cmd +3 -3
  47. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +0 -1
  48. package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +0 -7
  49. package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +6 -4
  50. package/template/.cowork-flow/scripts/adapters/host/codex_policy.py +8 -6
  51. package/template/.cowork-flow/scripts/adapters/host/edit_scope.py +113 -0
  52. package/template/.cowork-flow/scripts/adapters/host/opencode_policy.py +12 -0
  53. package/template/.cowork-flow/scripts/adapters/host/qoder_policy.py +7 -5
  54. package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +0 -91
  55. package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +15 -77
  56. package/template/.cowork-flow/scripts/run.py +0 -2
  57. package/template/.cowork-flow/scripts/services/context_paths.py +2 -2
  58. package/template/.cowork-flow/scripts/services/fact_view.py +49 -21
  59. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +1 -216
  60. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +0 -5
  61. package/template/.cowork-flow/scripts/services/task_gate_profiles.py +1 -1
  62. package/template/.cowork-flow/scripts/services/task_lifecycle.py +14 -31
  63. package/template/.cowork-flow/spec/backend/error-handling.md +0 -14
  64. package/template/.cowork-flow/spec/contracts/context-injection.md +53 -26
  65. package/template/.cowork-flow/spec/contracts/decision-anchor.md +3 -3
  66. package/template/.cowork-flow/spec/contracts/error-codes.md +1 -3
  67. package/template/.cowork-flow/spec/contracts/evidence-completion.md +6 -8
  68. package/template/.cowork-flow/spec/contracts/index.md +0 -1
  69. package/template/.cowork-flow/spec/contracts/plan-binding.md +3 -2
  70. package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +6 -21
  71. package/template/.cowork-flow/spec/contracts/task-adopt.md +1 -1
  72. package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -13
  73. package/template/.cowork-flow/spec/runtime/host-assets.json +10 -7
  74. package/template/.cowork-flow/spec/runtime/scope-rules.json +2 -2
  75. package/template/.kimi-code/agents/cowork-check.md +1 -1
  76. package/template/.kimi-code/agents/cowork-implement.md +1 -1
  77. package/template/.kimi-code/agents/cowork-research.md +1 -1
  78. package/template/.opencode/agents/cowork-check.md +1 -1
  79. package/template/.opencode/agents/cowork-implement.md +1 -1
  80. package/template/.opencode/agents/cowork-research.md +1 -1
  81. package/template/.opencode/cowork-flow/plugin-core.js +70 -88
  82. package/template/.opencode/plugins/cowork-flow.js +7 -9
  83. package/template/AGENTS.md +0 -1
  84. package/template/skills/agent-dispatch/SKILL.md +0 -15
  85. package/template/skills/runtime-health/scripts/doctor.py +83 -58
  86. package/template/skills/spec-sync/SKILL.md +0 -1
  87. package/template/skills/task-planning/SKILL.md +1 -1
  88. package/template/skills/task-review/SKILL.md +3 -3
  89. package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +0 -178
  90. package/template/.cowork-flow/scripts/services/spec_check.py +0 -573
  91. package/template/.cowork-flow/spec/contracts/spec-checks.md +0 -281
@@ -1,11 +1,11 @@
1
1
  // Transport shim behind the opencode plugin. Every workflow fact
2
2
  // (contract digest, workflow-state, decision anchor, stage contract,
3
- // runtime-context bind) is rendered by the Python source
4
- // <scripts>/adapters/host/inject.py --host opencode; this module moves
5
- // bytes only. What stays here is what the Python side cannot reach:
6
- // the config-hook skills registration, the shell.env context-key
7
- // export, and the edit-phase spec-check the tool.execute.after hook
8
- // delegates to the project runtime.
3
+ // runtime-context bind, edit-scope warning) is rendered by the Python
4
+ // source <scripts>/adapters/host/inject.py --host opencode; this module
5
+ // moves bytes only. What stays here is what the Python side cannot
6
+ // reach: the config-hook skills registration, the shell.env context-key
7
+ // export, and the tool-shape translation for the tool.execute.after
8
+ // edit-scope delegation.
9
9
  //
10
10
  // This module must NOT live under plugins/: opencode
11
11
  // treats every file directly inside plugins/ as a plugin
@@ -16,7 +16,7 @@
16
16
  // install, so the adapter's relative import resolves in
17
17
  // both.
18
18
 
19
- import { spawn } from "node:child_process"
19
+ import { spawn, spawnSync } from "node:child_process"
20
20
  import { existsSync } from "node:fs"
21
21
  import { dirname, resolve } from "node:path"
22
22
  import { fileURLToPath } from "node:url"
@@ -24,6 +24,27 @@ import { fileURLToPath } from "node:url"
24
24
  const DIR_WORKFLOW = ".cowork-flow"
25
25
  // One prompt assembly must not wait longer than a host hook event.
26
26
  const INJECT_TIMEOUT_MS = 4800
27
+ // The .cowork-flow/run floor, reused here: a candidate that cannot run
28
+ // Python 3.10+ is skipped like a missing binary, so a stale `python3`
29
+ // never hides a working `python` further down the list.
30
+ const MIN_PYTHON_CHECK =
31
+ "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)"
32
+
33
+ function interpreterIsUsable(candidate) {
34
+ try {
35
+ const probe = spawnSync(candidate[0], [...candidate.slice(1), "-c", MIN_PYTHON_CHECK], {
36
+ timeout: INJECT_TIMEOUT_MS,
37
+ windowsHide: true,
38
+ })
39
+ // A probe killed by its own timeout reached no verdict — a loaded machine,
40
+ // not a bad interpreter — so let the real spawn decide; it carries the
41
+ // same timeout and its own diagnostics.
42
+ if (probe.error && probe.error.code === "ETIMEDOUT") return true
43
+ return !probe.error && probe.status === 0
44
+ } catch {
45
+ return false
46
+ }
47
+ }
27
48
 
28
49
  // Last-resort repository root: this module lives at
29
50
  // <payload>/cowork-flow/plugin-core.js, next to plugins/,
@@ -175,8 +196,9 @@ function runInject(candidate, script, payload) {
175
196
  // inject.py and hand back whatever it printed. The payload spreads the
176
197
  // host input so every session/runtime-context key the Python identity
177
198
  // table knows arrives unchanged; hook_event_name and cwd are the two
178
- // fields the hook contract requires.
179
- async function renderViaInject(input) {
199
+ // fields the hook contract requires. `overrides` swaps the event name
200
+ // and adds the tool fields the PostToolUse contract needs.
201
+ async function renderViaInject(input, overrides = null) {
180
202
  if (
181
203
  process.env.COWORK_FLOW_HOOKS === "0" ||
182
204
  process.env.COWORK_FLOW_DISABLE_HOOKS === "1"
@@ -188,21 +210,57 @@ async function renderViaInject(input) {
188
210
  if (!script) {
189
211
  return ""
190
212
  }
213
+ const extra = overrides && typeof overrides === "object" ? overrides : {}
191
214
  const payload = {
192
215
  ...(input && typeof input === "object" ? input : {}),
193
- hook_event_name: "UserPromptSubmit",
216
+ ...extra,
217
+ hook_event_name: extra.hook_event_name || "UserPromptSubmit",
194
218
  cwd: inputCwd(input) || (root === pluginRoot ? null : root) || process.cwd(),
195
219
  }
196
220
  for (const candidate of pythonCandidates()) {
221
+ if (!interpreterIsUsable(candidate)) {
222
+ continue
223
+ }
197
224
  const result = await runInject(candidate, script, payload)
198
225
  if (!result.ran) {
199
226
  continue
200
227
  }
201
228
  return result.stdout.trim()
202
229
  }
230
+ console.error(
231
+ "cowork-flow opencode plugin: no Python 3.10+ interpreter found; skipping injection"
232
+ )
203
233
  return ""
204
234
  }
205
235
 
236
+ // Editor-phase scope warning: opencode sessions get the same
237
+ // single-line out-of-scope advisory the other hosts receive on
238
+ // PostToolUse. Best-effort — a missing runtime, a timeout, or any
239
+ // error stays silent and never blocks the edit that already happened.
240
+ const EDIT_TOOL_NAMES = new Set(["edit", "write"])
241
+
242
+ function editedFilePath(input) {
243
+ const tool = typeof input?.tool === "string" ? input.tool : ""
244
+ if (!EDIT_TOOL_NAMES.has(tool)) {
245
+ return ""
246
+ }
247
+ const args = input?.args
248
+ const candidate = args && typeof args === "object" ? args.filePath : null
249
+ return typeof candidate === "string" && candidate.trim() ? candidate.trim() : ""
250
+ }
251
+
252
+ async function renderEditScopeWarning(input) {
253
+ const filePath = editedFilePath(input)
254
+ if (!filePath) {
255
+ return ""
256
+ }
257
+ return renderViaInject(input, {
258
+ hook_event_name: "PostToolUse",
259
+ tool_name: typeof input?.tool === "string" ? input.tool : "edit",
260
+ tool_input: { file_path: filePath },
261
+ })
262
+ }
263
+
206
264
  function sanitize(value) {
207
265
  return String(value ?? "")
208
266
  .trim()
@@ -262,82 +320,6 @@ function injectShellEnv(input, output) {
262
320
  }
263
321
  }
264
322
 
265
- // Editor-phase spec-check: opencode sessions get the same
266
- // single-line violation hint zcode and claude-code
267
- // receive. Best-effort — a missing runtime, a timeout, or
268
- // any error stays silent and never blocks the edit that
269
- // already happened.
270
- const EDIT_TOOL_NAMES = new Set(["edit", "write"])
271
- const EDIT_CHECK_TIMEOUT_MS = 2500
272
-
273
- function editedFilePath(input) {
274
- const tool = typeof input?.tool === "string" ? input.tool : ""
275
- if (!EDIT_TOOL_NAMES.has(tool)) {
276
- return ""
277
- }
278
- const args = input?.args
279
- const candidate = args && typeof args === "object" ? args.filePath : null
280
- return typeof candidate === "string" && candidate.trim() ? candidate.trim() : ""
281
- }
282
-
283
- function runEditSpecCheck(root, filePath) {
284
- return new Promise((resolvePromise) => {
285
- let settled = false
286
- const finish = (value) => {
287
- if (!settled) {
288
- settled = true
289
- resolvePromise(value)
290
- }
291
- }
292
- const entry = resolve(
293
- root,
294
- DIR_WORKFLOW,
295
- process.platform === "win32" ? "run.cmd" : "run"
296
- )
297
- if (!existsSync(entry)) {
298
- finish("")
299
- return
300
- }
301
- let child
302
- try {
303
- child = spawn(
304
- entry,
305
- ["spec-check", "--phase", "edit", "--file", filePath, "--throttled"],
306
- {
307
- cwd: root,
308
- stdio: ["ignore", "pipe", "ignore"],
309
- shell: process.platform === "win32",
310
- }
311
- )
312
- } catch {
313
- finish("")
314
- return
315
- }
316
- const timer = setTimeout(() => {
317
- try {
318
- child.kill()
319
- } catch {
320
- // the edit must not be held up by a stuck checker
321
- }
322
- finish("")
323
- }, EDIT_CHECK_TIMEOUT_MS)
324
- let stdout = ""
325
- if (child.stdout) {
326
- child.stdout.on("data", (chunk) => {
327
- stdout += String(chunk)
328
- })
329
- }
330
- child.on("error", () => {
331
- clearTimeout(timer)
332
- finish("")
333
- })
334
- child.on("close", () => {
335
- clearTimeout(timer)
336
- finish(stdout.trim())
337
- })
338
- })
339
- }
340
-
341
323
  // The payload's own skills directory, located from this
342
324
  // module's file path and not from the repository root: the
343
325
  // machine install puts this file in
@@ -382,11 +364,11 @@ export function registerPayloadSkills(config) {
382
364
 
383
365
  // The plugin adapter imports the hook helpers; these are
384
366
  // the unit-test surface (transport, fail-open, shell env,
385
- // edit-phase delegation). opencode never reads this module.
367
+ // edit-scope delegation). opencode never reads this module.
386
368
  export {
387
369
  renderViaInject,
388
370
  editedFilePath,
371
+ renderEditScopeWarning,
389
372
  findRepoRoot,
390
373
  injectShellEnv,
391
- runEditSpecCheck,
392
374
  }
@@ -7,12 +7,10 @@
7
7
  // re-exported helper would be called as a plugin and break
8
8
  // host bootstrap.
9
9
  import {
10
- editedFilePath,
11
- findRepoRoot,
12
10
  injectShellEnv,
13
11
  registerPayloadSkills,
14
- renderViaInject,
15
- runEditSpecCheck
12
+ renderEditScopeWarning,
13
+ renderViaInject
16
14
  } from "../cowork-flow/plugin-core.js"
17
15
 
18
16
  export const CoworkFlowPlugin = async () => {
@@ -37,12 +35,12 @@ export const CoworkFlowPlugin = async () => {
37
35
  output.system.push(text)
38
36
  }
39
37
  },
38
+ // Editor-phase scope advisory: the tool already ran, so
39
+ // the single out-of-scope line is appended to its output
40
+ // and the call is never blocked. Non-edit tools and a
41
+ // missing/failing runtime stay silent.
40
42
  "tool.execute.after": async (input, output) => {
41
- const filePath = editedFilePath(input)
42
- if (!filePath) {
43
- return
44
- }
45
- const warning = await runEditSpecCheck(findRepoRoot(input), filePath)
43
+ const warning = await renderEditScopeWarning(input)
46
44
  if (warning) {
47
45
  output.output = [output.output, warning].filter(Boolean).join("\n")
48
46
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.1.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Qoder。",
5
5
  "displayName": "Cowork Flow",
6
6
  "author": {
@@ -41,7 +41,7 @@
41
41
  "${QODER_PLUGIN_ROOT}/hooks/inject-context.mjs"
42
42
  ],
43
43
  "timeout": 30,
44
- "statusMessage": "cowork-flow: 规范自查"
44
+ "statusMessage": "cowork-flow: 编辑越界自查"
45
45
  }
46
46
  ]
47
47
  }
@@ -33,6 +33,27 @@ const HOST = "qoder";
33
33
  const DIR_WORKFLOW = ".cowork-flow";
34
34
  // hooks.json grants 30s per event.
35
35
  const SPAWN_TIMEOUT_MS = 25000;
36
+ // The .cowork-flow/run floor, reused here: a candidate that cannot run
37
+ // Python 3.10+ is skipped like a missing binary, so a stale `python3`
38
+ // never hides a working `python` further down the list.
39
+ const MIN_PYTHON_CHECK = "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)";
40
+
41
+ function interpreterIsUsable(candidate) {
42
+ try {
43
+ const probe = spawnSync(
44
+ candidate[0],
45
+ [...candidate.slice(1), "-c", MIN_PYTHON_CHECK],
46
+ { timeout: SPAWN_TIMEOUT_MS, windowsHide: true }
47
+ );
48
+ // A probe killed by its own timeout reached no verdict — a loaded machine,
49
+ // not a bad interpreter — so let the real spawn decide; it carries the
50
+ // same timeout and its own diagnostics.
51
+ if (probe.error && probe.error.code === "ETIMEDOUT") return true;
52
+ return !probe.error && probe.status === 0;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
36
57
 
37
58
  function readHookInput() {
38
59
  if (process.stdin.isTTY) return { raw: "", parsed: {} };
@@ -113,6 +134,7 @@ function main() {
113
134
  if (!existsSync(injectScript)) process.exit(0);
114
135
 
115
136
  for (const candidate of pythonCandidates()) {
137
+ if (!interpreterIsUsable(candidate)) continue;
116
138
  let result;
117
139
  try {
118
140
  result = spawnSync(
@@ -142,8 +164,12 @@ function main() {
142
164
  }
143
165
  process.exit(0);
144
166
  }
145
- // No interpreter could run the entry: stay silent rather
146
- // than break the host's event stream (fail-open).
167
+ // No candidate could run the entry: stay fail-open, but say why once
168
+ // instead of hiding a misconfiguration (a Python below the floor, or
169
+ // none at all).
170
+ process.stderr.write(
171
+ "cowork-flow qoder hook: no Python 3.10+ interpreter found; skipping injection\n"
172
+ );
147
173
  process.exit(0);
148
174
  }
149
175
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.1.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
5
5
  "author": {
6
6
  "name": "fishlikewater",
@@ -35,6 +35,27 @@ const DIR_WORKFLOW = ".cowork-flow";
35
35
  const LIFECYCLE_BASH_RE = /\brun(?:\.cmd)?\s+(?:task|subagent|resume)\b/;
36
36
  // hooks.json grants 5s per event.
37
37
  const SPAWN_TIMEOUT_MS = 4800;
38
+ // The .cowork-flow/run floor, reused here: a candidate that cannot run
39
+ // Python 3.10+ is skipped like a missing binary, so a stale `python3`
40
+ // never hides a working `python` further down the list.
41
+ const MIN_PYTHON_CHECK = "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)";
42
+
43
+ function interpreterIsUsable(candidate) {
44
+ try {
45
+ const probe = spawnSync(
46
+ candidate[0],
47
+ [...candidate.slice(1), "-c", MIN_PYTHON_CHECK],
48
+ { timeout: SPAWN_TIMEOUT_MS, windowsHide: true }
49
+ );
50
+ // A probe killed by its own timeout reached no verdict — a loaded machine,
51
+ // not a bad interpreter — so let the real spawn decide; it carries the
52
+ // same timeout and its own diagnostics.
53
+ if (probe.error && probe.error.code === "ETIMEDOUT") return true;
54
+ return !probe.error && probe.status === 0;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
38
59
 
39
60
  function readHookInput() {
40
61
  if (process.stdin.isTTY) return { raw: "", parsed: {} };
@@ -152,6 +173,7 @@ function main() {
152
173
  if (!injectScript) process.exit(0);
153
174
 
154
175
  for (const candidate of pythonCandidates()) {
176
+ if (!interpreterIsUsable(candidate)) continue;
155
177
  let result;
156
178
  try {
157
179
  result = spawnSync(
@@ -180,9 +202,12 @@ function main() {
180
202
  }
181
203
  process.exit(0);
182
204
  }
183
- // No interpreter could run the entry: stay silent rather
184
- // than break the host's event stream (fail-open, like
185
- // the spec-edit path).
205
+ // No candidate could run the entry: stay fail-open, but say why once
206
+ // instead of hiding a misconfiguration (a Python below the floor, or
207
+ // none at all).
208
+ process.stderr.write(
209
+ "cowork-flow zcode hook: no Python 3.10+ interpreter found; skipping injection\n"
210
+ );
186
211
  process.exit(0);
187
212
  }
188
213
 
@@ -13,6 +13,8 @@ import { runInstallOpenCodePlugin, FLAGS as OPENCODE_PLUGIN_FLAGS } from './inst
13
13
  import { runInstallQoderPlugin, FLAGS as QODER_PLUGIN_FLAGS } from './install-qoder-plugin.js';
14
14
  import { runInstallZCodePlugin, FLAGS as ZCODE_PLUGIN_FLAGS } from './install-zcode-plugin.js';
15
15
 
16
+ const KIMI_ALL_FLAGS = [...new Set([...KIMI_HOOK_FLAGS, ...KIMI_PLUGIN_FLAGS])].sort();
17
+
16
18
 
17
19
  // Which machine-level integrations a host has.
18
20
  // Deliberately not part of host-assets.json: that manifest
@@ -58,10 +60,11 @@ export const HOST_COMPONENTS = {
58
60
  }
59
61
  },
60
62
  'kimi-code': {
61
- default: 'plugin',
63
+ default: 'all',
62
64
  components: {
63
65
  plugin: { summary: 'Kimi Code plugin: the bootstrap Skill it loads at session start', flags: KIMI_PLUGIN_FLAGS, run: runInstallKimiPlugin },
64
- hook: { summary: 'Kimi Code context-injection hook as a user-level config.toml [[hooks]] row', flags: KIMI_HOOK_FLAGS, run: runInstallKimiHook }
66
+ hook: { summary: 'Kimi Code context-injection hook as a user-level config.toml [[hooks]] row', flags: KIMI_HOOK_FLAGS, run: runInstallKimiHook },
67
+ all: { summary: 'Kimi Code hook plus plugin, installed in one command (the default)', flags: KIMI_ALL_FLAGS, run: runInstallKimiAll }
65
68
  }
66
69
  }
67
70
  };
@@ -78,6 +81,24 @@ export const HOST_FLAGS = [...new Set(
78
81
  )].sort();
79
82
 
80
83
 
84
+ function withoutForceFlag(args) {
85
+ return args.filter((arg) => arg !== '--force' && !arg.startsWith('--force='));
86
+ }
87
+
88
+
89
+ // kimi-code's default component: the hook (injection, the
90
+ // required half) first, then the plugin, in one command.
91
+ // Both installers stay the only validators — unknown flags
92
+ // pass through untouched and are rejected by whichever
93
+ // installer reaches them — but `--force` cannot: the hook
94
+ // has no such flag, so the combined path strips it for the
95
+ // hook segment only, and the plugin still receives it.
96
+ async function runInstallKimiAll(args) {
97
+ await runInstallKimiHook(withoutForceFlag(args));
98
+ await runInstallKimiPlugin(args);
99
+ }
100
+
101
+
81
102
  function resolveHost(token) {
82
103
  const id = hostRegistry.platformIdFor(token);
83
104
  if (id === null) {
@@ -1,7 +1,7 @@
1
1
  import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
- import { homedir } from 'node:os';
4
3
 
4
+ import { getDshHome } from '../lib/dsh-home.js';
5
5
  import { parseInstallArgs, pathExists } from '../lib/install-support.js';
6
6
  import { packageRoot } from '../lib/paths.js';
7
7
  import { removeManagedBlock, upsertManagedBlock } from '../lib/payload-install.js';
@@ -19,11 +19,6 @@ const MANAGED_MARK = '# cowork-flow: managed workflow-state-hook row. Run "cowor
19
19
  export const FLAGS = ['--dry-run', '--force', '--uninstall'];
20
20
 
21
21
 
22
- function getDshHome() {
23
- return process.env.DSH_HOME || join(homedir(), '.dsh');
24
- }
25
-
26
-
27
22
  async function readIfExists(file) {
28
23
  try {
29
24
  return await readFile(file, 'utf8');
@@ -1,8 +1,8 @@
1
1
  import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- import { homedir } from 'node:os';
4
3
  import { pathToFileURL } from 'node:url';
5
4
 
5
+ import { getDshHome } from '../lib/dsh-home.js';
6
6
  import { parseInstallArgs, pathExists, readJsonFile, writeJsonAtomic } from '../lib/install-support.js';
7
7
  import { packageRoot } from '../lib/paths.js';
8
8
  import { readPackageInfo } from '../lib/package-info.js';
@@ -34,11 +34,6 @@ const DESKTOP_PERSONA_KEY = /(- id: persona\n(?:.*\n)*? {4})text: /;
34
34
  export const FLAGS = ['--dry-run', '--force', '--uninstall'];
35
35
 
36
36
 
37
- function getDshHome() {
38
- return process.env.DSH_HOME || join(homedir(), '.dsh');
39
- }
40
-
41
-
42
37
  function getDshPresetRoot() {
43
38
  return join(getDshHome(), '.agent-presets');
44
39
  }
@@ -250,9 +250,8 @@ async function materializePlugin({ pluginSrc, destDir, cacheRoot, manifest, vers
250
250
  // folder; keep workflow runtime files out of scaffold.
251
251
  await rm(join(destDir, "scaffold", ".cowork-flow"), { recursive: true, force: true });
252
252
 
253
- // The presets/zcode/hooks/runtime/scripts copy is stale;
254
- // overwrite it with the authoritative version from the
255
- // template.
253
+ // The preset ships no runtime payload, so materialize
254
+ // the authoritative workflow scripts from the template.
256
255
  const mainScriptsSrc = join(templateRoot, ".cowork-flow", "scripts");
257
256
  const pluginScriptsDest = join(destDir, "hooks", "runtime", "scripts");
258
257
  if (await pathExists(mainScriptsSrc)) {
@@ -41,9 +41,11 @@ const HOST_NOTE = `Hosts: ${Object.entries(HOST_COMPONENTS)
41
41
  .join('; ')}.`;
42
42
  // The union across hosts contains one gap: kimi-code's
43
43
  // hook has no --force, so the help names it instead of
44
- // letting the user hit a usage error.
44
+ // letting the user hit a usage error. kimi-code's default
45
+ // component routes --force to the plugin segment only.
45
46
  const HOST_FORCE_NOTE = '`--force` applies to every component except kimi-code\'s hook, '
46
- + 'which overwrites unconditionally and therefore does not accept the flag.';
47
+ + 'which overwrites unconditionally and therefore does not accept the flag; '
48
+ + 'kimi-code\'s default component consumes it for the plugin only.';
47
49
 
48
50
  export const COMMANDS = [
49
51
  {
@@ -5,7 +5,7 @@ import { resolve } from 'node:path';
5
5
  const VERSION_PATH = '.cowork-flow/.version';
6
6
  const MATERIALIZED_ACTIONS = new Set(['create', 'update']);
7
7
 
8
- function sha256(content) {
8
+ export function sha256(content) {
9
9
  return createHash('sha256').update(content).digest('hex');
10
10
  }
11
11
 
@@ -0,0 +1,6 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+
4
+ export function getDshHome() {
5
+ return process.env.DSH_HOME || join(homedir(), '.dsh');
6
+ }
@@ -1,8 +1,7 @@
1
- import { createHash } from 'node:crypto';
2
1
  import * as defaultFileSystem from 'node:fs/promises';
3
2
  import { basename, dirname, join, resolve, sep } from 'node:path';
4
3
 
5
- import { planActions } from './asset-plan.js';
4
+ import { planActions, sha256 } from './asset-plan.js';
6
5
 
7
6
  const MATERIALIZED_ACTIONS = new Set(['create', 'update']);
8
7
  const VERSION_PATH = '.cowork-flow/.version';
@@ -15,10 +14,6 @@ const TRANSACTION_STATUSES = new Set([
15
14
  'committed'
16
15
  ]);
17
16
 
18
- function sha256(content) {
19
- return createHash('sha256').update(content).digest('hex');
20
- }
21
-
22
17
  async function pathExists(fileSystem, path) {
23
18
  try {
24
19
  await fileSystem.access(path);
@@ -23,6 +23,6 @@ Execution:
23
23
  Rules:
24
24
 
25
25
  - Do not use the Task tool or invoke subagents.
26
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
26
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
27
27
  - Treat backend/frontend natural-language specs as checklist context, not dynamic hard validators.
28
28
  - Do not accept shallow tests that would still pass when target behavior breaks.
@@ -24,6 +24,6 @@ Execution:
24
24
  Rules:
25
25
 
26
26
  - Do not use the Task tool or invoke subagents.
27
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
27
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
28
28
  - Do not create task-local review artifacts, `tdd.jsonl`, or TDD evidence records in `check.jsonl`.
29
29
  - Do not claim completion without current verification.
@@ -19,4 +19,4 @@ Rules:
19
19
 
20
20
  - Do not use the Task tool or invoke subagents.
21
21
  - Do not edit code, specs, task state, or git state.
22
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
22
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
@@ -19,7 +19,7 @@ Execution:
19
19
 
20
20
  Rules:
21
21
  - MUST NOT spawn, wait for, list, or close other agents.
22
- - MUST NOT run task start, finish, archive, unscoped resume, commit, or push.
22
+ - MUST NOT run task lifecycle transitions, unscoped resume, commit, or push.
23
23
  - Treat backend/frontend natural-language specs as checklist context, not dynamic hard validators.
24
24
  - Do not accept shallow tests that would still pass when target behavior breaks.
25
25
  """
@@ -20,7 +20,7 @@ Execution:
20
20
 
21
21
  Rules:
22
22
  - MUST NOT spawn, wait for, list, or close other agents.
23
- - MUST NOT run task start, finish, archive, unscoped resume, commit, or push.
23
+ - MUST NOT run task lifecycle transitions, unscoped resume, commit, or push.
24
24
  - Do not create task-local review artifacts, `tdd.jsonl`, or TDD evidence records in `check.jsonl`.
25
25
  - Do not claim completion without current verification.
26
26
  """
@@ -16,7 +16,7 @@ Execution:
16
16
  Rules:
17
17
  - MUST NOT spawn, wait for, list, or close other agents.
18
18
  - MUST NOT edit code, specs, task state, or git state.
19
- - MUST NOT run task start, finish, archive, unscoped resume, commit, or push.
19
+ - MUST NOT run task lifecycle transitions, unscoped resume, commit, or push.
20
20
  """
21
21
 
22
22
  [features]
@@ -15,7 +15,7 @@ constraints only; they are not the task.
15
15
 
16
16
  Rules:
17
17
  - MUST NOT spawn, wait for, list, or close other agents.
18
- - MUST NOT run task start, task finish, task archive, or unscoped resume.
18
+ - MUST NOT run task lifecycle transitions or unscoped resume.
19
19
  - MUST NOT commit or push.
20
20
  - Keep edits inside requested scope.
21
21
  - Report changed files and exact verification commands.
@@ -16,7 +16,7 @@ constraints only; they are not the task.
16
16
  Rules:
17
17
  - MUST NOT edit files unless the prompt explicitly allows it.
18
18
  - MUST NOT spawn, wait for, list, or close other agents.
19
- - MUST NOT run task start, task finish, task archive, or unscoped resume.
19
+ - MUST NOT run task lifecycle transitions or unscoped resume.
20
20
  - MUST NOT commit or push.
21
21
  - Prefer rg for file and text search.
22
22
  - Return concise findings with file and line evidence.
@@ -16,7 +16,7 @@ constraints only; they are not the task.
16
16
 
17
17
  Rules:
18
18
  - MUST NOT spawn, wait for, list, or close other agents.
19
- - MUST NOT run task start, task finish, task archive, or unscoped resume.
19
+ - MUST NOT run task lifecycle transitions or unscoped resume.
20
20
  - MUST NOT commit or push.
21
21
  - Keep edits inside requested scope.
22
22
  - Report changed files and exact verification commands.
@@ -1 +1 @@
1
- 1.9.2
1
+ 2.1.0
@@ -13,7 +13,7 @@ capabilities:
13
13
  runtimeContextDispatch: native
14
14
  runtimeContextBinding: shim
15
15
  runtimeContextCleanup: native
16
- editScopeWarning: unsupported
16
+ editScopeWarning: plugin
17
17
  dispatch:
18
18
  primitive: subagent
19
19
  agentsPath: .claude/agents
@@ -13,7 +13,7 @@ capabilities:
13
13
  runtimeContextDispatch: native
14
14
  runtimeContextBinding: shim
15
15
  runtimeContextCleanup: native
16
- editScopeWarning: unsupported
16
+ editScopeWarning: plugin
17
17
  dispatch:
18
18
  primitive: spawn_agent
19
19
  forkTurns: none