@dhananjay_kaushik/claude-orchestrator 0.1.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dhananjay Kaushik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # Claude Code Orchestrator
2
+
3
+ Claude Code Orchestrator is a TypeScript CLI for running Claude Code through a test-driven, task-by-task workflow with verification, logs, resumability, and safe Git commits.
4
+
5
+ The core rule is:
6
+
7
+ **Claude implements one selected task. The orchestrator owns lifecycle state.**
8
+
9
+ Claude task sessions do not mark tasks `DONE`, do not commit, and do not decide whether work is complete. The orchestrator validates the plan, runs Claude headlessly, checks structured results, runs verification commands, updates task state, and commits only after verification passes.
10
+
11
+ ## Status
12
+
13
+ The MVP described below is implemented and tested. The original implementation contract is documented in [PLAN.md](./PLAN.md), [SKILLS.md](./SKILLS.md), and [AGENTS.md](./AGENTS.md).
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install -g @dhananjay_kaushik/claude-orchestrator
19
+ ```
20
+
21
+ Requires Node.js >= 18 and the `claude` CLI on your `PATH`.
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ cd your-project
27
+ claude-orchestrator init # create .claude-orchestrator.json
28
+ claude-orchestrator plan # generate a Markdown task plan
29
+ claude-orchestrator run # execute the next task
30
+ claude-orchestrator run --loop # execute tasks until one needs attention
31
+ ```
32
+
33
+ ## Workflow
34
+
35
+ The intended workflow is:
36
+
37
+ 1. Create or validate config.
38
+ 2. Generate a Markdown implementation plan.
39
+ 3. Validate the plan.
40
+ 4. Select one task.
41
+ 5. Create or reuse an isolated per-task Git worktree.
42
+ 6. Run Claude Code headlessly for that task.
43
+ 7. Parse Claude's structured JSON result.
44
+ 8. Run verification commands.
45
+ 9. Mark the task `DONE` only if verification passes.
46
+ 10. Commit verified changes.
47
+ 11. Stop, resume later, or explicitly continue in loop mode.
48
+
49
+ ## Plan Format
50
+
51
+ Plans live in `planDir`, defaulting to `workflow_generated_plans`.
52
+
53
+ Tasks use a strict 5-state Markdown checkbox format:
54
+
55
+ - `- [ ]` or `* [ ]`: `NOT_DONE`
56
+ - `- [-]` or `* [-]`: `IN_PROGRESS`
57
+ - `- [x]`, `- [X]`, `* [x]`, or `* [X]`: `DONE`
58
+ - `- [f]`, `- [F]`, `* [f]`, or `* [F]`: `FAILED`
59
+ - `- [b]`, `- [B]`, `* [b]`, or `* [B]`: `BLOCKED`
60
+
61
+ Task identity is based on a stable slug/hash of normalized task text and heading context. Line numbers are not used as persistent task IDs because plan files can change as handoff notes or content are added.
62
+
63
+ Task text must be unique across the whole plan, not just within a section — the parser rejects a plan with two tasks that read the same, even under different headings.
64
+
65
+ Add a Status Tracker section near the top with running counts (`Total`, `NOT_DONE`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`) — the orchestrator's planning prompt generates one, and it's the fastest way for a human or a resumed session to see plan progress at a glance.
66
+
67
+ ## Claude Execution Contract
68
+
69
+ Planning and execution use different Claude modes:
70
+
71
+ - `plan` is interactive.
72
+ - `run` is headless.
73
+
74
+ Execution must use Claude Code's non-interactive JSON mode:
75
+
76
+ ```text
77
+ claude -p "<prompt>" --output-format json
78
+ ```
79
+
80
+ The orchestrator expects structured JSON fields such as:
81
+
82
+ - `result`
83
+ - `total_cost_usd`
84
+ - `usage`
85
+ - `session_id`
86
+ - `is_error`
87
+
88
+ The orchestrator stores the raw JSON response in the task log directory. Token usage and cost are read from structured JSON fields, not scraped from terminal text.
89
+
90
+ The `result` field must include an orchestrator sentinel:
91
+
92
+ - `ORCHESTRATOR_RESULT: SUCCESS`
93
+ - `ORCHESTRATOR_RESULT: BLOCKED`
94
+ - `ORCHESTRATOR_RESULT: NEEDS_RETRY_CONTEXT`
95
+
96
+ `DONE` is never inferred from Claude output. `DONE` requires successful verification.
97
+
98
+ ## Commands
99
+
100
+ ### `claude-orchestrator init`
101
+
102
+ Create or update `.claude-orchestrator.json` interactively.
103
+
104
+ Expected behavior:
105
+
106
+ - Prompt for plan directory, base branch, verification commands, timeouts, and Claude permission settings.
107
+ - Write a validated config file.
108
+ - Avoid adding post-MVP features such as notifications unless explicitly supported later.
109
+
110
+ ### `claude-orchestrator doctor`
111
+
112
+ Check whether the current project can safely run orchestration.
113
+
114
+ Checks include:
115
+
116
+ - Claude binary exists.
117
+ - Claude authentication can be verified if Claude exposes a stable check.
118
+ - Current directory is a Git repository.
119
+ - Config is valid.
120
+ - Plan directory is readable.
121
+ - State, log, and worktree directories are writable.
122
+ - Verification commands satisfy command policy.
123
+ - Claude permission settings are safe.
124
+
125
+ No task state is mutated by `doctor`.
126
+
127
+ ### `claude-orchestrator validate`
128
+
129
+ Validate config and plan files without executing Claude.
130
+
131
+ Checks include:
132
+
133
+ - `.claude-orchestrator.json` schema and defaults.
134
+ - Structured verification commands.
135
+ - Protected paths.
136
+ - Plan checkbox states.
137
+ - Duplicate or unstable task identity.
138
+ - Unsupported or ambiguous task markers.
139
+
140
+ ### `claude-orchestrator plan`
141
+
142
+ Start an interactive planning session with Claude Code.
143
+
144
+ Expected behavior:
145
+
146
+ - Prompt for planning model if needed.
147
+ - Ensure the plan directory exists.
148
+ - Inject the planning prompt.
149
+ - Ask Claude to create a machine-parseable Markdown plan using the 5-state checkbox system.
150
+
151
+ ### `claude-orchestrator run`
152
+
153
+ Execute one selected task from a plan.
154
+
155
+ Default behavior:
156
+
157
+ - Validate config and plan.
158
+ - Run Claude binary/auth preflight.
159
+ - Select the next executable task.
160
+ - Create or reuse a per-task worktree.
161
+ - Run Claude headlessly in JSON mode.
162
+ - Enforce `taskTimeoutMs`.
163
+ - Parse the sentinel from Claude's JSON `result`.
164
+ - Run verification commands only after a `SUCCESS` sentinel.
165
+ - Mark `DONE` and commit only after verification passes.
166
+ - Stop after one task.
167
+
168
+ ### `claude-orchestrator run --loop`
169
+
170
+ Run multiple tasks in sequence.
171
+
172
+ Loop mode must be explicit. It should still stop on:
173
+
174
+ - verification failure after retry cap
175
+ - `BLOCKED`
176
+ - session-limit pause
177
+ - timeout
178
+ - dirty worktree requiring user choice
179
+ - invalid plan/config
180
+
181
+ ### `claude-orchestrator run --dry-run`
182
+
183
+ Preview the next execution without mutating files.
184
+
185
+ Shows:
186
+
187
+ - selected plan
188
+ - selected task
189
+ - stable task ID
190
+ - intended worktree
191
+ - Claude command shape
192
+ - verification commands
193
+ - timeout settings
194
+ - log and state paths
195
+
196
+ Does not:
197
+
198
+ - spawn Claude
199
+ - edit Markdown
200
+ - create commits
201
+ - create branches
202
+ - run verification commands
203
+
204
+ ### `claude-orchestrator status`
205
+
206
+ Show current orchestration state.
207
+
208
+ Expected output:
209
+
210
+ - selected/current plan
211
+ - task counts by status
212
+ - active or next task
213
+ - current worktree state
214
+ - last Claude `session_id`
215
+ - last result
216
+ - retry count
217
+ - session-limit pause details if present
218
+ - resume command
219
+
220
+ ### `claude-orchestrator logs`
221
+
222
+ Show paths to relevant logs without dumping full verbose output by default.
223
+
224
+ Logs include:
225
+
226
+ - raw Claude JSON response
227
+ - Claude stderr
228
+ - verification stdout/stderr
229
+ - task state snapshots
230
+ - command policy decisions
231
+ - summary reports
232
+
233
+ ### Resuming interrupted work
234
+
235
+ There is no separate `resume` command. An interrupted, `IN_PROGRESS`, or `BLOCKED: SESSION_LIMIT` task is just state on disk, so re-running `claude-orchestrator run --plan <path>` picks it back up automatically:
236
+
237
+ - Re-validates config and plan.
238
+ - Detects dirty task worktree state and prompts whether to continue in the existing worktree, retry from a clean base, or halt.
239
+ - Preserves previous logs and retry context.
240
+ - Does not consume retry budget for session-limit pauses.
241
+
242
+ ## Configuration
243
+
244
+ Configuration lives in `.claude-orchestrator.json` at the project root.
245
+
246
+ Top-level config areas:
247
+
248
+ - `version`: config schema version.
249
+ - `planDir`: project-relative plan directory.
250
+ - `baseBranch`: base branch for task worktrees.
251
+ - `branchPrefix`: branch name prefix for orchestrator work.
252
+ - `models`: planning/execution model defaults.
253
+ - `claude`: Claude binary, permission mode, allowed tools, and safe extra args.
254
+ - `taskTimeoutMs`: per-task Claude wall-clock timeout.
255
+ - `verificationCommands`: ordered structured verification commands.
256
+ - `maxRetries`: bounded retry cap.
257
+ - `logsDir`: log directory.
258
+ - `stateDir`: state directory.
259
+ - `worktreeDir`: per-task worktree directory.
260
+ - `commitMessageTemplate`: verified task commit template.
261
+ - `sessionLimits`: Claude usage-limit behavior.
262
+ - `security`: command allowlist, denylist, protected paths, and network policy.
263
+
264
+ ### Verification Commands
265
+
266
+ Verification commands must be structured objects, not shell strings.
267
+
268
+ Required fields:
269
+
270
+ - `command`: executable name or absolute path.
271
+ - `args`: literal argument array.
272
+ - `timeoutMs`: finite timeout.
273
+
274
+ Optional fields:
275
+
276
+ - `name`: display name.
277
+ - `cwd`: project-relative working directory that must resolve inside the task worktree at execution time.
278
+ - `env`: explicit environment key/value overrides.
279
+ - `allowFailure`: advisory command flag, default false.
280
+
281
+ Commands run with `shell: false`. Shell chains, redirection, command substitution, and interpolated environment expressions are not part of the MVP command model.
282
+
283
+ ## Safety Model
284
+
285
+ The MVP safety model relies on real execution boundaries, not only prompt instructions.
286
+
287
+ - Each task runs in an isolated Git worktree or equivalent sandbox.
288
+ - Claude execution uses validated permission settings.
289
+ - `--dangerously-skip-permissions` is not allowed for normal execution.
290
+ - Protected path patterns are checked before and after execution.
291
+ - Destructive Git operations are blocked.
292
+ - Claude task sessions do not commit, amend, push, force-push, reset, clean, rebase, delete branches, or rewrite history.
293
+ - The orchestrator does not auto-push in the MVP.
294
+ - Verification commands are structured and policy-checked.
295
+ - Secret-looking values are redacted from logs.
296
+
297
+ Prompt instructions still tell Claude not to touch secrets or run destructive Git, but prompt text is treated as advisory unless backed by a technical boundary.
298
+
299
+ ## Timeouts, Retries & Resume
300
+
301
+ Every Claude task execution has a finite wall-clock timeout. Timeout behavior:
302
+
303
+ - Kill the Claude child process.
304
+ - Preserve logs.
305
+ - Preserve the task worktree for inspection.
306
+ - Mark or report the task according to tested executor rules.
307
+ - Do not commit.
308
+
309
+ Retry behavior:
310
+
311
+ - Verification or implementation failures increment retry count.
312
+ - Retry prompts include concise, redacted prior failure context.
313
+ - Session-limit pauses do not consume retry budget.
314
+ - Retries stop at the configured cap and hard maximum.
315
+
316
+ Resume behavior:
317
+
318
+ - Interrupted work remains inspectable in its task worktree.
319
+ - On resume, dirty worktree state is detected.
320
+ - The user chooses whether to continue, retry cleanly, or halt.
321
+
322
+ ## Session Limits
323
+
324
+ The orchestrator should show Claude session-limit information when Claude exposes it through stable output/status.
325
+
326
+ If remaining usage or reset time is available, it appears in pre-run and summary output. If unavailable, the CLI says `unknown` rather than guessing.
327
+
328
+ If Claude stops because of a session limit:
329
+
330
+ - the task is paused as resumable work
331
+ - normal retry count is not incremented
332
+ - reset time is saved when known
333
+ - the terminal shows the resume command
334
+ - no commit is created
335
+
336
+ Automatic resume after reset is post-MVP unless an explicit automation feature is added later.
337
+
338
+ ## Logs & Completion Overview
339
+
340
+ Terminal output should stay concise. Full details go to files under `logsDir`.
341
+
342
+ On task completion, the terminal should show:
343
+
344
+ - task result
345
+ - verification result
346
+ - retry count
347
+ - commit hash when created
348
+ - log path
349
+ - next action
350
+
351
+ When all tasks are `DONE`, the orchestrator should close cleanly and show:
352
+
353
+ - plan path
354
+ - final status
355
+ - completed, failed, and blocked counts
356
+ - commits created
357
+ - verification status
358
+ - retry summary
359
+ - estimated cost/usage from Claude JSON when available
360
+ - detailed log directory
361
+
362
+ ## Post-MVP
363
+
364
+ These features are explicitly post-MVP:
365
+
366
+ - desktop notifications
367
+ - webhooks
368
+ - automatic resume after session reset
369
+ - network-aware verification policy
370
+ - dangerous override modes
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/cli.js';
package/dist/cli.js ADDED
@@ -0,0 +1,79 @@
1
+ import { Command } from 'commander';
2
+ export const program = new Command();
3
+ program
4
+ .name('claude-orchestrator')
5
+ .description('Stateful Workflow Engine on top of Claude Code')
6
+ .version('0.1.0')
7
+ .option('-c, --config <path>', 'path to config file')
8
+ .option('-v, --verbose', 'enable verbose logging');
9
+ import { runInitCommand } from './commands/init.js';
10
+ program
11
+ .command('init')
12
+ .description('create or update .claude-orchestrator.json interactively')
13
+ .action(async () => {
14
+ const parentOpts = program.opts();
15
+ await runInitCommand({ config: parentOpts.config });
16
+ });
17
+ import { runDoctorCommand } from './commands/doctor.js';
18
+ import { runValidateCommand } from './commands/validate.js';
19
+ program
20
+ .command('doctor')
21
+ .description('check environment, config, Git, and Claude binary')
22
+ .action(async () => {
23
+ const parentOpts = program.opts();
24
+ await runDoctorCommand({ config: parentOpts.config });
25
+ });
26
+ program
27
+ .command('validate')
28
+ .description('validate config and plan files without executing Claude')
29
+ .option('--plan <path>', 'path to a specific plan file')
30
+ .action(async (options) => {
31
+ const parentOpts = program.opts();
32
+ await runValidateCommand({ ...options, config: parentOpts.config });
33
+ });
34
+ import { runPlanCommand } from './commands/plan.js';
35
+ import { runCommand } from './commands/run.js';
36
+ program
37
+ .command('plan')
38
+ .description('run interactive planning')
39
+ .option('--plan <path>', 'path to a specific plan file')
40
+ .action(async (options) => {
41
+ const parentOpts = program.opts();
42
+ await runPlanCommand({ ...options, config: parentOpts.config });
43
+ });
44
+ program
45
+ .command('run')
46
+ .description('execute one selected task by default')
47
+ .option('--plan <path>', 'path to a specific plan file')
48
+ .option('--task <id>', 'stable ID of a specific task to run')
49
+ .option('--loop', 'optional explicit loop mode; never implicit')
50
+ .option('--yes', 'auto-confirm prompts (e.g. commit with no verification configured) for unattended runs')
51
+ .option('--dry-run', 'show intended execution without mutating state')
52
+ .action(async (options) => {
53
+ const parentOpts = program.opts();
54
+ await runCommand({ ...options, config: parentOpts.config });
55
+ });
56
+ import { runStatusCommand } from './commands/status.js';
57
+ import { runLogsCommand } from './commands/logs.js';
58
+ program
59
+ .command('status')
60
+ .description('show selected plan state, active task, and resume command')
61
+ .option('--plan <path>', 'path to a specific plan file')
62
+ .option('--task <id>', 'stable ID of a specific task')
63
+ .action(async (options) => {
64
+ const parentOpts = program.opts();
65
+ await runStatusCommand({ ...options, config: parentOpts.config });
66
+ });
67
+ program
68
+ .command('logs')
69
+ .description('show paths to relevant logs')
70
+ .option('--plan <path>', 'path to a specific plan file')
71
+ .option('--task <id>', 'stable ID of a specific task')
72
+ .action(async (options) => {
73
+ const parentOpts = program.opts();
74
+ await runLogsCommand({ ...options, config: parentOpts.config });
75
+ });
76
+ const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST;
77
+ if (!isTest) {
78
+ program.parse(process.argv);
79
+ }
@@ -0,0 +1,43 @@
1
+ import { execa } from 'execa';
2
+ import * as p from '@clack/prompts';
3
+ import pc from 'picocolors';
4
+ import { loadConfig } from '../config/loader.js';
5
+ import { isGitRepository } from '../git/repo.js';
6
+ export async function runDoctorCommand(options) {
7
+ p.intro(pc.bgBlue(pc.white(' Claude Orchestrator: Doctor ')));
8
+ let ok = true;
9
+ const isGit = await isGitRepository();
10
+ if (isGit) {
11
+ p.log.success(pc.green('Git repository detected.'));
12
+ }
13
+ else {
14
+ ok = false;
15
+ p.log.error(pc.red('Not a Git repository. Run "git init" or "claude-orchestrator run" to initialize one.'));
16
+ }
17
+ let config;
18
+ try {
19
+ config = await loadConfig(options.config);
20
+ p.log.success(pc.green('Config loaded and valid.'));
21
+ }
22
+ catch (error) {
23
+ ok = false;
24
+ p.log.error(pc.red(`Config invalid: ${error instanceof Error ? error.message : String(error)}`));
25
+ }
26
+ if (config) {
27
+ try {
28
+ await execa(config.claude.binary, ['--version'], { timeout: 5000 });
29
+ p.log.success(pc.green(`Claude binary "${config.claude.binary}" is executable.`));
30
+ }
31
+ catch (error) {
32
+ ok = false;
33
+ p.log.error(pc.red(`Claude binary "${config.claude.binary}" is not runnable: ${error instanceof Error ? error.message : String(error)}`));
34
+ }
35
+ }
36
+ if (ok) {
37
+ p.outro(pc.green('All checks passed.'));
38
+ }
39
+ else {
40
+ p.outro(pc.red('One or more checks failed.'));
41
+ process.exit(1);
42
+ }
43
+ }
@@ -0,0 +1,36 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import * as p from '@clack/prompts';
4
+ import pc from 'picocolors';
5
+ import { defaultConfig } from '../config/defaults.js';
6
+ export async function runInitCommand(options) {
7
+ p.intro(pc.bgBlue(pc.white(' Claude Orchestrator: Init ')));
8
+ const configPath = path.resolve(process.cwd(), options.config || '.claude-orchestrator.json');
9
+ const configExists = await fs
10
+ .access(configPath)
11
+ .then(() => true)
12
+ .catch(() => false);
13
+ if (configExists) {
14
+ p.log.warn(`Config already exists at ${pc.cyan(configPath)}. Leaving it untouched.`);
15
+ }
16
+ else {
17
+ const planDirResult = await p.text({
18
+ message: 'Where should plan files be stored?',
19
+ initialValue: defaultConfig.planDir,
20
+ });
21
+ if (p.isCancel(planDirResult)) {
22
+ p.cancel('Init cancelled.');
23
+ process.exit(0);
24
+ }
25
+ const config = { ...defaultConfig, planDir: planDirResult };
26
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
27
+ p.log.success(`Created config at ${pc.cyan(configPath)}.`);
28
+ }
29
+ const config = configExists
30
+ ? JSON.parse(await fs.readFile(configPath, 'utf8'))
31
+ : { planDir: defaultConfig.planDir };
32
+ const resolvedPlanDir = path.resolve(process.cwd(), config.planDir || defaultConfig.planDir);
33
+ await fs.mkdir(resolvedPlanDir, { recursive: true });
34
+ p.log.success(`Plan directory ready at ${pc.cyan(resolvedPlanDir)}.`);
35
+ p.outro(pc.green(`Run ${pc.cyan('claude-orchestrator plan')} to create your first plan.`));
36
+ }
@@ -0,0 +1,43 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as p from '@clack/prompts';
4
+ import pc from 'picocolors';
5
+ import { loadConfig } from '../config/loader.js';
6
+ import { discoverPlan } from '../plans/discovery.js';
7
+ export async function runLogsCommand(options) {
8
+ p.intro(pc.bgBlue(pc.white(' Claude Orchestrator: Logs ')));
9
+ const config = await loadConfig(options.config);
10
+ let planPath = options.plan;
11
+ if (!planPath) {
12
+ const defaultPlanDir = config.planDir || '.claude-orchestrator/plans';
13
+ planPath = (await discoverPlan({ planDir: defaultPlanDir })) || undefined;
14
+ if (!planPath) {
15
+ process.exit(0);
16
+ }
17
+ }
18
+ const planId = path.basename(planPath, path.extname(planPath));
19
+ const logsDir = config.logsDir || '.claude-orchestrator/logs';
20
+ const stateDir = config.stateDir || '.claude-orchestrator/state';
21
+ const planLogDir = path.join(logsDir, planId);
22
+ const stateFile = path.join(stateDir, `${planId}.json`);
23
+ p.log.info(pc.blue('--- Log Locations ---'));
24
+ p.log.info(`State File: ${stateFile}${fs.existsSync(stateFile) ? '' : pc.yellow(' (not found)')}`);
25
+ if (options.task) {
26
+ const taskLogDir = path.join(planLogDir, options.task);
27
+ p.log.info(`Task Log Dir: ${taskLogDir}${fs.existsSync(taskLogDir) ? '' : pc.yellow(' (not found)')}`);
28
+ if (fs.existsSync(taskLogDir)) {
29
+ for (const file of fs.readdirSync(taskLogDir)) {
30
+ p.log.info(` - ${path.join(taskLogDir, file)}`);
31
+ }
32
+ }
33
+ }
34
+ else {
35
+ p.log.info(`Plan Log Dir: ${planLogDir}${fs.existsSync(planLogDir) ? '' : pc.yellow(' (not found)')}`);
36
+ if (fs.existsSync(planLogDir)) {
37
+ for (const taskId of fs.readdirSync(planLogDir)) {
38
+ p.log.info(` - ${path.join(planLogDir, taskId)}`);
39
+ }
40
+ }
41
+ }
42
+ p.outro(pc.green('Done.'));
43
+ }