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({
projectPath,
dimension: dimension as 'specs' | 'roadmap' | 'changelog' | 'personal' | undefined,
dimension: dimension as 'specs' | 'personal' | undefined,
keywords,
outputFormat: stdin ? 'hook' : 'cli',
stdinData,
@@ -355,12 +355,12 @@ ${chalk.bold('USAGE')}
${chalk.bold('SUBCOMMANDS')}
load Load specs matching dimension/keywords (CLI or Hook mode)
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)
init Create 4-dimension directory structure with seed MD documents
init Create 2-dimension directory structure with seed MD documents
${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)
--stdin Read input from stdin (Hook mode)
--json Output as JSON
@@ -368,7 +368,7 @@ ${chalk.bold('OPTIONS')}
${chalk.bold('KEYWORD CATEGORIES')}
Use these predefined keywords to load specs for specific workflow stages:
${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.bold('EXAMPLES')}
@@ -400,7 +400,7 @@ ${chalk.bold('EXAMPLES')}
ccw spec rebuild
${chalk.gray('# Rebuild single dimension:')}
ccw spec rebuild --dimension roadmap
ccw spec rebuild --dimension specs
${chalk.gray('# Check system status:')}
ccw spec status

View File

@@ -1,10 +1,10 @@
/**
* Spec Index Builder
*
* Scans .workflow/{dimension}/*.md files, parses YAML frontmatter via
* gray-matter, and writes .spec-index/{dimension}.index.json cache files.
* Scans .ccw/{dimension}/*.md files, parses YAML frontmatter via
* 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:
* ---
@@ -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];
@@ -100,9 +102,9 @@ const VALID_READ_MODES = ['required', 'optional'] 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';
// ============================================================================
@@ -114,27 +116,27 @@ const SPEC_INDEX_DIR = '.spec-index';
*
* @param projectPath - Project root directory
* @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 {
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 dimension - The dimension name
* @returns Absolute path to .workflow/{dimension}/
* @returns Absolute path to .ccw/{dimension}/
*/
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.
*
* 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.
*
* 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.
*
* @param projectPath - Project root directory
*/
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
if (!existsSync(indexDir)) {
@@ -283,7 +285,7 @@ export async function getDimensionIndex(
// Build fresh and cache
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)) {
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/,
* .workflow/personal/, and .workflow/.spec-index/ directories with
* seed MD documents containing YAML frontmatter templates.
* Creates .ccw/specs/, .ccw/personal/,
* and .ccw/.spec-index/ directories with seed MD documents
* containing YAML frontmatter templates.
*
* Idempotent: skips existing files, only creates missing directories/files.
*/
@@ -39,7 +39,7 @@ export interface InitResult {
// Constants
// ---------------------------------------------------------------------------
export const DIMENSIONS = ['specs', 'roadmap', 'changelog', 'personal'] as const;
export const DIMENSIONS = ['specs', 'personal'] as const;
export const INDEX_DIR = '.spec-index';
// ---------------------------------------------------------------------------
@@ -55,7 +55,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
frontmatter: {
title: 'Coding Conventions',
dimension: 'specs',
keywords: ['typescript', 'naming', 'style', 'convention'],
keywords: ['typescript', 'naming', 'style', 'convention', 'exploration', 'planning', 'execution'],
readMode: 'required',
priority: 'high',
},
@@ -91,7 +91,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
frontmatter: {
title: 'Architecture Constraints',
dimension: 'specs',
keywords: ['architecture', 'module', 'layer', 'pattern'],
keywords: ['architecture', 'module', 'layer', 'pattern', 'exploration', 'planning'],
readMode: 'required',
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
*/
export function initSpecSystem(projectPath: string): InitResult {
const workflowDir = join(projectPath, '.workflow');
const ccwDir = join(projectPath, '.ccw');
const result: InitResult = {
created: [],
skipped: [],
directories: [],
};
// Ensure .workflow root exists
if (!existsSync(workflowDir)) {
mkdirSync(workflowDir, { recursive: true });
// Ensure .ccw root exists
if (!existsSync(ccwDir)) {
mkdirSync(ccwDir, { recursive: true });
}
// Create dimension directories
for (const dim of DIMENSIONS) {
const dirPath = join(workflowDir, dim);
const dirPath = join(ccwDir, dim);
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
result.directories.push(dirPath);
}
}
// Create index directory at project root (matches spec-index-builder.ts location)
const indexPath = join(projectPath, INDEX_DIR);
// Create index directory inside .ccw (matches spec-index-builder.ts location)
const indexPath = join(ccwDir, INDEX_DIR);
if (!existsSync(indexPath)) {
mkdirSync(indexPath, { recursive: true });
result.directories.push(indexPath);
@@ -276,7 +242,7 @@ export function initSpecSystem(projectPath: string): InitResult {
// Write seed documents per dimension
for (const [dimension, docs] of SEED_DOCS) {
const dimDir = join(workflowDir, dimension);
const dimDir = join(ccwDir, dimension);
for (const doc of docs) {
const filePath = join(dimDir, doc.filename);

View File

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