@arvorco/relentless 0.5.3 → 0.6.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/CHANGELOG.md +39 -1
- package/bin/relentless.ts +164 -0
- package/package.json +1 -1
- package/src/agents/claude.ts +19 -1
- package/src/agents/exec.ts +9 -0
- package/src/agents/types.ts +2 -0
- package/src/execution/runner.ts +36 -0
- package/src/tasks/index.ts +79 -0
- package/src/tasks/sync.ts +380 -0
- package/src/tasks/tasklist.ts +425 -0
- package/src/tasks/types.ts +157 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0](https://github.com/ArvorCo/Relentless/releases/tag/v0.6.0) - 2026-01-23
|
|
11
|
+
|
|
12
|
+
### Major Features
|
|
13
|
+
|
|
14
|
+
#### Claude Code Tasks Integration - Cross-Session Coordination (#8)
|
|
15
|
+
- **TaskList-Per-Feature**: Each feature gets its own TaskList ID (e.g., `relentless-auth`)
|
|
16
|
+
- **Bidirectional Sync**: Convert PRD user stories ↔ Claude Tasks
|
|
17
|
+
- **Cross-session sharing**: Multiple Claude instances coordinate via `CLAUDE_CODE_TASK_LIST_ID` env var
|
|
18
|
+
- **New `--tasks` flag**: Enable Tasks integration with `relentless run --feature auth --tasks`
|
|
19
|
+
|
|
20
|
+
#### New Tasks Module (`src/tasks/`)
|
|
21
|
+
- `types.ts` - Task types and Zod schemas (`ClaudeTask`, `TaskList`, `SyncResult`, `ImportResult`)
|
|
22
|
+
- `tasklist.ts` - TaskList CRUD operations (create, load, save, delete, addTask, updateTask, etc.)
|
|
23
|
+
- `sync.ts` - Bidirectional sync: `syncPrdToTasks()`, `syncTasksToPrd()`, `importTasksToPrd()`, `bidirectionalSync()`
|
|
24
|
+
- `index.ts` - Module exports
|
|
25
|
+
|
|
26
|
+
#### New CLI Commands
|
|
27
|
+
```bash
|
|
28
|
+
# Run with Tasks integration
|
|
29
|
+
relentless run --feature auth --tasks
|
|
30
|
+
|
|
31
|
+
# Task management
|
|
32
|
+
relentless tasks list -f <feature> # List tasks for a feature
|
|
33
|
+
relentless tasks sync -f <feature> # Sync PRD ↔ Claude Tasks
|
|
34
|
+
relentless tasks import <id> -f <feature> # Import Claude Tasks to PRD
|
|
35
|
+
relentless tasks clear -f <feature> # Clear TaskList
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- `taskListId` option in `InvokeOptions` for agent adapters
|
|
40
|
+
- `env` option in `RunCommandOptions` for passing environment variables
|
|
41
|
+
- 38 new tests for Tasks module (types + tasklist CRUD)
|
|
42
|
+
|
|
10
43
|
### Fixed
|
|
44
|
+
- **Rate Limit Detection**: Fixed false positive when Claude mentions "model" and "not_found_error" in conversation
|
|
45
|
+
- Previous pattern was too broad and matched normal Claude output
|
|
46
|
+
- Now uses specific pattern for actual API error responses
|
|
11
47
|
- **Commands**: Use abstract `[skills_path]` pattern for cross-agent compatibility (#7)
|
|
12
48
|
- Commands now reference `[skills_path]/skillname/SKILL.md` instead of hardcoded `.claude/skills/`
|
|
13
49
|
- Each agent can resolve the path to their own skills directory
|
|
@@ -474,7 +510,9 @@ Relentless evolved from the [Ralph Wiggum Pattern](https://ghuntley.com/ralph/)
|
|
|
474
510
|
- **License**: MIT
|
|
475
511
|
- **Inspiration**: [Ralph Wiggum Pattern](https://ghuntley.com/ralph/) by Geoffrey Huntley
|
|
476
512
|
|
|
477
|
-
[Unreleased]: https://github.com/ArvorCo/Relentless/compare/v0.
|
|
513
|
+
[Unreleased]: https://github.com/ArvorCo/Relentless/compare/v0.6.0...HEAD
|
|
514
|
+
[0.6.0]: https://github.com/ArvorCo/Relentless/compare/v0.5.3...v0.6.0
|
|
515
|
+
[0.5.3]: https://github.com/ArvorCo/Relentless/compare/v0.5.2...v0.5.3
|
|
478
516
|
[0.5.2]: https://github.com/ArvorCo/Relentless/compare/v0.5.1...v0.5.2
|
|
479
517
|
[0.5.1]: https://github.com/ArvorCo/Relentless/compare/v0.5.0...v0.5.1
|
|
480
518
|
[0.5.0]: https://github.com/ArvorCo/Relentless/compare/v0.4.5...v0.5.0
|
package/bin/relentless.ts
CHANGED
|
@@ -64,6 +64,7 @@ program
|
|
|
64
64
|
.option("--review-mode <mode>", `Review quality mode (${VALID_REVIEW_MODES.join(", ")})`)
|
|
65
65
|
.option("--dry-run", "Show what would be executed without running", false)
|
|
66
66
|
.option("--tui", "Use beautiful terminal UI interface", false)
|
|
67
|
+
.option("--tasks", "Enable Claude Code Tasks integration for cross-session coordination", false)
|
|
67
68
|
.option("-d, --dir <path>", "Working directory", process.cwd())
|
|
68
69
|
.action(async (options) => {
|
|
69
70
|
// Load config first to get defaultAgent
|
|
@@ -200,6 +201,8 @@ program
|
|
|
200
201
|
fallbackOrder,
|
|
201
202
|
skipReview,
|
|
202
203
|
reviewMode,
|
|
204
|
+
enableTasks: options.tasks,
|
|
205
|
+
featureName: options.feature,
|
|
203
206
|
});
|
|
204
207
|
|
|
205
208
|
if (result.success) {
|
|
@@ -747,6 +750,167 @@ queue
|
|
|
747
750
|
}
|
|
748
751
|
});
|
|
749
752
|
|
|
753
|
+
// Tasks commands - Claude Code Tasks integration
|
|
754
|
+
const tasks = program.command("tasks").description("Manage Claude Code Tasks for cross-session coordination");
|
|
755
|
+
|
|
756
|
+
tasks
|
|
757
|
+
.command("list")
|
|
758
|
+
.description("List tasks for a feature")
|
|
759
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
760
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
761
|
+
.action(async (options) => {
|
|
762
|
+
const { generateTaskListId, loadTaskList, getTaskListSummary } = await import("../src/tasks");
|
|
763
|
+
|
|
764
|
+
const taskListId = generateTaskListId(options.feature);
|
|
765
|
+
const taskList = await loadTaskList(taskListId);
|
|
766
|
+
|
|
767
|
+
if (!taskList) {
|
|
768
|
+
console.log(chalk.dim(`\nNo task list found for feature: ${options.feature}`));
|
|
769
|
+
console.log(chalk.dim(`Run 'relentless run --feature ${options.feature} --tasks' to create one.\n`));
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const summary = await getTaskListSummary(taskListId);
|
|
774
|
+
if (!summary) {
|
|
775
|
+
console.log(chalk.dim(`\nNo task list found for feature: ${options.feature}\n`));
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
console.log(chalk.bold(`\n📋 Tasks: ${options.feature}\n`));
|
|
780
|
+
console.log(chalk.dim(`TaskList ID: ${taskListId}`));
|
|
781
|
+
console.log(chalk.dim(`Progress: ${summary.completedTasks}/${summary.totalTasks} complete\n`));
|
|
782
|
+
|
|
783
|
+
for (const task of taskList.tasks) {
|
|
784
|
+
const status = task.status === "completed"
|
|
785
|
+
? chalk.green("✓")
|
|
786
|
+
: task.status === "in_progress"
|
|
787
|
+
? chalk.yellow("◐")
|
|
788
|
+
: chalk.dim("○");
|
|
789
|
+
const content = task.status === "completed"
|
|
790
|
+
? chalk.strikethrough(chalk.dim(task.content))
|
|
791
|
+
: task.content;
|
|
792
|
+
console.log(` ${status} ${content}`);
|
|
793
|
+
}
|
|
794
|
+
console.log("");
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
tasks
|
|
798
|
+
.command("sync")
|
|
799
|
+
.description("Sync PRD stories with Claude Tasks")
|
|
800
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
801
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
802
|
+
.action(async (options) => {
|
|
803
|
+
const { bidirectionalSync } = await import("../src/tasks");
|
|
804
|
+
|
|
805
|
+
const relentlessDir = findRelentlessDir(options.dir);
|
|
806
|
+
if (!relentlessDir) {
|
|
807
|
+
console.error(chalk.red("Relentless not initialized. Run: relentless init"));
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const featureDir = join(relentlessDir, "features", options.feature);
|
|
812
|
+
const prdPath = join(featureDir, "prd.json");
|
|
813
|
+
|
|
814
|
+
if (!existsSync(prdPath)) {
|
|
815
|
+
console.error(chalk.red(`Feature '${options.feature}' not found or has no prd.json`));
|
|
816
|
+
console.log(chalk.dim(`Available features: ${listFeatures(options.dir).join(", ") || "none"}`));
|
|
817
|
+
process.exit(1);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
console.log(chalk.dim(`Syncing tasks for ${options.feature}...`));
|
|
821
|
+
|
|
822
|
+
const result = await bidirectionalSync(prdPath, options.feature);
|
|
823
|
+
|
|
824
|
+
if (result.success) {
|
|
825
|
+
console.log(chalk.green(`\n✓ Sync complete`));
|
|
826
|
+
if (result.added > 0) {
|
|
827
|
+
console.log(chalk.dim(` Added: ${result.added} tasks`));
|
|
828
|
+
}
|
|
829
|
+
if (result.updated > 0) {
|
|
830
|
+
console.log(chalk.dim(` Updated: ${result.updated} tasks`));
|
|
831
|
+
}
|
|
832
|
+
if (result.removed > 0) {
|
|
833
|
+
console.log(chalk.dim(` Removed: ${result.removed} tasks`));
|
|
834
|
+
}
|
|
835
|
+
} else {
|
|
836
|
+
console.error(chalk.red(`\n❌ Sync failed`));
|
|
837
|
+
for (const error of result.errors) {
|
|
838
|
+
console.error(chalk.red(` ${error}`));
|
|
839
|
+
}
|
|
840
|
+
process.exit(1);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
for (const warning of result.warnings) {
|
|
844
|
+
console.log(chalk.yellow(` ⚠️ ${warning}`));
|
|
845
|
+
}
|
|
846
|
+
console.log("");
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
tasks
|
|
850
|
+
.command("import <taskListId>")
|
|
851
|
+
.description("Import Claude Tasks to create a new PRD")
|
|
852
|
+
.requiredOption("-f, --feature <name>", "Feature name for the new PRD")
|
|
853
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
854
|
+
.action(async (taskListId, options) => {
|
|
855
|
+
const { importTasksToPrd } = await import("../src/tasks");
|
|
856
|
+
|
|
857
|
+
const relentlessDir = findRelentlessDir(options.dir);
|
|
858
|
+
if (!relentlessDir) {
|
|
859
|
+
console.error(chalk.red("Relentless not initialized. Run: relentless init"));
|
|
860
|
+
process.exit(1);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Create feature directory if it doesn't exist
|
|
864
|
+
const featureDir = join(relentlessDir, "features", options.feature);
|
|
865
|
+
if (!existsSync(featureDir)) {
|
|
866
|
+
mkdirSync(featureDir, { recursive: true });
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
console.log(chalk.dim(`Importing tasks from ${taskListId}...`));
|
|
870
|
+
|
|
871
|
+
const result = await importTasksToPrd(taskListId, options.feature, featureDir);
|
|
872
|
+
|
|
873
|
+
if (result.success) {
|
|
874
|
+
console.log(chalk.green(`\n✓ Import complete`));
|
|
875
|
+
console.log(chalk.dim(` Created ${result.storiesCreated} stories`));
|
|
876
|
+
console.log(chalk.dim(` PRD saved to: ${result.prdPath}`));
|
|
877
|
+
console.log(chalk.dim(`\nNext step:`));
|
|
878
|
+
console.log(chalk.cyan(` relentless run --feature ${options.feature} --tasks`));
|
|
879
|
+
} else {
|
|
880
|
+
console.error(chalk.red(`\n❌ Import failed`));
|
|
881
|
+
for (const error of result.errors) {
|
|
882
|
+
console.error(chalk.red(` ${error}`));
|
|
883
|
+
}
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
886
|
+
console.log("");
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
tasks
|
|
890
|
+
.command("clear")
|
|
891
|
+
.description("Clear all tasks for a feature")
|
|
892
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
893
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
894
|
+
.action(async (options) => {
|
|
895
|
+
const { generateTaskListId, clearTaskList, taskListExists } = await import("../src/tasks");
|
|
896
|
+
|
|
897
|
+
const taskListId = generateTaskListId(options.feature);
|
|
898
|
+
|
|
899
|
+
if (!await taskListExists(taskListId)) {
|
|
900
|
+
console.log(chalk.dim(`\nNo task list found for feature: ${options.feature}\n`));
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const cleared = await clearTaskList(taskListId);
|
|
905
|
+
|
|
906
|
+
if (cleared) {
|
|
907
|
+
console.log(chalk.green(`\n✓ Cleared all tasks for ${options.feature}\n`));
|
|
908
|
+
} else {
|
|
909
|
+
console.error(chalk.red(`\n❌ Failed to clear tasks\n`));
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
750
914
|
// Analyze command
|
|
751
915
|
program
|
|
752
916
|
.command("analyze")
|
package/package.json
CHANGED
package/src/agents/claude.ts
CHANGED
|
@@ -79,10 +79,17 @@ export const claudeAdapter: AgentAdapter = {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// Build environment variables
|
|
83
|
+
const env: Record<string, string> = {};
|
|
84
|
+
if (options?.taskListId) {
|
|
85
|
+
env.CLAUDE_CODE_TASK_LIST_ID = options.taskListId;
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
const result = await runCommand(["claude", ...args], {
|
|
83
89
|
cwd: options?.workingDirectory,
|
|
84
90
|
stdin: new Blob([prompt]),
|
|
85
91
|
timeoutMs: options?.timeout,
|
|
92
|
+
env: Object.keys(env).length > 0 ? env : undefined,
|
|
86
93
|
});
|
|
87
94
|
|
|
88
95
|
const timeoutNote =
|
|
@@ -120,11 +127,20 @@ export const claudeAdapter: AgentAdapter = {
|
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
|
|
130
|
+
// Build environment variables for streaming
|
|
131
|
+
const streamEnv: Record<string, string> = {};
|
|
132
|
+
if (options?.taskListId) {
|
|
133
|
+
streamEnv.CLAUDE_CODE_TASK_LIST_ID = options.taskListId;
|
|
134
|
+
}
|
|
135
|
+
|
|
123
136
|
const proc = Bun.spawn(["claude", ...args], {
|
|
124
137
|
cwd: options?.workingDirectory,
|
|
125
138
|
stdin: new Blob([prompt]),
|
|
126
139
|
stdout: "pipe",
|
|
127
140
|
stderr: "pipe",
|
|
141
|
+
env: Object.keys(streamEnv).length > 0
|
|
142
|
+
? { ...process.env, ...streamEnv }
|
|
143
|
+
: undefined,
|
|
128
144
|
});
|
|
129
145
|
|
|
130
146
|
const decoder = new TextDecoder();
|
|
@@ -178,7 +194,9 @@ export const claudeAdapter: AgentAdapter = {
|
|
|
178
194
|
};
|
|
179
195
|
}
|
|
180
196
|
|
|
181
|
-
|
|
197
|
+
// More specific pattern for actual API model not found errors
|
|
198
|
+
// Avoid matching if Claude just mentions "model" and "not_found_error" in conversation
|
|
199
|
+
if (/error.*model.*not[_\s]?found|model.*not[_\s]?found.*error|"type":\s*"not_found_error"/i.test(output)) {
|
|
182
200
|
return {
|
|
183
201
|
limited: true,
|
|
184
202
|
message: "Claude model not found",
|
package/src/agents/exec.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface RunCommandOptions {
|
|
|
6
6
|
cwd?: string;
|
|
7
7
|
stdin?: Blob;
|
|
8
8
|
timeoutMs?: number;
|
|
9
|
+
/** Environment variables to pass to the command */
|
|
10
|
+
env?: Record<string, string>;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface RunCommandResult {
|
|
@@ -48,11 +50,18 @@ export async function runCommand(
|
|
|
48
50
|
options: RunCommandOptions = {}
|
|
49
51
|
): Promise<RunCommandResult> {
|
|
50
52
|
const startTime = Date.now();
|
|
53
|
+
|
|
54
|
+
// Merge custom env vars with current process env
|
|
55
|
+
const env = options.env
|
|
56
|
+
? { ...process.env, ...options.env }
|
|
57
|
+
: undefined;
|
|
58
|
+
|
|
51
59
|
const proc = Bun.spawn(command, {
|
|
52
60
|
cwd: options.cwd,
|
|
53
61
|
stdin: options.stdin,
|
|
54
62
|
stdout: "pipe",
|
|
55
63
|
stderr: "pipe",
|
|
64
|
+
env,
|
|
56
65
|
});
|
|
57
66
|
|
|
58
67
|
let lastOutput = Date.now();
|
package/src/agents/types.ts
CHANGED
package/src/execution/runner.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
logSkipRejectedToProgress,
|
|
43
43
|
formatSkipMessage,
|
|
44
44
|
} from "./commands";
|
|
45
|
+
import { generateTaskListId, syncPrdToTasks, syncTasksToPrd } from "../tasks";
|
|
45
46
|
|
|
46
47
|
export interface RunOptions {
|
|
47
48
|
/** Agent to use (or "auto" for smart routing) */
|
|
@@ -66,6 +67,10 @@ export interface RunOptions {
|
|
|
66
67
|
skipReview?: boolean;
|
|
67
68
|
/** Review quality mode (can differ from execution mode) */
|
|
68
69
|
reviewMode?: "free" | "cheap" | "good" | "genius";
|
|
70
|
+
/** Enable Claude Code Tasks integration for cross-session coordination */
|
|
71
|
+
enableTasks?: boolean;
|
|
72
|
+
/** Feature name (for TaskList ID generation) */
|
|
73
|
+
featureName?: string;
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
export interface RunResult {
|
|
@@ -479,6 +484,23 @@ export async function run(options: RunOptions): Promise<RunResult> {
|
|
|
479
484
|
console.log(chalk.yellow("\n⚠️ Dry run mode - not executing\n"));
|
|
480
485
|
}
|
|
481
486
|
|
|
487
|
+
// Generate TaskList ID if tasks integration is enabled
|
|
488
|
+
let taskListId: string | undefined;
|
|
489
|
+
if (options.enableTasks && options.featureName) {
|
|
490
|
+
taskListId = generateTaskListId(options.featureName);
|
|
491
|
+
console.log(`Tasks: ${chalk.green("enabled")} (${taskListId})`);
|
|
492
|
+
|
|
493
|
+
// Sync PRD to tasks at start
|
|
494
|
+
try {
|
|
495
|
+
const syncResult = await syncPrdToTasks(prd, options.featureName);
|
|
496
|
+
if (syncResult.added > 0 || syncResult.updated > 0) {
|
|
497
|
+
console.log(chalk.dim(` Synced ${syncResult.added} new, ${syncResult.updated} updated tasks`));
|
|
498
|
+
}
|
|
499
|
+
} catch (syncError) {
|
|
500
|
+
console.warn(chalk.yellow(` ⚠️ Failed to sync PRD to tasks: ${syncError}`));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
482
504
|
// Main loop
|
|
483
505
|
for (let i = 1; i <= options.maxIterations; i++) {
|
|
484
506
|
iterations = i;
|
|
@@ -737,6 +759,7 @@ export async function run(options: RunOptions): Promise<RunResult> {
|
|
|
737
759
|
dangerouslyAllowAll: options.config.agents[agent.name]?.dangerouslyAllowAll ?? true,
|
|
738
760
|
model: autoModeEnabled ? autoRoutingModel : options.config.agents[agent.name]?.model,
|
|
739
761
|
timeout: options.config.execution.timeout,
|
|
762
|
+
taskListId,
|
|
740
763
|
});
|
|
741
764
|
|
|
742
765
|
// Check for rate limit during research phase
|
|
@@ -798,6 +821,7 @@ export async function run(options: RunOptions): Promise<RunResult> {
|
|
|
798
821
|
dangerouslyAllowAll: options.config.agents[agent.name]?.dangerouslyAllowAll ?? true,
|
|
799
822
|
model: autoModeEnabled ? autoRoutingModel : options.config.agents[agent.name]?.model,
|
|
800
823
|
timeout: options.config.execution.timeout,
|
|
824
|
+
taskListId,
|
|
801
825
|
});
|
|
802
826
|
|
|
803
827
|
// Check for rate limit
|
|
@@ -942,6 +966,18 @@ export async function run(options: RunOptions): Promise<RunResult> {
|
|
|
942
966
|
const success = isComplete(finalPRD);
|
|
943
967
|
const duration = Date.now() - startTime;
|
|
944
968
|
|
|
969
|
+
// Final sync tasks back to PRD
|
|
970
|
+
if (options.enableTasks && options.featureName) {
|
|
971
|
+
try {
|
|
972
|
+
const syncResult = await syncTasksToPrd(options.prdPath, options.featureName);
|
|
973
|
+
if (syncResult.updated > 0) {
|
|
974
|
+
console.log(chalk.dim(` Synced ${syncResult.updated} task updates back to PRD`));
|
|
975
|
+
}
|
|
976
|
+
} catch (syncError) {
|
|
977
|
+
console.warn(chalk.yellow(` ⚠️ Failed to sync tasks to PRD: ${syncError}`));
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
945
981
|
if (!success) {
|
|
946
982
|
console.log(chalk.yellow(`\n⚠️ Reached max iterations (${options.maxIterations}) without completing all stories.`));
|
|
947
983
|
console.log(chalk.dim(`Check progress.txt for status.`));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Tasks Module
|
|
3
|
+
*
|
|
4
|
+
* Integration with Claude Code's native Tasks feature for cross-session
|
|
5
|
+
* coordination and parallel story execution.
|
|
6
|
+
*
|
|
7
|
+
* ## Usage
|
|
8
|
+
*
|
|
9
|
+
* ### Enable TaskList for a feature
|
|
10
|
+
* ```bash
|
|
11
|
+
* relentless run --feature auth --tasks
|
|
12
|
+
* # Sets CLAUDE_CODE_TASK_LIST_ID=relentless-auth
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* ### Sync PRD to Tasks
|
|
16
|
+
* ```bash
|
|
17
|
+
* relentless tasks sync -f auth
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* ### Import Claude Tasks to PRD
|
|
21
|
+
* ```bash
|
|
22
|
+
* relentless tasks import my-task-list --feature my-feature
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* ## Key Concepts
|
|
26
|
+
*
|
|
27
|
+
* - **TaskList**: A collection of tasks stored in ~/.claude/tasks/{id}.json
|
|
28
|
+
* - **Task**: Individual work item with status, dependencies, and story link
|
|
29
|
+
* - **Sync**: Bidirectional sync between PRD stories and Claude Tasks
|
|
30
|
+
*
|
|
31
|
+
* @see https://docs.anthropic.com/claude-code
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// Types
|
|
35
|
+
export {
|
|
36
|
+
type TaskStatus,
|
|
37
|
+
type ClaudeTask,
|
|
38
|
+
type TaskList,
|
|
39
|
+
type TaskListSummary,
|
|
40
|
+
type SyncResult,
|
|
41
|
+
type ImportResult,
|
|
42
|
+
ClaudeTaskSchema,
|
|
43
|
+
TaskListSchema,
|
|
44
|
+
generateTaskListId,
|
|
45
|
+
getTasksDirectory,
|
|
46
|
+
getTaskListPath,
|
|
47
|
+
} from "./types";
|
|
48
|
+
|
|
49
|
+
// TaskList CRUD operations
|
|
50
|
+
export {
|
|
51
|
+
ensureTasksDirectory,
|
|
52
|
+
taskListExists,
|
|
53
|
+
loadTaskList,
|
|
54
|
+
saveTaskList,
|
|
55
|
+
createTaskList,
|
|
56
|
+
deleteTaskList,
|
|
57
|
+
listTaskLists,
|
|
58
|
+
getTaskListSummary,
|
|
59
|
+
addTask,
|
|
60
|
+
updateTask,
|
|
61
|
+
removeTask,
|
|
62
|
+
markTaskComplete,
|
|
63
|
+
markTaskInProgress,
|
|
64
|
+
getNextTask,
|
|
65
|
+
getParallelTasks,
|
|
66
|
+
isTaskListComplete,
|
|
67
|
+
clearTaskList,
|
|
68
|
+
getOrCreateTaskList,
|
|
69
|
+
} from "./tasklist";
|
|
70
|
+
|
|
71
|
+
// PRD Sync operations
|
|
72
|
+
export {
|
|
73
|
+
storyToTask,
|
|
74
|
+
taskToStory,
|
|
75
|
+
syncPrdToTasks,
|
|
76
|
+
syncTasksToPrd,
|
|
77
|
+
importTasksToPrd,
|
|
78
|
+
bidirectionalSync,
|
|
79
|
+
} from "./sync";
|