@shepai/cli 1.15.1 → 1.17.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.
Files changed (118) hide show
  1. package/README.md +168 -22
  2. package/dist/src/application/ports/output/agents/agent-executor-provider.interface.d.ts +5 -0
  3. package/dist/src/application/ports/output/agents/agent-executor-provider.interface.d.ts.map +1 -0
  4. package/dist/src/application/ports/output/agents/agent-executor-provider.interface.js +1 -0
  5. package/dist/src/application/ports/output/agents/agent-executor.interface.d.ts +2 -0
  6. package/dist/src/application/ports/output/agents/agent-executor.interface.d.ts.map +1 -1
  7. package/dist/src/application/ports/output/agents/feature-agent-process.interface.d.ts +2 -0
  8. package/dist/src/application/ports/output/agents/feature-agent-process.interface.d.ts.map +1 -1
  9. package/dist/src/application/ports/output/agents/index.d.ts +1 -0
  10. package/dist/src/application/ports/output/agents/index.d.ts.map +1 -1
  11. package/dist/src/application/ports/output/index.d.ts +1 -1
  12. package/dist/src/application/ports/output/index.d.ts.map +1 -1
  13. package/dist/src/application/use-cases/features/create-feature.use-case.d.ts +3 -3
  14. package/dist/src/application/use-cases/features/create-feature.use-case.d.ts.map +1 -1
  15. package/dist/src/application/use-cases/features/create-feature.use-case.js +10 -7
  16. package/dist/src/application/use-cases/features/resume-feature.use-case.d.ts.map +1 -1
  17. package/dist/src/application/use-cases/features/resume-feature.use-case.js +6 -1
  18. package/dist/src/infrastructure/di/container.d.ts.map +1 -1
  19. package/dist/src/infrastructure/di/container.js +9 -2
  20. package/dist/src/infrastructure/services/agents/common/agent-executor-factory.service.d.ts +1 -1
  21. package/dist/src/infrastructure/services/agents/common/agent-executor-factory.service.d.ts.map +1 -1
  22. package/dist/src/infrastructure/services/agents/common/agent-executor-factory.service.js +6 -2
  23. package/dist/src/infrastructure/services/agents/common/agent-executor-provider.service.d.ts +9 -0
  24. package/dist/src/infrastructure/services/agents/common/agent-executor-provider.service.d.ts.map +1 -0
  25. package/dist/src/infrastructure/services/agents/common/agent-executor-provider.service.js +11 -0
  26. package/dist/src/infrastructure/services/agents/common/agent-runner.service.d.ts +3 -3
  27. package/dist/src/infrastructure/services/agents/common/agent-runner.service.d.ts.map +1 -1
  28. package/dist/src/infrastructure/services/agents/common/agent-runner.service.js +4 -4
  29. package/dist/src/infrastructure/services/agents/common/agent-validator.service.d.ts +1 -9
  30. package/dist/src/infrastructure/services/agents/common/agent-validator.service.d.ts.map +1 -1
  31. package/dist/src/infrastructure/services/agents/common/agent-validator.service.js +1 -0
  32. package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts +3 -7
  33. package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts.map +1 -1
  34. package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.js +5 -0
  35. package/dist/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts +38 -0
  36. package/dist/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts.map +1 -0
  37. package/dist/src/infrastructure/services/agents/common/executors/cursor-executor.service.js +296 -0
  38. package/dist/src/infrastructure/services/agents/common/types.d.ts +23 -0
  39. package/dist/src/infrastructure/services/agents/common/types.d.ts.map +1 -0
  40. package/dist/src/infrastructure/services/agents/common/types.js +7 -0
  41. package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.d.ts +2 -0
  42. package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.d.ts.map +1 -1
  43. package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.js +6 -0
  44. package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts +2 -0
  45. package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts.map +1 -1
  46. package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-worker.js +32 -12
  47. package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.d.ts +8 -7
  48. package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.d.ts.map +1 -1
  49. package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.js +139 -16
  50. package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts +6 -0
  51. package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts.map +1 -1
  52. package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.js +29 -5
  53. package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.d.ts +44 -4
  54. package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.d.ts.map +1 -1
  55. package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.js +86 -14
  56. package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
  57. package/dist/src/presentation/cli/commands/feat/ls.command.js +42 -7
  58. package/dist/src/presentation/cli/commands/feat/new.command.d.ts.map +1 -1
  59. package/dist/src/presentation/cli/commands/feat/new.command.js +3 -3
  60. package/dist/src/presentation/cli/ui/index.d.ts +1 -0
  61. package/dist/src/presentation/cli/ui/index.d.ts.map +1 -1
  62. package/dist/src/presentation/cli/ui/index.js +1 -0
  63. package/dist/src/presentation/cli/ui/spinner.d.ts +21 -0
  64. package/dist/src/presentation/cli/ui/spinner.d.ts.map +1 -0
  65. package/dist/src/presentation/cli/ui/spinner.js +45 -0
  66. package/dist/src/presentation/tui/prompts/agent-select.prompt.d.ts +1 -1
  67. package/dist/src/presentation/tui/prompts/agent-select.prompt.js +1 -1
  68. package/dist/tsconfig.build.tsbuildinfo +1 -1
  69. package/package.json +1 -1
  70. package/web/.next/BUILD_ID +1 -1
  71. package/web/.next/build-manifest.json +2 -2
  72. package/web/.next/cache/.previewinfo +1 -1
  73. package/web/.next/cache/.rscinfo +1 -1
  74. package/web/.next/cache/config.json +3 -3
  75. package/web/.next/fallback-build-manifest.json +2 -2
  76. package/web/.next/prerender-manifest.json +3 -3
  77. package/web/.next/required-server-files.js +1 -1
  78. package/web/.next/required-server-files.json +1 -1
  79. package/web/.next/server/app/_global-error.html +2 -2
  80. package/web/.next/server/app/_global-error.rsc +1 -1
  81. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  82. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  83. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  84. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  85. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  86. package/web/.next/server/app/_not-found.html +2 -2
  87. package/web/.next/server/app/_not-found.rsc +1 -1
  88. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  89. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  90. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  91. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  92. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  93. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  94. package/web/.next/server/app/index.html +2 -2
  95. package/web/.next/server/app/index.rsc +1 -1
  96. package/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  97. package/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  98. package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  99. package/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  100. package/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  101. package/web/.next/server/app/version.html +2 -2
  102. package/web/.next/server/app/version.rsc +1 -1
  103. package/web/.next/server/app/version.segments/_full.segment.rsc +1 -1
  104. package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
  105. package/web/.next/server/app/version.segments/_index.segment.rsc +1 -1
  106. package/web/.next/server/app/version.segments/_tree.segment.rsc +1 -1
  107. package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +1 -1
  108. package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
  109. package/web/.next/server/chunks/ssr/_19be0743._.js +1 -1
  110. package/web/.next/server/pages/404.html +2 -2
  111. package/web/.next/server/pages/500.html +2 -2
  112. package/web/.next/server/server-reference-manifest.js +1 -1
  113. package/web/.next/server/server-reference-manifest.json +1 -1
  114. package/web/.next/trace +1 -1
  115. package/web/.next/trace-build +1 -1
  116. /package/web/.next/static/{iYaq62xFNOChDTbH3WMrr → Wgd1DC_QfVhpAi62LjpwI}/_buildManifest.js +0 -0
  117. /package/web/.next/static/{iYaq62xFNOChDTbH3WMrr → Wgd1DC_QfVhpAi62LjpwI}/_clientMiddlewareManifest.json +0 -0
  118. /package/web/.next/static/{iYaq62xFNOChDTbH3WMrr → Wgd1DC_QfVhpAi62LjpwI}/_ssgManifest.js +0 -0
@@ -1,23 +1,146 @@
1
- import { createNodeLogger } from './node-helpers.js';
2
- import { reportNodeStart } from '../heartbeat.js';
3
- import { getImplementPlaceholderJoke } from './prompts/implement.prompt.js';
4
1
  /**
5
- * Creates the implement node — PLACEHOLDER.
2
+ * Implement Node Phase-Level Orchestrator
6
3
  *
7
- * Implementation execution is not yet wired up.
8
- * Returns a programming joke to clearly signal this is a placeholder
9
- * while keeping the graph pipeline intact.
4
+ * Reads plan.yaml and tasks.yaml, then executes each phase by building
5
+ * a focused implementation prompt and calling the executor. Parallel
6
+ * phases spawn concurrent executor calls. Progress is tracked in
7
+ * feature.yaml between phases.
8
+ */
9
+ import yaml from 'js-yaml';
10
+ import { writeFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
13
+ import { createNodeLogger, readSpecFile, buildExecutorOptions, shouldInterrupt, safeYamlLoad, } from './node-helpers.js';
14
+ import { reportNodeStart } from '../heartbeat.js';
15
+ import { buildImplementPhasePrompt, } from './prompts/implement.prompt.js';
16
+ /**
17
+ * Update feature.yaml with current implementation progress.
18
+ * Silently no-ops if the file is missing or unparseable.
10
19
  */
11
- export function createImplementNode(_executor) {
20
+ function updateFeatureProgress(specDir, completedCount, totalCount, currentPhase, currentTaskId, log) {
21
+ try {
22
+ const content = readSpecFile(specDir, 'feature.yaml');
23
+ if (!content)
24
+ return;
25
+ const data = yaml.load(content);
26
+ if (!data || typeof data !== 'object')
27
+ return;
28
+ const status = (data.status ?? {});
29
+ data.status = {
30
+ ...status,
31
+ phase: currentPhase,
32
+ progress: {
33
+ completed: completedCount,
34
+ total: totalCount,
35
+ percentage: totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0,
36
+ },
37
+ currentTask: currentTaskId,
38
+ lastUpdated: new Date().toISOString(),
39
+ lastUpdatedBy: 'feature-agent:implement',
40
+ };
41
+ writeFileSync(join(specDir, 'feature.yaml'), yaml.dump(data, { indent: 2, lineWidth: -1 }), 'utf-8');
42
+ }
43
+ catch (err) {
44
+ log.error(`Failed to update feature.yaml: ${err instanceof Error ? err.message : String(err)}`);
45
+ }
46
+ }
47
+ export function createImplementNode(executor) {
12
48
  const log = createNodeLogger('implement');
13
- return async (_state) => {
14
- log.info('Starting... (placeholder — implementation not yet wired)');
49
+ return async (state) => {
50
+ log.info('Starting implementation phase orchestration');
15
51
  reportNodeStart('implement');
16
- const joke = getImplementPlaceholderJoke();
17
- log.info(`Placeholder complete. ${joke}`);
18
- return {
19
- currentNode: 'implement',
20
- messages: [`[implement] Placeholder ${joke}`],
21
- };
52
+ const startTime = Date.now();
53
+ const messages = [];
54
+ try {
55
+ // --- Parse plan & tasks ---
56
+ const planContent = readSpecFile(state.specDir, 'plan.yaml');
57
+ const tasksContent = readSpecFile(state.specDir, 'tasks.yaml');
58
+ if (!planContent || !tasksContent) {
59
+ const msg = 'Missing plan.yaml or tasks.yaml — cannot implement';
60
+ log.error(msg);
61
+ return { currentNode: 'implement', error: msg, messages: [`[implement] Error: ${msg}`] };
62
+ }
63
+ const planData = safeYamlLoad(planContent);
64
+ const tasksData = safeYamlLoad(tasksContent);
65
+ if (!planData?.phases?.length || !tasksData?.tasks?.length) {
66
+ const msg = 'Empty phases or tasks — nothing to implement';
67
+ log.error(msg);
68
+ return { currentNode: 'implement', error: msg, messages: [`[implement] Error: ${msg}`] };
69
+ }
70
+ const taskMap = new Map(tasksData.tasks.map((t) => [t.id, t]));
71
+ const totalTasks = tasksData.tasks.length;
72
+ let completedTasks = 0;
73
+ const totalPhases = planData.phases.length;
74
+ // --- Execute phases in order ---
75
+ for (let i = 0; i < totalPhases; i++) {
76
+ const phase = planData.phases[i];
77
+ const isLastPhase = i === totalPhases - 1;
78
+ // Resolve tasks: use explicit taskIds if present, otherwise match by phaseId
79
+ const phaseTasks = phase.taskIds?.length
80
+ ? phase.taskIds
81
+ .map((id) => taskMap.get(id))
82
+ .filter((t) => t !== undefined)
83
+ : tasksData.tasks.filter((t) => t.phaseId === phase.id);
84
+ if (phaseTasks.length === 0) {
85
+ log.info(`Phase ${phase.id} "${phase.name}" — no tasks, skipping`);
86
+ continue;
87
+ }
88
+ log.info(`Phase ${phase.id} "${phase.name}" — ${phaseTasks.length} task(s), parallel: ${phase.parallel}`);
89
+ updateFeatureProgress(state.specDir, completedTasks, totalTasks, `implementing-${phase.id}`, phaseTasks[0].id, log);
90
+ const options = buildExecutorOptions(state);
91
+ const promptContext = { isLastPhase, phaseIndex: i, totalPhases };
92
+ if (phase.parallel && phaseTasks.length > 1) {
93
+ // Parallel: one executor call per task
94
+ log.info(`Spawning ${phaseTasks.length} parallel executor calls`);
95
+ const results = await Promise.all(phaseTasks.map((task) => {
96
+ const singleTaskPhase = {
97
+ ...phase,
98
+ taskIds: [task.id],
99
+ };
100
+ const prompt = buildImplementPhasePrompt(state, singleTaskPhase, [task], promptContext);
101
+ log.info(` [parallel] Task ${task.id}: "${task.title}" — ${prompt.length} chars`);
102
+ return executor.execute(prompt, options);
103
+ }));
104
+ for (let j = 0; j < results.length; j++) {
105
+ log.info(` [parallel] Task ${phaseTasks[j].id} complete (${results[j].result.length} chars)`);
106
+ }
107
+ }
108
+ else {
109
+ // Sequential: single executor call with all phase tasks
110
+ const prompt = buildImplementPhasePrompt(state, phase, phaseTasks, promptContext);
111
+ log.info(`Executing phase prompt — ${prompt.length} chars`);
112
+ const result = await executor.execute(prompt, options);
113
+ log.info(`Phase complete (${result.result.length} chars)`);
114
+ }
115
+ completedTasks += phaseTasks.length;
116
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
117
+ messages.push(`[implement] Phase ${phase.id} "${phase.name}" — ${phaseTasks.length} task(s) done (${elapsed}s)`);
118
+ updateFeatureProgress(state.specDir, completedTasks, totalTasks, `implementing-${phase.id}`, null, log);
119
+ }
120
+ // --- Final ---
121
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
122
+ log.info(`All phases complete — ${completedTasks}/${totalTasks} tasks (${elapsed}s)`);
123
+ updateFeatureProgress(state.specDir, totalTasks, totalTasks, 'implementation-complete', null, log);
124
+ messages.push(`[implement] Complete: ${totalTasks} tasks across ${totalPhases} phases (${elapsed}s)`);
125
+ if (shouldInterrupt('implement', state.approvalMode)) {
126
+ log.info('Interrupting for human approval');
127
+ interrupt({
128
+ node: 'implement',
129
+ message: `Implementation complete: ${totalTasks} tasks across ${totalPhases} phases. Approve to finish.`,
130
+ });
131
+ }
132
+ return { currentNode: 'implement', messages };
133
+ }
134
+ catch (err) {
135
+ if (isGraphBubbleUp(err))
136
+ throw err;
137
+ const message = err instanceof Error ? err.message : String(err);
138
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
139
+ log.error(`${message} (after ${elapsed}s)`);
140
+ // Throw so LangGraph does NOT checkpoint this node as "completed".
141
+ // The worker catch block marks the run as failed, and on resume
142
+ // LangGraph re-executes from the last successfully checkpointed node.
143
+ throw new Error(`[implement] ${message}`);
144
+ }
22
145
  };
23
146
  }
@@ -23,6 +23,12 @@ export declare function readSpecFile(specDir: string, filename: string): string;
23
23
  * Build executor options with cwd. Each node gets a clean agent context.
24
24
  */
25
25
  export declare function buildExecutorOptions(state: FeatureAgentState): AgentExecutionOptions;
26
+ /**
27
+ * Parse YAML with automatic sanitization of common AI-generated issues.
28
+ * First attempts normal parsing; on failure, sanitizes unquoted braces
29
+ * in list items and retries.
30
+ */
31
+ export declare function safeYamlLoad(content: string): unknown;
26
32
  /**
27
33
  * Determine whether the current node should trigger an interrupt
28
34
  * for human approval given the configured approval mode.
@@ -1 +1 @@
1
- {"version":3,"file":"node-helpers.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/node-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACtB,MAAM,+DAA+D,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM;kBAE/B,MAAM,GAAG,IAAI;mBAIZ,MAAM,GAAG,IAAI;EAK/B;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE7D;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,qBAAqB,CAIpF;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI3F;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,UAAU,KAAK,MAAM,GACjE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAiDnE"}
1
+ {"version":3,"file":"node-helpers.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/node-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACtB,MAAM,+DAA+D,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM;kBAE/B,MAAM,GAAG,IAAI;mBAIZ,MAAM,GAAG,IAAI;EAK/B;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE7D;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,qBAAqB,CAIpF;AAeD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQrD;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI3F;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,UAAU,KAAK,MAAM,GACjE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAgDnE"}
@@ -4,6 +4,7 @@
4
4
  * Provides consistent logging, spec file reading, executor invocation,
5
5
  * and error handling so every node follows the same patterns.
6
6
  */
7
+ import yaml from 'js-yaml';
7
8
  import { readFileSync } from 'node:fs';
8
9
  import { join } from 'node:path';
9
10
  import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
@@ -43,6 +44,30 @@ export function buildExecutorOptions(state) {
43
44
  cwd: state.worktreePath || state.repositoryPath,
44
45
  };
45
46
  }
47
+ /**
48
+ * Sanitize YAML content to handle common AI-generated issues.
49
+ * Quotes unquoted list items containing `{` or `}` characters
50
+ * which js-yaml interprets as flow mappings.
51
+ */
52
+ function sanitizeYamlBraces(content) {
53
+ return content.replace(/^(\s*- )(?!['"])(.+[{}].+)$/gm, (_match, prefix, value) => `${prefix}"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`);
54
+ }
55
+ /**
56
+ * Parse YAML with automatic sanitization of common AI-generated issues.
57
+ * First attempts normal parsing; on failure, sanitizes unquoted braces
58
+ * in list items and retries.
59
+ */
60
+ export function safeYamlLoad(content) {
61
+ try {
62
+ return yaml.load(content);
63
+ }
64
+ catch (firstError) {
65
+ const sanitized = sanitizeYamlBraces(content);
66
+ if (sanitized === content)
67
+ throw firstError; // nothing to sanitize
68
+ return yaml.load(sanitized);
69
+ }
70
+ }
46
71
  /** Nodes that are auto-approved (no interrupt) for each approval mode. */
47
72
  const AUTO_APPROVED_NODES = {
48
73
  interactive: [],
@@ -104,11 +129,10 @@ export function executeNode(nodeName, executor, buildPrompt) {
104
129
  const message = err instanceof Error ? err.message : String(err);
105
130
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
106
131
  log.error(`${message} (after ${elapsed}s)`);
107
- return {
108
- currentNode: nodeName,
109
- error: message,
110
- messages: [`[${nodeName}] Error: ${message}`],
111
- };
132
+ // Throw so LangGraph does NOT checkpoint this node as "completed".
133
+ // The worker catch block marks the run as failed, and on resume
134
+ // LangGraph re-executes from the last successfully checkpointed node.
135
+ throw new Error(`[${nodeName}] ${message}`);
112
136
  }
113
137
  };
114
138
  }
@@ -1,8 +1,48 @@
1
1
  /**
2
- * Implement Phase Prompt — PLACEHOLDER
2
+ * Implement Phase Prompt
3
3
  *
4
- * The implement phase is not yet wired up. This returns a programming joke
5
- * to clearly signal it's a placeholder while keeping the graph pipeline intact.
4
+ * Builds per-phase implementation prompts that include full spec/research/plan
5
+ * context plus the specific tasks for the current phase, with TDD guidance
6
+ * and verification instructions.
6
7
  */
7
- export declare function getImplementPlaceholderJoke(): string;
8
+ import type { FeatureAgentState } from '../../state.js';
9
+ export interface PhaseTask {
10
+ id: string;
11
+ phaseId?: string;
12
+ title: string;
13
+ description: string;
14
+ state: string;
15
+ dependencies: string[];
16
+ acceptanceCriteria: string[];
17
+ tdd: {
18
+ red: string[];
19
+ green: string[];
20
+ refactor: string[];
21
+ } | null;
22
+ estimatedEffort: string;
23
+ }
24
+ export interface PlanPhase {
25
+ id: string;
26
+ name: string;
27
+ description?: string;
28
+ parallel: boolean;
29
+ /** Optional — if absent, tasks are matched via their phaseId field */
30
+ taskIds?: string[];
31
+ }
32
+ export interface PlanYaml {
33
+ phases: PlanPhase[];
34
+ }
35
+ export interface TasksYaml {
36
+ tasks: PhaseTask[];
37
+ }
38
+ /**
39
+ * Build the implementation prompt for a single phase (or a single task
40
+ * within a parallel phase). Includes full project context so each
41
+ * executor session is self-contained.
42
+ */
43
+ export declare function buildImplementPhasePrompt(state: FeatureAgentState, phase: PlanPhase, tasks: PhaseTask[], context: {
44
+ isLastPhase: boolean;
45
+ phaseIndex: number;
46
+ totalPhases: number;
47
+ }): string;
8
48
  //# sourceMappingURL=implement.prompt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"implement.prompt.d.ts","sourceRoot":"","sources":["../../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD"}
1
+ {"version":3,"file":"implement.prompt.d.ts","sourceRoot":"","sources":["../../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACnE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAwBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACzE,MAAM,CA8DR"}
@@ -1,18 +1,90 @@
1
1
  /**
2
- * Implement Phase Prompt — PLACEHOLDER
2
+ * Implement Phase Prompt
3
3
  *
4
- * The implement phase is not yet wired up. This returns a programming joke
5
- * to clearly signal it's a placeholder while keeping the graph pipeline intact.
4
+ * Builds per-phase implementation prompts that include full spec/research/plan
5
+ * context plus the specific tasks for the current phase, with TDD guidance
6
+ * and verification instructions.
6
7
  */
7
- const JOKES = [
8
- 'Why do programmers prefer dark mode? Because light attracts bugs.',
9
- 'A SQL query walks into a bar, walks up to two tables and asks... "Can I JOIN you?"',
10
- "There are only 10 types of people: those who understand binary, and those who don't.",
11
- "Why was the JavaScript developer sad? Because he didn't Node how to Express himself.",
12
- "What's a programmer's favorite hangout place? Foo Bar.",
13
- 'To understand recursion, you must first understand recursion.',
14
- "!false it's funny because it's true.",
15
- ];
16
- export function getImplementPlaceholderJoke() {
17
- return JOKES[Math.floor(Math.random() * JOKES.length)];
8
+ import { readSpecFile } from '../node-helpers.js';
9
+ function formatTaskSection(task) {
10
+ let section = `### Task ${task.id}: ${task.title}\n${task.description}\n`;
11
+ section += `\n**Acceptance Criteria:**\n`;
12
+ for (const criterion of task.acceptanceCriteria) {
13
+ section += `- ${criterion}\n`;
14
+ }
15
+ if (task.tdd) {
16
+ section += `\n**TDD Guidance:**\n`;
17
+ section += `- Write tests for: ${task.tdd.red.join('; ')}\n`;
18
+ section += `- Implement: ${task.tdd.green.join('; ')}\n`;
19
+ section += `- Then consider: ${task.tdd.refactor.join('; ')}\n`;
20
+ }
21
+ if (task.dependencies.length > 0) {
22
+ section += `\nDepends on: ${task.dependencies.join(', ')}\n`;
23
+ }
24
+ return section;
25
+ }
26
+ /**
27
+ * Build the implementation prompt for a single phase (or a single task
28
+ * within a parallel phase). Includes full project context so each
29
+ * executor session is self-contained.
30
+ */
31
+ export function buildImplementPhasePrompt(state, phase, tasks, context) {
32
+ const specContent = readSpecFile(state.specDir, 'spec.yaml');
33
+ const researchContent = readSpecFile(state.specDir, 'research.yaml');
34
+ const planContent = readSpecFile(state.specDir, 'plan.yaml');
35
+ const cwd = state.worktreePath || state.repositoryPath;
36
+ const taskSections = tasks.map(formatTaskSection).join('\n');
37
+ const verificationBlock = context.isLastPhase
38
+ ? `4. Run full project validation:
39
+ - \`pnpm test\` (all tests must pass)
40
+ - \`pnpm lint\` (no lint errors)
41
+ - \`pnpm typecheck\` (no type errors)
42
+ - Fix any issues before finishing`
43
+ : `4. Run tests relevant to what you changed:
44
+ - Run targeted tests for modified files (e.g. \`pnpm test:single <test-file>\`)
45
+ - Fix any test failures before moving on`;
46
+ return `You are a senior software engineer performing autonomous implementation.
47
+ You are executing phase ${context.phaseIndex + 1} of ${context.totalPhases}: "${phase.name}".
48
+
49
+ ## Feature Specification
50
+
51
+ \`\`\`yaml
52
+ ${specContent}
53
+ \`\`\`
54
+
55
+ ## Research Decisions
56
+
57
+ \`\`\`yaml
58
+ ${researchContent}
59
+ \`\`\`
60
+
61
+ ## Implementation Plan
62
+
63
+ \`\`\`yaml
64
+ ${planContent}
65
+ \`\`\`
66
+
67
+ ## Your Tasks for This Phase: "${phase.name}"
68
+
69
+ ${taskSections}
70
+
71
+ ## Implementation Instructions
72
+
73
+ 1. Work through each task in dependency order
74
+ 2. For tasks with TDD guidance: write tests alongside implementation following the hints provided — use them as guidance, not rigid steps
75
+ 3. Follow existing codebase conventions for file placement, naming patterns, and architecture layers
76
+ ${verificationBlock}
77
+ 5. Commit your work with descriptive conventional commit messages (e.g. \`feat(scope): description\`)
78
+
79
+ ## Working Directory
80
+
81
+ ${cwd}
82
+
83
+ ## Constraints
84
+
85
+ - Implement ONLY the tasks listed above — do not work ahead to future phases
86
+ - Follow existing codebase conventions and architecture patterns
87
+ - Do NOT modify any spec YAML files (spec.yaml, research.yaml, plan.yaml, tasks.yaml, feature.yaml)
88
+ - Do NOT skip writing tests for tasks that have TDD guidance
89
+ - Keep changes focused and minimal — avoid unnecessary refactoring beyond what the tasks specify`;
18
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoEpC,wBAAgB,eAAe,IAAI,OAAO,CAqDzC"}
1
+ {"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsGpC,wBAAgB,eAAe,IAAI,OAAO,CAqDzC"}
@@ -10,6 +10,7 @@
10
10
  * $ shep feat ls
11
11
  * $ shep feat ls --repo /path/to/project
12
12
  */
13
+ import path from 'node:path';
13
14
  import { Command } from 'commander';
14
15
  import { container } from '../../../../infrastructure/di/container.js';
15
16
  import { ListFeaturesUseCase } from '../../../../application/use-cases/features/list-features.use-case.js';
@@ -65,6 +66,39 @@ function isProcessAlive(pid) {
65
66
  return false;
66
67
  }
67
68
  }
69
+ /** Format a duration in ms to a compact human-readable string. */
70
+ function formatDuration(ms) {
71
+ const seconds = Math.floor(ms / 1000);
72
+ if (seconds < 60)
73
+ return `${seconds}s`;
74
+ const minutes = Math.floor(seconds / 60);
75
+ if (minutes < 60)
76
+ return `${minutes}m`;
77
+ const hours = Math.floor(minutes / 60);
78
+ const remMin = minutes % 60;
79
+ if (hours < 24)
80
+ return remMin > 0 ? `${hours}h ${remMin}m` : `${hours}h`;
81
+ const days = Math.floor(hours / 24);
82
+ return `${days}d`;
83
+ }
84
+ /** Format the time column based on run status. */
85
+ function formatTime(feature, run) {
86
+ const now = Date.now();
87
+ if (run) {
88
+ const isRunning = run.status === 'running' || run.status === 'pending';
89
+ if (isRunning && run.startedAt) {
90
+ const started = new Date(run.startedAt).getTime();
91
+ return colors.info(formatDuration(now - started));
92
+ }
93
+ if (run.status === 'completed' && run.completedAt) {
94
+ const completed = new Date(run.completedAt).getTime();
95
+ return colors.muted(`${formatDuration(now - completed)} ago`);
96
+ }
97
+ }
98
+ // Fallback to updatedAt
99
+ const updated = new Date(feature.updatedAt).getTime();
100
+ return colors.muted(`${formatDuration(now - updated)} ago`);
101
+ }
68
102
  export function createLsCommand() {
69
103
  return new Command('ls')
70
104
  .description('List features')
@@ -79,15 +113,15 @@ export function createLsCommand() {
79
113
  const runs = await Promise.all(features.map((f) => f.agentRunId ? runRepo.findById(f.agentRunId) : Promise.resolve(null)));
80
114
  const rows = features.map((feature, i) => {
81
115
  const run = runs[i];
82
- const updated = feature.updatedAt instanceof Date
83
- ? feature.updatedAt.toLocaleDateString()
84
- : String(feature.updatedAt);
116
+ const repo = path.basename(feature.repositoryPath);
117
+ const agent = run?.agentType ?? colors.muted('—');
85
118
  return [
86
119
  feature.id.slice(0, 8),
87
120
  feature.name.slice(0, 30),
88
121
  formatStatus(feature, run),
89
- colors.muted(feature.branch),
90
- colors.muted(updated),
122
+ colors.muted(repo),
123
+ agent,
124
+ formatTime(feature, run),
91
125
  ];
92
126
  });
93
127
  renderListView({
@@ -96,8 +130,9 @@ export function createLsCommand() {
96
130
  { label: 'ID', width: 10 },
97
131
  { label: 'Name', width: 32 },
98
132
  { label: 'Status', width: 20 },
99
- { label: 'Branch', width: 28 },
100
- { label: 'Updated', width: 12 },
133
+ { label: 'Repo', width: 20 },
134
+ { label: 'Agent', width: 14 },
135
+ { label: 'Time', width: 12 },
101
136
  ],
102
137
  rows,
103
138
  emptyMessage: 'No features found',
@@ -1 +1 @@
1
- {"version":3,"file":"new.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/new.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CA6C1C"}
1
+ {"version":3,"file":"new.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/new.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CA+C1C"}
@@ -12,7 +12,7 @@
12
12
  import { Command } from 'commander';
13
13
  import { container } from '../../../../infrastructure/di/container.js';
14
14
  import { CreateFeatureUseCase } from '../../../../application/use-cases/features/create-feature.use-case.js';
15
- import { colors, messages } from '../../ui/index.js';
15
+ import { colors, messages, spinner } from '../../ui/index.js';
16
16
  /**
17
17
  * Create the feat new command
18
18
  */
@@ -39,11 +39,11 @@ export function createNewCommand() {
39
39
  approvalMode = 'allow-plan';
40
40
  if (options.allowAll)
41
41
  approvalMode = 'allow-all';
42
- const feature = await useCase.execute({
42
+ const feature = await spinner('Thinking', () => useCase.execute({
43
43
  userInput: description,
44
44
  repositoryPath: repoPath,
45
45
  approvalMode,
46
- });
46
+ }));
47
47
  messages.newline();
48
48
  messages.success('Feature created');
49
49
  console.log(` ${colors.muted('ID:')} ${colors.accent(feature.id)}`);
@@ -19,4 +19,5 @@ export { TableFormatter, type DatabaseMeta } from './tables.js';
19
19
  export { OutputFormatter, type OutputFormat } from './output.js';
20
20
  export { renderDetailView, type DetailViewConfig, type DetailSection, type DetailField, type DetailTextBlock, } from './detail-view.js';
21
21
  export { renderListView, type ListViewConfig, type ListColumn } from './list-view.js';
22
+ export { spinner } from './spinner.js';
22
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,GAAG,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,GAAG,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACtF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
@@ -19,3 +19,4 @@ export { TableFormatter } from './tables.js';
19
19
  export { OutputFormatter } from './output.js';
20
20
  export { renderDetailView, } from './detail-view.js';
21
21
  export { renderListView } from './list-view.js';
22
+ export { spinner } from './spinner.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * CLI Design System - Spinner
3
+ *
4
+ * Lightweight terminal spinner for async operations.
5
+ * Uses the braille spinner frames from the symbols module.
6
+ *
7
+ * @example
8
+ * import { spinner } from './spinner';
9
+ * const result = await spinner('Thinking', async () => {
10
+ * return await someAsyncWork();
11
+ * });
12
+ */
13
+ /**
14
+ * Show a spinner while an async operation runs, then clear it.
15
+ *
16
+ * @param label - Text shown next to the spinner (e.g. "Thinking")
17
+ * @param fn - Async function to execute
18
+ * @returns The result of the async function
19
+ */
20
+ export declare function spinner<T>(label: string, fn: () => Promise<T>): Promise<T>;
21
+ //# sourceMappingURL=spinner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/ui/spinner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAyBhF"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * CLI Design System - Spinner
3
+ *
4
+ * Lightweight terminal spinner for async operations.
5
+ * Uses the braille spinner frames from the symbols module.
6
+ *
7
+ * @example
8
+ * import { spinner } from './spinner';
9
+ * const result = await spinner('Thinking', async () => {
10
+ * return await someAsyncWork();
11
+ * });
12
+ */
13
+ import { symbols } from './symbols.js';
14
+ import { colors } from './colors.js';
15
+ /**
16
+ * Show a spinner while an async operation runs, then clear it.
17
+ *
18
+ * @param label - Text shown next to the spinner (e.g. "Thinking")
19
+ * @param fn - Async function to execute
20
+ * @returns The result of the async function
21
+ */
22
+ export async function spinner(label, fn) {
23
+ const frames = symbols.spinner;
24
+ let i = 0;
25
+ // Write initial frame
26
+ const write = () => {
27
+ const frame = frames[i % frames.length];
28
+ process.stderr.write(`\r${colors.muted(`${frame} ${label}...`)}`);
29
+ i++;
30
+ };
31
+ write();
32
+ const interval = setInterval(write, 80);
33
+ try {
34
+ const result = await fn();
35
+ clearInterval(interval);
36
+ // Clear the spinner line
37
+ process.stderr.write(`\r${' '.repeat(label.length + 6)}\r`);
38
+ return result;
39
+ }
40
+ catch (error) {
41
+ clearInterval(interval);
42
+ process.stderr.write(`\r${' '.repeat(label.length + 6)}\r`);
43
+ throw error;
44
+ }
45
+ }
@@ -32,7 +32,7 @@ export declare function createAgentSelectConfig(): {
32
32
  }, {
33
33
  readonly name: "Cursor";
34
34
  readonly value: AgentType.Cursor;
35
- readonly disabled: "(Coming Soon)";
35
+ readonly description: "Cursor AI coding agent";
36
36
  }];
37
37
  readonly theme: {
38
38
  readonly prefix: {
@@ -39,7 +39,7 @@ export function createAgentSelectConfig() {
39
39
  {
40
40
  name: 'Cursor',
41
41
  value: AgentType.Cursor,
42
- disabled: '(Coming Soon)',
42
+ description: 'Cursor AI coding agent',
43
43
  },
44
44
  ],
45
45
  theme: shepTheme,