@stoneforge/quarry 0.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.
- package/LICENSE +13 -0
- package/README.md +160 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/quarry-api.d.ts +268 -0
- package/dist/api/quarry-api.d.ts.map +1 -0
- package/dist/api/quarry-api.js +3905 -0
- package/dist/api/quarry-api.js.map +1 -0
- package/dist/api/types.d.ts +1359 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +204 -0
- package/dist/api/types.js.map +1 -0
- package/dist/bin/sf.d.ts +3 -0
- package/dist/bin/sf.d.ts.map +1 -0
- package/dist/bin/sf.js +9 -0
- package/dist/bin/sf.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +11 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +465 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cli/commands/alias.d.ts +8 -0
- package/dist/cli/commands/alias.d.ts.map +1 -0
- package/dist/cli/commands/alias.js +70 -0
- package/dist/cli/commands/alias.js.map +1 -0
- package/dist/cli/commands/channel.d.ts +13 -0
- package/dist/cli/commands/channel.d.ts.map +1 -0
- package/dist/cli/commands/channel.js +680 -0
- package/dist/cli/commands/channel.js.map +1 -0
- package/dist/cli/commands/completion.d.ts +8 -0
- package/dist/cli/commands/completion.d.ts.map +1 -0
- package/dist/cli/commands/completion.js +87 -0
- package/dist/cli/commands/completion.js.map +1 -0
- package/dist/cli/commands/config.d.ts +12 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +242 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/crud.d.ts +64 -0
- package/dist/cli/commands/crud.d.ts.map +1 -0
- package/dist/cli/commands/crud.js +805 -0
- package/dist/cli/commands/crud.js.map +1 -0
- package/dist/cli/commands/dep.d.ts +16 -0
- package/dist/cli/commands/dep.d.ts.map +1 -0
- package/dist/cli/commands/dep.js +499 -0
- package/dist/cli/commands/dep.js.map +1 -0
- package/dist/cli/commands/document.d.ts +12 -0
- package/dist/cli/commands/document.d.ts.map +1 -0
- package/dist/cli/commands/document.js +1039 -0
- package/dist/cli/commands/document.js.map +1 -0
- package/dist/cli/commands/embeddings.d.ts +12 -0
- package/dist/cli/commands/embeddings.d.ts.map +1 -0
- package/dist/cli/commands/embeddings.js +273 -0
- package/dist/cli/commands/embeddings.js.map +1 -0
- package/dist/cli/commands/entity.d.ts +16 -0
- package/dist/cli/commands/entity.d.ts.map +1 -0
- package/dist/cli/commands/entity.js +522 -0
- package/dist/cli/commands/entity.js.map +1 -0
- package/dist/cli/commands/gc.d.ts +10 -0
- package/dist/cli/commands/gc.d.ts.map +1 -0
- package/dist/cli/commands/gc.js +257 -0
- package/dist/cli/commands/gc.js.map +1 -0
- package/dist/cli/commands/help.d.ts +11 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/help.js +169 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/history.d.ts +9 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +160 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/identity.d.ts +18 -0
- package/dist/cli/commands/identity.d.ts.map +1 -0
- package/dist/cli/commands/identity.js +698 -0
- package/dist/cli/commands/identity.js.map +1 -0
- package/dist/cli/commands/inbox.d.ts +20 -0
- package/dist/cli/commands/inbox.d.ts.map +1 -0
- package/dist/cli/commands/inbox.js +493 -0
- package/dist/cli/commands/inbox.js.map +1 -0
- package/dist/cli/commands/init.d.ts +20 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +144 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install.d.ts +9 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +200 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/library.d.ts +12 -0
- package/dist/cli/commands/library.d.ts.map +1 -0
- package/dist/cli/commands/library.js +665 -0
- package/dist/cli/commands/library.js.map +1 -0
- package/dist/cli/commands/message.d.ts +11 -0
- package/dist/cli/commands/message.d.ts.map +1 -0
- package/dist/cli/commands/message.js +608 -0
- package/dist/cli/commands/message.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +17 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +698 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/playbook.d.ts +12 -0
- package/dist/cli/commands/playbook.d.ts.map +1 -0
- package/dist/cli/commands/playbook.js +730 -0
- package/dist/cli/commands/playbook.js.map +1 -0
- package/dist/cli/commands/reset.d.ts +12 -0
- package/dist/cli/commands/reset.d.ts.map +1 -0
- package/dist/cli/commands/reset.js +306 -0
- package/dist/cli/commands/reset.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +11 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +106 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +8 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +82 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +14 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +370 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/task.d.ts +25 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +1153 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/team.d.ts +13 -0
- package/dist/cli/commands/team.d.ts.map +1 -0
- package/dist/cli/commands/team.js +471 -0
- package/dist/cli/commands/team.js.map +1 -0
- package/dist/cli/commands/workflow.d.ts +16 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +753 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/completion.d.ts +28 -0
- package/dist/cli/completion.d.ts.map +1 -0
- package/dist/cli/completion.js +295 -0
- package/dist/cli/completion.js.map +1 -0
- package/dist/cli/db.d.ts +38 -0
- package/dist/cli/db.d.ts.map +1 -0
- package/dist/cli/db.js +90 -0
- package/dist/cli/db.js.map +1 -0
- package/dist/cli/formatter.d.ts +87 -0
- package/dist/cli/formatter.d.ts.map +1 -0
- package/dist/cli/formatter.js +464 -0
- package/dist/cli/formatter.js.map +1 -0
- package/dist/cli/index.d.ts +33 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +38 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parser.d.ts +45 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +256 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/plugin-loader.d.ts +39 -0
- package/dist/cli/plugin-loader.d.ts.map +1 -0
- package/dist/cli/plugin-loader.js +165 -0
- package/dist/cli/plugin-loader.js.map +1 -0
- package/dist/cli/plugin-registry.d.ts +50 -0
- package/dist/cli/plugin-registry.d.ts.map +1 -0
- package/dist/cli/plugin-registry.js +206 -0
- package/dist/cli/plugin-registry.js.map +1 -0
- package/dist/cli/plugin-types.d.ts +106 -0
- package/dist/cli/plugin-types.d.ts.map +1 -0
- package/dist/cli/plugin-types.js +103 -0
- package/dist/cli/plugin-types.js.map +1 -0
- package/dist/cli/runner.d.ts +35 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +340 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli/suggest.d.ts +15 -0
- package/dist/cli/suggest.d.ts.map +1 -0
- package/dist/cli/suggest.js +49 -0
- package/dist/cli/suggest.js.map +1 -0
- package/dist/cli/types.d.ts +138 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +63 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/config/config.d.ts +86 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +348 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +66 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +114 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/duration.d.ts +75 -0
- package/dist/config/duration.d.ts.map +1 -0
- package/dist/config/duration.js +190 -0
- package/dist/config/duration.js.map +1 -0
- package/dist/config/env.d.ts +67 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +207 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/file.d.ts +97 -0
- package/dist/config/file.d.ts.map +1 -0
- package/dist/config/file.js +365 -0
- package/dist/config/file.js.map +1 -0
- package/dist/config/index.d.ts +35 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +41 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/merge.d.ts +53 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/merge.js +226 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/config/types.d.ts +257 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +72 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validation.d.ts +55 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +251 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/http/index.d.ts +8 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +12 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/sync-handlers.d.ts +162 -0
- package/dist/http/sync-handlers.d.ts.map +1 -0
- package/dist/http/sync-handlers.js +271 -0
- package/dist/http/sync-handlers.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +34 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3329 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/static.d.ts +18 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +71 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/ws/broadcaster.d.ts +8 -0
- package/dist/server/ws/broadcaster.d.ts.map +1 -0
- package/dist/server/ws/broadcaster.js +7 -0
- package/dist/server/ws/broadcaster.js.map +1 -0
- package/dist/server/ws/handler.d.ts +55 -0
- package/dist/server/ws/handler.d.ts.map +1 -0
- package/dist/server/ws/handler.js +160 -0
- package/dist/server/ws/handler.js.map +1 -0
- package/dist/services/blocked-cache.d.ts +297 -0
- package/dist/services/blocked-cache.d.ts.map +1 -0
- package/dist/services/blocked-cache.js +755 -0
- package/dist/services/blocked-cache.js.map +1 -0
- package/dist/services/dependency.d.ts +205 -0
- package/dist/services/dependency.d.ts.map +1 -0
- package/dist/services/dependency.js +566 -0
- package/dist/services/dependency.js.map +1 -0
- package/dist/services/embeddings/fusion.d.ts +33 -0
- package/dist/services/embeddings/fusion.d.ts.map +1 -0
- package/dist/services/embeddings/fusion.js +34 -0
- package/dist/services/embeddings/fusion.js.map +1 -0
- package/dist/services/embeddings/index.d.ts +12 -0
- package/dist/services/embeddings/index.d.ts.map +1 -0
- package/dist/services/embeddings/index.js +10 -0
- package/dist/services/embeddings/index.js.map +1 -0
- package/dist/services/embeddings/local-provider.d.ts +31 -0
- package/dist/services/embeddings/local-provider.d.ts.map +1 -0
- package/dist/services/embeddings/local-provider.js +80 -0
- package/dist/services/embeddings/local-provider.js.map +1 -0
- package/dist/services/embeddings/service.d.ts +76 -0
- package/dist/services/embeddings/service.d.ts.map +1 -0
- package/dist/services/embeddings/service.js +153 -0
- package/dist/services/embeddings/service.js.map +1 -0
- package/dist/services/embeddings/types.d.ts +70 -0
- package/dist/services/embeddings/types.d.ts.map +1 -0
- package/dist/services/embeddings/types.js +8 -0
- package/dist/services/embeddings/types.js.map +1 -0
- package/dist/services/id-length-cache.d.ts +156 -0
- package/dist/services/id-length-cache.d.ts.map +1 -0
- package/dist/services/id-length-cache.js +197 -0
- package/dist/services/id-length-cache.js.map +1 -0
- package/dist/services/inbox.d.ts +147 -0
- package/dist/services/inbox.d.ts.map +1 -0
- package/dist/services/inbox.js +428 -0
- package/dist/services/inbox.js.map +1 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +10 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/priority-service.d.ts +145 -0
- package/dist/services/priority-service.d.ts.map +1 -0
- package/dist/services/priority-service.js +272 -0
- package/dist/services/priority-service.js.map +1 -0
- package/dist/services/search-utils.d.ts +47 -0
- package/dist/services/search-utils.d.ts.map +1 -0
- package/dist/services/search-utils.js +83 -0
- package/dist/services/search-utils.js.map +1 -0
- package/dist/sync/hash.d.ts +48 -0
- package/dist/sync/hash.d.ts.map +1 -0
- package/dist/sync/hash.js +136 -0
- package/dist/sync/hash.js.map +1 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +16 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/merge.d.ts +80 -0
- package/dist/sync/merge.d.ts.map +1 -0
- package/dist/sync/merge.js +310 -0
- package/dist/sync/merge.js.map +1 -0
- package/dist/sync/serialization.d.ts +132 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/serialization.js +306 -0
- package/dist/sync/serialization.js.map +1 -0
- package/dist/sync/service.d.ts +102 -0
- package/dist/sync/service.d.ts.map +1 -0
- package/dist/sync/service.js +493 -0
- package/dist/sync/service.js.map +1 -0
- package/dist/sync/types.d.ts +275 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +76 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/systems/identity.d.ts +479 -0
- package/dist/systems/identity.d.ts.map +1 -0
- package/dist/systems/identity.js +817 -0
- package/dist/systems/identity.js.map +1 -0
- package/dist/systems/index.d.ts +8 -0
- package/dist/systems/index.d.ts.map +1 -0
- package/dist/systems/index.js +29 -0
- package/dist/systems/index.js.map +1 -0
- package/package.json +121 -0
- package/web/assets/charts-vendor-D1YcbGux.js +55 -0
- package/web/assets/dnd-vendor-DmxE-_ZH.js +5 -0
- package/web/assets/editor-vendor-BxraAWts.js +279 -0
- package/web/assets/index-B77vv208.js +341 -0
- package/web/assets/index-CF_XnVLh.css +1 -0
- package/web/assets/router-vendor-BCKpRBrB.js +41 -0
- package/web/assets/ui-vendor-DUahGnbT.js +45 -0
- package/web/assets/utils-vendor-CfYKiENT.js +813 -0
- package/web/favicon.ico +0 -0
- package/web/index.html +23 -0
- package/web/logo.png +0 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Commands - Collection command interface for plans
|
|
3
|
+
*
|
|
4
|
+
* Provides CLI commands for plan operations:
|
|
5
|
+
* - plan create: Create a new plan
|
|
6
|
+
* - plan list: List plans with filtering
|
|
7
|
+
* - plan show: Show plan details with progress
|
|
8
|
+
* - plan activate: Activate a plan (draft -> active)
|
|
9
|
+
* - plan complete: Complete a plan (active -> completed)
|
|
10
|
+
* - plan cancel: Cancel a plan
|
|
11
|
+
* - plan add-task: Add a task to a plan
|
|
12
|
+
* - plan remove-task: Remove a task from a plan
|
|
13
|
+
* - plan tasks: List tasks in a plan
|
|
14
|
+
*/
|
|
15
|
+
import { success, failure, ExitCode } from '../types.js';
|
|
16
|
+
import { getFormatter, getOutputMode, getStatusIcon } from '../formatter.js';
|
|
17
|
+
import { createPlan, PlanStatus } from '@stoneforge/core';
|
|
18
|
+
import { suggestCommands } from '../suggest.js';
|
|
19
|
+
import { resolveActor, createAPI } from '../db.js';
|
|
20
|
+
const planCreateOptions = [
|
|
21
|
+
{
|
|
22
|
+
name: 'title',
|
|
23
|
+
short: 't',
|
|
24
|
+
description: 'Plan title (required)',
|
|
25
|
+
hasValue: true,
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'status',
|
|
30
|
+
short: 's',
|
|
31
|
+
description: 'Initial status (draft, active). Default: draft',
|
|
32
|
+
hasValue: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'tag',
|
|
36
|
+
description: 'Add a tag (can be repeated)',
|
|
37
|
+
hasValue: true,
|
|
38
|
+
array: true,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
async function planCreateHandler(_args, options) {
|
|
42
|
+
if (!options.title) {
|
|
43
|
+
return failure('--title is required for creating a plan', ExitCode.INVALID_ARGUMENTS);
|
|
44
|
+
}
|
|
45
|
+
// Validate status if provided
|
|
46
|
+
if (options.status) {
|
|
47
|
+
const validStatuses = [PlanStatus.DRAFT, PlanStatus.ACTIVE];
|
|
48
|
+
if (!validStatuses.includes(options.status)) {
|
|
49
|
+
return failure(`Invalid initial status: ${options.status}. Must be one of: draft, active`, ExitCode.VALIDATION);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const { api, error } = createAPI(options, true);
|
|
53
|
+
if (error) {
|
|
54
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const actor = resolveActor(options);
|
|
58
|
+
// Handle tags
|
|
59
|
+
let tags;
|
|
60
|
+
if (options.tag) {
|
|
61
|
+
tags = Array.isArray(options.tag) ? options.tag : [options.tag];
|
|
62
|
+
}
|
|
63
|
+
const input = {
|
|
64
|
+
title: options.title,
|
|
65
|
+
createdBy: actor,
|
|
66
|
+
status: options.status ?? PlanStatus.DRAFT,
|
|
67
|
+
...(tags && { tags }),
|
|
68
|
+
};
|
|
69
|
+
const plan = await createPlan(input);
|
|
70
|
+
const created = await api.create(plan);
|
|
71
|
+
return success(created, `Created plan ${created.id}`);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
+
return failure(`Failed to create plan: ${message}`, ExitCode.GENERAL_ERROR);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const planCreateCommand = {
|
|
79
|
+
name: 'create',
|
|
80
|
+
description: 'Create a new plan',
|
|
81
|
+
usage: 'sf plan create --title <title> [options]',
|
|
82
|
+
help: `Create a new plan to organize related tasks.
|
|
83
|
+
|
|
84
|
+
Options:
|
|
85
|
+
-t, --title <text> Plan title (required)
|
|
86
|
+
-s, --status <status> Initial status: draft (default) or active
|
|
87
|
+
--tag <tag> Add a tag (can be repeated)
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
sf plan create --title "Q1 Feature Roadmap"
|
|
91
|
+
sf plan create -t "Sprint 3" --status active
|
|
92
|
+
sf plan create --title "Bug Backlog" --tag urgent --tag backend`,
|
|
93
|
+
options: planCreateOptions,
|
|
94
|
+
handler: planCreateHandler,
|
|
95
|
+
};
|
|
96
|
+
const planListOptions = [
|
|
97
|
+
{
|
|
98
|
+
name: 'status',
|
|
99
|
+
short: 's',
|
|
100
|
+
description: 'Filter by status (draft, active, completed, cancelled)',
|
|
101
|
+
hasValue: true,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'tag',
|
|
105
|
+
description: 'Filter by tag (can be repeated for AND)',
|
|
106
|
+
hasValue: true,
|
|
107
|
+
array: true,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'limit',
|
|
111
|
+
short: 'l',
|
|
112
|
+
description: 'Maximum number of results',
|
|
113
|
+
hasValue: true,
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
async function planListHandler(_args, options) {
|
|
117
|
+
const { api, error } = createAPI(options);
|
|
118
|
+
if (error) {
|
|
119
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
// Build filter
|
|
123
|
+
const filter = {
|
|
124
|
+
type: 'plan',
|
|
125
|
+
};
|
|
126
|
+
// Status filter
|
|
127
|
+
if (options.status) {
|
|
128
|
+
const validStatuses = Object.values(PlanStatus);
|
|
129
|
+
if (!validStatuses.includes(options.status)) {
|
|
130
|
+
return failure(`Invalid status: ${options.status}. Must be one of: ${validStatuses.join(', ')}`, ExitCode.VALIDATION);
|
|
131
|
+
}
|
|
132
|
+
filter.status = options.status;
|
|
133
|
+
}
|
|
134
|
+
// Tag filter
|
|
135
|
+
if (options.tag) {
|
|
136
|
+
filter.tags = Array.isArray(options.tag) ? options.tag : [options.tag];
|
|
137
|
+
}
|
|
138
|
+
// Limit
|
|
139
|
+
if (options.limit) {
|
|
140
|
+
const limit = parseInt(options.limit, 10);
|
|
141
|
+
if (isNaN(limit) || limit < 1) {
|
|
142
|
+
return failure('Limit must be a positive number', ExitCode.VALIDATION);
|
|
143
|
+
}
|
|
144
|
+
filter.limit = limit;
|
|
145
|
+
}
|
|
146
|
+
const result = await api.listPaginated(filter);
|
|
147
|
+
// Post-filter by status since the API doesn't filter by status for plans
|
|
148
|
+
let items = result.items;
|
|
149
|
+
if (options.status) {
|
|
150
|
+
items = items.filter((p) => p.status === options.status);
|
|
151
|
+
}
|
|
152
|
+
const mode = getOutputMode(options);
|
|
153
|
+
const formatter = getFormatter(mode);
|
|
154
|
+
if (mode === 'json') {
|
|
155
|
+
return success(items);
|
|
156
|
+
}
|
|
157
|
+
if (mode === 'quiet') {
|
|
158
|
+
return success(items.map((p) => p.id).join('\n'));
|
|
159
|
+
}
|
|
160
|
+
if (items.length === 0) {
|
|
161
|
+
return success(null, 'No plans found');
|
|
162
|
+
}
|
|
163
|
+
// Build table with progress info
|
|
164
|
+
const headers = ['ID', 'TITLE', 'STATUS', 'PROGRESS', 'CREATED'];
|
|
165
|
+
const rows = [];
|
|
166
|
+
for (const plan of items) {
|
|
167
|
+
// Get progress for each plan
|
|
168
|
+
let progressStr = '-';
|
|
169
|
+
try {
|
|
170
|
+
const progress = await api.getPlanProgress(plan.id);
|
|
171
|
+
progressStr = `${progress.completionPercentage}% (${progress.completedTasks}/${progress.totalTasks})`;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Ignore progress fetch errors
|
|
175
|
+
}
|
|
176
|
+
const status = `${getStatusIcon(plan.status)} ${plan.status}`;
|
|
177
|
+
const created = plan.createdAt.split('T')[0];
|
|
178
|
+
rows.push([plan.id, plan.title, status, progressStr, created]);
|
|
179
|
+
}
|
|
180
|
+
const table = formatter.table(headers, rows);
|
|
181
|
+
const summary = `\nShowing ${items.length} of ${result.total} plans`;
|
|
182
|
+
return success(items, table + summary);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
186
|
+
return failure(`Failed to list plans: ${message}`, ExitCode.GENERAL_ERROR);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const planListCommand = {
|
|
190
|
+
name: 'list',
|
|
191
|
+
description: 'List plans',
|
|
192
|
+
usage: 'sf plan list [options]',
|
|
193
|
+
help: `List plans with optional filtering.
|
|
194
|
+
|
|
195
|
+
Options:
|
|
196
|
+
-s, --status <status> Filter by status: draft, active, completed, cancelled
|
|
197
|
+
--tag <tag> Filter by tag (can be repeated)
|
|
198
|
+
-l, --limit <n> Maximum results
|
|
199
|
+
|
|
200
|
+
Examples:
|
|
201
|
+
sf plan list
|
|
202
|
+
sf plan list --status active
|
|
203
|
+
sf plan list --tag sprint --tag q1`,
|
|
204
|
+
options: planListOptions,
|
|
205
|
+
handler: planListHandler,
|
|
206
|
+
};
|
|
207
|
+
const planShowOptions = [
|
|
208
|
+
{
|
|
209
|
+
name: 'tasks',
|
|
210
|
+
short: 't',
|
|
211
|
+
description: 'Include task list',
|
|
212
|
+
hasValue: false,
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
async function planShowHandler(args, options) {
|
|
216
|
+
const [id] = args;
|
|
217
|
+
if (!id) {
|
|
218
|
+
return failure('Usage: sf plan show <id>\nExample: sf plan show el-abc123', ExitCode.INVALID_ARGUMENTS);
|
|
219
|
+
}
|
|
220
|
+
const { api, error } = createAPI(options);
|
|
221
|
+
if (error) {
|
|
222
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const plan = await api.get(id);
|
|
226
|
+
if (!plan) {
|
|
227
|
+
return failure(`Plan not found: ${id}`, ExitCode.NOT_FOUND);
|
|
228
|
+
}
|
|
229
|
+
if (plan.type !== 'plan') {
|
|
230
|
+
return failure(`Element ${id} is not a plan (type: ${plan.type})`, ExitCode.VALIDATION);
|
|
231
|
+
}
|
|
232
|
+
// Get progress
|
|
233
|
+
const progress = await api.getPlanProgress(id);
|
|
234
|
+
// Get tasks if requested
|
|
235
|
+
let tasks;
|
|
236
|
+
if (options.tasks) {
|
|
237
|
+
tasks = await api.getTasksInPlan(id);
|
|
238
|
+
}
|
|
239
|
+
const mode = getOutputMode(options);
|
|
240
|
+
const formatter = getFormatter(mode);
|
|
241
|
+
if (mode === 'json') {
|
|
242
|
+
return success({ plan, progress, ...(tasks && { tasks }) });
|
|
243
|
+
}
|
|
244
|
+
if (mode === 'quiet') {
|
|
245
|
+
return success(plan.id);
|
|
246
|
+
}
|
|
247
|
+
// Human-readable output
|
|
248
|
+
let output = formatter.element(plan);
|
|
249
|
+
// Add progress section
|
|
250
|
+
output += '\n\n--- Task Progress ---\n';
|
|
251
|
+
output += `Total: ${progress.totalTasks}\n`;
|
|
252
|
+
output += `Completed: ${progress.completedTasks}\n`;
|
|
253
|
+
output += `In Progress: ${progress.inProgressTasks}\n`;
|
|
254
|
+
output += `Blocked: ${progress.blockedTasks}\n`;
|
|
255
|
+
output += `Ready: ${progress.remainingTasks}\n`;
|
|
256
|
+
output += `Progress: ${progress.completionPercentage}%`;
|
|
257
|
+
// Add tasks section if requested
|
|
258
|
+
if (tasks && tasks.length > 0) {
|
|
259
|
+
output += '\n\n--- Tasks ---\n';
|
|
260
|
+
const taskHeaders = ['ID', 'TITLE', 'STATUS', 'PRIORITY'];
|
|
261
|
+
const taskRows = tasks.map((t) => [
|
|
262
|
+
t.id,
|
|
263
|
+
t.title.length > 40 ? t.title.substring(0, 37) + '...' : t.title,
|
|
264
|
+
`${getStatusIcon(t.status)} ${t.status}`,
|
|
265
|
+
`P${t.priority ?? 3}`,
|
|
266
|
+
]);
|
|
267
|
+
output += formatter.table(taskHeaders, taskRows);
|
|
268
|
+
}
|
|
269
|
+
else if (options.tasks) {
|
|
270
|
+
output += '\n\n--- Tasks ---\nNo tasks';
|
|
271
|
+
}
|
|
272
|
+
return success({ plan, progress }, output);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
276
|
+
return failure(`Failed to show plan: ${message}`, ExitCode.GENERAL_ERROR);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const planShowCommand = {
|
|
280
|
+
name: 'show',
|
|
281
|
+
description: 'Show plan details',
|
|
282
|
+
usage: 'sf plan show <id> [options]',
|
|
283
|
+
help: `Display detailed information about a plan including progress.
|
|
284
|
+
|
|
285
|
+
Arguments:
|
|
286
|
+
id Plan identifier (e.g., el-abc123)
|
|
287
|
+
|
|
288
|
+
Options:
|
|
289
|
+
-t, --tasks Include list of tasks in the plan
|
|
290
|
+
|
|
291
|
+
Examples:
|
|
292
|
+
sf plan show el-abc123
|
|
293
|
+
sf plan show el-abc123 --tasks
|
|
294
|
+
sf plan show el-abc123 --json`,
|
|
295
|
+
options: planShowOptions,
|
|
296
|
+
handler: planShowHandler,
|
|
297
|
+
};
|
|
298
|
+
// ============================================================================
|
|
299
|
+
// Plan Activate Command
|
|
300
|
+
// ============================================================================
|
|
301
|
+
async function planActivateHandler(args, options) {
|
|
302
|
+
const [id] = args;
|
|
303
|
+
if (!id) {
|
|
304
|
+
return failure('Usage: sf plan activate <id>\nExample: sf plan activate el-abc123', ExitCode.INVALID_ARGUMENTS);
|
|
305
|
+
}
|
|
306
|
+
const { api, error } = createAPI(options);
|
|
307
|
+
if (error) {
|
|
308
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const plan = await api.get(id);
|
|
312
|
+
if (!plan) {
|
|
313
|
+
return failure(`Plan not found: ${id}`, ExitCode.NOT_FOUND);
|
|
314
|
+
}
|
|
315
|
+
if (plan.type !== 'plan') {
|
|
316
|
+
return failure(`Element ${id} is not a plan (type: ${plan.type})`, ExitCode.VALIDATION);
|
|
317
|
+
}
|
|
318
|
+
if (plan.status === PlanStatus.ACTIVE) {
|
|
319
|
+
return success(plan, `Plan ${id} is already active`);
|
|
320
|
+
}
|
|
321
|
+
if (plan.status !== PlanStatus.DRAFT) {
|
|
322
|
+
return failure(`Cannot activate plan: current status is '${plan.status}'. Only draft plans can be activated.`, ExitCode.VALIDATION);
|
|
323
|
+
}
|
|
324
|
+
const actor = resolveActor(options);
|
|
325
|
+
const updated = await api.update(id, { status: PlanStatus.ACTIVE }, { actor });
|
|
326
|
+
return success(updated, `Activated plan ${id}`);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
330
|
+
return failure(`Failed to activate plan: ${message}`, ExitCode.GENERAL_ERROR);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const planActivateCommand = {
|
|
334
|
+
name: 'activate',
|
|
335
|
+
description: 'Activate a draft plan',
|
|
336
|
+
usage: 'sf plan activate <id>',
|
|
337
|
+
help: `Transition a plan from draft to active status.
|
|
338
|
+
|
|
339
|
+
Arguments:
|
|
340
|
+
id Plan identifier
|
|
341
|
+
|
|
342
|
+
Examples:
|
|
343
|
+
sf plan activate el-abc123`,
|
|
344
|
+
handler: planActivateHandler,
|
|
345
|
+
};
|
|
346
|
+
// ============================================================================
|
|
347
|
+
// Plan Complete Command
|
|
348
|
+
// ============================================================================
|
|
349
|
+
async function planCompleteHandler(args, options) {
|
|
350
|
+
const [id] = args;
|
|
351
|
+
if (!id) {
|
|
352
|
+
return failure('Usage: sf plan complete <id>\nExample: sf plan complete el-abc123', ExitCode.INVALID_ARGUMENTS);
|
|
353
|
+
}
|
|
354
|
+
const { api, error } = createAPI(options);
|
|
355
|
+
if (error) {
|
|
356
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
const plan = await api.get(id);
|
|
360
|
+
if (!plan) {
|
|
361
|
+
return failure(`Plan not found: ${id}`, ExitCode.NOT_FOUND);
|
|
362
|
+
}
|
|
363
|
+
if (plan.type !== 'plan') {
|
|
364
|
+
return failure(`Element ${id} is not a plan (type: ${plan.type})`, ExitCode.VALIDATION);
|
|
365
|
+
}
|
|
366
|
+
if (plan.status === PlanStatus.COMPLETED) {
|
|
367
|
+
return success(plan, `Plan ${id} is already completed`);
|
|
368
|
+
}
|
|
369
|
+
if (plan.status !== PlanStatus.ACTIVE) {
|
|
370
|
+
return failure(`Cannot complete plan: current status is '${plan.status}'. Only active plans can be completed.`, ExitCode.VALIDATION);
|
|
371
|
+
}
|
|
372
|
+
const actor = resolveActor(options);
|
|
373
|
+
const now = new Date().toISOString();
|
|
374
|
+
const updated = await api.update(id, { status: PlanStatus.COMPLETED, completedAt: now }, { actor });
|
|
375
|
+
return success(updated, `Completed plan ${id}`);
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
379
|
+
return failure(`Failed to complete plan: ${message}`, ExitCode.GENERAL_ERROR);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const planCompleteCommand = {
|
|
383
|
+
name: 'complete',
|
|
384
|
+
description: 'Complete an active plan',
|
|
385
|
+
usage: 'sf plan complete <id>',
|
|
386
|
+
help: `Transition a plan from active to completed status.
|
|
387
|
+
|
|
388
|
+
Arguments:
|
|
389
|
+
id Plan identifier
|
|
390
|
+
|
|
391
|
+
Examples:
|
|
392
|
+
sf plan complete el-abc123`,
|
|
393
|
+
handler: planCompleteHandler,
|
|
394
|
+
};
|
|
395
|
+
const planCancelOptions = [
|
|
396
|
+
{
|
|
397
|
+
name: 'reason',
|
|
398
|
+
short: 'r',
|
|
399
|
+
description: 'Cancellation reason',
|
|
400
|
+
hasValue: true,
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
async function planCancelHandler(args, options) {
|
|
404
|
+
const [id] = args;
|
|
405
|
+
if (!id) {
|
|
406
|
+
return failure('Usage: sf plan cancel <id>\nExample: sf plan cancel el-abc123', ExitCode.INVALID_ARGUMENTS);
|
|
407
|
+
}
|
|
408
|
+
const { api, error } = createAPI(options);
|
|
409
|
+
if (error) {
|
|
410
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
const plan = await api.get(id);
|
|
414
|
+
if (!plan) {
|
|
415
|
+
return failure(`Plan not found: ${id}`, ExitCode.NOT_FOUND);
|
|
416
|
+
}
|
|
417
|
+
if (plan.type !== 'plan') {
|
|
418
|
+
return failure(`Element ${id} is not a plan (type: ${plan.type})`, ExitCode.VALIDATION);
|
|
419
|
+
}
|
|
420
|
+
if (plan.status === PlanStatus.CANCELLED) {
|
|
421
|
+
return success(plan, `Plan ${id} is already cancelled`);
|
|
422
|
+
}
|
|
423
|
+
if (plan.status === PlanStatus.COMPLETED) {
|
|
424
|
+
return failure(`Cannot cancel plan: current status is '${plan.status}'. Completed plans cannot be cancelled.`, ExitCode.VALIDATION);
|
|
425
|
+
}
|
|
426
|
+
const actor = resolveActor(options);
|
|
427
|
+
const now = new Date().toISOString();
|
|
428
|
+
const updates = {
|
|
429
|
+
status: PlanStatus.CANCELLED,
|
|
430
|
+
cancelledAt: now,
|
|
431
|
+
};
|
|
432
|
+
if (options.reason) {
|
|
433
|
+
updates.cancelReason = options.reason;
|
|
434
|
+
}
|
|
435
|
+
const updated = await api.update(id, updates, { actor });
|
|
436
|
+
return success(updated, `Cancelled plan ${id}`);
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
440
|
+
return failure(`Failed to cancel plan: ${message}`, ExitCode.GENERAL_ERROR);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const planCancelCommand = {
|
|
444
|
+
name: 'cancel',
|
|
445
|
+
description: 'Cancel a plan',
|
|
446
|
+
usage: 'sf plan cancel <id> [options]',
|
|
447
|
+
help: `Cancel a draft or active plan.
|
|
448
|
+
|
|
449
|
+
Arguments:
|
|
450
|
+
id Plan identifier
|
|
451
|
+
|
|
452
|
+
Options:
|
|
453
|
+
-r, --reason <text> Cancellation reason
|
|
454
|
+
|
|
455
|
+
Examples:
|
|
456
|
+
sf plan cancel el-abc123
|
|
457
|
+
sf plan cancel el-abc123 --reason "Requirements changed"`,
|
|
458
|
+
options: planCancelOptions,
|
|
459
|
+
handler: planCancelHandler,
|
|
460
|
+
};
|
|
461
|
+
// ============================================================================
|
|
462
|
+
// Plan Add Task Command
|
|
463
|
+
// ============================================================================
|
|
464
|
+
async function planAddTaskHandler(args, options) {
|
|
465
|
+
const [planId, taskId] = args;
|
|
466
|
+
if (!planId || !taskId) {
|
|
467
|
+
return failure('Usage: sf plan add-task <plan-id> <task-id>\nExample: sf plan add-task el-plan123 el-task456', ExitCode.INVALID_ARGUMENTS);
|
|
468
|
+
}
|
|
469
|
+
const { api, error } = createAPI(options);
|
|
470
|
+
if (error) {
|
|
471
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
// Verify plan exists
|
|
475
|
+
const plan = await api.get(planId);
|
|
476
|
+
if (!plan) {
|
|
477
|
+
return failure(`Plan not found: ${planId}`, ExitCode.NOT_FOUND);
|
|
478
|
+
}
|
|
479
|
+
if (plan.type !== 'plan') {
|
|
480
|
+
return failure(`Element ${planId} is not a plan (type: ${plan.type})`, ExitCode.VALIDATION);
|
|
481
|
+
}
|
|
482
|
+
if (plan.status === PlanStatus.CANCELLED) {
|
|
483
|
+
return failure(`Cannot add task to plan with status 'cancelled'`, ExitCode.VALIDATION);
|
|
484
|
+
}
|
|
485
|
+
// Verify task exists
|
|
486
|
+
const task = await api.get(taskId);
|
|
487
|
+
if (!task) {
|
|
488
|
+
return failure(`Task not found: ${taskId}`, ExitCode.NOT_FOUND);
|
|
489
|
+
}
|
|
490
|
+
if (task.type !== 'task') {
|
|
491
|
+
return failure(`Element ${taskId} is not a task (type: ${task.type})`, ExitCode.VALIDATION);
|
|
492
|
+
}
|
|
493
|
+
const actor = resolveActor(options);
|
|
494
|
+
await api.addTaskToPlan(taskId, planId, { actor });
|
|
495
|
+
return success({ planId, taskId }, `Added task ${taskId} to plan ${planId}`);
|
|
496
|
+
}
|
|
497
|
+
catch (err) {
|
|
498
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
499
|
+
return failure(`Failed to add task to plan: ${message}`, ExitCode.GENERAL_ERROR);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const planAddTaskCommand = {
|
|
503
|
+
name: 'add-task',
|
|
504
|
+
description: 'Add a task to a plan',
|
|
505
|
+
usage: 'sf plan add-task <plan-id> <task-id>',
|
|
506
|
+
help: `Add an existing task to a plan.
|
|
507
|
+
|
|
508
|
+
Arguments:
|
|
509
|
+
plan-id Plan identifier
|
|
510
|
+
task-id Task identifier to add
|
|
511
|
+
|
|
512
|
+
Examples:
|
|
513
|
+
sf plan add-task el-plan123 el-task456`,
|
|
514
|
+
handler: planAddTaskHandler,
|
|
515
|
+
};
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// Plan Remove Task Command
|
|
518
|
+
// ============================================================================
|
|
519
|
+
async function planRemoveTaskHandler(args, options) {
|
|
520
|
+
const [planId, taskId] = args;
|
|
521
|
+
if (!planId || !taskId) {
|
|
522
|
+
return failure('Usage: sf plan remove-task <plan-id> <task-id>\nExample: sf plan remove-task el-plan123 el-task456', ExitCode.INVALID_ARGUMENTS);
|
|
523
|
+
}
|
|
524
|
+
const { api, error } = createAPI(options);
|
|
525
|
+
if (error) {
|
|
526
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const actor = resolveActor(options);
|
|
530
|
+
await api.removeTaskFromPlan(taskId, planId, actor);
|
|
531
|
+
return success({ planId, taskId }, `Removed task ${taskId} from plan ${planId}`);
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
535
|
+
return failure(`Failed to remove task from plan: ${message}`, ExitCode.GENERAL_ERROR);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const planRemoveTaskCommand = {
|
|
539
|
+
name: 'remove-task',
|
|
540
|
+
description: 'Remove a task from a plan',
|
|
541
|
+
usage: 'sf plan remove-task <plan-id> <task-id>',
|
|
542
|
+
help: `Remove a task from a plan.
|
|
543
|
+
|
|
544
|
+
Arguments:
|
|
545
|
+
plan-id Plan identifier
|
|
546
|
+
task-id Task identifier to remove
|
|
547
|
+
|
|
548
|
+
Examples:
|
|
549
|
+
sf plan remove-task el-plan123 el-task456`,
|
|
550
|
+
handler: planRemoveTaskHandler,
|
|
551
|
+
};
|
|
552
|
+
const planTasksOptions = [
|
|
553
|
+
{
|
|
554
|
+
name: 'status',
|
|
555
|
+
short: 's',
|
|
556
|
+
description: 'Filter by task status',
|
|
557
|
+
hasValue: true,
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: 'limit',
|
|
561
|
+
short: 'l',
|
|
562
|
+
description: 'Maximum number of results',
|
|
563
|
+
hasValue: true,
|
|
564
|
+
},
|
|
565
|
+
];
|
|
566
|
+
async function planTasksHandler(args, options) {
|
|
567
|
+
const [planId] = args;
|
|
568
|
+
if (!planId) {
|
|
569
|
+
return failure('Usage: sf plan tasks <plan-id>\nExample: sf plan tasks el-abc123', ExitCode.INVALID_ARGUMENTS);
|
|
570
|
+
}
|
|
571
|
+
const { api, error } = createAPI(options);
|
|
572
|
+
if (error) {
|
|
573
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
// Build filter
|
|
577
|
+
const filter = {};
|
|
578
|
+
if (options.status) {
|
|
579
|
+
filter.status = options.status;
|
|
580
|
+
}
|
|
581
|
+
let limit;
|
|
582
|
+
if (options.limit) {
|
|
583
|
+
limit = parseInt(options.limit, 10);
|
|
584
|
+
if (isNaN(limit) || limit < 1) {
|
|
585
|
+
return failure('Limit must be a positive number', ExitCode.VALIDATION);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
let tasks = await api.getTasksInPlan(planId, filter);
|
|
589
|
+
// Apply limit (since getTasksInPlan doesn't support pagination directly)
|
|
590
|
+
if (limit !== undefined && tasks.length > limit) {
|
|
591
|
+
tasks = tasks.slice(0, limit);
|
|
592
|
+
}
|
|
593
|
+
const mode = getOutputMode(options);
|
|
594
|
+
const formatter = getFormatter(mode);
|
|
595
|
+
if (mode === 'json') {
|
|
596
|
+
return success(tasks);
|
|
597
|
+
}
|
|
598
|
+
if (mode === 'quiet') {
|
|
599
|
+
return success(tasks.map((t) => t.id).join('\n'));
|
|
600
|
+
}
|
|
601
|
+
if (tasks.length === 0) {
|
|
602
|
+
return success(null, 'No tasks in plan');
|
|
603
|
+
}
|
|
604
|
+
const headers = ['ID', 'TITLE', 'STATUS', 'PRIORITY', 'ASSIGNEE'];
|
|
605
|
+
const rows = tasks.map((t) => [
|
|
606
|
+
t.id,
|
|
607
|
+
t.title.length > 40 ? t.title.substring(0, 37) + '...' : t.title,
|
|
608
|
+
`${getStatusIcon(t.status)} ${t.status}`,
|
|
609
|
+
`P${t.priority ?? 3}`,
|
|
610
|
+
t.assignee ?? '-',
|
|
611
|
+
]);
|
|
612
|
+
const table = formatter.table(headers, rows);
|
|
613
|
+
return success(tasks, table + `\n${tasks.length} task(s)`);
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
617
|
+
return failure(`Failed to list plan tasks: ${message}`, ExitCode.GENERAL_ERROR);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const planTasksCommand = {
|
|
621
|
+
name: 'tasks',
|
|
622
|
+
description: 'List tasks in a plan',
|
|
623
|
+
usage: 'sf plan tasks <plan-id> [options]',
|
|
624
|
+
help: `List tasks belonging to a plan.
|
|
625
|
+
|
|
626
|
+
Arguments:
|
|
627
|
+
plan-id Plan identifier
|
|
628
|
+
|
|
629
|
+
Options:
|
|
630
|
+
-s, --status <status> Filter by status
|
|
631
|
+
-l, --limit <n> Maximum results
|
|
632
|
+
|
|
633
|
+
Examples:
|
|
634
|
+
sf plan tasks el-abc123
|
|
635
|
+
sf plan tasks el-abc123 --status open`,
|
|
636
|
+
options: planTasksOptions,
|
|
637
|
+
handler: planTasksHandler,
|
|
638
|
+
};
|
|
639
|
+
// ============================================================================
|
|
640
|
+
// Plan Root Command
|
|
641
|
+
// ============================================================================
|
|
642
|
+
export const planCommand = {
|
|
643
|
+
name: 'plan',
|
|
644
|
+
description: 'Manage plans (task collections)',
|
|
645
|
+
usage: 'sf plan <subcommand> [options]',
|
|
646
|
+
help: `Manage plans - collections of related tasks.
|
|
647
|
+
|
|
648
|
+
Subcommands:
|
|
649
|
+
create Create a new plan
|
|
650
|
+
list List plans
|
|
651
|
+
show Show plan details with progress
|
|
652
|
+
activate Activate a draft plan
|
|
653
|
+
complete Complete an active plan
|
|
654
|
+
cancel Cancel a plan
|
|
655
|
+
add-task Add a task to a plan
|
|
656
|
+
remove-task Remove a task from a plan
|
|
657
|
+
tasks List tasks in a plan
|
|
658
|
+
|
|
659
|
+
Examples:
|
|
660
|
+
sf plan create --title "Q1 Roadmap"
|
|
661
|
+
sf plan list --status active
|
|
662
|
+
sf plan show el-abc123 --tasks
|
|
663
|
+
sf plan activate el-abc123
|
|
664
|
+
sf plan add-task el-plan123 el-task456`,
|
|
665
|
+
subcommands: {
|
|
666
|
+
create: planCreateCommand,
|
|
667
|
+
list: planListCommand,
|
|
668
|
+
show: planShowCommand,
|
|
669
|
+
activate: planActivateCommand,
|
|
670
|
+
complete: planCompleteCommand,
|
|
671
|
+
cancel: planCancelCommand,
|
|
672
|
+
'add-task': planAddTaskCommand,
|
|
673
|
+
'remove-task': planRemoveTaskCommand,
|
|
674
|
+
tasks: planTasksCommand,
|
|
675
|
+
// Aliases (hidden from --help via dedup in getCommandHelp)
|
|
676
|
+
new: planCreateCommand,
|
|
677
|
+
add: planCreateCommand,
|
|
678
|
+
ls: planListCommand,
|
|
679
|
+
get: planShowCommand,
|
|
680
|
+
view: planShowCommand,
|
|
681
|
+
},
|
|
682
|
+
handler: async (args, options) => {
|
|
683
|
+
// Default to list if no subcommand
|
|
684
|
+
if (args.length === 0) {
|
|
685
|
+
return planListHandler(args, options);
|
|
686
|
+
}
|
|
687
|
+
// Show "did you mean?" for unknown subcommands
|
|
688
|
+
const subNames = Object.keys(planCommand.subcommands);
|
|
689
|
+
const suggestions = suggestCommands(args[0], subNames);
|
|
690
|
+
let msg = `Unknown subcommand: ${args[0]}`;
|
|
691
|
+
if (suggestions.length > 0) {
|
|
692
|
+
msg += `\n\nDid you mean?\n${suggestions.map(s => ` ${s}`).join('\n')}`;
|
|
693
|
+
}
|
|
694
|
+
msg += '\n\nRun "sf plan --help" to see available subcommands.';
|
|
695
|
+
return failure(msg, ExitCode.INVALID_ARGUMENTS);
|
|
696
|
+
},
|
|
697
|
+
};
|
|
698
|
+
//# sourceMappingURL=plan.js.map
|