cowork-flow 0.0.47 → 0.0.49

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 (90) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +102 -23
  3. package/package.json +8 -3
  4. package/src/cli.js +6 -0
  5. package/src/commands/init.js +0 -83
  6. package/src/commands/source-refresh.js +40 -0
  7. package/src/commands/sync.js +6 -0
  8. package/src/commands/update.js +59 -6
  9. package/src/lib/copy-template.js +200 -50
  10. package/src/lib/host-assets.js +367 -64
  11. package/src/lib/plan-applier.js +47 -0
  12. package/template/.claude/hooks/inject-workflow-state.py +7 -0
  13. package/template/.codex/hooks/inject-workflow-state.py +7 -0
  14. package/template/.codex/hooks.json +1 -1
  15. package/template/.cowork-flow/.version +1 -1
  16. package/template/.cowork-flow/config.yaml +0 -11
  17. package/template/.cowork-flow/scripts/adapters/cli/init_developer.py +0 -1
  18. package/template/.cowork-flow/scripts/adapters/cli/subagent.py +7 -4
  19. package/template/.cowork-flow/scripts/adapters/cli/task.py +8 -484
  20. package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +15 -134
  21. package/template/.cowork-flow/scripts/adapters/cli/task_context_commands.py +7 -1
  22. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +388 -0
  23. package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +163 -2
  24. package/template/.cowork-flow/scripts/adapters/git/git_context.py +16 -193
  25. package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +170 -84
  26. package/template/.cowork-flow/scripts/infra/config.py +0 -17
  27. package/template/.cowork-flow/scripts/infra/developer_profile.py +0 -95
  28. package/template/.cowork-flow/scripts/infra/paths.py +0 -79
  29. package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
  30. package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
  31. package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
  32. package/template/.cowork-flow/scripts/kernel/workflow_route.py +15 -15
  33. package/template/.cowork-flow/scripts/run.py +0 -5
  34. package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
  35. package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
  36. package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
  37. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +186 -69
  38. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
  39. package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
  40. package/template/.cowork-flow/scripts/services/readiness.py +3 -170
  41. package/template/.cowork-flow/scripts/services/task_archive.py +115 -24
  42. package/template/.cowork-flow/scripts/services/task_context.py +103 -365
  43. package/template/.cowork-flow/scripts/services/task_creation.py +40 -5
  44. package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
  45. package/template/.cowork-flow/scripts/services/task_routing.py +80 -67
  46. package/template/.cowork-flow/scripts/services/task_utils.py +0 -146
  47. package/template/.cowork-flow/scripts/services/workflow_runtime.py +1 -1
  48. package/template/.cowork-flow/spec/contracts/capabilities.md +23 -0
  49. package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
  50. package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
  51. package/template/.cowork-flow/spec/contracts/index.md +1 -0
  52. package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +20 -0
  53. package/template/.cowork-flow/spec/contracts/plan-binding.md +50 -0
  54. package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +31 -7
  55. package/template/.cowork-flow/spec/guides/index.md +3 -1
  56. package/template/.cowork-flow/spec/index.md +5 -4
  57. package/template/.cowork-flow/spec/references/definition-of-done.md +2 -3
  58. package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
  59. package/template/.cowork-flow/spec/runtime/contract-registry.json +14 -0
  60. package/template/.cowork-flow/spec/runtime/host-assets.json +73 -3
  61. package/template/.cowork-flow/spec/runtime/index.md +1 -1
  62. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +150 -8
  63. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  64. package/template/skills/batch-execution/SKILL.md +37 -3
  65. package/template/skills/batch-execution/scripts/batch_action.py +4 -0
  66. package/template/skills/batch-execution/scripts/batch_execution.py +120 -499
  67. package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
  68. package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
  69. package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
  70. package/template/skills/brainstorming/SKILL.md +8 -7
  71. package/template/skills/cowork-flow/SKILL.md +3 -0
  72. package/template/skills/party-mode/scripts/party_action_contract.py +113 -0
  73. package/template/skills/party-mode/scripts/party_actions.py +95 -0
  74. package/template/skills/party-mode/scripts/party_board_store.py +148 -0
  75. package/template/skills/party-mode/scripts/party_mode_v2.py +345 -317
  76. package/template/skills/party-mode/scripts/party_reports.py +177 -0
  77. package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
  78. package/template/skills/runtime-health/SKILL.md +14 -2
  79. package/template/skills/runtime-health/scripts/doctor.py +278 -41
  80. package/template/skills/task-planning/SKILL.md +105 -61
  81. package/template/skills/task-review/SKILL.md +4 -4
  82. package/template/skills/task-review/scripts/review_check.py +277 -20
  83. package/template/skills/test-first/SKILL.md +18 -3
  84. package/template/skills/test-first/manifest.json +8 -4
  85. package/template/.cowork-flow/changes/archive/.gitkeep +0 -1
  86. package/template/.cowork-flow/scripts/adapters/cli/add_session.py +0 -465
  87. package/template/.cowork-flow/scripts/adapters/cli/change.py +0 -412
  88. package/template/.cowork-flow/workspace/index.md +0 -102
  89. package/template/.zcode/scaffold/AGENTS.md +0 -145
  90. package/template/.zcode/scaffold/CLAUDE.md +0 -5
@@ -4,9 +4,8 @@ import { join, relative } from 'node:path';
4
4
 
5
5
  import { createAssetPlan, planActions } from './asset-plan.js';
6
6
  import { hostRegistry } from './host-assets.js';
7
- import { applyAssetPlan } from './plan-applier.js';
7
+ import { applyAssetPlan, inspectAssetTransactions } from './plan-applier.js';
8
8
  import { templateRoot } from './paths.js';
9
- import { shouldIncludeForPlatforms, skillDestinationForPlatform } from './platforms.js';
10
9
 
11
10
  async function pathExists(path) {
12
11
  try {
@@ -25,10 +24,13 @@ async function listFiles(root, current = root) {
25
24
  const files = [];
26
25
 
27
26
  for (const entry of entries) {
27
+ if (entry.name === '__pycache__') {
28
+ continue;
29
+ }
28
30
  const absolute = join(current, entry.name);
29
31
  if (entry.isDirectory()) {
30
32
  files.push(...await listFiles(root, absolute));
31
- } else if (entry.isFile()) {
33
+ } else if (entry.isFile() && !entry.name.endsWith('.pyc')) {
32
34
  files.push(relative(root, absolute));
33
35
  }
34
36
  }
@@ -91,7 +93,7 @@ async function appendSkillFileActions(actions, {
91
93
  for (const skill of await listSkillDirs()) {
92
94
  const skillFiles = await listSkillFiles(skill);
93
95
  for (const platform of platforms) {
94
- const destBase = skillDestinationForPlatform(platform);
96
+ const destBase = hostRegistry.skillDestination(platform);
95
97
  if (!destBase) continue;
96
98
  for (const skillFile of skillFiles) {
97
99
  const destination = join(
@@ -117,6 +119,67 @@ async function appendSkillFileActions(actions, {
117
119
  }
118
120
  }
119
121
 
122
+ async function appendSkillTargetActions(actions, {
123
+ targetDir,
124
+ skillTargets,
125
+ seen,
126
+ sync = false
127
+ }) {
128
+ const normalizedTargets = [...new Set(skillTargets.map(toTemplatePath))].sort();
129
+ for (const skill of await listSkillDirs()) {
130
+ const skillFiles = await listSkillFiles(skill);
131
+ for (const destBase of normalizedTargets) {
132
+ for (const skillFile of skillFiles) {
133
+ const destination = join(
134
+ targetDir,
135
+ destBase,
136
+ skill.id,
137
+ skillFile.skillRelativePath
138
+ );
139
+ if (seen.has(destination)) continue;
140
+ seen.add(destination);
141
+ const exists = await pathExists(destination);
142
+ const unchanged = sync && exists
143
+ ? await sourceMatchesDestination(skillFile.source, destination)
144
+ : false;
145
+ actions.push({
146
+ action: exists ? (sync ? (unchanged ? 'skip' : 'update') : 'skip') : 'create',
147
+ source: skillFile.source,
148
+ destination,
149
+ relativePath: join(destBase, skill.id, skillFile.skillRelativePath)
150
+ });
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ function isSourceCheckoutProtected(relativePath) {
157
+ const normalized = toTemplatePath(relativePath);
158
+ if (hostRegistry.syncPolicy.protectedFiles.includes(normalized)) {
159
+ return true;
160
+ }
161
+ return hostRegistry.syncPolicy.protectedPrefixes
162
+ .filter((prefix) => prefix !== '.cowork-flow/spec/')
163
+ .some((prefix) => normalized.startsWith(prefix))
164
+ || normalized.startsWith('.cowork-flow/.runtime/');
165
+ }
166
+
167
+ function shouldIncludeSourceCheckoutRuntimeFile(relativePath) {
168
+ const normalized = toTemplatePath(relativePath);
169
+ return normalized.startsWith('.cowork-flow/')
170
+ && normalized !== '.cowork-flow/.version'
171
+ && !isSourceCheckoutProtected(normalized);
172
+ }
173
+
174
+ function isSourceRefreshObsoleteFile(relativePath) {
175
+ const normalized = toTemplatePath(relativePath);
176
+ if (isSourceCheckoutProtected(normalized)) {
177
+ return false;
178
+ }
179
+ return normalized.startsWith('.cowork-flow/')
180
+ || hostRegistry.skillTargets.some((target) => normalized.startsWith(`${target}/`));
181
+ }
182
+
120
183
  export async function buildInitPlan(targetDir, options = {}) {
121
184
  const files = await listFiles(templateRoot);
122
185
  const actions = [];
@@ -124,7 +187,7 @@ export async function buildInitPlan(targetDir, options = {}) {
124
187
  const seen = new Set();
125
188
 
126
189
  for (const file of files) {
127
- if (!shouldIncludeForPlatforms(file, platforms)) {
190
+ if (!hostRegistry.shouldInclude(file, platforms)) {
128
191
  continue;
129
192
  }
130
193
 
@@ -169,43 +232,9 @@ export async function buildInitPlan(targetDir, options = {}) {
169
232
  });
170
233
  }
171
234
 
172
- const PROTECTED_SYNC_FILES = new Set(
173
- hostRegistry.syncPolicy.protectedFiles
174
- );
175
- const PROTECTED_SYNC_PREFIXES = hostRegistry.syncPolicy.protectedPrefixes;
176
- const SAFE_SYNC_FILES = new Set(hostRegistry.syncPolicy.safeFiles);
177
- const SAFE_SYNC_PREFIXES = [
178
- ...hostRegistry.syncPolicy.safePrefixes,
179
- ...hostRegistry.assetPrefixes,
180
- ...hostRegistry.skillTargets.map((target) => `${target}/`)
181
- ];
182
- const MANAGED_BLOCK_FILES = new Set(
183
- hostRegistry.syncPolicy.managedBlockFiles
184
- );
185
- const OBSOLETE_SYNC_FILES = new Set(
186
- hostRegistry.syncPolicy.obsoleteFiles
187
- );
188
-
189
235
  const COWORK_FLOW_START = '<!-- COWORK-FLOW:START -->';
190
236
  const COWORK_FLOW_END = '<!-- COWORK-FLOW:END -->';
191
237
 
192
- function isProtectedSyncFile(relativePath) {
193
- const templatePath = toTemplatePath(relativePath);
194
- if (SAFE_SYNC_FILES.has(templatePath)) {
195
- return false;
196
- }
197
- return PROTECTED_SYNC_FILES.has(templatePath)
198
- || PROTECTED_SYNC_PREFIXES.some((prefix) => templatePath.startsWith(prefix));
199
- }
200
-
201
- function isSafeSyncFile(relativePath) {
202
- const templatePath = toTemplatePath(relativePath);
203
- return SAFE_SYNC_FILES.has(templatePath)
204
- || SAFE_SYNC_PREFIXES.some((prefix) => templatePath.startsWith(prefix))
205
- || hostRegistry.isKnownPlatformAsset(templatePath)
206
- || templatePath.endsWith('/.gitkeep');
207
- }
208
-
209
238
  function isCoveredByDeletedParent(relativePath, deletedParents) {
210
239
  return deletedParents.some((parent) => relativePath.startsWith(`${parent}/`));
211
240
  }
@@ -265,6 +294,71 @@ async function buildFileSyncAction({ source, destination, relativePath }) {
265
294
  };
266
295
  }
267
296
 
297
+ export async function buildSourceCheckoutRefreshPlan(targetDir, options = {}) {
298
+ const files = await listFiles(templateRoot);
299
+ const actions = [];
300
+ const seen = new Set();
301
+
302
+ for (const file of files) {
303
+ const templatePath = toTemplatePath(file);
304
+ if (!shouldIncludeSourceCheckoutRuntimeFile(templatePath)) {
305
+ continue;
306
+ }
307
+ const source = join(templateRoot, file);
308
+ const destination = join(targetDir, file);
309
+ seen.add(destination);
310
+ const exists = await pathExists(destination);
311
+ if (!exists) {
312
+ actions.push({ action: 'create', source, destination, relativePath: file });
313
+ continue;
314
+ }
315
+ actions.push(await buildFileSyncAction({
316
+ source,
317
+ destination,
318
+ relativePath: file
319
+ }));
320
+ }
321
+
322
+ await appendSkillTargetActions(actions, {
323
+ targetDir,
324
+ skillTargets: options.skillTargets ?? hostRegistry.skillTargets,
325
+ seen,
326
+ sync: true
327
+ });
328
+
329
+ const deletedObsoleteParents = [];
330
+ for (const file of hostRegistry.obsoleteSyncFiles()) {
331
+ if (!isSourceRefreshObsoleteFile(file) || isCoveredByDeletedParent(file, deletedObsoleteParents)) {
332
+ continue;
333
+ }
334
+ const destination = join(targetDir, file);
335
+ if (await pathExists(destination)) {
336
+ deletedObsoleteParents.push(file);
337
+ actions.push({ action: 'delete', source: null, destination, relativePath: file });
338
+ }
339
+ }
340
+
341
+ const versionDestination = join(targetDir, '.cowork-flow', '.version');
342
+ const versionSource = join(templateRoot, '.cowork-flow', '.version');
343
+ const versionContent = await readFile(versionSource, 'utf8');
344
+ const versionExists = await pathExists(versionDestination);
345
+ const versionUnchanged = versionExists
346
+ && (await readFile(versionDestination, 'utf8')) === versionContent;
347
+ actions.push({
348
+ action: versionExists ? (versionUnchanged ? 'skip' : 'update') : 'create',
349
+ source: null,
350
+ destination: versionDestination,
351
+ relativePath: '.cowork-flow/.version',
352
+ content: versionContent
353
+ });
354
+
355
+ return createAssetPlan({
356
+ kind: 'source-refresh',
357
+ targetDir,
358
+ actions
359
+ });
360
+ }
361
+
268
362
  export async function buildSyncPlan(targetDir, options = {}) {
269
363
  if (!await pathExists(join(targetDir, '.cowork-flow'))) {
270
364
  throw new Error(`Target is not initialized: ${targetDir}`);
@@ -276,7 +370,7 @@ export async function buildSyncPlan(targetDir, options = {}) {
276
370
  const seen = new Set();
277
371
 
278
372
  for (const file of files) {
279
- if (!shouldIncludeForPlatforms(file, platforms)) {
373
+ if (!hostRegistry.shouldInclude(file, platforms)) {
280
374
  continue;
281
375
  }
282
376
 
@@ -289,13 +383,13 @@ export async function buildSyncPlan(targetDir, options = {}) {
289
383
 
290
384
  const source = join(templateRoot, file);
291
385
  const exists = await pathExists(destination);
292
- if (MANAGED_BLOCK_FILES.has(toTemplatePath(file))) {
386
+ if (hostRegistry.isManagedBlockFile(file)) {
293
387
  actions.push(await buildManagedBlockSyncAction({ source, destination, exists, options, relativePath: file }));
294
388
  continue;
295
389
  }
296
390
 
297
- const protectedFile = isProtectedSyncFile(file) && !options.force && exists;
298
- const safeFile = isSafeSyncFile(file);
391
+ const protectedFile = hostRegistry.isProtectedSyncFile(file) && !options.force && exists;
392
+ const safeFile = hostRegistry.isSafeSyncFile(file);
299
393
 
300
394
  if (protectedFile) {
301
395
  actions.push({ action: 'protected', source, destination, relativePath: file });
@@ -315,13 +409,7 @@ export async function buildSyncPlan(targetDir, options = {}) {
315
409
  await appendSkillFileActions(actions, { targetDir, platforms, seen, sync: true });
316
410
 
317
411
  const deletedObsoleteParents = [];
318
- const obsoleteFiles = [...OBSOLETE_SYNC_FILES].sort((left, right) => {
319
- if (left.length !== right.length) {
320
- return left.length - right.length;
321
- }
322
- return left.localeCompare(right);
323
- });
324
- for (const file of obsoleteFiles) {
412
+ for (const file of hostRegistry.obsoleteSyncFiles()) {
325
413
  if (isCoveredByDeletedParent(file, deletedObsoleteParents)) {
326
414
  continue;
327
415
  }
@@ -355,6 +443,68 @@ export async function detectInstalledPlatforms(targetDir) {
355
443
  return hostRegistry.detectInstalledPlatforms(targetDir, pathExists);
356
444
  }
357
445
 
446
+ function normalizeReportPath(value) {
447
+ return String(value).replaceAll('\\', '/');
448
+ }
449
+
450
+ function hostAssetOwners(relativePath) {
451
+ const normalized = normalizeReportPath(relativePath);
452
+ const owners = new Set(hostRegistry.assetOwners(normalized));
453
+ for (const platform of hostRegistry.platforms) {
454
+ const skillTarget = platform.skillTarget ? normalizeReportPath(platform.skillTarget) : null;
455
+ if (skillTarget && normalized.startsWith(`${skillTarget}/`)) {
456
+ owners.add(platform.id);
457
+ }
458
+ }
459
+ return [...owners];
460
+ }
461
+
462
+ function readinessAction(action) {
463
+ const item = { path: action.relativePath, action: action.action };
464
+ const platforms = hostAssetOwners(action.relativePath);
465
+ if (platforms.length > 0) {
466
+ item.platforms = platforms;
467
+ }
468
+ return item;
469
+ }
470
+
471
+ function recoveryWarning(transaction) {
472
+ if (transaction.error) {
473
+ return `pending recovery metadata unreadable at ${transaction.path}: ${transaction.error}`;
474
+ }
475
+ if (transaction.status === 'committed') {
476
+ return `stale committed transaction cleanup pending at ${transaction.path}`;
477
+ }
478
+ return `pending ${transaction.status} transaction recovery at ${transaction.path}`;
479
+ }
480
+
481
+ export async function buildReadinessReport(plan, options = {}) {
482
+ const actions = planActions(plan);
483
+ const pendingRecovery = await inspectAssetTransactions(plan.targetDir, {
484
+ fileSystem: options.fileSystem
485
+ });
486
+ return {
487
+ wouldCopy: actions
488
+ .filter((action) => action.action === 'create' || action.action === 'update')
489
+ .map(readinessAction),
490
+ wouldSkipProtected: actions
491
+ .filter((action) => action.action === 'protected')
492
+ .map(readinessAction),
493
+ wouldRemoveObsolete: actions
494
+ .filter((action) => action.action === 'delete')
495
+ .map(readinessAction),
496
+ hostAssetRefresh: actions
497
+ .filter((action) => action.action !== 'skip' && hostAssetOwners(action.relativePath).length > 0)
498
+ .map(readinessAction),
499
+ pendingRecovery,
500
+ warnings: pendingRecovery.map(recoveryWarning)
501
+ };
502
+ }
503
+
504
+ export function formatReadinessReport(report) {
505
+ return `readiness=${JSON.stringify(report)}\n`;
506
+ }
507
+
358
508
  export function summarizePlan(plan, dryRun = false) {
359
509
  const actions = planActions(plan);
360
510
  const counts = { create: 0, update: 0, skip: 0, protected: 0, delete: 0 };