diff --git a/src/cli/commands/decision.ts b/src/cli/commands/decision.ts index 66b693b..f3b162a 100644 --- a/src/cli/commands/decision.ts +++ b/src/cli/commands/decision.ts @@ -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 = {}): Promise { 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}`); } diff --git a/src/core/services/decisionService.ts b/src/core/services/decisionService.ts new file mode 100644 index 0000000..fb6cf7b --- /dev/null +++ b/src/core/services/decisionService.ts @@ -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 { + 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 { + 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 }; +} diff --git a/tests/decisionService.test.ts b/tests/decisionService.test.ts new file mode 100644 index 0000000..8980a4e --- /dev/null +++ b/tests/decisionService.test.ts @@ -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); + }); +});