feat(services): add decision service and refactor CLI

This commit is contained in:
chahinebrini 2026-06-25 12:23:23 +02:00
parent 6c8ee37a08
commit fff8a61ad9
3 changed files with 75 additions and 36 deletions

View File

@ -1,54 +1,22 @@
import { input } from '@inquirer/prompts';
import { join } from 'path';
import { getEntityDir } from '../../core/paths.js';
import { getNextId } from '../../core/counter.js';
import { readEntity, writeEntity } from '../../core/files.js';
import { DecisionSchema, type Decision } from '../../core/schema.js';
import { Index } from '../../core/index.js';
import { createDecision, listDecisions } from '../../core/services/decisionService.js';
import type { Decision } from '../../core/schema.js';
export async function decisionCreate(cwd: string, options: Partial<Decision> = {}): Promise<void> {
const title = options.title ?? await input({ message: 'Decision title:' });
const context = options.context ?? await input({ message: 'Context:' });
const decision = options.decision ?? await input({ message: 'Decision:' });
const now = new Date().toISOString();
const record: Decision = DecisionSchema.parse({
id: getNextId(cwd, 'decision'),
title,
context,
decision,
createdAt: now,
updatedAt: now,
});
const filePath = join(getEntityDir(cwd, 'decisions'), `${record.id}.md`);
writeEntity(filePath, record, `# ${record.title}\n\n## Decision\n\n${record.decision}\n\n## Context\n\n${record.context}`);
const index = new Index(cwd);
index.upsert({
id: record.id,
type: 'decision',
title: record.title,
content: `${record.context} ${record.decision}`,
filePath,
createdAt: record.createdAt,
updatedAt: record.updatedAt,
});
index.close();
const record = createDecision(cwd, { title, context, decision });
console.log(`Decision recorded: ${record.id}`);
}
export function decisionList(cwd: string): void {
const index = new Index(cwd);
const decisions = index.list('decision');
index.close();
const decisions = listDecisions(cwd);
if (decisions.length === 0) {
console.log('No decisions found.');
return;
}
for (const d of decisions) {
console.log(`${d.id}: ${d.title}`);
}

View File

@ -0,0 +1,52 @@
import { join } from 'path';
import { getEntityDir } from '../paths.js';
import { getNextId } from '../counter.js';
import { readEntity, writeEntity } from '../files.js';
import { DecisionSchema, type Decision } from '../schema.js';
import { Index } from '../index.js';
export function createDecision(cwd: string, options: Partial<Decision> = {}): Decision {
const now = new Date().toISOString();
const record: Decision = DecisionSchema.parse({
id: getNextId(cwd, 'decision'),
title: options.title ?? 'Decision',
context: options.context ?? '',
decision: options.decision ?? '',
status: options.status ?? 'accepted',
consequences: options.consequences ?? [],
alternatives: options.alternatives ?? [],
relatedDecisions: options.relatedDecisions ?? [],
createdAt: now,
updatedAt: now,
});
const filePath = join(getEntityDir(cwd, 'decisions'), `${record.id}.md`);
writeEntity(filePath, record, `# ${record.title}\n\n## Decision\n\n${record.decision}\n\n## Context\n\n${record.context}`);
const index = new Index(cwd);
index.upsert({
id: record.id,
type: 'decision',
title: record.title,
content: `${record.context} ${record.decision}`,
filePath,
createdAt: record.createdAt,
updatedAt: record.updatedAt,
});
index.close();
return record;
}
export function listDecisions(cwd: string): ReturnType<Index['list']> {
const index = new Index(cwd);
const decisions = index.list('decision');
index.close();
return decisions;
}
export function getDecision(cwd: string, id: string): { decision: Decision; body: string; filePath: string } {
const filePath = join(getEntityDir(cwd, 'decisions'), `${id}.md`);
const { frontmatter, body } = readEntity(filePath);
return { decision: DecisionSchema.parse(frontmatter), body, filePath };
}

View File

@ -0,0 +1,19 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { createDecision, listDecisions, getDecision } from '../src/core/services/decisionService.js';
describe('decisionService', () => {
let cwd: string;
beforeEach(() => { cwd = mkdtempSync(join(tmpdir(), 'ah-dec-')); });
afterEach(() => { rmSync(cwd, { recursive: true, force: true }); });
it('creates and reads a decision', () => {
const d = createDecision(cwd, { title: 'Use SQLite', context: 'c', decision: 'd' });
expect(d.id).toBe('DEC-0001');
expect(getDecision(cwd, d.id).decision.title).toBe('Use SQLite');
expect(listDecisions(cwd)).toHaveLength(1);
});
});