speci 0.1.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 +523 -0
- package/bin/speci.ts +228 -0
- package/lib/commands/init.ts +299 -0
- package/lib/commands/monitor.ts +579 -0
- package/lib/commands/plan.ts +112 -0
- package/lib/commands/refactor.ts +157 -0
- package/lib/commands/run.ts +531 -0
- package/lib/commands/status.ts +209 -0
- package/lib/commands/task.ts +133 -0
- package/lib/config.ts +644 -0
- package/lib/copilot.ts +229 -0
- package/lib/errors.ts +166 -0
- package/lib/state.ts +148 -0
- package/lib/ui/banner.ts +109 -0
- package/lib/ui/box.ts +161 -0
- package/lib/ui/colors.ts +110 -0
- package/lib/ui/glyphs.ts +91 -0
- package/lib/ui/palette.ts +118 -0
- package/lib/ui/terminal.ts +118 -0
- package/lib/utils/atomic-write.ts +147 -0
- package/lib/utils/gate.ts +197 -0
- package/lib/utils/i18n.ts +92 -0
- package/lib/utils/lock.ts +189 -0
- package/lib/utils/logger.ts +143 -0
- package/lib/utils/preflight.ts +236 -0
- package/lib/utils/process.ts +127 -0
- package/lib/utils/signals.ts +145 -0
- package/lib/utils/suggest.ts +71 -0
- package/package.json +38 -0
- package/templates/agents/speci-fix.md +107 -0
- package/templates/agents/speci-impl.md +152 -0
- package/templates/agents/speci-plan.md +771 -0
- package/templates/agents/speci-refactor.md +652 -0
- package/templates/agents/speci-review.md +169 -0
- package/templates/agents/speci-task.md +369 -0
- package/templates/agents/speci-tidy.md +84 -0
- package/templates/agents/subagents/final_reviewer.prompt.md +143 -0
- package/templates/agents/subagents/mvt_generator.prompt.md +171 -0
- package/templates/agents/subagents/plan_codebase_context.prompt.md +29 -0
- package/templates/agents/subagents/plan_initial_planner.prompt.md +31 -0
- package/templates/agents/subagents/plan_refine_architecture.prompt.md +21 -0
- package/templates/agents/subagents/plan_refine_dataflow.prompt.md +23 -0
- package/templates/agents/subagents/plan_refine_edgecases.prompt.md +23 -0
- package/templates/agents/subagents/plan_refine_errors.prompt.md +22 -0
- package/templates/agents/subagents/plan_refine_final.prompt.md +25 -0
- package/templates/agents/subagents/plan_refine_integration.prompt.md +22 -0
- package/templates/agents/subagents/plan_refine_performance.prompt.md +23 -0
- package/templates/agents/subagents/plan_refine_requirements.prompt.md +16 -0
- package/templates/agents/subagents/plan_refine_security.prompt.md +22 -0
- package/templates/agents/subagents/plan_refine_testing.prompt.md +21 -0
- package/templates/agents/subagents/plan_requirements_deep_dive.prompt.md +30 -0
- package/templates/agents/subagents/progress_generator.prompt.md +178 -0
- package/templates/agents/subagents/refactor_analyze_crosscutting.prompt.md +66 -0
- package/templates/agents/subagents/refactor_analyze_duplication.prompt.md +65 -0
- package/templates/agents/subagents/refactor_analyze_errors.prompt.md +65 -0
- package/templates/agents/subagents/refactor_analyze_functions.prompt.md +66 -0
- package/templates/agents/subagents/refactor_analyze_naming.prompt.md +65 -0
- package/templates/agents/subagents/refactor_analyze_performance.prompt.md +66 -0
- package/templates/agents/subagents/refactor_analyze_state.prompt.md +66 -0
- package/templates/agents/subagents/refactor_analyze_structure.prompt.md +64 -0
- package/templates/agents/subagents/refactor_analyze_testing.prompt.md +66 -0
- package/templates/agents/subagents/refactor_analyze_types.prompt.md +66 -0
- package/templates/agents/subagents/refactor_review_completeness.prompt.md +63 -0
- package/templates/agents/subagents/refactor_review_final.prompt.md +63 -0
- package/templates/agents/subagents/refactor_review_risks.prompt.md +63 -0
- package/templates/agents/subagents/refactor_review_roadmap.prompt.md +63 -0
- package/templates/agents/subagents/refactor_review_technical.prompt.md +63 -0
- package/templates/agents/subagents/task_generator.prompt.md +145 -0
- package/templates/agents/subagents/task_reviewer.prompt.md +85 -0
- package/templates/speci.config.json +36 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Command Implementation
|
|
3
|
+
*
|
|
4
|
+
* Displays current loop state, task statistics, and execution information.
|
|
5
|
+
* Read-only command for quick project progress snapshots.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { loadConfig } from '../config.js';
|
|
9
|
+
import { getState, getTaskStats } from '../state.js';
|
|
10
|
+
import { getLockInfo } from '../utils/lock.js';
|
|
11
|
+
import { renderBanner } from '../ui/banner.js';
|
|
12
|
+
import { colorize } from '../ui/colors.js';
|
|
13
|
+
import { getGlyph } from '../ui/glyphs.js';
|
|
14
|
+
import { drawBox } from '../ui/box.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Status command options
|
|
18
|
+
*/
|
|
19
|
+
export interface StatusOptions {
|
|
20
|
+
json?: boolean;
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Status data structure
|
|
26
|
+
*/
|
|
27
|
+
interface StatusData {
|
|
28
|
+
state: string;
|
|
29
|
+
stats: {
|
|
30
|
+
total: number;
|
|
31
|
+
completed: number;
|
|
32
|
+
pending: number;
|
|
33
|
+
inReview: number;
|
|
34
|
+
blocked: number;
|
|
35
|
+
};
|
|
36
|
+
lock: {
|
|
37
|
+
isLocked: boolean;
|
|
38
|
+
pid: number | null;
|
|
39
|
+
startTime: string | null;
|
|
40
|
+
elapsed: string | null;
|
|
41
|
+
};
|
|
42
|
+
lastActivity?: string;
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Status command handler
|
|
48
|
+
* @param options - Command options
|
|
49
|
+
*/
|
|
50
|
+
export async function status(options: StatusOptions = {}): Promise<void> {
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
|
|
53
|
+
// 1. Load configuration
|
|
54
|
+
const config = await loadConfig();
|
|
55
|
+
|
|
56
|
+
// 2. Gather data concurrently
|
|
57
|
+
const [state, rawStats, lockInfo] = await Promise.all([
|
|
58
|
+
getState(config),
|
|
59
|
+
getTaskStats(config),
|
|
60
|
+
getLockInfo(config),
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
// 3. Build status data
|
|
64
|
+
const statusData: StatusData = {
|
|
65
|
+
state: state,
|
|
66
|
+
stats: {
|
|
67
|
+
total: rawStats.total,
|
|
68
|
+
completed: rawStats.completed,
|
|
69
|
+
pending: rawStats.remaining,
|
|
70
|
+
inReview: rawStats.inReview,
|
|
71
|
+
blocked: rawStats.blocked,
|
|
72
|
+
},
|
|
73
|
+
lock: {
|
|
74
|
+
isLocked: lockInfo.locked,
|
|
75
|
+
pid: lockInfo.pid,
|
|
76
|
+
startTime: lockInfo.started ? formatTimestamp(lockInfo.started) : null,
|
|
77
|
+
elapsed: lockInfo.elapsed,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// 4. Output based on format
|
|
82
|
+
if (options.json) {
|
|
83
|
+
console.log(JSON.stringify(statusData, null, 2));
|
|
84
|
+
} else {
|
|
85
|
+
renderBanner({ showVersion: false });
|
|
86
|
+
renderStatusDisplay(statusData);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 5. Performance check
|
|
90
|
+
const elapsed = Date.now() - startTime;
|
|
91
|
+
if (options.verbose) {
|
|
92
|
+
console.log(colorize(`Status command completed in ${elapsed}ms`, 'dim'));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Render styled status display
|
|
98
|
+
* @param data - Status data to display
|
|
99
|
+
*/
|
|
100
|
+
function renderStatusDisplay(data: StatusData): void {
|
|
101
|
+
// State header with semantic color
|
|
102
|
+
const stateColor = getStateColor(data.state);
|
|
103
|
+
const stateIcon = getStateIcon(data.state);
|
|
104
|
+
|
|
105
|
+
console.log();
|
|
106
|
+
console.log(
|
|
107
|
+
colorize(`${stateIcon} Current State: ${data.state}`, stateColor)
|
|
108
|
+
);
|
|
109
|
+
console.log();
|
|
110
|
+
|
|
111
|
+
// Task statistics box
|
|
112
|
+
const statsContent = [
|
|
113
|
+
`${getGlyph('success')} Completed: ${data.stats.completed}/${data.stats.total}`,
|
|
114
|
+
`${getGlyph('arrow')} Pending: ${data.stats.pending}`,
|
|
115
|
+
`${getGlyph('bullet')} In Review: ${data.stats.inReview}`,
|
|
116
|
+
`${getGlyph('error')} Blocked: ${data.stats.blocked}`,
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
console.log(drawBox(statsContent, { title: 'Task Progress' }));
|
|
120
|
+
|
|
121
|
+
// Progress bar
|
|
122
|
+
renderProgressBar(data.stats.completed, data.stats.total);
|
|
123
|
+
|
|
124
|
+
// Lock status
|
|
125
|
+
if (data.lock.isLocked && data.lock.pid && data.lock.startTime) {
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(
|
|
128
|
+
colorize(
|
|
129
|
+
`${getGlyph('bullet')} Speci run is active (PID: ${data.lock.pid})`,
|
|
130
|
+
'sky400'
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
console.log(colorize(` Started: ${data.lock.startTime}`, 'dim'));
|
|
134
|
+
if (data.lock.elapsed) {
|
|
135
|
+
console.log(colorize(` Elapsed: ${data.lock.elapsed}`, 'dim'));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get semantic color for state
|
|
144
|
+
* @param state - State string
|
|
145
|
+
* @returns Color name
|
|
146
|
+
*/
|
|
147
|
+
function getStateColor(
|
|
148
|
+
state: string
|
|
149
|
+
): 'success' | 'sky400' | 'warning' | 'error' | 'dim' {
|
|
150
|
+
const colors: Record<
|
|
151
|
+
string,
|
|
152
|
+
'success' | 'sky400' | 'warning' | 'error' | 'dim'
|
|
153
|
+
> = {
|
|
154
|
+
DONE: 'success',
|
|
155
|
+
WORK_LEFT: 'sky400',
|
|
156
|
+
IN_REVIEW: 'warning',
|
|
157
|
+
BLOCKED: 'error',
|
|
158
|
+
NO_PROGRESS: 'dim',
|
|
159
|
+
};
|
|
160
|
+
return colors[state] || 'sky400';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get icon for state
|
|
165
|
+
* @param state - State string
|
|
166
|
+
* @returns Glyph string
|
|
167
|
+
*/
|
|
168
|
+
function getStateIcon(state: string): string {
|
|
169
|
+
const icons: Record<string, string> = {
|
|
170
|
+
DONE: getGlyph('success') as string,
|
|
171
|
+
WORK_LEFT: getGlyph('arrow') as string,
|
|
172
|
+
IN_REVIEW: getGlyph('bullet') as string,
|
|
173
|
+
BLOCKED: getGlyph('error') as string,
|
|
174
|
+
NO_PROGRESS: getGlyph('warning') as string,
|
|
175
|
+
};
|
|
176
|
+
return icons[state] || (getGlyph('bullet') as string);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Render progress bar
|
|
181
|
+
* @param completed - Number of completed tasks
|
|
182
|
+
* @param total - Total number of tasks
|
|
183
|
+
*/
|
|
184
|
+
function renderProgressBar(completed: number, total: number): void {
|
|
185
|
+
const width = 40;
|
|
186
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
187
|
+
const filled = total > 0 ? Math.round((completed / total) * width) : 0;
|
|
188
|
+
const empty = width - filled;
|
|
189
|
+
|
|
190
|
+
const bar =
|
|
191
|
+
colorize('█'.repeat(filled), 'success') +
|
|
192
|
+
colorize('░'.repeat(empty), 'dim');
|
|
193
|
+
|
|
194
|
+
console.log();
|
|
195
|
+
console.log(` [${bar}] ${percentage}%`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Format timestamp for display
|
|
200
|
+
* @param date - Date to format
|
|
201
|
+
* @returns Formatted timestamp string
|
|
202
|
+
*/
|
|
203
|
+
function formatTimestamp(date: Date): string {
|
|
204
|
+
const pad = (n: number) => n.toString().padStart(2, '0');
|
|
205
|
+
return (
|
|
206
|
+
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
|
207
|
+
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Command Module
|
|
3
|
+
*
|
|
4
|
+
* Invokes GitHub Copilot CLI in one-shot mode for task generation from a plan file.
|
|
5
|
+
* The command validates the plan file exists and is readable before invoking Copilot.
|
|
6
|
+
* Output streams to terminal in real-time using stdio: 'inherit'.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, accessSync, constants } from 'node:fs';
|
|
10
|
+
import { resolve } from 'node:path';
|
|
11
|
+
import { loadConfig, resolveAgentPath } from '../config.js';
|
|
12
|
+
import { buildCopilotArgs, spawnCopilot } from '../copilot.js';
|
|
13
|
+
import { preflight } from '../utils/preflight.js';
|
|
14
|
+
import { renderBanner } from '../ui/banner.js';
|
|
15
|
+
import { log } from '../utils/logger.js';
|
|
16
|
+
import { infoBox } from '../ui/box.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options for the task command
|
|
20
|
+
*/
|
|
21
|
+
export interface TaskOptions {
|
|
22
|
+
/** Required: path to plan file */
|
|
23
|
+
plan: string;
|
|
24
|
+
/** Custom agent path override */
|
|
25
|
+
agent?: string;
|
|
26
|
+
/** Show detailed output */
|
|
27
|
+
verbose?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate that plan file exists and is readable
|
|
32
|
+
*
|
|
33
|
+
* @param planPath - Absolute path to plan file
|
|
34
|
+
* @throws Will exit process with code 1 if validation fails
|
|
35
|
+
*/
|
|
36
|
+
function validatePlanFile(planPath: string): void {
|
|
37
|
+
// Check existence
|
|
38
|
+
if (!existsSync(planPath)) {
|
|
39
|
+
log.error(`Plan file not found: ${planPath}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check readability
|
|
44
|
+
try {
|
|
45
|
+
accessSync(planPath, constants.R_OK);
|
|
46
|
+
} catch {
|
|
47
|
+
log.error(`Plan file not readable: ${planPath}`);
|
|
48
|
+
log.info('Check file permissions and try again.');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Task command handler
|
|
55
|
+
* Initializes one-shot Copilot session for task generation
|
|
56
|
+
*
|
|
57
|
+
* @param options - Command options
|
|
58
|
+
*/
|
|
59
|
+
export async function task(options: TaskOptions): Promise<void> {
|
|
60
|
+
try {
|
|
61
|
+
// Display banner
|
|
62
|
+
renderBanner();
|
|
63
|
+
console.log();
|
|
64
|
+
|
|
65
|
+
// Validate required option
|
|
66
|
+
if (!options.plan) {
|
|
67
|
+
log.error('Missing required option: --plan <path>');
|
|
68
|
+
log.info('Usage: speci task --plan <path-to-plan.md>');
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Resolve and validate plan file
|
|
73
|
+
const planPath = resolve(process.cwd(), options.plan);
|
|
74
|
+
validatePlanFile(planPath);
|
|
75
|
+
|
|
76
|
+
// Load configuration
|
|
77
|
+
const config = loadConfig();
|
|
78
|
+
|
|
79
|
+
// Run preflight checks
|
|
80
|
+
await preflight(config, {
|
|
81
|
+
requireCopilot: true,
|
|
82
|
+
requireConfig: true,
|
|
83
|
+
requireProgress: false,
|
|
84
|
+
requireGit: false,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Resolve agent path (override or config)
|
|
88
|
+
const agentPath = options.agent
|
|
89
|
+
? options.agent
|
|
90
|
+
: resolveAgentPath(config, 'task');
|
|
91
|
+
|
|
92
|
+
// Validate agent file exists
|
|
93
|
+
if (!existsSync(agentPath)) {
|
|
94
|
+
log.error(`Agent file not found: ${agentPath}`);
|
|
95
|
+
log.info('Check config.agents.task or provide --agent flag');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Display command info
|
|
100
|
+
console.log(
|
|
101
|
+
infoBox('Task Generation', {
|
|
102
|
+
Plan: planPath,
|
|
103
|
+
Agent: agentPath,
|
|
104
|
+
Mode: 'One-shot',
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
console.log();
|
|
108
|
+
|
|
109
|
+
// Build Copilot args for one-shot mode with plan context
|
|
110
|
+
const args = buildCopilotArgs(config, {
|
|
111
|
+
interactive: false,
|
|
112
|
+
prompt: `Read the plan file at ${planPath} and generate implementation tasks.`,
|
|
113
|
+
agent: agentPath,
|
|
114
|
+
allowAll: true,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Spawn Copilot with stdio:inherit
|
|
118
|
+
log.debug(`Spawning: copilot ${args.join(' ')}`);
|
|
119
|
+
const exitCode = await spawnCopilot(args, { inherit: true });
|
|
120
|
+
|
|
121
|
+
// Exit with Copilot's exit code
|
|
122
|
+
process.exit(exitCode);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof Error) {
|
|
125
|
+
log.error(`Task command failed: ${error.message}`);
|
|
126
|
+
} else {
|
|
127
|
+
log.error(`Task command failed: ${String(error)}`);
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default task;
|