ralphctl 0.1.0 → 0.1.2
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 +58 -24
- package/dist/add-HGJCLWED.mjs +14 -0
- package/dist/add-MRGCS3US.mjs +14 -0
- package/dist/chunk-6PYTKGB5.mjs +316 -0
- package/dist/chunk-7TG3EAQ2.mjs +20 -0
- package/dist/chunk-EKMZZRWI.mjs +521 -0
- package/dist/chunk-JON4GCLR.mjs +59 -0
- package/dist/chunk-LOR7QBXX.mjs +3683 -0
- package/dist/chunk-MNMQC36F.mjs +556 -0
- package/dist/chunk-MRKOFVTM.mjs +537 -0
- package/dist/chunk-NTWO2LXB.mjs +52 -0
- package/dist/chunk-QBXHAXHI.mjs +562 -0
- package/dist/chunk-WGHJI3OI.mjs +214 -0
- package/dist/cli.mjs +4245 -0
- package/dist/create-MG7E7PLQ.mjs +10 -0
- package/dist/handle-UG5M2OON.mjs +22 -0
- package/dist/multiline-OHSNFCRG.mjs +40 -0
- package/dist/project-NT3L4FTB.mjs +28 -0
- package/dist/resolver-WSFWKACM.mjs +153 -0
- package/dist/sprint-4VHDLGFN.mjs +37 -0
- package/dist/wizard-LRELAN2J.mjs +196 -0
- package/package.json +19 -28
- package/CHANGELOG.md +0 -94
- package/bin/ralphctl +0 -13
- package/src/ai/executor.ts +0 -973
- package/src/ai/lifecycle.ts +0 -45
- package/src/ai/parser.ts +0 -40
- package/src/ai/permissions.ts +0 -207
- package/src/ai/process-manager.ts +0 -248
- package/src/ai/prompts/index.ts +0 -89
- package/src/ai/rate-limiter.ts +0 -89
- package/src/ai/runner.ts +0 -478
- package/src/ai/session.ts +0 -319
- package/src/ai/task-context.ts +0 -270
- package/src/cli-metadata.ts +0 -7
- package/src/cli.ts +0 -65
- package/src/commands/completion/index.ts +0 -33
- package/src/commands/config/config.ts +0 -58
- package/src/commands/config/index.ts +0 -33
- package/src/commands/dashboard/dashboard.ts +0 -5
- package/src/commands/dashboard/index.ts +0 -6
- package/src/commands/doctor/doctor.ts +0 -271
- package/src/commands/doctor/index.ts +0 -25
- package/src/commands/progress/index.ts +0 -25
- package/src/commands/progress/log.ts +0 -64
- package/src/commands/progress/show.ts +0 -14
- package/src/commands/project/add.ts +0 -336
- package/src/commands/project/index.ts +0 -104
- package/src/commands/project/list.ts +0 -31
- package/src/commands/project/remove.ts +0 -43
- package/src/commands/project/repo.ts +0 -118
- package/src/commands/project/show.ts +0 -49
- package/src/commands/sprint/close.ts +0 -180
- package/src/commands/sprint/context.ts +0 -109
- package/src/commands/sprint/create.ts +0 -60
- package/src/commands/sprint/current.ts +0 -75
- package/src/commands/sprint/delete.ts +0 -72
- package/src/commands/sprint/health.ts +0 -229
- package/src/commands/sprint/ideate.ts +0 -496
- package/src/commands/sprint/index.ts +0 -226
- package/src/commands/sprint/list.ts +0 -86
- package/src/commands/sprint/plan-utils.ts +0 -207
- package/src/commands/sprint/plan.ts +0 -549
- package/src/commands/sprint/refine.ts +0 -359
- package/src/commands/sprint/requirements.ts +0 -58
- package/src/commands/sprint/show.ts +0 -140
- package/src/commands/sprint/start.ts +0 -119
- package/src/commands/sprint/switch.ts +0 -20
- package/src/commands/task/add.ts +0 -316
- package/src/commands/task/import.ts +0 -150
- package/src/commands/task/index.ts +0 -123
- package/src/commands/task/list.ts +0 -145
- package/src/commands/task/next.ts +0 -45
- package/src/commands/task/remove.ts +0 -47
- package/src/commands/task/reorder.ts +0 -45
- package/src/commands/task/show.ts +0 -111
- package/src/commands/task/status.ts +0 -99
- package/src/commands/ticket/add.ts +0 -265
- package/src/commands/ticket/edit.ts +0 -166
- package/src/commands/ticket/index.ts +0 -114
- package/src/commands/ticket/list.ts +0 -128
- package/src/commands/ticket/refine-utils.ts +0 -89
- package/src/commands/ticket/refine.ts +0 -268
- package/src/commands/ticket/remove.ts +0 -48
- package/src/commands/ticket/show.ts +0 -74
- package/src/completion/handle.ts +0 -30
- package/src/completion/resolver.ts +0 -241
- package/src/interactive/dashboard.ts +0 -268
- package/src/interactive/escapable.ts +0 -81
- package/src/interactive/file-browser.ts +0 -153
- package/src/interactive/index.ts +0 -429
- package/src/interactive/menu.ts +0 -403
- package/src/interactive/selectors.ts +0 -273
- package/src/interactive/wizard.ts +0 -221
- package/src/providers/claude.ts +0 -53
- package/src/providers/copilot.ts +0 -86
- package/src/providers/index.ts +0 -43
- package/src/providers/types.ts +0 -85
- package/src/schemas/index.ts +0 -130
- package/src/store/config.ts +0 -74
- package/src/store/progress.ts +0 -230
- package/src/store/project.ts +0 -276
- package/src/store/sprint.ts +0 -229
- package/src/store/task.ts +0 -443
- package/src/store/ticket.ts +0 -178
- package/src/theme/index.ts +0 -215
- package/src/theme/ui.ts +0 -872
- package/src/utils/detect-scripts.ts +0 -247
- package/src/utils/editor-input.ts +0 -41
- package/src/utils/editor.ts +0 -37
- package/src/utils/exit-codes.ts +0 -27
- package/src/utils/file-lock.ts +0 -135
- package/src/utils/git.ts +0 -185
- package/src/utils/ids.ts +0 -37
- package/src/utils/issue-fetch.ts +0 -244
- package/src/utils/json-extract.ts +0 -62
- package/src/utils/multiline.ts +0 -61
- package/src/utils/path-selector.ts +0 -236
- package/src/utils/paths.ts +0 -108
- package/src/utils/provider.ts +0 -34
- package/src/utils/requirements-export.ts +0 -63
- package/src/utils/storage.ts +0 -107
- package/tsconfig.json +0 -25
- /package/{src/ai → dist}/prompts/ideate-auto.md +0 -0
- /package/{src/ai → dist}/prompts/ideate.md +0 -0
- /package/{src/ai → dist}/prompts/plan-auto.md +0 -0
- /package/{src/ai → dist}/prompts/plan-common.md +0 -0
- /package/{src/ai → dist}/prompts/plan-interactive.md +0 -0
- /package/{src/ai → dist}/prompts/task-execution.md +0 -0
- /package/{src/ai → dist}/prompts/ticket-refine.md +0 -0
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { AiProviderSchema } from '@src/schemas/index.ts';
|
|
2
|
-
import { getAiProvider, getEditor, setAiProvider, setEditor } from '@src/store/config.ts';
|
|
3
|
-
import { field, icons, log, printHeader, showError, showSuccess } from '@src/theme/ui.ts';
|
|
4
|
-
|
|
5
|
-
export async function configSetCommand(args: string[]): Promise<void> {
|
|
6
|
-
if (args.length < 2) {
|
|
7
|
-
showError('Usage: ralphctl config set <key> <value>');
|
|
8
|
-
log.dim('Available keys: provider, editor');
|
|
9
|
-
log.newline();
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const [key, value] = args;
|
|
14
|
-
|
|
15
|
-
if (key === 'provider') {
|
|
16
|
-
const parsed = AiProviderSchema.safeParse(value);
|
|
17
|
-
if (!parsed.success) {
|
|
18
|
-
showError(`Invalid provider: ${value ?? '(empty)'}`);
|
|
19
|
-
log.dim('Valid providers: claude, copilot');
|
|
20
|
-
log.newline();
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
await setAiProvider(parsed.data);
|
|
25
|
-
showSuccess(`AI provider set to: ${parsed.data}`);
|
|
26
|
-
log.newline();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (key === 'editor') {
|
|
31
|
-
const trimmed = value?.trim();
|
|
32
|
-
if (!trimmed) {
|
|
33
|
-
showError('Editor command cannot be empty');
|
|
34
|
-
log.dim('Examples: "subl -w", "code --wait", "vim", "nano"');
|
|
35
|
-
log.newline();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
await setEditor(trimmed);
|
|
40
|
-
showSuccess(`Editor set to: ${trimmed}`);
|
|
41
|
-
log.newline();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
showError(`Unknown config key: ${key ?? '(empty)'}`);
|
|
46
|
-
log.dim('Available keys: provider, editor');
|
|
47
|
-
log.newline();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function configShowCommand(): Promise<void> {
|
|
51
|
-
const provider = await getAiProvider();
|
|
52
|
-
const editorCmd = await getEditor();
|
|
53
|
-
|
|
54
|
-
printHeader('Configuration', icons.info);
|
|
55
|
-
console.log(field('AI Provider', provider ?? '(not set — will prompt on first use)'));
|
|
56
|
-
console.log(field('Editor', editorCmd ?? '(not set — will prompt on first use)'));
|
|
57
|
-
log.newline();
|
|
58
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import { configSetCommand, configShowCommand } from '@src/commands/config/config.ts';
|
|
3
|
-
|
|
4
|
-
export function registerConfigCommands(program: Command): void {
|
|
5
|
-
const config = program.command('config').description('Manage configuration');
|
|
6
|
-
|
|
7
|
-
config.addHelpText(
|
|
8
|
-
'after',
|
|
9
|
-
`
|
|
10
|
-
Examples:
|
|
11
|
-
$ ralphctl config show # Show current configuration
|
|
12
|
-
$ ralphctl config set provider claude # Use Claude Code
|
|
13
|
-
$ ralphctl config set provider copilot # Use GitHub Copilot
|
|
14
|
-
$ ralphctl config set editor "subl -w" # Use Sublime Text for multiline input
|
|
15
|
-
$ ralphctl config set editor "code --wait" # Use VS Code for multiline input
|
|
16
|
-
$ ralphctl config set editor vim # Use Vim for multiline input
|
|
17
|
-
`
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
config
|
|
21
|
-
.command('show')
|
|
22
|
-
.description('Show current configuration')
|
|
23
|
-
.action(async () => {
|
|
24
|
-
await configShowCommand();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
config
|
|
28
|
-
.command('set <key> <value>')
|
|
29
|
-
.description('Set a configuration value')
|
|
30
|
-
.action(async (key: string, value: string) => {
|
|
31
|
-
await configSetCommand([key, value]);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import { dashboardCommand } from '@src/commands/dashboard/dashboard.ts';
|
|
3
|
-
|
|
4
|
-
export function registerDashboardCommands(program: Command): void {
|
|
5
|
-
program.command('status').description('Show current sprint overview').action(dashboardCommand);
|
|
6
|
-
}
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { access, constants } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import { getConfig } from '@src/store/config.ts';
|
|
5
|
-
import { listProjects } from '@src/store/project.ts';
|
|
6
|
-
import { SprintSchema } from '@src/schemas/index.ts';
|
|
7
|
-
import { getDataDir, getSprintFilePath } from '@src/utils/paths.ts';
|
|
8
|
-
import { validateProjectPath } from '@src/utils/paths.ts';
|
|
9
|
-
import { fileExists, readValidatedJson } from '@src/utils/storage.ts';
|
|
10
|
-
import { colors, getQuoteForContext } from '@src/theme/index.ts';
|
|
11
|
-
import { icons, log, printHeader } from '@src/theme/ui.ts';
|
|
12
|
-
import { EXIT_ERROR } from '@src/utils/exit-codes.ts';
|
|
13
|
-
import { isGlabAvailable } from '@src/utils/git.ts';
|
|
14
|
-
|
|
15
|
-
const REQUIRED_NODE_MAJOR = 24;
|
|
16
|
-
|
|
17
|
-
export interface CheckResult {
|
|
18
|
-
name: string;
|
|
19
|
-
status: 'pass' | 'warn' | 'fail' | 'skip';
|
|
20
|
-
detail?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check Node.js version >= 24.0.0
|
|
25
|
-
*/
|
|
26
|
-
export function checkNodeVersion(): CheckResult {
|
|
27
|
-
const version = process.version; // e.g., "v24.1.0"
|
|
28
|
-
const match = /^v(\d+)/.exec(version);
|
|
29
|
-
const major = match ? Number(match[1]) : 0;
|
|
30
|
-
|
|
31
|
-
if (major >= REQUIRED_NODE_MAJOR) {
|
|
32
|
-
return { name: 'Node.js version', status: 'pass', detail: version };
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
name: 'Node.js version',
|
|
36
|
-
status: 'fail',
|
|
37
|
-
detail: `${version} (requires >= ${String(REQUIRED_NODE_MAJOR)}.0.0)`,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Check git is installed
|
|
43
|
-
*/
|
|
44
|
-
export function checkGitInstalled(): CheckResult {
|
|
45
|
-
const result = spawnSync('git', ['--version'], {
|
|
46
|
-
encoding: 'utf-8',
|
|
47
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
if (result.status === 0) {
|
|
51
|
-
const version = result.stdout.trim();
|
|
52
|
-
return { name: 'Git installed', status: 'pass', detail: version };
|
|
53
|
-
}
|
|
54
|
-
return { name: 'Git installed', status: 'fail', detail: 'git not found in PATH' };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Check git identity (user.name and user.email)
|
|
59
|
-
*/
|
|
60
|
-
export function checkGitIdentity(): CheckResult {
|
|
61
|
-
const nameResult = spawnSync('git', ['config', 'user.name'], {
|
|
62
|
-
encoding: 'utf-8',
|
|
63
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
64
|
-
});
|
|
65
|
-
const emailResult = spawnSync('git', ['config', 'user.email'], {
|
|
66
|
-
encoding: 'utf-8',
|
|
67
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const name = nameResult.status === 0 ? nameResult.stdout.trim() : '';
|
|
71
|
-
const email = emailResult.status === 0 ? emailResult.stdout.trim() : '';
|
|
72
|
-
|
|
73
|
-
if (name && email) {
|
|
74
|
-
return { name: 'Git identity', status: 'pass', detail: `${name} <${email}>` };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const missing: string[] = [];
|
|
78
|
-
if (!name) missing.push('user.name');
|
|
79
|
-
if (!email) missing.push('user.email');
|
|
80
|
-
return { name: 'Git identity', status: 'warn', detail: `missing: ${missing.join(', ')}` };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Check AI provider binary is on PATH
|
|
85
|
-
*/
|
|
86
|
-
export async function checkAiProvider(): Promise<CheckResult> {
|
|
87
|
-
const config = await getConfig();
|
|
88
|
-
const provider = config.aiProvider;
|
|
89
|
-
|
|
90
|
-
if (!provider) {
|
|
91
|
-
return { name: 'AI provider binary', status: 'skip', detail: 'not configured' };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const binary = provider === 'claude' ? 'claude' : 'copilot';
|
|
95
|
-
const result = spawnSync('which', [binary], {
|
|
96
|
-
encoding: 'utf-8',
|
|
97
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (result.status === 0) {
|
|
101
|
-
return { name: 'AI provider binary', status: 'pass', detail: `${binary} found` };
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
name: 'AI provider binary',
|
|
105
|
-
status: 'fail',
|
|
106
|
-
detail: `${binary} not found in PATH (provider: ${provider})`,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Check glab CLI availability (informational — for GitLab issue enrichment)
|
|
112
|
-
*/
|
|
113
|
-
export function checkGlabInstalled(): CheckResult {
|
|
114
|
-
if (isGlabAvailable()) {
|
|
115
|
-
return { name: 'GitLab CLI (glab)', status: 'pass', detail: 'installed' };
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
name: 'GitLab CLI (glab)',
|
|
119
|
-
status: 'skip',
|
|
120
|
-
detail: 'not installed (optional — needed for GitLab issue enrichment)',
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Check data directory exists and is writable
|
|
126
|
-
*/
|
|
127
|
-
export async function checkDataDirectory(): Promise<CheckResult> {
|
|
128
|
-
const dataDir = getDataDir();
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
await access(dataDir, constants.R_OK | constants.W_OK);
|
|
132
|
-
return { name: 'Data directory', status: 'pass', detail: dataDir };
|
|
133
|
-
} catch {
|
|
134
|
-
return { name: 'Data directory', status: 'fail', detail: `${dataDir} not accessible or writable` };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Check project paths exist and are git repos
|
|
140
|
-
*/
|
|
141
|
-
export async function checkProjectPaths(): Promise<CheckResult> {
|
|
142
|
-
const projects = await listProjects();
|
|
143
|
-
|
|
144
|
-
if (projects.length === 0) {
|
|
145
|
-
return { name: 'Project paths', status: 'skip', detail: 'no projects registered' };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const issues: string[] = [];
|
|
149
|
-
|
|
150
|
-
for (const project of projects) {
|
|
151
|
-
for (const repo of project.repositories) {
|
|
152
|
-
const validation = await validateProjectPath(repo.path);
|
|
153
|
-
if (validation !== true) {
|
|
154
|
-
issues.push(`${project.name}/${repo.name}: ${validation}`);
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const gitDir = join(repo.path, '.git');
|
|
159
|
-
if (!(await fileExists(gitDir))) {
|
|
160
|
-
issues.push(`${project.name}/${repo.name}: not a git repository`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (issues.length === 0) {
|
|
166
|
-
const repoCount = projects.reduce((sum, p) => sum + p.repositories.length, 0);
|
|
167
|
-
return {
|
|
168
|
-
name: 'Project paths',
|
|
169
|
-
status: 'pass',
|
|
170
|
-
detail: `${String(repoCount)} repo${repoCount !== 1 ? 's' : ''} verified`,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return { name: 'Project paths', status: 'fail', detail: issues.join('; ') };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Check current sprint validity
|
|
179
|
-
*/
|
|
180
|
-
export async function checkCurrentSprint(): Promise<CheckResult> {
|
|
181
|
-
const config = await getConfig();
|
|
182
|
-
const sprintId = config.currentSprint;
|
|
183
|
-
|
|
184
|
-
if (!sprintId) {
|
|
185
|
-
return { name: 'Current sprint', status: 'skip', detail: 'no current sprint set' };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const sprintPath = getSprintFilePath(sprintId);
|
|
189
|
-
if (!(await fileExists(sprintPath))) {
|
|
190
|
-
return { name: 'Current sprint', status: 'fail', detail: `sprint file missing: ${sprintId}` };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
const sprint = await readValidatedJson(sprintPath, SprintSchema);
|
|
195
|
-
return { name: 'Current sprint', status: 'pass', detail: `${sprint.name} (${sprint.status})` };
|
|
196
|
-
} catch (err) {
|
|
197
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
198
|
-
return { name: 'Current sprint', status: 'fail', detail: `invalid sprint data: ${message}` };
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Run all doctor checks and print results.
|
|
204
|
-
*/
|
|
205
|
-
export async function doctorCommand(): Promise<void> {
|
|
206
|
-
printHeader('System Health Check', icons.info);
|
|
207
|
-
|
|
208
|
-
const results: CheckResult[] = [];
|
|
209
|
-
|
|
210
|
-
// Synchronous checks
|
|
211
|
-
results.push(checkNodeVersion());
|
|
212
|
-
results.push(checkGitInstalled());
|
|
213
|
-
results.push(checkGitIdentity());
|
|
214
|
-
results.push(checkGlabInstalled());
|
|
215
|
-
|
|
216
|
-
// Async checks (independent — run in parallel)
|
|
217
|
-
const asyncResults = await Promise.all([
|
|
218
|
-
checkAiProvider(),
|
|
219
|
-
checkDataDirectory(),
|
|
220
|
-
checkProjectPaths(),
|
|
221
|
-
checkCurrentSprint(),
|
|
222
|
-
]);
|
|
223
|
-
results.push(...asyncResults);
|
|
224
|
-
|
|
225
|
-
// Print results
|
|
226
|
-
for (const result of results) {
|
|
227
|
-
if (result.status === 'pass') {
|
|
228
|
-
log.success(`${result.name}${result.detail ? colors.muted(` — ${result.detail}`) : ''}`);
|
|
229
|
-
} else if (result.status === 'warn') {
|
|
230
|
-
log.warn(`${result.name}${result.detail ? colors.muted(` — ${result.detail}`) : ''}`);
|
|
231
|
-
} else if (result.status === 'fail') {
|
|
232
|
-
log.error(result.name);
|
|
233
|
-
if (result.detail) {
|
|
234
|
-
log.dim(` ${result.detail}`);
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
log.raw(
|
|
238
|
-
`${icons.bullet} ${colors.muted(result.name)} ${colors.muted('—')} ${colors.muted(result.detail ?? 'skipped')}`
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Summary
|
|
244
|
-
log.newline();
|
|
245
|
-
const passed = results.filter((r) => r.status === 'pass').length;
|
|
246
|
-
const warned = results.filter((r) => r.status === 'warn').length;
|
|
247
|
-
const failed = results.filter((r) => r.status === 'fail').length;
|
|
248
|
-
const total = results.filter((r) => r.status !== 'skip').length;
|
|
249
|
-
|
|
250
|
-
if (failed === 0 && warned === 0) {
|
|
251
|
-
log.success(`All checks passed (${String(passed)}/${String(total)})`);
|
|
252
|
-
log.newline();
|
|
253
|
-
const quote = getQuoteForContext('success');
|
|
254
|
-
log.dim(`"${quote}"`);
|
|
255
|
-
} else if (failed === 0) {
|
|
256
|
-
log.success(
|
|
257
|
-
`${String(passed)}/${String(total)} checks passed, ${String(warned)} warning${warned !== 1 ? 's' : ''}`
|
|
258
|
-
);
|
|
259
|
-
log.newline();
|
|
260
|
-
const quote = getQuoteForContext('success');
|
|
261
|
-
log.dim(`"${quote}"`);
|
|
262
|
-
} else {
|
|
263
|
-
log.error(`${String(passed)}/${String(total)} checks passed, ${String(failed)} failed`);
|
|
264
|
-
log.newline();
|
|
265
|
-
const quote = getQuoteForContext('error');
|
|
266
|
-
log.dim(`"${quote}"`);
|
|
267
|
-
process.exitCode = EXIT_ERROR;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
log.newline();
|
|
271
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import { doctorCommand } from '@src/commands/doctor/doctor.ts';
|
|
3
|
-
|
|
4
|
-
export function registerDoctorCommands(program: Command): void {
|
|
5
|
-
program
|
|
6
|
-
.command('doctor')
|
|
7
|
-
.description('Check environment health and diagnose setup issues')
|
|
8
|
-
.addHelpText(
|
|
9
|
-
'after',
|
|
10
|
-
`
|
|
11
|
-
Examples:
|
|
12
|
-
$ ralphctl doctor # Run all health checks
|
|
13
|
-
|
|
14
|
-
Checks performed:
|
|
15
|
-
- Node.js version (>= 24)
|
|
16
|
-
- Git installation and identity
|
|
17
|
-
- AI provider binary (claude or copilot)
|
|
18
|
-
- Data directory accessibility
|
|
19
|
-
- Project repository paths
|
|
20
|
-
- Current sprint validity`
|
|
21
|
-
)
|
|
22
|
-
.action(async () => {
|
|
23
|
-
await doctorCommand();
|
|
24
|
-
});
|
|
25
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import { progressLogCommand } from '@src/commands/progress/log.ts';
|
|
3
|
-
import { progressShowCommand } from '@src/commands/progress/show.ts';
|
|
4
|
-
|
|
5
|
-
export function registerProgressCommands(program: Command): void {
|
|
6
|
-
const progress = program.command('progress').description('Log and view progress');
|
|
7
|
-
|
|
8
|
-
progress.addHelpText(
|
|
9
|
-
'after',
|
|
10
|
-
`
|
|
11
|
-
Examples:
|
|
12
|
-
$ ralphctl progress log "Completed auth flow"
|
|
13
|
-
$ ralphctl progress show
|
|
14
|
-
`
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
progress
|
|
18
|
-
.command('log [message]')
|
|
19
|
-
.description('Append to progress log (opens editor if no message)')
|
|
20
|
-
.action(async (message?: string) => {
|
|
21
|
-
await progressLogCommand(message ? [message] : []);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
progress.command('show').description('Display progress log').action(progressShowCommand);
|
|
25
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { log, showError, showNextStep, showSuccess } from '@src/theme/ui.ts';
|
|
2
|
-
import { logProgress } from '@src/store/progress.ts';
|
|
3
|
-
import {
|
|
4
|
-
assertSprintStatus,
|
|
5
|
-
getSprint,
|
|
6
|
-
NoCurrentSprintError,
|
|
7
|
-
resolveSprintId,
|
|
8
|
-
SprintStatusError,
|
|
9
|
-
} from '@src/store/sprint.ts';
|
|
10
|
-
import { editorInput } from '@src/utils/editor-input.ts';
|
|
11
|
-
|
|
12
|
-
export async function progressLogCommand(args: string[]): Promise<void> {
|
|
13
|
-
// FAIL FAST: Check sprint status before collecting any input
|
|
14
|
-
try {
|
|
15
|
-
const sprintId = await resolveSprintId();
|
|
16
|
-
const sprint = await getSprint(sprintId);
|
|
17
|
-
assertSprintStatus(sprint, ['active'], 'log progress');
|
|
18
|
-
} catch (err) {
|
|
19
|
-
if (err instanceof SprintStatusError) {
|
|
20
|
-
const mainError = err.message.split('\n')[0] ?? err.message;
|
|
21
|
-
showError(mainError);
|
|
22
|
-
showNextStep('ralphctl sprint start', 'activate the sprint');
|
|
23
|
-
log.newline();
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (err instanceof NoCurrentSprintError) {
|
|
27
|
-
showError('No current sprint set.');
|
|
28
|
-
showNextStep('ralphctl sprint create', 'create a new sprint');
|
|
29
|
-
log.newline();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
throw err;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Validation passed - now collect input
|
|
36
|
-
let message = args.join(' ').trim();
|
|
37
|
-
|
|
38
|
-
if (!message) {
|
|
39
|
-
message = await editorInput({
|
|
40
|
-
message: 'Progress message:',
|
|
41
|
-
});
|
|
42
|
-
message = message.trim();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!message) {
|
|
46
|
-
showError('No message provided.');
|
|
47
|
-
log.newline();
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
await logProgress(message);
|
|
53
|
-
showSuccess('Progress logged.');
|
|
54
|
-
log.newline();
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (err instanceof SprintStatusError) {
|
|
57
|
-
// Fallback handler (shouldn't reach here due to early check)
|
|
58
|
-
showError(err.message);
|
|
59
|
-
log.newline();
|
|
60
|
-
} else {
|
|
61
|
-
throw err;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { getProgress } from '@src/store/progress.ts';
|
|
2
|
-
import { printHeader, showEmpty } from '@src/theme/ui.ts';
|
|
3
|
-
|
|
4
|
-
export async function progressShowCommand(): Promise<void> {
|
|
5
|
-
const content = await getProgress();
|
|
6
|
-
|
|
7
|
-
if (!content.trim()) {
|
|
8
|
-
showEmpty('progress entries', 'Log with: ralphctl progress log');
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
printHeader('Progress Log');
|
|
13
|
-
console.log(content);
|
|
14
|
-
}
|