Skip to content

Commit b3e6df2

Browse files
committed
fix: address CodeRabbit review suggestions for PR #64
- doctor.ts: use execFileSync, typed AgentType, remove as-any casts - activity.ts: validate parseInt with radix and NaN guard - issue.ts: wrap JSON.parse in try/catch for gh CLI output - issue-planner.ts: guard JSON.parse, fix includeTests no-op ternary, remove duplicate IssuePlanMetadata (import from types.ts) - status.ts: typed StatusOverview interface, remove as-any casts - snapshot-manager.ts: align observations param with SessionSnapshot type - session.ts: proper type mapping for observations (no double cast) - activity-log.ts: min-length guard for SHA prefix match - e2e tests: use top-level imports instead of inline require() - commands.test.ts: add assertions for DoctorCommand, IssuePlanCommand, IssueListCommand
1 parent 58ff71e commit b3e6df2

File tree

10 files changed

+78
-47
lines changed

10 files changed

+78
-47
lines changed

packages/cli/src/__tests__/commands.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ describe('CLI Commands', () => {
2222
expect(commands.RecommendCommand).toBeDefined();
2323

2424
expect(commands.ActivityCommand).toBeDefined();
25+
expect(commands.DoctorCommand).toBeDefined();
26+
expect(commands.IssuePlanCommand).toBeDefined();
27+
expect(commands.IssueListCommand).toBeDefined();
2528
expect(commands.SessionSnapshotSaveCommand).toBeDefined();
2629
expect(commands.SessionSnapshotRestoreCommand).toBeDefined();
2730
expect(commands.SessionSnapshotListCommand).toBeDefined();

packages/cli/src/__tests__/e2e-session-features.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
22
import { execSync } from 'node:child_process';
3-
import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
3+
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
44
import { join } from 'node:path';
55
import { tmpdir } from 'node:os';
6+
import { stringify } from 'yaml';
67

78
const CLI = join(__dirname, '../../../../apps/skillkit/dist/cli.js');
89

@@ -82,8 +83,6 @@ describe('E2E: Session Features', () => {
8283
});
8384

8485
it('should save and restore snapshot with active session', () => {
85-
const { stringify } = require('yaml');
86-
8786
const skillkitDir = join(testDir, '.skillkit');
8887
mkdirSync(skillkitDir, { recursive: true });
8988

@@ -111,7 +110,6 @@ describe('E2E: Session Features', () => {
111110
decisions: [{ key: 'test-key', value: 'test-value', madeAt: new Date().toISOString() }],
112111
};
113112

114-
const { writeFileSync } = require('node:fs');
115113
writeFileSync(
116114
join(skillkitDir, 'session.yaml'),
117115
stringify(sessionState)
@@ -158,9 +156,6 @@ describe('E2E: Session Features', () => {
158156
});
159157

160158
it('should explain session with active execution', () => {
161-
const { stringify } = require('yaml');
162-
const { writeFileSync } = require('node:fs');
163-
164159
const skillkitDir = join(testDir, '.skillkit');
165160
mkdirSync(skillkitDir, { recursive: true });
166161

packages/cli/src/commands/activity.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export class ActivityCommand extends Command {
2929
async execute(): Promise<number> {
3030
const projectPath = process.cwd();
3131
const log = new ActivityLog(projectPath);
32-
const limit = this.limit ? parseInt(this.limit) : 10;
32+
const parsed = this.limit ? parseInt(this.limit, 10) : 10;
33+
const limit = Number.isFinite(parsed) && parsed > 0 ? parsed : 10;
3334

3435
const activities = this.skill
3536
? log.getBySkill(this.skill)

packages/cli/src/commands/doctor.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Command, Option } from 'clipanion';
2-
import { execSync } from 'node:child_process';
2+
import { execFileSync } from 'node:child_process';
33
import { existsSync, lstatSync, readlinkSync, readdirSync, mkdirSync, unlinkSync } from 'node:fs';
44
import { resolve, join } from 'node:path';
55
import { homedir } from 'node:os';
66
import { detectAgent, getAllAdapters, getAdapter } from '@skillkit/agents';
7+
import type { AgentType } from '@skillkit/core';
78
import { findAllSkills, validateSkill, loadConfig, getProjectConfigPath } from '@skillkit/core';
89
import { getSearchDirs } from '../helpers.js';
910
import { colors, symbols } from '../onboarding/index.js';
@@ -81,7 +82,7 @@ export class DoctorCommand extends Command {
8182
}
8283

8384
// --- Agent Detection ---
84-
let detectedAgent = 'universal';
85+
let detectedAgent: AgentType = 'universal';
8586
try {
8687
detectedAgent = await detectAgent();
8788
results.push({ name: 'Detected agent', status: 'pass', message: `Detected: ${detectedAgent}` });
@@ -110,7 +111,7 @@ export class DoctorCommand extends Command {
110111
}
111112

112113
// --- Skills ---
113-
const adapter = getAdapter(detectedAgent as any);
114+
const adapter = getAdapter(detectedAgent);
114115
const skillsDir = resolve(adapter.skillsDir);
115116
const skillsDirExists = existsSync(skillsDir);
116117

@@ -142,7 +143,7 @@ export class DoctorCommand extends Command {
142143
// Skill validation
143144
let searchDirs: string[] = [];
144145
try {
145-
searchDirs = getSearchDirs(detectedAgent as any);
146+
searchDirs = getSearchDirs(detectedAgent);
146147
} catch {
147148
searchDirs = skillsDirExists ? [skillsDir] : [];
148149
}
@@ -285,7 +286,7 @@ export class DoctorCommand extends Command {
285286

286287
private whichVersion(cmd: string): string | null {
287288
try {
288-
return execSync(`${cmd} --version`, { encoding: 'utf-8', timeout: 5000 })
289+
return execFileSync(cmd, ['--version'], { encoding: 'utf-8', timeout: 5000 })
289290
.trim()
290291
.replace(/^v/, '');
291292
} catch {

packages/cli/src/commands/issue.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,18 @@ export class IssueListCommand extends Command {
245245
return 1;
246246
}
247247

248-
const issues = JSON.parse(result) as Array<{
248+
let issues: Array<{
249249
number: number;
250250
title: string;
251251
labels: Array<{ name: string }>;
252252
assignees: Array<{ login: string }>;
253253
}>;
254+
try {
255+
issues = JSON.parse(result);
256+
} catch {
257+
this.context.stderr.write('Error: Failed to parse GitHub CLI response.\n');
258+
return 1;
259+
}
254260

255261
if (this.json) {
256262
this.context.stdout.write(JSON.stringify(issues, null, 2) + '\n');

packages/cli/src/commands/session.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
SessionExplainer,
1313
ObservationStore,
1414
type SessionFile,
15+
type SessionSnapshot,
1516
} from '@skillkit/core';
1617

1718
export class SessionCommand extends Command {
@@ -357,9 +358,18 @@ export class SessionSnapshotSaveCommand extends Command {
357358
return 1;
358359
}
359360

360-
let observations: Array<Record<string, unknown>> = [];
361+
let observations: SessionSnapshot['observations'] = [];
361362
try {
362-
observations = new ObservationStore(projectPath).getAll() as unknown as Array<Record<string, unknown>>;
363+
const raw = new ObservationStore(projectPath).getAll();
364+
observations = raw.map((o) => ({
365+
id: o.id,
366+
timestamp: o.timestamp,
367+
sessionId: o.sessionId,
368+
agent: o.agent as string,
369+
type: o.type as string,
370+
content: { ...o.content } as Record<string, unknown>,
371+
relevance: o.relevance,
372+
}));
363373
} catch {
364374
// No observations available
365375
}

packages/cli/src/commands/status.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,23 @@ import { Command, Option } from 'clipanion';
22
import { resolve } from 'node:path';
33
import { existsSync } from 'node:fs';
44
import chalk from 'chalk';
5+
import type { AgentType } from '@skillkit/core';
56
import { SessionManager, findAllSkills, loadConfig, getProjectConfigPath } from '@skillkit/core';
67
import { detectAgent, getAdapter } from '@skillkit/agents';
78
import { getSearchDirs } from '../helpers.js';
89

10+
interface StatusOverview {
11+
agent: string;
12+
config: string | null;
13+
configAgent: string;
14+
version: string;
15+
totalSkills: number;
16+
projectSkills: number;
17+
globalSkills: number;
18+
skillsDir: string;
19+
recentHistory: Array<{ status: string; skillName: string; completedAt: string }>;
20+
}
21+
922
/**
1023
* Status command - show current session state
1124
*/
@@ -137,8 +150,8 @@ export class StatusCommand extends Command {
137150
return 0;
138151
}
139152

140-
private async buildOverview(manager: SessionManager): Promise<Record<string, unknown>> {
141-
let agent = 'universal';
153+
private async buildOverview(manager: SessionManager): Promise<StatusOverview> {
154+
let agent: AgentType = 'universal';
142155
try {
143156
agent = await detectAgent();
144157
} catch {
@@ -160,15 +173,15 @@ export class StatusCommand extends Command {
160173

161174
let searchDirs: string[] = [];
162175
try {
163-
searchDirs = getSearchDirs(agent as any);
176+
searchDirs = getSearchDirs(agent);
164177
} catch {
165178
// fallback
166179
}
167180

168181
const allSkills = findAllSkills(searchDirs);
169182
const projectSkills = allSkills.filter(s => s.location === 'project');
170183
const globalSkills = allSkills.filter(s => s.location === 'global');
171-
const adapter = getAdapter(agent as any);
184+
const adapter = getAdapter(agent);
172185
const recentHistory = manager.getHistory(3);
173186

174187
return {
@@ -184,26 +197,24 @@ export class StatusCommand extends Command {
184197
};
185198
}
186199

187-
private showOverview(overview: Record<string, unknown>): void {
200+
private showOverview(overview: StatusOverview): void {
188201
console.log('');
189202
console.log(chalk.cyan(' Project Overview'));
190-
console.log(` Agent: ${chalk.bold(String(overview.agent))}`);
203+
console.log(` Agent: ${chalk.bold(overview.agent)}`);
191204
console.log(` Config: ${overview.config ? chalk.green('skillkit.yaml') : chalk.dim('none (defaults)')}`);
192-
console.log(` Version: ${chalk.bold(String(overview.version))}`);
205+
console.log(` Version: ${chalk.bold(overview.version)}`);
193206
console.log('');
194207

195-
const total = overview.totalSkills as number;
196-
console.log(chalk.cyan(` Skills (${total} installed)`));
208+
console.log(chalk.cyan(` Skills (${overview.totalSkills} installed)`));
197209
console.log(` Project: ${overview.projectSkills} skills in ${overview.skillsDir}`);
198210
console.log(` Global: ${overview.globalSkills} skills`);
199211
console.log('');
200212

201213
console.log(chalk.cyan(' Recent Activity'));
202-
const history = overview.recentHistory as Array<{ status: string; skillName: string; completedAt: string }>;
203-
if (history.length === 0) {
214+
if (overview.recentHistory.length === 0) {
204215
console.log(chalk.dim(' No recent executions.'));
205216
} else {
206-
for (const entry of history) {
217+
for (const entry of overview.recentHistory) {
207218
const statusColor = entry.status === 'completed' ? chalk.green : chalk.red;
208219
console.log(` ${statusColor('\u25cf')} ${entry.skillName} - ${new Date(entry.completedAt).toLocaleDateString()}`);
209220
}

packages/core/src/plan/issue-planner.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { execFileSync } from 'node:child_process';
22
import { PlanGenerator } from './generator.js';
3-
import type { StructuredPlan, PlanTaskFiles } from './types.js';
3+
import type { StructuredPlan, PlanTaskFiles, IssuePlanMetadata } from './types.js';
4+
5+
export type { IssuePlanMetadata };
46

57
export interface GitHubIssue {
68
number: number;
@@ -19,14 +21,6 @@ export interface IssuePlanOptions {
1921
includeTests?: boolean;
2022
}
2123

22-
export interface IssuePlanMetadata {
23-
issueNumber: number;
24-
issueUrl: string;
25-
issueLabels: string[];
26-
agent: string;
27-
generatedAt: string;
28-
}
29-
3024
interface ParsedRef {
3125
owner?: string;
3226
repo?: string;
@@ -110,17 +104,26 @@ export class IssuePlanner {
110104
timeout: 15_000,
111105
});
112106

113-
const data = JSON.parse(result);
107+
let data: {
108+
number: number;
109+
title: string;
110+
body?: string;
111+
labels?: Array<{ name: string }>;
112+
assignees?: Array<{ login: string }>;
113+
url: string;
114+
state: string;
115+
};
116+
try {
117+
data = JSON.parse(result);
118+
} catch {
119+
throw new Error(`Failed to parse GitHub CLI response for issue ${parsed.number}`);
120+
}
114121
return {
115122
number: data.number,
116123
title: data.title,
117124
body: data.body || '',
118-
labels: (data.labels || []).map(
119-
(l: { name: string }) => l.name
120-
),
121-
assignees: (data.assignees || []).map(
122-
(a: { login: string }) => a.login
123-
),
125+
labels: (data.labels || []).map((l) => l.name),
126+
assignees: (data.assignees || []).map((a) => a.login),
124127
url: data.url,
125128
state: data.state,
126129
};
@@ -205,7 +208,7 @@ export class IssuePlanner {
205208
steps: [
206209
{ type: 'implement', description: item.name },
207210
...(includeTests
208-
? []
211+
? [{ type: 'test' as const, description: `Write tests for ${item.name}` }]
209212
: []),
210213
],
211214
});

packages/core/src/session/activity-log.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export class ActivityLog {
7373
}
7474

7575
getByCommit(sha: string): SkillActivity | undefined {
76+
if (sha.length < 4) return undefined;
7677
const data = this.load();
7778
return data.activities.find(
7879
(a) => a.commitSha === sha || a.commitSha.startsWith(sha)

packages/core/src/session/snapshot-manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class SnapshotManager {
4040
save(
4141
name: string,
4242
sessionState: SessionState,
43-
observations: Array<Record<string, unknown>>,
43+
observations: SessionSnapshot['observations'],
4444
description?: string
4545
): void {
4646
this.ensureDir();
@@ -51,7 +51,7 @@ export class SnapshotManager {
5151
createdAt: new Date().toISOString(),
5252
description,
5353
sessionState,
54-
observations: observations as SessionSnapshot['observations'],
54+
observations,
5555
};
5656

5757
writeFileSync(this.getPath(name), stringify(snapshot));

0 commit comments

Comments
 (0)