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.
Files changed (70) hide show
  1. package/README.md +523 -0
  2. package/bin/speci.ts +228 -0
  3. package/lib/commands/init.ts +299 -0
  4. package/lib/commands/monitor.ts +579 -0
  5. package/lib/commands/plan.ts +112 -0
  6. package/lib/commands/refactor.ts +157 -0
  7. package/lib/commands/run.ts +531 -0
  8. package/lib/commands/status.ts +209 -0
  9. package/lib/commands/task.ts +133 -0
  10. package/lib/config.ts +644 -0
  11. package/lib/copilot.ts +229 -0
  12. package/lib/errors.ts +166 -0
  13. package/lib/state.ts +148 -0
  14. package/lib/ui/banner.ts +109 -0
  15. package/lib/ui/box.ts +161 -0
  16. package/lib/ui/colors.ts +110 -0
  17. package/lib/ui/glyphs.ts +91 -0
  18. package/lib/ui/palette.ts +118 -0
  19. package/lib/ui/terminal.ts +118 -0
  20. package/lib/utils/atomic-write.ts +147 -0
  21. package/lib/utils/gate.ts +197 -0
  22. package/lib/utils/i18n.ts +92 -0
  23. package/lib/utils/lock.ts +189 -0
  24. package/lib/utils/logger.ts +143 -0
  25. package/lib/utils/preflight.ts +236 -0
  26. package/lib/utils/process.ts +127 -0
  27. package/lib/utils/signals.ts +145 -0
  28. package/lib/utils/suggest.ts +71 -0
  29. package/package.json +38 -0
  30. package/templates/agents/speci-fix.md +107 -0
  31. package/templates/agents/speci-impl.md +152 -0
  32. package/templates/agents/speci-plan.md +771 -0
  33. package/templates/agents/speci-refactor.md +652 -0
  34. package/templates/agents/speci-review.md +169 -0
  35. package/templates/agents/speci-task.md +369 -0
  36. package/templates/agents/speci-tidy.md +84 -0
  37. package/templates/agents/subagents/final_reviewer.prompt.md +143 -0
  38. package/templates/agents/subagents/mvt_generator.prompt.md +171 -0
  39. package/templates/agents/subagents/plan_codebase_context.prompt.md +29 -0
  40. package/templates/agents/subagents/plan_initial_planner.prompt.md +31 -0
  41. package/templates/agents/subagents/plan_refine_architecture.prompt.md +21 -0
  42. package/templates/agents/subagents/plan_refine_dataflow.prompt.md +23 -0
  43. package/templates/agents/subagents/plan_refine_edgecases.prompt.md +23 -0
  44. package/templates/agents/subagents/plan_refine_errors.prompt.md +22 -0
  45. package/templates/agents/subagents/plan_refine_final.prompt.md +25 -0
  46. package/templates/agents/subagents/plan_refine_integration.prompt.md +22 -0
  47. package/templates/agents/subagents/plan_refine_performance.prompt.md +23 -0
  48. package/templates/agents/subagents/plan_refine_requirements.prompt.md +16 -0
  49. package/templates/agents/subagents/plan_refine_security.prompt.md +22 -0
  50. package/templates/agents/subagents/plan_refine_testing.prompt.md +21 -0
  51. package/templates/agents/subagents/plan_requirements_deep_dive.prompt.md +30 -0
  52. package/templates/agents/subagents/progress_generator.prompt.md +178 -0
  53. package/templates/agents/subagents/refactor_analyze_crosscutting.prompt.md +66 -0
  54. package/templates/agents/subagents/refactor_analyze_duplication.prompt.md +65 -0
  55. package/templates/agents/subagents/refactor_analyze_errors.prompt.md +65 -0
  56. package/templates/agents/subagents/refactor_analyze_functions.prompt.md +66 -0
  57. package/templates/agents/subagents/refactor_analyze_naming.prompt.md +65 -0
  58. package/templates/agents/subagents/refactor_analyze_performance.prompt.md +66 -0
  59. package/templates/agents/subagents/refactor_analyze_state.prompt.md +66 -0
  60. package/templates/agents/subagents/refactor_analyze_structure.prompt.md +64 -0
  61. package/templates/agents/subagents/refactor_analyze_testing.prompt.md +66 -0
  62. package/templates/agents/subagents/refactor_analyze_types.prompt.md +66 -0
  63. package/templates/agents/subagents/refactor_review_completeness.prompt.md +63 -0
  64. package/templates/agents/subagents/refactor_review_final.prompt.md +63 -0
  65. package/templates/agents/subagents/refactor_review_risks.prompt.md +63 -0
  66. package/templates/agents/subagents/refactor_review_roadmap.prompt.md +63 -0
  67. package/templates/agents/subagents/refactor_review_technical.prompt.md +63 -0
  68. package/templates/agents/subagents/task_generator.prompt.md +145 -0
  69. package/templates/agents/subagents/task_reviewer.prompt.md +85 -0
  70. 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;