refactor(spec): update folder structure and dimensions

- Change folder from .workflow/ to .ccw/
- Reduce dimensions from 4 to 2: specs, personal
- Remove changelog and roadmap dimensions
- Update help text and examples

Folder structure:
- .ccw/specs/ - Project rules and conventions
- .ccw/personal/ - Personal preferences (supports global ~/.ccw/personal/)
- .ccw/.spec-index/ - Index cache

Keyword categories for workflow stages:
- exploration - Code exploration, analysis, debugging
- planning - Task planning, requirements
- execution - Implementation, testing, deployment
This commit is contained in:
catlog22
2026-02-26 23:13:00 +08:00
parent 151b81ee4a
commit ffe79d28e2
5 changed files with 41 additions and 75 deletions

View File

@@ -95,7 +95,7 @@ async function loadAction(options: SpecOptions): Promise<void> {
const result = await loadSpecs({ const result = await loadSpecs({
projectPath, projectPath,
dimension: dimension as 'specs' | 'roadmap' | 'changelog' | 'personal' | undefined, dimension: dimension as 'specs' | 'personal' | undefined,
keywords, keywords,
outputFormat: stdin ? 'hook' : 'cli', outputFormat: stdin ? 'hook' : 'cli',
stdinData, stdinData,
@@ -355,12 +355,12 @@ ${chalk.bold('USAGE')}
${chalk.bold('SUBCOMMANDS')} ${chalk.bold('SUBCOMMANDS')}
load Load specs matching dimension/keywords (CLI or Hook mode) load Load specs matching dimension/keywords (CLI or Hook mode)
list List all indexed specs with readMode and keyword info list List all indexed specs with readMode and keyword info
rebuild Force re-scan of MD files and rebuild .spec-index cache rebuild Force re-scan of MD files and rebuild .ccw/.spec-index cache
status Show per-dimension stats (total, required, optional, freshness) status Show per-dimension stats (total, required, optional, freshness)
init Create 4-dimension directory structure with seed MD documents init Create 2-dimension directory structure with seed MD documents
${chalk.bold('OPTIONS')} ${chalk.bold('OPTIONS')}
--dimension <dim> Target dimension: specs, roadmap, changelog, personal --dimension <dim> Target dimension: specs, personal
--keywords <text> Keywords for spec matching (space or comma separated) --keywords <text> Keywords for spec matching (space or comma separated)
--stdin Read input from stdin (Hook mode) --stdin Read input from stdin (Hook mode)
--json Output as JSON --json Output as JSON
@@ -368,7 +368,7 @@ ${chalk.bold('OPTIONS')}
${chalk.bold('KEYWORD CATEGORIES')} ${chalk.bold('KEYWORD CATEGORIES')}
Use these predefined keywords to load specs for specific workflow stages: Use these predefined keywords to load specs for specific workflow stages:
${chalk.cyan('exploration')} - Code exploration, analysis, debugging context ${chalk.cyan('exploration')} - Code exploration, analysis, debugging context
${chalk.cyan('planning')} - Task planning, roadmap, requirements context ${chalk.cyan('planning')} - Task planning, requirements context
${chalk.cyan('execution')} - Implementation, testing, deployment context ${chalk.cyan('execution')} - Implementation, testing, deployment context
${chalk.bold('EXAMPLES')} ${chalk.bold('EXAMPLES')}
@@ -400,7 +400,7 @@ ${chalk.bold('EXAMPLES')}
ccw spec rebuild ccw spec rebuild
${chalk.gray('# Rebuild single dimension:')} ${chalk.gray('# Rebuild single dimension:')}
ccw spec rebuild --dimension roadmap ccw spec rebuild --dimension specs
${chalk.gray('# Check system status:')} ${chalk.gray('# Check system status:')}
ccw spec status ccw spec status

View File

@@ -1,10 +1,10 @@
/** /**
* Spec Index Builder * Spec Index Builder
* *
* Scans .workflow/{dimension}/*.md files, parses YAML frontmatter via * Scans .ccw/{dimension}/*.md files, parses YAML frontmatter via
* gray-matter, and writes .spec-index/{dimension}.index.json cache files. * gray-matter, and writes .ccw/.spec-index/{dimension}.index.json cache files.
* *
* Supports 4 dimensions: specs, roadmap, changelog, personal * Supports 2 dimensions: specs, personal
* *
* YAML Frontmatter Schema: * YAML Frontmatter Schema:
* --- * ---
@@ -83,9 +83,11 @@ export interface DimensionIndex {
// ============================================================================ // ============================================================================
/** /**
* The 4 supported spec dimensions. * The 2 supported spec dimensions.
* - specs: Project rules and conventions
* - personal: Personal preferences (supports global ~/.ccw/personal/)
*/ */
export const SPEC_DIMENSIONS = ['specs', 'roadmap', 'changelog', 'personal'] as const; export const SPEC_DIMENSIONS = ['specs', 'personal'] as const;
export type SpecDimension = typeof SPEC_DIMENSIONS[number]; export type SpecDimension = typeof SPEC_DIMENSIONS[number];
@@ -100,9 +102,9 @@ const VALID_READ_MODES = ['required', 'optional'] as const;
const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'] as const; const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'] as const;
/** /**
* Directory name for spec index cache files (inside .workflow/). * Directory name for spec index cache files (inside .ccw/).
*/ */
const WORKFLOW_DIR = '.workflow'; const CCW_DIR = '.ccw';
const SPEC_INDEX_DIR = '.spec-index'; const SPEC_INDEX_DIR = '.spec-index';
// ============================================================================ // ============================================================================
@@ -114,27 +116,27 @@ const SPEC_INDEX_DIR = '.spec-index';
* *
* @param projectPath - Project root directory * @param projectPath - Project root directory
* @param dimension - The dimension name * @param dimension - The dimension name
* @returns Absolute path to .workflow/.spec-index/{dimension}.index.json * @returns Absolute path to .ccw/.spec-index/{dimension}.index.json
*/ */
export function getIndexPath(projectPath: string, dimension: string): string { export function getIndexPath(projectPath: string, dimension: string): string {
return join(projectPath, WORKFLOW_DIR, SPEC_INDEX_DIR, `${dimension}.index.json`); return join(projectPath, CCW_DIR, SPEC_INDEX_DIR, `${dimension}.index.json`);
} }
/** /**
* Get the path to the .workflow/{dimension} directory. * Get the path to the .ccw/{dimension} directory.
* *
* @param projectPath - Project root directory * @param projectPath - Project root directory
* @param dimension - The dimension name * @param dimension - The dimension name
* @returns Absolute path to .workflow/{dimension}/ * @returns Absolute path to .ccw/{dimension}/
*/ */
export function getDimensionDir(projectPath: string, dimension: string): string { export function getDimensionDir(projectPath: string, dimension: string): string {
return join(projectPath, '.workflow', dimension); return join(projectPath, CCW_DIR, dimension);
} }
/** /**
* Build the index for a single dimension. * Build the index for a single dimension.
* *
* Scans .workflow/{dimension}/*.md files, parses YAML frontmatter, * Scans .ccw/{dimension}/*.md files, parses YAML frontmatter,
* extracts the 5 required fields, and returns a DimensionIndex. * extracts the 5 required fields, and returns a DimensionIndex.
* *
* Files with malformed or missing frontmatter are skipped gracefully. * Files with malformed or missing frontmatter are skipped gracefully.
@@ -194,15 +196,15 @@ export async function buildDimensionIndex(
} }
/** /**
* Build indices for all 4 dimensions and write to .spec-index/. * Build indices for all dimensions and write to .ccw/.spec-index/.
* *
* Creates .spec-index/ directory if it doesn't exist. * Creates .ccw/.spec-index/ directory if it doesn't exist.
* Writes {dimension}.index.json for each dimension. * Writes {dimension}.index.json for each dimension.
* *
* @param projectPath - Project root directory * @param projectPath - Project root directory
*/ */
export async function buildAllIndices(projectPath: string): Promise<void> { export async function buildAllIndices(projectPath: string): Promise<void> {
const indexDir = join(projectPath, WORKFLOW_DIR, SPEC_INDEX_DIR); const indexDir = join(projectPath, CCW_DIR, SPEC_INDEX_DIR);
// Ensure .spec-index directory exists // Ensure .spec-index directory exists
if (!existsSync(indexDir)) { if (!existsSync(indexDir)) {
@@ -283,7 +285,7 @@ export async function getDimensionIndex(
// Build fresh and cache // Build fresh and cache
const index = await buildDimensionIndex(projectPath, dimension); const index = await buildDimensionIndex(projectPath, dimension);
const indexDir = join(projectPath, WORKFLOW_DIR, SPEC_INDEX_DIR); const indexDir = join(projectPath, CCW_DIR, SPEC_INDEX_DIR);
if (!existsSync(indexDir)) { if (!existsSync(indexDir)) {
mkdirSync(indexDir, { recursive: true }); mkdirSync(indexDir, { recursive: true });
} }

View File

@@ -1,9 +1,9 @@
/** /**
* Spec Init - Initialize the 4-dimension spec system * Spec Init - Initialize the 2-dimension spec system
* *
* Creates .workflow/specs/, .workflow/roadmap/, .workflow/changelog/, * Creates .ccw/specs/, .ccw/personal/,
* .workflow/personal/, and .workflow/.spec-index/ directories with * and .ccw/.spec-index/ directories with seed MD documents
* seed MD documents containing YAML frontmatter templates. * containing YAML frontmatter templates.
* *
* Idempotent: skips existing files, only creates missing directories/files. * Idempotent: skips existing files, only creates missing directories/files.
*/ */
@@ -39,7 +39,7 @@ export interface InitResult {
// Constants // Constants
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export const DIMENSIONS = ['specs', 'roadmap', 'changelog', 'personal'] as const; export const DIMENSIONS = ['specs', 'personal'] as const;
export const INDEX_DIR = '.spec-index'; export const INDEX_DIR = '.spec-index';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -55,7 +55,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
frontmatter: { frontmatter: {
title: 'Coding Conventions', title: 'Coding Conventions',
dimension: 'specs', dimension: 'specs',
keywords: ['typescript', 'naming', 'style', 'convention'], keywords: ['typescript', 'naming', 'style', 'convention', 'exploration', 'planning', 'execution'],
readMode: 'required', readMode: 'required',
priority: 'high', priority: 'high',
}, },
@@ -91,7 +91,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
frontmatter: { frontmatter: {
title: 'Architecture Constraints', title: 'Architecture Constraints',
dimension: 'specs', dimension: 'specs',
keywords: ['architecture', 'module', 'layer', 'pattern'], keywords: ['architecture', 'module', 'layer', 'pattern', 'exploration', 'planning'],
readMode: 'required', readMode: 'required',
priority: 'high', priority: 'high',
}, },
@@ -174,40 +174,6 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
}, },
], ],
], ],
[
'roadmap',
[
{
filename: 'current.md',
frontmatter: {
title: 'Current Roadmap',
dimension: 'roadmap',
keywords: ['roadmap', 'plan', 'milestone'],
readMode: 'optional',
priority: 'medium',
},
body: `# Current Roadmap
## Active Milestone
- Milestone name and target date
- Key deliverables
## Upcoming
- Next planned features or improvements
## Completed
- Recently completed milestones for reference
`,
},
],
],
[
'changelog',
[],
],
]); ]);
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -246,29 +212,29 @@ export function formatFrontmatter(fm: SpecFrontmatter): string {
* @returns InitResult with lists of created/skipped paths * @returns InitResult with lists of created/skipped paths
*/ */
export function initSpecSystem(projectPath: string): InitResult { export function initSpecSystem(projectPath: string): InitResult {
const workflowDir = join(projectPath, '.workflow'); const ccwDir = join(projectPath, '.ccw');
const result: InitResult = { const result: InitResult = {
created: [], created: [],
skipped: [], skipped: [],
directories: [], directories: [],
}; };
// Ensure .workflow root exists // Ensure .ccw root exists
if (!existsSync(workflowDir)) { if (!existsSync(ccwDir)) {
mkdirSync(workflowDir, { recursive: true }); mkdirSync(ccwDir, { recursive: true });
} }
// Create dimension directories // Create dimension directories
for (const dim of DIMENSIONS) { for (const dim of DIMENSIONS) {
const dirPath = join(workflowDir, dim); const dirPath = join(ccwDir, dim);
if (!existsSync(dirPath)) { if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true }); mkdirSync(dirPath, { recursive: true });
result.directories.push(dirPath); result.directories.push(dirPath);
} }
} }
// Create index directory at project root (matches spec-index-builder.ts location) // Create index directory inside .ccw (matches spec-index-builder.ts location)
const indexPath = join(projectPath, INDEX_DIR); const indexPath = join(ccwDir, INDEX_DIR);
if (!existsSync(indexPath)) { if (!existsSync(indexPath)) {
mkdirSync(indexPath, { recursive: true }); mkdirSync(indexPath, { recursive: true });
result.directories.push(indexPath); result.directories.push(indexPath);
@@ -276,7 +242,7 @@ export function initSpecSystem(projectPath: string): InitResult {
// Write seed documents per dimension // Write seed documents per dimension
for (const [dimension, docs] of SEED_DOCS) { for (const [dimension, docs] of SEED_DOCS) {
const dimDir = join(workflowDir, dimension); const dimDir = join(ccwDir, dimension);
for (const doc of docs) { for (const doc of docs) {
const filePath = join(dimDir, doc.filename); const filePath = join(dimDir, doc.filename);

View File

@@ -105,9 +105,7 @@ interface LoadedSpec {
*/ */
const DIMENSION_PRIORITY: Record<string, number> = { const DIMENSION_PRIORITY: Record<string, number> = {
personal: 1, personal: 1,
changelog: 2, specs: 2,
roadmap: 3,
specs: 4,
}; };
/** /**