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.
- package/CHANGELOG.md +41 -0
- package/README.md +102 -23
- package/package.json +8 -3
- package/src/cli.js +6 -0
- package/src/commands/init.js +0 -83
- package/src/commands/source-refresh.js +40 -0
- package/src/commands/sync.js +6 -0
- package/src/commands/update.js +59 -6
- package/src/lib/copy-template.js +200 -50
- package/src/lib/host-assets.js +367 -64
- package/src/lib/plan-applier.js +47 -0
- package/template/.claude/hooks/inject-workflow-state.py +7 -0
- package/template/.codex/hooks/inject-workflow-state.py +7 -0
- package/template/.codex/hooks.json +1 -1
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/config.yaml +0 -11
- package/template/.cowork-flow/scripts/adapters/cli/init_developer.py +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/subagent.py +7 -4
- package/template/.cowork-flow/scripts/adapters/cli/task.py +8 -484
- package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +15 -134
- package/template/.cowork-flow/scripts/adapters/cli/task_context_commands.py +7 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +388 -0
- package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +163 -2
- package/template/.cowork-flow/scripts/adapters/git/git_context.py +16 -193
- package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +170 -84
- package/template/.cowork-flow/scripts/infra/config.py +0 -17
- package/template/.cowork-flow/scripts/infra/developer_profile.py +0 -95
- package/template/.cowork-flow/scripts/infra/paths.py +0 -79
- package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
- package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
- package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
- package/template/.cowork-flow/scripts/kernel/workflow_route.py +15 -15
- package/template/.cowork-flow/scripts/run.py +0 -5
- package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
- package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
- package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +186 -69
- package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
- package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
- package/template/.cowork-flow/scripts/services/readiness.py +3 -170
- package/template/.cowork-flow/scripts/services/task_archive.py +115 -24
- package/template/.cowork-flow/scripts/services/task_context.py +103 -365
- package/template/.cowork-flow/scripts/services/task_creation.py +40 -5
- package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
- package/template/.cowork-flow/scripts/services/task_routing.py +80 -67
- package/template/.cowork-flow/scripts/services/task_utils.py +0 -146
- package/template/.cowork-flow/scripts/services/workflow_runtime.py +1 -1
- package/template/.cowork-flow/spec/contracts/capabilities.md +23 -0
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
- package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
- package/template/.cowork-flow/spec/contracts/index.md +1 -0
- package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +20 -0
- package/template/.cowork-flow/spec/contracts/plan-binding.md +50 -0
- package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +31 -7
- package/template/.cowork-flow/spec/guides/index.md +3 -1
- package/template/.cowork-flow/spec/index.md +5 -4
- package/template/.cowork-flow/spec/references/definition-of-done.md +2 -3
- package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
- package/template/.cowork-flow/spec/runtime/contract-registry.json +14 -0
- package/template/.cowork-flow/spec/runtime/host-assets.json +73 -3
- package/template/.cowork-flow/spec/runtime/index.md +1 -1
- package/template/.cowork-flow/spec/schemas/host-assets.schema.json +150 -8
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/skills/batch-execution/SKILL.md +37 -3
- package/template/skills/batch-execution/scripts/batch_action.py +4 -0
- package/template/skills/batch-execution/scripts/batch_execution.py +120 -499
- package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
- package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
- package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
- package/template/skills/brainstorming/SKILL.md +8 -7
- package/template/skills/cowork-flow/SKILL.md +3 -0
- package/template/skills/party-mode/scripts/party_action_contract.py +113 -0
- package/template/skills/party-mode/scripts/party_actions.py +95 -0
- package/template/skills/party-mode/scripts/party_board_store.py +148 -0
- package/template/skills/party-mode/scripts/party_mode_v2.py +345 -317
- package/template/skills/party-mode/scripts/party_reports.py +177 -0
- package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
- package/template/skills/runtime-health/SKILL.md +14 -2
- package/template/skills/runtime-health/scripts/doctor.py +278 -41
- package/template/skills/task-planning/SKILL.md +105 -61
- package/template/skills/task-review/SKILL.md +4 -4
- package/template/skills/task-review/scripts/review_check.py +277 -20
- package/template/skills/test-first/SKILL.md +18 -3
- package/template/skills/test-first/manifest.json +8 -4
- package/template/.cowork-flow/changes/archive/.gitkeep +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/add_session.py +0 -465
- package/template/.cowork-flow/scripts/adapters/cli/change.py +0 -412
- package/template/.cowork-flow/workspace/index.md +0 -102
- package/template/.zcode/scaffold/AGENTS.md +0 -145
- package/template/.zcode/scaffold/CLAUDE.md +0 -5
package/src/lib/copy-template.js
CHANGED
|
@@ -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 =
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
|
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 };
|