@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.
- package/README.md +168 -22
- package/dist/src/application/ports/output/agents/agent-executor-provider.interface.d.ts +5 -0
- package/dist/src/application/ports/output/agents/agent-executor-provider.interface.d.ts.map +1 -0
- package/dist/src/application/ports/output/agents/agent-executor-provider.interface.js +1 -0
- package/dist/src/application/ports/output/agents/agent-executor.interface.d.ts +2 -0
- package/dist/src/application/ports/output/agents/agent-executor.interface.d.ts.map +1 -1
- package/dist/src/application/ports/output/agents/feature-agent-process.interface.d.ts +2 -0
- package/dist/src/application/ports/output/agents/feature-agent-process.interface.d.ts.map +1 -1
- package/dist/src/application/ports/output/agents/index.d.ts +1 -0
- package/dist/src/application/ports/output/agents/index.d.ts.map +1 -1
- package/dist/src/application/ports/output/index.d.ts +1 -1
- package/dist/src/application/ports/output/index.d.ts.map +1 -1
- package/dist/src/application/use-cases/features/create-feature.use-case.d.ts +3 -3
- package/dist/src/application/use-cases/features/create-feature.use-case.d.ts.map +1 -1
- package/dist/src/application/use-cases/features/create-feature.use-case.js +10 -7
- package/dist/src/application/use-cases/features/resume-feature.use-case.d.ts.map +1 -1
- package/dist/src/application/use-cases/features/resume-feature.use-case.js +6 -1
- package/dist/src/infrastructure/di/container.d.ts.map +1 -1
- package/dist/src/infrastructure/di/container.js +9 -2
- package/dist/src/infrastructure/services/agents/common/agent-executor-factory.service.d.ts +1 -1
- package/dist/src/infrastructure/services/agents/common/agent-executor-factory.service.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/common/agent-executor-factory.service.js +6 -2
- package/dist/src/infrastructure/services/agents/common/agent-executor-provider.service.d.ts +9 -0
- package/dist/src/infrastructure/services/agents/common/agent-executor-provider.service.d.ts.map +1 -0
- package/dist/src/infrastructure/services/agents/common/agent-executor-provider.service.js +11 -0
- package/dist/src/infrastructure/services/agents/common/agent-runner.service.d.ts +3 -3
- package/dist/src/infrastructure/services/agents/common/agent-runner.service.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/common/agent-runner.service.js +4 -4
- package/dist/src/infrastructure/services/agents/common/agent-validator.service.d.ts +1 -9
- package/dist/src/infrastructure/services/agents/common/agent-validator.service.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/common/agent-validator.service.js +1 -0
- package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts +3 -7
- package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.js +5 -0
- package/dist/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts +38 -0
- package/dist/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts.map +1 -0
- package/dist/src/infrastructure/services/agents/common/executors/cursor-executor.service.js +296 -0
- package/dist/src/infrastructure/services/agents/common/types.d.ts +23 -0
- package/dist/src/infrastructure/services/agents/common/types.d.ts.map +1 -0
- package/dist/src/infrastructure/services/agents/common/types.js +7 -0
- package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.d.ts +2 -0
- package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.js +6 -0
- package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts +2 -0
- package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/feature-agent/feature-agent-worker.js +32 -12
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.d.ts +8 -7
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.js +139 -16
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts +6 -0
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.js +29 -5
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.d.ts +44 -4
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.d.ts.map +1 -1
- package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.js +86 -14
- package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/feat/ls.command.js +42 -7
- package/dist/src/presentation/cli/commands/feat/new.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/feat/new.command.js +3 -3
- package/dist/src/presentation/cli/ui/index.d.ts +1 -0
- package/dist/src/presentation/cli/ui/index.d.ts.map +1 -1
- package/dist/src/presentation/cli/ui/index.js +1 -0
- package/dist/src/presentation/cli/ui/spinner.d.ts +21 -0
- package/dist/src/presentation/cli/ui/spinner.d.ts.map +1 -0
- package/dist/src/presentation/cli/ui/spinner.js +45 -0
- package/dist/src/presentation/tui/prompts/agent-select.prompt.d.ts +1 -1
- package/dist/src/presentation/tui/prompts/agent-select.prompt.js +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.html +2 -2
- package/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/index.html +2 -2
- package/web/.next/server/app/index.rsc +1 -1
- package/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/version.html +2 -2
- package/web/.next/server/app/version.rsc +1 -1
- package/web/.next/server/app/version.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/version.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/version.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
- package/web/.next/server/chunks/ssr/_19be0743._.js +1 -1
- package/web/.next/server/pages/404.html +2 -2
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- /package/web/.next/static/{iYaq62xFNOChDTbH3WMrr → Wgd1DC_QfVhpAi62LjpwI}/_buildManifest.js +0 -0
- /package/web/.next/static/{iYaq62xFNOChDTbH3WMrr → Wgd1DC_QfVhpAi62LjpwI}/_clientMiddlewareManifest.json +0 -0
- /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
|
-
*
|
|
2
|
+
* Implement Node — Phase-Level Orchestrator
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
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 (
|
|
14
|
-
log.info('Starting
|
|
49
|
+
return async (state) => {
|
|
50
|
+
log.info('Starting implementation phase orchestration');
|
|
15
51
|
reportNodeStart('implement');
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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;
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
}
|
package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.d.ts
CHANGED
|
@@ -1,8 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Implement Phase Prompt
|
|
2
|
+
* Implement Phase Prompt
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
|
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"}
|
package/dist/src/infrastructure/services/agents/feature-agent/nodes/prompts/implement.prompt.js
CHANGED
|
@@ -1,18 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Implement Phase Prompt
|
|
2
|
+
* Implement Phase Prompt
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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;
|
|
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
|
|
83
|
-
|
|
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(
|
|
90
|
-
|
|
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: '
|
|
100
|
-
{ label: '
|
|
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,
|
|
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"}
|
|
@@ -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
|
|
35
|
+
readonly description: "Cursor AI coding agent";
|
|
36
36
|
}];
|
|
37
37
|
readonly theme: {
|
|
38
38
|
readonly prefix: {
|