@arvorco/relentless 0.5.2 → 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.
@@ -7,7 +7,7 @@ handoffs:
7
7
  send: true
8
8
  ---
9
9
 
10
- Load the analyze skill and validate cross-artifact consistency.
10
+ Load the analyze skill (`[skills_path]/analyze/SKILL.md`) and validate cross-artifact consistency.
11
11
 
12
12
  **Context:** $ARGUMENTS
13
13
 
@@ -2,7 +2,7 @@
2
2
  description: Generate quality validation checklist from feature artifacts.
3
3
  ---
4
4
 
5
- Load the checklist skill and generate quality validation checklist.
5
+ Load the checklist skill (`[skills_path]/checklist/SKILL.md`) and generate quality validation checklist.
6
6
 
7
7
  **Context:** $ARGUMENTS
8
8
 
@@ -6,7 +6,7 @@ handoffs:
6
6
  prompt: Create technical plan with clarifications
7
7
  ---
8
8
 
9
- Load the clarify skill and resolve specification ambiguities.
9
+ Load the clarify skill (`[skills_path]/clarify/SKILL.md`) and resolve specification ambiguities.
10
10
 
11
11
  **Context:** $ARGUMENTS
12
12
 
@@ -10,7 +10,7 @@ Load the constitution skill and create or update the project constitution at `re
10
10
 
11
11
  **Context:** $ARGUMENTS
12
12
 
13
- The constitution skill (`.claude/skills/constitution/SKILL.md`) will guide you through:
13
+ The constitution skill (`[skills_path]/constitution/SKILL.md`) will guide you through:
14
14
 
15
15
  ## Process
16
16
 
@@ -22,7 +22,7 @@ The constitution skill (`.claude/skills/constitution/SKILL.md`) will guide you t
22
22
  - Version control (branches, commits, CI/CD)
23
23
 
24
24
  2. **Generate Constitution**: Create personalized governance document
25
- - Load template from `.claude/skills/constitution/templates/constitution.md`
25
+ - Load template from `[skills_path]/constitution/templates/constitution.md`
26
26
  - Replace all placeholders with concrete values from user answers
27
27
  - Ensure MUST/SHOULD rules are clear and testable
28
28
  - Set version 1.0.0 for new, increment semantically for updates
@@ -11,7 +11,7 @@ handoffs:
11
11
  send: true
12
12
  ---
13
13
 
14
- Load the convert skill and convert tasks.md to prd.json with routing.
14
+ Load the convert skill (`[skills_path]/convert/SKILL.md`) and convert tasks.md to prd.json with routing.
15
15
 
16
16
  **Context:** $ARGUMENTS
17
17
 
@@ -2,7 +2,7 @@
2
2
  description: Execute implementation workflow for a feature manually, story by story.
3
3
  ---
4
4
 
5
- Load the implement skill and begin systematic implementation.
5
+ Load the implement skill (`[skills_path]/implement/SKILL.md`) and begin systematic implementation.
6
6
 
7
7
  **Usage:** `/relentless.implement [story-id]`
8
8
 
@@ -11,7 +11,7 @@ handoffs:
11
11
  send: true
12
12
  ---
13
13
 
14
- Load the plan skill and create technical implementation plan.
14
+ Load the plan skill (`[skills_path]/plan/SKILL.md`) and create technical implementation plan.
15
15
 
16
16
  **Context:** $ARGUMENTS
17
17
 
@@ -14,20 +14,20 @@ Load the specify skill and create a feature specification in `relentless/feature
14
14
 
15
15
  **Feature Description:** $ARGUMENTS
16
16
 
17
- The specify skill (`.claude/skills/specify/SKILL.md`) will guide you through:
17
+ The specify skill (`[skills_path]/specify/SKILL.md`) will guide you through:
18
18
 
19
19
  ## Process
20
20
 
21
21
  1. **Create Feature Structure**
22
22
  - Generate short name (2-4 words) from description
23
23
  - Check existing branches to find next number
24
- - Run `.claude/skills/specify/scripts/bash/create-new-feature.sh --json "FEATURE_DESCRIPTION"`
24
+ - Run `[skills_path]/specify/scripts/bash/create-new-feature.sh --json "FEATURE_DESCRIPTION"`
25
25
  - Parse JSON output for BRANCH_NAME, SPEC_FILE, FEATURE_DIR
26
26
 
27
27
  2. **Load Context**
28
28
  - Read `relentless/constitution.md` for governance rules
29
29
  - Note MUST/SHOULD requirements for specifications
30
- - Load spec template from `.claude/skills/specify/templates/spec.md`
30
+ - Load spec template from `[skills_path]/specify/templates/spec.md`
31
31
 
32
32
  3. **Generate Specification**
33
33
  - Extract key concepts: actors, actions, data, constraints
@@ -11,7 +11,7 @@ handoffs:
11
11
  send: true
12
12
  ---
13
13
 
14
- Load the tasks skill and generate user stories and implementation tasks.
14
+ Load the tasks skill (`[skills_path]/tasks/SKILL.md`) and generate user stories and implementation tasks.
15
15
 
16
16
  **Context:** $ARGUMENTS
17
17
 
@@ -2,7 +2,7 @@
2
2
  description: Convert user stories to GitHub issues.
3
3
  ---
4
4
 
5
- Load the taskstoissues skill and create GitHub issues from user stories.
5
+ Load the taskstoissues skill (`[skills_path]/taskstoissues/SKILL.md`) and create GitHub issues from user stories.
6
6
 
7
7
  **Context:** $ARGUMENTS
8
8
 
package/CHANGELOG.md CHANGED
@@ -5,6 +5,53 @@ All notable changes to Relentless will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
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
+
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
47
+ - **Commands**: Use abstract `[skills_path]` pattern for cross-agent compatibility (#7)
48
+ - Commands now reference `[skills_path]/skillname/SKILL.md` instead of hardcoded `.claude/skills/`
49
+ - Each agent can resolve the path to their own skills directory
50
+ - Updated all 10 command files: plan, tasks, analyze, clarify, implement, checklist, convert, taskstoissues, specify, constitution
51
+ - **Config**: CLI now respects `config.defaultAgent` when no `--agent` flag is provided (#2)
52
+ - Previously hardcoded to "auto", now reads from user configuration
53
+ - Allows users to set their preferred default agent in `relentless/config.json`
54
+
8
55
  ## [0.5.2](https://github.com/ArvorCo/Relentless/releases/tag/v0.5.2) - 2026-01-23
9
56
 
10
57
  ### Fixed
@@ -463,7 +510,9 @@ Relentless evolved from the [Ralph Wiggum Pattern](https://ghuntley.com/ralph/)
463
510
  - **License**: MIT
464
511
  - **Inspiration**: [Ralph Wiggum Pattern](https://ghuntley.com/ralph/) by Geoffrey Huntley
465
512
 
466
- [Unreleased]: https://github.com/ArvorCo/Relentless/compare/v0.5.2...HEAD
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
467
516
  [0.5.2]: https://github.com/ArvorCo/Relentless/compare/v0.5.1...v0.5.2
468
517
  [0.5.1]: https://github.com/ArvorCo/Relentless/compare/v0.5.0...v0.5.1
469
518
  [0.5.0]: https://github.com/ArvorCo/Relentless/compare/v0.4.5...v0.5.0
package/bin/relentless.ts CHANGED
@@ -56,7 +56,7 @@ program
56
56
  .command("run")
57
57
  .description("Run the orchestration loop for a feature")
58
58
  .requiredOption("-f, --feature <name>", "Feature name to run")
59
- .option("-a, --agent <name>", "Agent to use (claude, amp, opencode, codex, droid, gemini, auto)", "auto")
59
+ .option("-a, --agent <name>", "Agent to use (claude, amp, opencode, codex, droid, gemini, auto)")
60
60
  .option("-m, --max-iterations <n>", "Maximum iterations", "20")
61
61
  .option("--mode <mode>", `Cost optimization mode (${VALID_MODES.join(", ")})`)
62
62
  .option("--fallback-order <harnesses>", `Harness fallback order (${VALID_HARNESSES.join(",")})`)
@@ -64,9 +64,16 @@ 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
- const agent = options.agent.toLowerCase();
70
+ // Load config first to get defaultAgent
71
+ const config = await loadConfig();
72
+
73
+ // Use CLI option if provided, otherwise use config.defaultAgent
74
+ const agentOption = options.agent?.toLowerCase() ?? config.defaultAgent;
75
+ const agent = agentOption.toLowerCase();
76
+
70
77
  if (agent !== "auto" && !isValidAgentName(agent)) {
71
78
  console.error(chalk.red(`Invalid agent: ${agent}`));
72
79
  console.log(`Valid agents: ${getAllAgentNames().join(", ")}, auto`);
@@ -129,7 +136,6 @@ program
129
136
  process.exit(1);
130
137
  }
131
138
 
132
- const config = await loadConfig();
133
139
  const prd = await loadPRD(prdPath);
134
140
  const prdPreferredMode =
135
141
  prd.routingPreference?.type === "auto" ? prd.routingPreference.mode : undefined;
@@ -195,6 +201,8 @@ program
195
201
  fallbackOrder,
196
202
  skipReview,
197
203
  reviewMode,
204
+ enableTasks: options.tasks,
205
+ featureName: options.feature,
198
206
  });
199
207
 
200
208
  if (result.success) {
@@ -742,6 +750,167 @@ queue
742
750
  }
743
751
  });
744
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
+
745
914
  // Analyze command
746
915
  program
747
916
  .command("analyze")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arvorco/relentless",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Universal AI agent orchestrator - works with Claude Code, Amp, OpenCode, Codex, Droid, and Gemini",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -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
- if (/not_found_error/i.test(output) && /model/i.test(output)) {
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",
@@ -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();
@@ -16,6 +16,8 @@ export interface InvokeOptions {
16
16
  model?: string;
17
17
  /** Skip all permission prompts */
18
18
  dangerouslyAllowAll?: boolean;
19
+ /** Claude Code TaskList ID for cross-session coordination */
20
+ taskListId?: string;
19
21
  }
20
22
 
21
23
  /**
@@ -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";