@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.
- package/.claude/commands/relentless.analyze.md +1 -1
- package/.claude/commands/relentless.checklist.md +1 -1
- package/.claude/commands/relentless.clarify.md +1 -1
- package/.claude/commands/relentless.constitution.md +2 -2
- package/.claude/commands/relentless.convert.md +1 -1
- package/.claude/commands/relentless.implement.md +1 -1
- package/.claude/commands/relentless.plan.md +1 -1
- package/.claude/commands/relentless.specify.md +3 -3
- package/.claude/commands/relentless.tasks.md +1 -1
- package/.claude/commands/relentless.taskstoissues.md +1 -1
- package/CHANGELOG.md +50 -1
- package/bin/relentless.ts +172 -3
- 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
|
@@ -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 (
|
|
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
|
|
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
|
|
@@ -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
|
|
|
@@ -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 (
|
|
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
|
|
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
|
|
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
|
|
@@ -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.
|
|
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)"
|
|
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
|
-
|
|
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
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";
|