diff --git a/package.json b/package.json index aa81b5e..f32d70e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "next start", "lint": "next lint", "typecheck": "tsc --noEmit", - "test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/lib/kanban.test.ts && node --import tsx --test tests/lib/read-issues.test.ts && node --test tests/guards/no-direct-jsonl-write.test.mjs && node --test tests/guards/no-inline-style-in-kanban.test.mjs && node --test tests/guards/kanban-responsive-contract.test.mjs" + "test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/lib/project-context.test.ts && node --import tsx --test tests/lib/kanban.test.ts && node --import tsx --test tests/lib/read-issues.test.ts && node --test tests/guards/no-direct-jsonl-write.test.mjs && node --test tests/guards/no-inline-style-in-kanban.test.mjs && node --test tests/guards/kanban-responsive-contract.test.mjs" }, "dependencies": { "framer-motion": "^11.18.2", diff --git a/src/lib/project-context.ts b/src/lib/project-context.ts new file mode 100644 index 0000000..0e885c9 --- /dev/null +++ b/src/lib/project-context.ts @@ -0,0 +1,25 @@ +import path from 'node:path'; + +import { canonicalizeWindowsPath, toDisplayPath, windowsPathKey } from './pathing'; +import type { ProjectContext, ProjectSource } from './types'; + +interface BuildProjectContextOptions { + source?: ProjectSource; + addedAt?: string | null; +} + +export function buildProjectContext(root: string, options: BuildProjectContextOptions = {}): ProjectContext { + if (!root) { + throw new Error('Project root is required to build project context.'); + } + + const normalizedRoot = canonicalizeWindowsPath(root); + return { + key: windowsPathKey(normalizedRoot), + root: normalizedRoot, + displayPath: toDisplayPath(normalizedRoot), + name: path.basename(normalizedRoot), + source: options.source ?? 'local', + addedAt: options.addedAt ?? null, + }; +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 06f2567..46a9d4f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -59,3 +59,16 @@ export interface ParseableBeadIssue extends Partial { id: string; title: string; } + +export type ProjectSource = 'local' | 'registry' | 'scanner'; + +export interface ProjectContext { + key: string; + root: string; + displayPath: string; + name: string; + source: ProjectSource; + addedAt: string | null; +} + +export type BeadIssueWithProject = BeadIssue & { project: ProjectContext }; diff --git a/tests/lib/project-context.test.ts b/tests/lib/project-context.test.ts new file mode 100644 index 0000000..5ecd3c1 --- /dev/null +++ b/tests/lib/project-context.test.ts @@ -0,0 +1,15 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { buildProjectContext } from '../../src/lib/project-context'; + +test('buildProjectContext derives normalized project identity', () => { + const project = buildProjectContext('C:/Repo/Project'); + + assert.equal(project.root, 'C:\\Repo\\Project'); + assert.equal(project.key, 'c:\\repo\\project'); + assert.equal(project.displayPath, 'C:/Repo/Project'); + assert.equal(project.name, 'Project'); + assert.equal(project.source, 'local'); + assert.equal(project.addedAt, null); +});