feat: add SpecDialog component for editing spec frontmatter

- Implement SpecDialog for managing spec details including title, read mode, priority, and keywords.
- Add validation and keyword management functionality.
- Integrate SpecDialog into SpecsSettingsPage for editing specs.

feat: create index file for specs components

- Export SpecCard, SpecDialog, and related types from a new index file for better organization.

feat: implement SpecsSettingsPage for managing specs and hooks

- Create main settings page with tabs for Project Specs, Personal Specs, Hooks, Injection, and Settings.
- Integrate SpecDialog and HookDialog for editing specs and hooks.
- Add search functionality and mock data for specs and hooks.

feat: add spec management API routes

- Implement API endpoints for listing specs, getting spec details, updating frontmatter, rebuilding indices, and initializing the spec system.
- Handle errors and responses appropriately for each endpoint.
This commit is contained in:
catlog22
2026-02-26 22:03:13 +08:00
parent 430d817e43
commit 6155fcc7b8
115 changed files with 4883 additions and 21127 deletions

View File

@@ -716,7 +716,7 @@ async function notifyAction(options: HookOptions): Promise<void> {
}
/**
* Project state action - reads project-tech.json and project-guidelines.json
* Project state action - reads project-tech.json and specs
* and outputs a concise summary for session context injection.
*
* Used as SessionStart hook: stdout → injected as system message.
@@ -767,31 +767,19 @@ async function projectStateAction(options: HookOptions): Promise<void> {
} catch { /* ignore parse errors */ }
}
// Read project-guidelines.json
const guidelinesPath = join(projectPath, '.workflow', 'project-guidelines.json');
if (existsSync(guidelinesPath)) {
try {
const gl = JSON.parse(readFileSync(guidelinesPath, 'utf8'));
// constraints is Record<string, array> - flatten all categories
const allConstraints: string[] = [];
if (gl.constraints && typeof gl.constraints === 'object') {
for (const entries of Object.values(gl.constraints)) {
if (Array.isArray(entries)) {
for (const c of entries) {
allConstraints.push(typeof c === 'string' ? c : (c as { rule?: string }).rule || JSON.stringify(c));
}
}
}
// Read specs from spec system (ccw spec load --dimension specs)
try {
const { getDimensionIndex } = await import('../tools/spec-index-builder.js');
const specsIndex = await getDimensionIndex(projectPath, 'specs');
const constraints: string[] = [];
for (const entry of specsIndex.entries) {
if (entry.readMode === 'required') {
constraints.push(entry.title);
}
result.guidelines.constraints = allConstraints.slice(0, limit);
const learnings = Array.isArray(gl.learnings) ? gl.learnings : [];
learnings.sort((a: { date?: string }, b: { date?: string }) => (b.date || '').localeCompare(a.date || ''));
result.guidelines.recent_learnings = learnings.slice(0, limit).map(
(l: { insight?: string; date?: string }) => ({ insight: l.insight || '', date: l.date || '' })
);
} catch { /* ignore parse errors */ }
}
}
result.guidelines.constraints = constraints.slice(0, limit);
result.guidelines.recent_learnings = [];
} catch { /* ignore errors */ }
if (stdin) {
// Format as <project-state> tag for system message injection