feat: add Kilo CLI runtime support
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { GSDTools, GSDToolsError, resolveGsdToolsPath } from './gsd-tools.js';
|
||||
import { mkdir, writeFile, rm } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir, homedir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const BUNDLED_GSD_TOOLS_PATH = fileURLToPath(
|
||||
new URL('../../get-shit-done/bin/gsd-tools.cjs', import.meta.url),
|
||||
);
|
||||
|
||||
describe('GSDTools', () => {
|
||||
let tmpDir: string;
|
||||
@@ -330,11 +336,13 @@ describe('GSDTools', () => {
|
||||
expect(result).toBe(join(localBinDir, 'gsd-tools.cjs'));
|
||||
});
|
||||
|
||||
it('falls back to global path when repo-local does not exist', () => {
|
||||
it('falls back to bundled repo path when repo-local does not exist', () => {
|
||||
const result = resolveGsdToolsPath(tmpDir);
|
||||
expect(result).toBe(
|
||||
join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs'),
|
||||
);
|
||||
const expected = existsSync(BUNDLED_GSD_TOOLS_PATH)
|
||||
? BUNDLED_GSD_TOOLS_PATH
|
||||
: join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs');
|
||||
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('constructor uses repo-local path when available', async () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { readFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { InitNewProjectInfo, PhaseOpInfo, PhasePlanIndex, RoadmapAnalysis } from './types.js';
|
||||
|
||||
// ─── Error type ──────────────────────────────────────────────────────────────
|
||||
@@ -30,6 +31,9 @@ export class GSDToolsError extends Error {
|
||||
// ─── GSDTools class ──────────────────────────────────────────────────────────
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 30_000;
|
||||
const BUNDLED_GSD_TOOLS_PATH = fileURLToPath(
|
||||
new URL('../../get-shit-done/bin/gsd-tools.cjs', import.meta.url),
|
||||
);
|
||||
|
||||
export class GSDTools {
|
||||
private readonly projectDir: string;
|
||||
@@ -58,7 +62,7 @@ export class GSDTools {
|
||||
|
||||
return new Promise<unknown>((resolve, reject) => {
|
||||
const child = execFile(
|
||||
'node',
|
||||
process.execPath,
|
||||
fullArgs,
|
||||
{
|
||||
cwd: this.projectDir,
|
||||
@@ -160,7 +164,7 @@ export class GSDTools {
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const child = execFile(
|
||||
'node',
|
||||
process.execPath,
|
||||
fullArgs,
|
||||
{
|
||||
cwd: this.projectDir,
|
||||
@@ -285,11 +289,15 @@ export class GSDTools {
|
||||
// ─── Path resolution ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Resolve gsd-tools.cjs path with repo-local fallback.
|
||||
* Probe order: repo-local → global home directory.
|
||||
* Resolve gsd-tools.cjs path with bundled-repo fallback.
|
||||
* Probe order: project-local → repo-bundled → global home directory.
|
||||
*/
|
||||
export function resolveGsdToolsPath(projectDir: string): string {
|
||||
const localPath = join(projectDir, '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs');
|
||||
if (existsSync(localPath)) return localPath;
|
||||
return join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs');
|
||||
const candidates = [
|
||||
join(projectDir, '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs'),
|
||||
BUNDLED_GSD_TOOLS_PATH,
|
||||
join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs'),
|
||||
];
|
||||
|
||||
return candidates.find(candidate => existsSync(candidate)) ?? candidates[candidates.length - 1]!;
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { mkdtemp, rm, readFile, stat } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
import { InitRunner } from './init-runner.js';
|
||||
import { GSDTools } from './gsd-tools.js';
|
||||
import { GSDTools, resolveGsdToolsPath } from './gsd-tools.js';
|
||||
import { GSDEventStream } from './event-stream.js';
|
||||
import { GSDEventType } from './types.js';
|
||||
import type { GSDEvent } from './types.js';
|
||||
@@ -36,11 +36,12 @@ try {
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const sdkPromptsDir = join(__dirname, '..', 'prompts');
|
||||
const GSD_TOOLS_PATH = join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs');
|
||||
const GSD_TOOLS_PATH = resolveGsdToolsPath(process.cwd());
|
||||
const gsdToolsAvailable = existsSync(GSD_TOOLS_PATH);
|
||||
|
||||
// ─── Test suite ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe.skipIf(!cliAvailable)('E2E: InitRunner.run() full workflow', () => {
|
||||
describe.skipIf(!cliAvailable || !gsdToolsAvailable)('E2E: InitRunner.run() full workflow', () => {
|
||||
let tmpDir: string;
|
||||
let events: GSDEvent[];
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { mkdtemp, rm, readFile, stat, readdir } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
import { GSD } from './index.js';
|
||||
import { InitRunner } from './init-runner.js';
|
||||
import { GSDTools } from './gsd-tools.js';
|
||||
import { GSDTools, resolveGsdToolsPath } from './gsd-tools.js';
|
||||
import { GSDEventStream } from './event-stream.js';
|
||||
import { GSDEventType, PhaseStepType } from './types.js';
|
||||
import type { GSDEvent, PhaseRunnerResult, RoadmapAnalysis } from './types.js';
|
||||
@@ -38,7 +38,8 @@ try {
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const sdkPromptsDir = join(__dirname, '..', 'prompts');
|
||||
const GSD_TOOLS_PATH = join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs');
|
||||
const GSD_TOOLS_PATH = resolveGsdToolsPath(process.cwd());
|
||||
const gsdToolsAvailable = existsSync(GSD_TOOLS_PATH);
|
||||
|
||||
// ─── Lifecycle step ordering for monotonicity check ──────────────────────────
|
||||
|
||||
@@ -54,7 +55,7 @@ const STEP_ORDER: Record<string, number> = {
|
||||
|
||||
// ─── Test suite ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe.skipIf(!cliAvailable)('E2E Lifecycle: InitRunner → GSD.runPhase() full lifecycle', () => {
|
||||
describe.skipIf(!cliAvailable || !gsdToolsAvailable)('E2E Lifecycle: InitRunner → GSD.runPhase() full lifecycle', () => {
|
||||
let tmpDir: string;
|
||||
let initSuccess: boolean = false;
|
||||
let phase1Number: string | null = null;
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
import { GSDTools } from './gsd-tools.js';
|
||||
import { GSDTools, resolveGsdToolsPath } from './gsd-tools.js';
|
||||
import { PhaseRunner } from './phase-runner.js';
|
||||
import type { PhaseRunnerDeps } from './phase-runner.js';
|
||||
import { ContextEngine } from './context-engine.js';
|
||||
@@ -24,7 +24,8 @@ import { GSDEventType, PhaseStepType } from './types.js';
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const GSD_TOOLS_PATH = join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs');
|
||||
const GSD_TOOLS_PATH = resolveGsdToolsPath(process.cwd());
|
||||
const gsdToolsAvailable = existsSync(GSD_TOOLS_PATH);
|
||||
|
||||
async function createTempPlanningDir(): Promise<string> {
|
||||
const tmpDir = await mkdtemp(join(tmpdir(), 'gsd-sdk-phase-int-'));
|
||||
@@ -65,7 +66,7 @@ async function createTempPlanningDir(): Promise<string> {
|
||||
|
||||
// ─── Test suite ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Integration: PhaseRunner against real gsd-tools.cjs', () => {
|
||||
describe.skipIf(!gsdToolsAvailable)('Integration: PhaseRunner against real gsd-tools.cjs', () => {
|
||||
let tmpDir: string;
|
||||
let tools: GSDTools;
|
||||
|
||||
@@ -303,7 +304,7 @@ must_haves:
|
||||
return tmpDir;
|
||||
}
|
||||
|
||||
describe('Integration: phasePlanIndex and wave execution', () => {
|
||||
describe.skipIf(!gsdToolsAvailable)('Integration: phasePlanIndex and wave execution', () => {
|
||||
let tmpDir: string;
|
||||
let tools: GSDTools;
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ export interface PlanResult {
|
||||
export interface GSDOptions {
|
||||
/** Root directory of the project. */
|
||||
projectDir: string;
|
||||
/** Path to gsd-tools.cjs. Falls back to <projectDir>/.claude/ then ~/.claude/. */
|
||||
/** Path to gsd-tools.cjs. Falls back to <projectDir>/.claude/, then the bundled repo path, then ~/.claude/. */
|
||||
gsdToolsPath?: string;
|
||||
/** Model to use for execution sessions. */
|
||||
model?: string;
|
||||
|
||||
Reference in New Issue
Block a user