@superspec/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cli/index.js +1329 -0
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/index.d.ts +52 -0
  4. package/dist/index.js +607 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +63 -0
  7. package/prompts/agents.md +150 -0
  8. package/prompts/cursor-rules.md +154 -0
  9. package/templates/en/checklist.md +62 -0
  10. package/templates/en/clarify.md +44 -0
  11. package/templates/en/commands/ss-apply.md +19 -0
  12. package/templates/en/commands/ss-archive.md +15 -0
  13. package/templates/en/commands/ss-checklist.md +16 -0
  14. package/templates/en/commands/ss-clarify.md +17 -0
  15. package/templates/en/commands/ss-create.md +19 -0
  16. package/templates/en/commands/ss-deps.md +11 -0
  17. package/templates/en/commands/ss-link.md +11 -0
  18. package/templates/en/commands/ss-lint.md +15 -0
  19. package/templates/en/commands/ss-resume.md +17 -0
  20. package/templates/en/commands/ss-search.md +12 -0
  21. package/templates/en/commands/ss-status.md +12 -0
  22. package/templates/en/commands/ss-tasks.md +19 -0
  23. package/templates/en/commands/ss-validate.md +15 -0
  24. package/templates/en/proposal.md +53 -0
  25. package/templates/en/spec.md +73 -0
  26. package/templates/en/tasks.md +52 -0
  27. package/templates/zh/checklist.md +62 -0
  28. package/templates/zh/clarify.md +44 -0
  29. package/templates/zh/commands/ss-apply.md +19 -0
  30. package/templates/zh/commands/ss-archive.md +15 -0
  31. package/templates/zh/commands/ss-checklist.md +16 -0
  32. package/templates/zh/commands/ss-clarify.md +17 -0
  33. package/templates/zh/commands/ss-create.md +19 -0
  34. package/templates/zh/commands/ss-deps.md +11 -0
  35. package/templates/zh/commands/ss-link.md +11 -0
  36. package/templates/zh/commands/ss-lint.md +15 -0
  37. package/templates/zh/commands/ss-resume.md +17 -0
  38. package/templates/zh/commands/ss-search.md +12 -0
  39. package/templates/zh/commands/ss-status.md +12 -0
  40. package/templates/zh/commands/ss-tasks.md +19 -0
  41. package/templates/zh/commands/ss-validate.md +15 -0
  42. package/templates/zh/proposal.md +59 -0
  43. package/templates/zh/spec.md +73 -0
  44. package/templates/zh/tasks.md +52 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/config.ts","../src/core/template.ts","../src/utils/paths.ts","../src/utils/fs.ts","../src/core/lint.ts","../src/core/validate.ts","../src/core/context.ts","../src/utils/date.ts","../src/utils/git.ts","../src/commands/init.ts","../src/prompts/index.ts","../src/ui/index.ts","../src/commands/create.ts","../src/commands/archive.ts","../src/commands/update.ts","../src/commands/lint.ts","../src/commands/validate.ts","../src/commands/search.ts","../src/commands/link.ts","../src/commands/status.ts","../src/commands/context.ts","../src/commands/sync.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Strategy = 'follow' | 'create';\nexport type AIEditorType = 'claude' | 'cursor' | 'qwen' | 'opencode' | 'codex' | 'codebuddy' | 'qoder';\n\nexport interface SuperSpecConfig {\n lang: 'zh' | 'en';\n aiEditor: AIEditorType;\n specDir: string;\n branchPrefix: string;\n branchTemplate: string;\n boost: boolean;\n strategy: Strategy;\n context: string[];\n templates: Record<string, string>;\n archive: {\n dir: string;\n datePrefix: boolean;\n };\n limits: {\n targetLines: number;\n hardLines: number;\n };\n artifacts: string[];\n boostArtifacts: string[];\n}\n\nconst DEFAULT_CONFIG: SuperSpecConfig = {\n lang: 'zh',\n aiEditor: 'cursor',\n specDir: 'superspec',\n branchPrefix: 'spec/',\n branchTemplate: '{prefix}{name}',\n boost: false,\n strategy: 'follow',\n context: [],\n templates: {\n spec: 'spec.md',\n proposal: 'proposal.md',\n tasks: 'tasks.md',\n clarify: 'clarify.md',\n checklist: 'checklist.md',\n },\n archive: {\n dir: 'archive',\n datePrefix: true,\n },\n limits: {\n targetLines: 300,\n hardLines: 400,\n },\n artifacts: ['proposal'],\n boostArtifacts: ['proposal', 'spec', 'tasks', 'checklist'],\n};\n\nexport function loadConfig(projectRoot: string = process.cwd()): SuperSpecConfig {\n const configPath = join(projectRoot, 'superspec.config.json');\n let userConfig: Partial<SuperSpecConfig> = {};\n\n if (existsSync(configPath)) {\n try {\n userConfig = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch (e: any) {\n console.warn(`⚠ 配置文件解析失败: ${e.message}`);\n }\n }\n\n return deepMerge(DEFAULT_CONFIG, userConfig) as SuperSpecConfig;\n}\n\nexport function getDefaultConfig(): SuperSpecConfig {\n return structuredClone(DEFAULT_CONFIG);\n}\n\nfunction deepMerge(target: Record<string, any>, source: Record<string, any>): Record<string, any> {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === 'object' &&\n !Array.isArray(source[key]) &&\n target[key] &&\n typeof target[key] === 'object'\n ) {\n result[key] = deepMerge(target[key], source[key]);\n } else {\n result[key] = source[key];\n }\n }\n return result;\n}\n","import { existsSync, copyFileSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { getPackageRoot } from '../utils/paths.js';\nimport { ensureDir } from '../utils/fs.js';\n\nexport function resolveTemplatePath(templateName: string, lang: string = 'zh'): string {\n const root = getPackageRoot();\n const langPath = join(root, 'templates', lang, templateName);\n if (existsSync(langPath)) return langPath;\n const fallback = join(root, 'templates', 'zh', templateName);\n if (existsSync(fallback)) return fallback;\n throw new Error(`Template not found: ${templateName} (lang: ${lang})`);\n}\n\nexport function copyTemplate(templateName: string, destPath: string, lang: string = 'zh'): void {\n const srcPath = resolveTemplatePath(templateName, lang);\n ensureDir(dirname(destPath));\n copyFileSync(srcPath, destPath);\n}\n\n/**\n * Simple template engine supporting:\n * - {{variable}} - variable substitution\n * - {{#if variable}}...{{/if}} - conditional blocks (shown if variable is truthy)\n */\nexport function renderTemplate(templateName: string, vars: Record<string, string> = {}, lang: string = 'zh'): string {\n const srcPath = resolveTemplatePath(templateName, lang);\n let content = readFileSync(srcPath, 'utf-8');\n\n // Process conditionals first: {{#if variable}}...{{/if}}\n content = processConditionals(content, vars);\n\n // Process simple variable substitution: {{variable}}\n for (const [key, value] of Object.entries(vars)) {\n content = content.replaceAll(`{{${key}}}`, value);\n }\n\n return content;\n}\n\nfunction processConditionals(content: string, vars: Record<string, string>): string {\n // Match {{#if variable}}...{{/if}} patterns\n const ifRegex = /\\{\\{#if\\s+(\\w+)\\}\\}([\\s\\S]*?)\\{\\{\\/if\\}\\}/g;\n\n return content.replace(ifRegex, (match, varName, innerContent) => {\n const value = vars[varName];\n // Show content if variable exists and is not empty\n if (value && value.trim() !== '') {\n return innerContent;\n }\n return '';\n });\n}\n\nexport function writeRenderedTemplate(\n templateName: string,\n destPath: string,\n vars: Record<string, string> = {},\n lang: string = 'zh',\n): void {\n const content = renderTemplate(templateName, vars, lang);\n ensureDir(dirname(destPath));\n writeFileSync(destPath, content, 'utf-8');\n}\n","import { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport function getPackageRoot(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, '..', '..');\n}\n","import { mkdirSync, existsSync } from 'node:fs';\n\nexport function ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n","import { readFileSync, existsSync, readdirSync } from 'node:fs';\nimport { join, basename } from 'node:path';\n\nexport interface LintResult {\n artifact: string;\n lines: number;\n status: 'ok' | 'warn' | 'error';\n message: string;\n}\n\nexport function lintArtifact(filePath: string, targetLines: number, hardLines: number): LintResult {\n const artifact = basename(filePath);\n if (!existsSync(filePath)) {\n return { artifact, lines: 0, status: 'ok', message: 'not found' };\n }\n\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').length;\n\n if (lines > hardLines) {\n return { artifact, lines, status: 'error', message: `${lines} lines exceeds hard limit (${hardLines}). Must split.` };\n }\n if (lines > targetLines) {\n return { artifact, lines, status: 'warn', message: `${lines} lines exceeds target (${targetLines}). Consider splitting.` };\n }\n return { artifact, lines, status: 'ok', message: `${lines} lines` };\n}\n\nexport function lintChange(changePath: string, targetLines: number, hardLines: number): LintResult[] {\n if (!existsSync(changePath)) return [];\n\n const files = readdirSync(changePath).filter((f) => f.endsWith('.md'));\n return files.map((f) => lintArtifact(join(changePath, f), targetLines, hardLines));\n}\n","import { readFileSync, existsSync, readdirSync } from 'node:fs';\nimport { join, basename } from 'node:path';\nimport { parseFrontmatter } from './frontmatter.js';\n\nexport interface ValidationIssue {\n level: 'error' | 'warn' | 'info';\n artifact: string;\n message: string;\n}\n\nfunction extractIds(content: string, pattern: RegExp): string[] {\n const matches = content.match(pattern);\n return matches ? [...new Set(matches)] : [];\n}\n\nexport function validateChange(changePath: string, checkDeps = false): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n const read = (name: string): string | null => {\n const p = join(changePath, name);\n return existsSync(p) ? readFileSync(p, 'utf-8') : null;\n };\n\n const proposal = read('proposal.md');\n const spec = read('spec.md');\n const tasks = read('tasks.md');\n\n if (!proposal) issues.push({ level: 'warn', artifact: 'proposal.md', message: 'missing' });\n if (!tasks) issues.push({ level: 'warn', artifact: 'tasks.md', message: 'missing' });\n\n if (proposal && spec) {\n const proposalGoals = extractIds(proposal, /US-\\d+/g);\n const specUS = extractIds(spec, /US-\\d+/g);\n for (const id of proposalGoals) {\n if (!specUS.includes(id)) {\n issues.push({ level: 'error', artifact: 'spec.md', message: `${id} from proposal not found in spec` });\n }\n }\n }\n\n if (spec && tasks) {\n const specFR = extractIds(spec, /FR-\\d+/g);\n const tasksFR = extractIds(tasks, /FR-\\d+/g);\n\n for (const id of specFR) {\n if (!tasksFR.includes(id)) {\n issues.push({ level: 'warn', artifact: 'tasks.md', message: `${id} from spec not referenced in tasks` });\n }\n }\n\n for (const id of tasksFR) {\n if (!specFR.includes(id)) {\n issues.push({ level: 'error', artifact: 'tasks.md', message: `${id} referenced but not defined in spec` });\n }\n }\n }\n\n if (spec) {\n const acIds = extractIds(spec, /AC-\\d+\\.\\d+/g);\n const frIds = extractIds(spec, /FR-\\d+/g);\n for (const ac of acIds) {\n const frNum = ac.replace('AC-', '').split('.')[0];\n const parentFR = `FR-${frNum}`;\n if (!frIds.includes(parentFR)) {\n issues.push({ level: 'warn', artifact: 'spec.md', message: `${ac} has no parent ${parentFR}` });\n }\n }\n }\n\n if (checkDeps && proposal) {\n const { meta, body } = parseFrontmatter(proposal);\n const fmDeps: string[] = Array.isArray(meta.depends_on) ? meta.depends_on : [];\n\n const contentRefs = extractIds(body, /depends[_ ]on[:\\s]+(\\S+)/gi);\n const mentioned = contentRefs.map((m) => m.replace(/depends[_ ]on[:\\s]+/i, ''));\n\n for (const dep of mentioned) {\n if (!fmDeps.includes(dep)) {\n issues.push({ level: 'warn', artifact: 'proposal.md', message: `content mentions \"${dep}\" but not in frontmatter depends_on` });\n }\n }\n\n const changesDir = join(changePath, '..');\n for (const dep of fmDeps) {\n if (!existsSync(join(changesDir, dep))) {\n issues.push({ level: 'error', artifact: 'proposal.md', message: `depends_on \"${dep}\" not found in changes` });\n }\n }\n }\n\n return issues;\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { parseFrontmatter, serializeFrontmatter } from './frontmatter.js';\nimport { getDateString } from '../utils/date.js';\nimport { getDiffFiles, type GitChange } from '../utils/git.js';\n\nexport interface ContextData {\n name: string;\n status: string;\n strategy: string;\n mode: string;\n updated: string;\n goals: string[];\n progress: { total: number; done: number; items: string[] };\n decisions: string[];\n files: string[];\n}\n\nfunction extractSection(body: string, heading: string): string[] {\n const regex = new RegExp(`^##\\\\s+${heading}[\\\\s\\\\S]*?$`, 'im');\n const match = body.match(regex);\n if (!match) return [];\n\n const startIdx = body.indexOf(match[0]) + match[0].length;\n const rest = body.slice(startIdx);\n const nextSection = rest.match(/^##\\s+/m);\n const sectionContent = nextSection ? rest.slice(0, nextSection.index) : rest;\n\n return sectionContent\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.length > 0 && !l.startsWith('<!--'));\n}\n\nfunction parseTaskItems(body: string): { total: number; done: number; items: string[] } {\n const lines = body.split('\\n');\n const items: string[] = [];\n let done = 0;\n let total = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n const checked = trimmed.match(/^-\\s*\\[x\\]\\s+(.*)/i);\n const unchecked = trimmed.match(/^-\\s*\\[\\s\\]\\s+(.*)/);\n\n if (checked) {\n total++;\n done++;\n const desc = checked[1].trim();\n items.push(`- [x] ${desc}`);\n } else if (unchecked) {\n total++;\n const desc = unchecked[1].trim();\n items.push(`- [ ] ${desc}`);\n }\n }\n\n return { total, done, items };\n}\n\nfunction extractFilePaths(body: string): string[] {\n const paths = new Set<string>();\n const regex = /`([^`]+\\.[a-zA-Z]+)`/g;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(body)) !== null) {\n const p = match[1];\n if (p.includes('/') && !p.startsWith('http') && !p.includes(' ')) {\n paths.add(p);\n }\n }\n return [...paths];\n}\n\nfunction extractDecisions(body: string): string[] {\n const lines = body.split('\\n');\n const decisions: string[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (/^\\|?\\s*D\\d+\\s*\\|/.test(trimmed)) {\n const cells = trimmed.split('|').map((c) => c.trim()).filter(Boolean);\n if (cells.length >= 2) {\n decisions.push(`- ${cells[0]}: ${cells[1]}`);\n }\n }\n }\n return decisions;\n}\n\nexport interface GenerateContextOptions {\n gitDiff?: boolean;\n baseBranch?: string;\n}\n\nconst STATUS_LABELS: Record<string, string> = {\n A: 'added',\n M: 'modified',\n D: 'deleted',\n R: 'renamed',\n};\n\nfunction classifyGitChanges(gitChanges: GitChange[], taskFiles: string[]): string[] {\n const taskFileSet = new Set(taskFiles);\n const lines: string[] = [];\n for (const { status, file } of gitChanges) {\n const label = STATUS_LABELS[status] || status;\n const inTasks = taskFileSet.has(file) || taskFiles.some((tf) => file.endsWith(tf) || tf.endsWith(file));\n const tag = inTasks ? '' : ' (unplanned)';\n lines.push(`- ${label}: ${file}${tag}`);\n }\n return lines;\n}\n\nexport function generateContext(changePath: string, changeName: string, options: GenerateContextOptions = {}): string {\n const read = (name: string): string | null => {\n const p = join(changePath, name);\n return existsSync(p) ? readFileSync(p, 'utf-8') : null;\n };\n\n const proposal = read('proposal.md');\n const spec = read('spec.md');\n const tasks = read('tasks.md');\n const clarify = read('clarify.md');\n\n let strategy = 'follow';\n let status = 'in-progress';\n const mode = spec ? 'boost' : 'standard';\n\n if (proposal) {\n const { meta } = parseFrontmatter(proposal);\n if (meta.strategy) strategy = meta.strategy;\n if (meta.status) status = meta.status;\n }\n\n const goals: string[] = [];\n if (proposal) {\n const { body } = parseFrontmatter(proposal);\n const goalLines = extractSection(body, '目标|Goals');\n for (const line of goalLines) {\n const cleaned = line.replace(/^-\\s*\\[.\\]\\s*/, '- ').replace(/^-\\s*/, '');\n if (cleaned) goals.push(`- ${cleaned}`);\n }\n }\n\n let progress = { total: 0, done: 0, items: [] as string[] };\n let files: string[] = [];\n if (tasks) {\n const { body } = parseFrontmatter(tasks);\n progress = parseTaskItems(body);\n files = extractFilePaths(body);\n }\n\n const decisions: string[] = [];\n if (clarify) {\n const { body } = parseFrontmatter(clarify);\n decisions.push(...extractDecisions(body));\n }\n\n const existingContext = read('context.md');\n let notes = '';\n if (existingContext) {\n const { body } = parseFrontmatter(existingContext);\n const notesSection = extractSection(body, 'Notes');\n if (notesSection.length > 0) {\n notes = notesSection.join('\\n');\n }\n }\n\n const fm = serializeFrontmatter({\n name: changeName,\n status,\n strategy,\n mode,\n updated: getDateString(),\n });\n\n const lines: string[] = [fm, ''];\n\n if (goals.length > 0) {\n lines.push('## Goals', ...goals, '');\n }\n\n if (progress.total > 0) {\n lines.push(`## Progress (${progress.done}/${progress.total} tasks)`);\n lines.push(...progress.items, '');\n }\n\n if (decisions.length > 0) {\n lines.push('## Decisions', ...decisions, '');\n }\n\n if (files.length > 0) {\n lines.push('## Affected Files');\n for (const f of files) {\n lines.push(`- ${f}`);\n }\n lines.push('');\n }\n\n if (options.gitDiff !== false) {\n const gitChanges = getDiffFiles(options.baseBranch);\n if (gitChanges.length > 0) {\n const classified = classifyGitChanges(gitChanges, files);\n lines.push('## Git Changes', ...classified, '');\n }\n }\n\n lines.push('## Notes');\n if (notes) {\n lines.push(notes);\n }\n lines.push('');\n\n return lines.join('\\n');\n}\n","export function getDateString(): string {\n const d = new Date();\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n}\n","import { execSync } from 'node:child_process';\n\nexport function isGitRepo(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getCurrentBranch(): string | null {\n try {\n return execSync('git branch --show-current', { encoding: 'utf-8' }).trim();\n } catch {\n return null;\n }\n}\n\nconst SAFE_BRANCH_RE = /^[a-zA-Z0-9._\\-/]+$/;\n\nexport function createBranch(branchName: string): void {\n if (!SAFE_BRANCH_RE.test(branchName)) {\n throw new Error(`invalid branch name: ${branchName}`);\n }\n execSync(`git checkout -b ${branchName}`, { stdio: 'inherit' });\n}\n\nexport function getDefaultBranch(): string {\n try {\n const ref = execSync('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf-8' }).trim();\n return ref.replace('refs/remotes/origin/', '');\n } catch {\n try {\n execSync('git rev-parse --verify main', { stdio: 'ignore' });\n return 'main';\n } catch {\n return 'master';\n }\n }\n}\n\nexport interface GitChange {\n status: string;\n file: string;\n}\n\nexport function getDiffFiles(base?: string): GitChange[] {\n const baseBranch = base || getDefaultBranch();\n try {\n const mergeBase = execSync(`git merge-base ${baseBranch} HEAD`, { encoding: 'utf-8' }).trim();\n const output = execSync(`git diff --name-status ${mergeBase}`, { encoding: 'utf-8' }).trim();\n if (!output) return [];\n return output.split('\\n').map((line) => {\n const [status, ...parts] = line.split('\\t');\n return { status: status.charAt(0), file: parts.join('\\t') };\n });\n } catch {\n return [];\n }\n}\n","import { existsSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { getDefaultConfig } from '../core/config.js';\nimport { copyTemplate } from '../core/template.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { isGitRepo } from '../utils/git.js';\nimport { installRules, installAgentsMd, installCommands, AI_EDITORS, type AIEditor } from '../prompts/index.js';\nimport { log, symbol, printLogo, printSummary, theme } from '../ui/index.js';\n\nexport interface InitOptions {\n ai: string;\n lang: string;\n force?: boolean;\n git?: boolean;\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n const cwd = process.cwd();\n const configPath = join(cwd, 'superspec.config.json');\n\n if (existsSync(configPath) && !options.force) {\n log.warn(`${symbol.warn} superspec.config.json already exists, use --force to overwrite`);\n return;\n }\n\n const lang = options.lang || 'zh';\n\n // Print logo\n printLogo('small');\n console.log(theme.dim(' Spec-Driven Development Toolkit\\n'));\n\n const config = getDefaultConfig();\n config.lang = lang as 'zh' | 'en';\n\n // Set AI editor in config\n const aiEditor = options.ai as AIEditor;\n if (aiEditor && AI_EDITORS[aiEditor]) {\n config.aiEditor = aiEditor;\n }\n\n const specDir = join(cwd, config.specDir);\n\n // Check if directory is not empty\n const existingFiles = readdirSync(cwd).filter(f => !f.startsWith('.') && f !== 'node_modules');\n if (existingFiles.length > 0 && !options.force) {\n log.warn(`${symbol.warn} Current directory is not empty (${existingFiles.length} items)`);\n log.dim(' Template files will be merged with existing content');\n console.log();\n }\n\n log.section('Creating Configuration');\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n log.success(`${symbol.file} superspec.config.json`);\n\n log.section('Creating Directory Structure');\n ensureDir(join(specDir, 'changes'));\n ensureDir(join(specDir, 'templates'));\n log.success(`${symbol.folder} ${config.specDir}/changes/`);\n log.success(`${symbol.folder} ${config.specDir}/templates/`);\n\n log.section('Installing Templates');\n const templates = ['spec.md', 'proposal.md', 'tasks.md', 'clarify.md', 'checklist.md'];\n for (const tpl of templates) {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n }\n log.success(`${symbol.ok} ${templates.length} templates (${lang})`);\n\n log.section('Installing AI Agent Files');\n installAgentsMd(cwd);\n\n // Install rules and commands for the selected AI editor\n if (aiEditor && AI_EDITORS[aiEditor]) {\n installRules(cwd, aiEditor);\n installCommands(cwd, aiEditor, lang);\n }\n\n if (options.git !== false && !isGitRepo()) {\n execSync('git init', { cwd, stdio: 'inherit' });\n log.success(`${symbol.git} git init`);\n }\n\n // Print summary\n console.log();\n printSummary([\n { label: 'Config', value: 'superspec.config.json' },\n { label: 'Spec dir', value: `${config.specDir}/` },\n { label: 'AI agent', value: options.ai },\n { label: 'Language', value: lang },\n ]);\n\n log.done('SuperSpec initialized successfully!');\n log.dim('Next: Run superspec create <name> to create a change');\n}\n","import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getPackageRoot } from '../utils/paths.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { log, symbol } from '../ui/index.js';\n\n// Supported AI editors configuration\nexport const AI_EDITORS = {\n claude: {\n commands: '.claude/commands',\n rules: null, // Claude doesn't use rules files\n },\n cursor: {\n commands: '.cursor/commands',\n rules: '.cursor/rules',\n rulesFile: 'superspec.mdc',\n },\n qwen: {\n commands: '.qwen/commands',\n rules: '.qwen/rules',\n rulesFile: 'superspec.md',\n },\n opencode: {\n commands: '.opencode/commands',\n rules: null,\n },\n codex: {\n commands: '.codex/commands',\n rules: null,\n },\n codebuddy: {\n commands: '.codebuddy/commands',\n rules: '.codebuddy/rules',\n rulesFile: 'superspec.md',\n },\n qoder: {\n commands: '.qoder/commands',\n rules: '.qoder/rules',\n rulesFile: 'superspec.md',\n },\n} as const;\n\nexport type AIEditor = keyof typeof AI_EDITORS;\n\n/**\n * Install rules file for a specific AI editor\n */\nexport function installRules(cwd: string, editor: AIEditor): void {\n const config = AI_EDITORS[editor];\n\n // Skip if this editor doesn't use rules files\n if (!config.rules) {\n return;\n }\n\n const rulesDir = join(cwd, config.rules);\n ensureDir(rulesDir);\n\n const promptSrc = join(getPackageRoot(), 'prompts', 'cursor-rules.md');\n if (existsSync(promptSrc)) {\n const content = readFileSync(promptSrc, 'utf-8');\n const rulesFile = 'rulesFile' in config ? config.rulesFile : 'superspec.md';\n const destPath = join(rulesDir, rulesFile as string);\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} ${config.rules}/${rulesFile}`);\n }\n}\n\nconst SS_START = '<!-- superspec:start -->';\nconst SS_END = '<!-- superspec:end -->';\n\nexport function installAgentsMd(cwd: string): void {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n const agentPromptSrc = join(getPackageRoot(), 'prompts', 'agents.md');\n\n if (!existsSync(agentPromptSrc)) return;\n\n const newContent = readFileSync(agentPromptSrc, 'utf-8');\n const wrapped = `${SS_START}\\n${newContent}\\n${SS_END}`;\n\n if (existsSync(agentsMdPath)) {\n const existing = readFileSync(agentsMdPath, 'utf-8');\n const startIdx = existing.indexOf(SS_START);\n const endIdx = existing.indexOf(SS_END);\n\n if (startIdx !== -1 && endIdx !== -1) {\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + SS_END.length);\n writeFileSync(agentsMdPath, before + wrapped + after, 'utf-8');\n } else if (existing.includes('SuperSpec')) {\n writeFileSync(agentsMdPath, existing, 'utf-8');\n } else {\n writeFileSync(agentsMdPath, existing + '\\n\\n' + wrapped, 'utf-8');\n }\n } else {\n writeFileSync(agentsMdPath, wrapped, 'utf-8');\n }\n log.success(`${symbol.ok} AGENTS.md`);\n}\n\n/**\n * Install commands for a specific AI editor\n */\nexport function installCommands(cwd: string, editor: AIEditor, lang: string = 'zh'): void {\n const config = AI_EDITORS[editor];\n const commandsDir = join(cwd, config.commands);\n ensureDir(commandsDir);\n\n // Copy command templates from templates/{lang}/commands/\n const templatesDir = join(getPackageRoot(), 'templates', lang, 'commands');\n const fallbackDir = join(getPackageRoot(), 'templates', 'zh', 'commands');\n const sourceDir = existsSync(templatesDir) ? templatesDir : fallbackDir;\n\n if (!existsSync(sourceDir)) {\n log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);\n return;\n }\n\n const commandFiles = readdirSync(sourceDir).filter(f => f.endsWith('.md'));\n\n for (const file of commandFiles) {\n const srcPath = join(sourceDir, file);\n const destPath = join(commandsDir, file);\n const content = readFileSync(srcPath, 'utf-8');\n writeFileSync(destPath, content, 'utf-8');\n }\n\n log.success(`${symbol.ok} ${config.commands}/ (${commandFiles.length} commands)`);\n}\n\n/**\n * Install commands for all supported AI editors\n */\nexport function installAllCommands(cwd: string, lang: string = 'zh'): void {\n for (const editor of Object.keys(AI_EDITORS) as AIEditor[]) {\n installCommands(cwd, editor, lang);\n }\n}\n","import chalk from 'chalk';\n\nexport const theme = {\n primary: chalk.hex('#6366f1'),\n success: chalk.hex('#22c55e'),\n warning: chalk.hex('#f59e0b'),\n error: chalk.hex('#ef4444'),\n info: chalk.hex('#3b82f6'),\n boost: chalk.hex('#a855f7'),\n dim: chalk.hex('#6b7280'),\n highlight: chalk.hex('#f472b6'),\n border: chalk.hex('#374151'),\n gradient1: chalk.hex('#818cf8'),\n gradient2: chalk.hex('#6366f1'),\n gradient3: chalk.hex('#4f46e5'),\n};\n\n// ASCII Art Logo for SuperSpec (S-U-P-E-R-S-P-E-C)\nexport const logo = {\n small: `\n${theme.gradient1(' ███████╗██╗ ██╗██████╗ ███████╗██████╗ ███████╗██████╗ ███████╗ ██████╗')}\n${theme.gradient2(' ██╔════╝██║ ██║██╔══██╗██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝')}\n${theme.gradient3(' ███████╗██║ ██║██████╔╝█████╗ ██████╔╝███████╗██████╔╝█████╗ ██║ ')}\n${theme.gradient2(' ╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗╚════██║██╔═══╝ ██╔══╝ ██║ ')}\n${theme.gradient1(' ███████║╚██████╔╝██║ ███████╗██║ ██║███████║██║ ███████╗╚██████╗')}\n${theme.gradient1(' ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝')}\n `,\n tiny: `\n${theme.gradient1(' ███████╗██╗ ██╗██████╗ ███████╗██████╗ ███████╗██████╗ ███████╗ ██████╗')}\n${theme.gradient2(' ╚═════╗██║ ██║██╔══██║██╔════╝██╔══██╗╚═════╗██╔══██║██╔════╝██╔════╝')}\n${theme.gradient3(' ███████║██║ ██║██████╔╝█████╗ ██████╔╝███████║██████╔╝█████╗ ██║ ')} ${theme.highlight('Spec-Driven Development')}\n${theme.gradient2(' ╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗╚════██║██╔═══╝ ██╔══╝ ██║ ')}\n${theme.gradient1(' ███████║╚██████╔╝██║ ███████╗██║ ██║███████║██║ ███████╗╚██████╗')}\n${theme.gradient1(' ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝')}\n `,\n};\n\nconst box = {\n topLeft: '╭',\n topRight: '╮',\n bottomLeft: '╰',\n bottomRight: '╯',\n horizontal: '─',\n vertical: '│',\n};\n\nfunction boxText(text: string, width: number = 50): string {\n const padding = ' '.repeat(Math.max(0, width - text.length - 4));\n return `${theme.border(box.vertical)} ${theme.highlight(text)}${padding} ${theme.border(box.vertical)}`;\n}\n\nfunction createBox(lines: string[], width: number = 52): string {\n const top = theme.border(`${box.topLeft}${box.horizontal.repeat(width - 2)}${box.topRight}`);\n const bottom = theme.border(`${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`);\n const middle = lines.map(line => boxText(line, width));\n return [top, ...middle, bottom].join('\\n');\n}\n\nexport const log = {\n info: (msg: string) => console.log(theme.info(msg)),\n success: (msg: string) => console.log(theme.success(msg)),\n warn: (msg: string) => console.log(theme.warning(msg)),\n error: (msg: string) => console.log(theme.error(msg)),\n dim: (msg: string) => console.log(theme.dim(msg)),\n boost: (msg: string) => console.log(theme.boost(msg)),\n highlight: (msg: string) => console.log(theme.highlight(msg)),\n title: (msg: string) => {\n console.log();\n console.log(createBox([msg]));\n console.log();\n },\n section: (msg: string) => {\n console.log();\n console.log(theme.primary(`◆ ${msg}`));\n console.log(theme.border('─'.repeat(50)));\n },\n done: (msg: string) => {\n console.log();\n console.log(theme.success(`✨ ${msg}`));\n console.log();\n },\n};\n\nexport const symbol = {\n start: theme.primary('◆'),\n ok: theme.success('✓'),\n fail: theme.error('✗'),\n warn: theme.warning('⚠'),\n bolt: theme.boost('⚡'),\n arrow: theme.dim('→'),\n bullet: theme.dim('•'),\n sparkle: theme.highlight('✨'),\n folder: theme.primary('📁'),\n file: theme.info('📄'),\n git: theme.warning('🌿'),\n ai: theme.boost('🤖'),\n} as const;\n\n// Helper to print the logo\nexport function printLogo(size: 'small' | 'tiny' = 'small'): void {\n console.log(logo[size]);\n}\n\n// Helper to print a summary box\nexport function printSummary(items: { label: string; value: string }[]): void {\n const maxLabel = Math.max(...items.map(i => i.label.length));\n const width = 50;\n\n console.log(theme.border('╭' + '─'.repeat(width - 2) + '╮'));\n for (const { label, value } of items) {\n const padding = ' '.repeat(maxLabel - label.length);\n const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;\n // Strip ANSI codes for length calculation\n const plainLine = line.replace(/\\u001b\\[\\d+m/g, '');\n const rightPad = ' '.repeat(Math.max(0, width - plainLine.length - 4));\n console.log(theme.border('│ ') + line + rightPad + theme.border(' │'));\n }\n console.log(theme.border('╰' + '─'.repeat(width - 2) + '╯'));\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { writeRenderedTemplate } from '../core/template.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { isGitRepo, createBranch } from '../utils/git.js';\nimport { getDateString } from '../utils/date.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface CreateOptions {\n boost?: boolean;\n creative?: boolean;\n branch?: boolean;\n specDir?: string;\n branchPrefix?: string;\n description?: string;\n}\n\nexport async function createCommand(name: string, options: CreateOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n\n const specDir = options.specDir || config.specDir;\n const branchPrefix = options.branchPrefix || config.branchPrefix;\n const boost = options.boost || config.boost;\n const strategy = options.creative ? 'create' : config.strategy;\n const lang = config.lang || 'zh';\n const description = options.description || '';\n\n const changePath = join(cwd, specDir, 'changes', name);\n\n if (existsSync(changePath)) {\n log.warn(`${symbol.warn} Change \"${name}\" already exists: ${changePath}`);\n return;\n }\n\n log.title(`Creating Change: ${name}`);\n\n if (boost) {\n log.boost(`${symbol.bolt} Boost mode enabled`);\n }\n if (strategy === 'create') {\n log.boost(`${symbol.bolt} Creative mode: exploring new solutions`);\n }\n\n ensureDir(changePath);\n\n const vars: Record<string, string> = {\n name,\n date: getDateString(),\n boost: boost ? 'true' : 'false',\n strategy,\n description,\n };\n\n const artifacts = boost ? config.boostArtifacts : config.artifacts;\n\n log.section('Generating Artifacts');\n for (const artifact of artifacts) {\n const templateFile = config.templates[artifact] || `${artifact}.md`;\n const destPath = join(changePath, `${artifact}.md`);\n try {\n writeRenderedTemplate(templateFile, destPath, vars, lang);\n log.success(`${symbol.ok} ${artifact}.md`);\n } catch (e: any) {\n log.error(`${symbol.fail} ${artifact}.md: ${e.message}`);\n }\n }\n\n if (options.branch !== false && isGitRepo()) {\n const branchTemplate = config.branchTemplate || '{prefix}{name}';\n const branchName = branchTemplate.replace('{prefix}', branchPrefix).replace('{name}', name);\n try {\n createBranch(branchName);\n log.success(`${symbol.ok} Branch: ${branchName}`);\n } catch (e: any) {\n log.warn(`${symbol.warn} Branch creation failed: ${e.message}`);\n }\n }\n\n log.done('Change created successfully!');\n log.dim(`Path: ${specDir}/changes/${name}/`);\n\n if (boost) {\n log.dim(`Workflow: /ss-create → /ss-tasks → /ss-apply (boost)`);\n } else {\n log.dim(`Workflow: /ss-tasks → /ss-apply`);\n }\n}\n","import { existsSync, readdirSync, renameSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig, type SuperSpecConfig } from '../core/config.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { getDateString } from '../utils/date.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface ArchiveOptions {\n all?: boolean;\n}\n\nexport async function archiveCommand(name: string | undefined, options: ArchiveOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n const archiveDir = join(cwd, config.specDir, 'changes', config.archive.dir);\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} 没有找到 changes 目录`);\n return;\n }\n\n if (options.all) {\n const entries = readdirSync(changesDir, { withFileTypes: true }).filter(\n (e) => e.isDirectory() && e.name !== config.archive.dir,\n );\n\n if (entries.length === 0) {\n log.warn(`${symbol.warn} 没有可归档的变更`);\n return;\n }\n\n log.info(`${symbol.start} 归档所有变更...`);\n for (const entry of entries) {\n archiveOne(entry.name, changesDir, archiveDir, config);\n }\n } else if (name) {\n const changePath = join(changesDir, name);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} 变更 \"${name}\" 不存在`);\n return;\n }\n log.info(`${symbol.start} 归档变更: ${name}`);\n archiveOne(name, changesDir, archiveDir, config);\n } else {\n log.warn(`${symbol.warn} 请指定变更名称或使用 --all`);\n return;\n }\n\n log.info(`${symbol.start} 归档完成!`);\n}\n\nfunction archiveOne(name: string, changesDir: string, archiveDir: string, config: SuperSpecConfig): void {\n ensureDir(archiveDir);\n const src = join(changesDir, name);\n const dateStr = config.archive.datePrefix ? `${getDateString()}-` : '';\n const dest = join(archiveDir, `${dateStr}${name}`);\n\n if (existsSync(dest)) {\n log.warn(` ${symbol.warn} 归档目标已存在: ${dest}`);\n return;\n }\n\n renameSync(src, dest);\n log.success(` ${symbol.ok} ${name} → archive/${dateStr}${name}`);\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { copyTemplate } from '../core/template.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { installRules, installAgentsMd, installCommands, AI_EDITORS, type AIEditor } from '../prompts/index.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function updateCommand(): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const specDir = join(cwd, config.specDir);\n const lang = config.lang || 'zh';\n\n if (!existsSync(join(cwd, 'superspec.config.json'))) {\n log.warn(`${symbol.warn} 当前目录未初始化 SuperSpec,请先运行 superspec init`);\n return;\n }\n\n log.info(`${symbol.start} 更新 SuperSpec...`);\n\n const templates = ['spec.md', 'proposal.md', 'tasks.md', 'clarify.md', 'checklist.md'];\n ensureDir(join(specDir, 'templates'));\n for (const tpl of templates) {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n }\n log.success(` ${symbol.ok} 模板更新 (${lang})`);\n\n installAgentsMd(cwd);\n\n // Update rules and commands for the configured AI editor\n const aiEditor = config.aiEditor as AIEditor;\n if (aiEditor && AI_EDITORS[aiEditor]) {\n installRules(cwd, aiEditor);\n installCommands(cwd, aiEditor, lang);\n }\n\n log.info(`${symbol.start} 更新完成!`);\n}\n","import { existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { lintChange } from '../core/lint.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function lintCommand(name: string | undefined): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n const { targetLines, hardLines } = config.limits;\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes to lint`);\n return;\n }\n\n let hasIssues = false;\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const results = lintChange(changePath, targetLines, hardLines);\n log.info(`${symbol.start} ${n}`);\n\n for (const r of results) {\n if (r.status === 'error') {\n log.error(` ${symbol.fail} ${r.artifact}: ${r.message}`);\n hasIssues = true;\n } else if (r.status === 'warn') {\n log.warn(` ${symbol.warn} ${r.artifact}: ${r.message}`);\n hasIssues = true;\n } else {\n log.success(` ${symbol.ok} ${r.artifact}: ${r.message}`);\n }\n }\n }\n\n if (!hasIssues) {\n log.info(`${symbol.start} all artifacts within limits`);\n }\n}\n","import { existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { validateChange, type ValidationIssue } from '../core/validate.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface ValidateOptions {\n checkDeps?: boolean;\n}\n\nexport async function validateCommand(name: string | undefined, options: ValidateOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes to validate`);\n return;\n }\n\n let totalIssues = 0;\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const issues = validateChange(changePath, options.checkDeps);\n log.info(`${symbol.start} ${n}`);\n\n if (issues.length === 0) {\n log.success(` ${symbol.ok} all checks passed`);\n } else {\n for (const issue of issues) {\n totalIssues++;\n if (issue.level === 'error') {\n log.error(` ${symbol.fail} [${issue.artifact}] ${issue.message}`);\n } else if (issue.level === 'warn') {\n log.warn(` ${symbol.warn} [${issue.artifact}] ${issue.message}`);\n } else {\n log.dim(` ℹ [${issue.artifact}] ${issue.message}`);\n }\n }\n }\n }\n\n if (totalIssues === 0) {\n log.success(`${symbol.ok} all validations passed`);\n } else {\n log.warn(`${symbol.warn} ${totalIssues} issue(s) found`);\n }\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join, basename } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface SearchOptions {\n archived?: boolean;\n artifact?: string;\n}\n\ninterface SearchHit {\n change: string;\n artifact: string;\n line: number;\n text: string;\n}\n\nexport async function searchCommand(query: string, options: SearchOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const dirs: { name: string; path: string }[] = [];\n\n const activeEntries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n for (const e of activeEntries) {\n dirs.push({ name: e.name, path: join(changesDir, e.name) });\n }\n\n if (options.archived) {\n const archiveDir = join(changesDir, config.archive.dir);\n if (existsSync(archiveDir)) {\n const archivedEntries = readdirSync(archiveDir, { withFileTypes: true })\n .filter((e) => e.isDirectory());\n for (const e of archivedEntries) {\n dirs.push({ name: `${config.archive.dir}/${e.name}`, path: join(archiveDir, e.name) });\n }\n }\n }\n\n const queryLower = query.toLowerCase();\n const hits: SearchHit[] = [];\n\n for (const dir of dirs) {\n if (!existsSync(dir.path)) continue;\n const files = readdirSync(dir.path).filter((f) => f.endsWith('.md'));\n\n for (const file of files) {\n if (options.artifact) {\n const artType = basename(file, '.md');\n if (artType !== options.artifact) continue;\n }\n\n const filePath = join(dir.path, file);\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n');\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].toLowerCase().includes(queryLower)) {\n hits.push({\n change: dir.name,\n artifact: file,\n line: i + 1,\n text: lines[i].trim(),\n });\n }\n }\n }\n }\n\n if (hits.length === 0) {\n log.warn(`${symbol.warn} no results for \"${query}\"`);\n return;\n }\n\n log.info(`${symbol.start} ${hits.length} result(s) for \"${query}\"`);\n for (const hit of hits) {\n log.dim(` ${hit.change}/${hit.artifact}:${hit.line} ${hit.text}`);\n }\n}\n","import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { addDependency, removeDependency, parseFrontmatter } from '../core/frontmatter.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface LinkOptions {\n dependsOn: string;\n}\n\nexport async function linkCommand(name: string, options: LinkOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const proposalPath = join(cwd, config.specDir, 'changes', name, 'proposal.md');\n\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" not found`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = addDependency(content, options.dependsOn);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → depends_on: ${options.dependsOn}`);\n}\n\nexport async function unlinkCommand(name: string, options: LinkOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const proposalPath = join(cwd, config.specDir, 'changes', name, 'proposal.md');\n\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" not found`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = removeDependency(content, options.dependsOn);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → removed dependency: ${options.dependsOn}`);\n}\n\nexport async function depsCommand(name: string): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (name) {\n const proposalPath = join(changesDir, name, 'proposal.md');\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" not found`);\n return;\n }\n const content = readFileSync(proposalPath, 'utf-8');\n const { meta } = parseFrontmatter(content);\n const deps: string[] = Array.isArray(meta.depends_on) ? meta.depends_on : [];\n log.info(`${symbol.start} ${name}`);\n if (deps.length === 0) {\n log.dim(' no dependencies');\n } else {\n for (const d of deps) {\n log.dim(` → ${d}`);\n }\n }\n return;\n }\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory`);\n return;\n }\n\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n\n log.info(`${symbol.start} dependency graph`);\n for (const entry of entries) {\n const proposalPath = join(changesDir, entry.name, 'proposal.md');\n if (!existsSync(proposalPath)) continue;\n const content = readFileSync(proposalPath, 'utf-8');\n const { meta } = parseFrontmatter(content);\n const deps: string[] = Array.isArray(meta.depends_on) ? meta.depends_on : [];\n if (deps.length > 0) {\n log.dim(` ${entry.name} → [${deps.join(', ')}]`);\n } else {\n log.dim(` ${entry.name}`);\n }\n }\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { parseFrontmatter } from '../core/frontmatter.js';\nimport { log, symbol } from '../ui/index.js';\n\nconst ARTIFACT_TYPES = ['proposal', 'spec', 'tasks', 'clarify', 'checklist'] as const;\n\nfunction readStatus(changePath: string, artifact: string): string {\n const filePath = join(changePath, `${artifact}.md`);\n if (!existsSync(filePath)) return '—';\n const content = readFileSync(filePath, 'utf-8');\n const { meta } = parseFrontmatter(content);\n if (meta.status) return meta.status;\n if (content.includes('✅')) return 'done';\n if (content.includes('🟢')) return 'ready';\n return 'draft';\n}\n\nfunction statusIcon(s: string): string {\n if (s === '—') return '—';\n if (s === 'done' || s === 'complete') return '✅';\n if (s === 'ready') return '🟢';\n return '🟡';\n}\n\nfunction overallStatus(statuses: Record<string, string>): string {\n const vals = Object.values(statuses).filter((v) => v !== '—');\n if (vals.length === 0) return 'empty';\n if (vals.every((v) => v === 'done' || v === 'complete')) return '✅ Done';\n if (vals.every((v) => v === 'ready' || v === 'done' || v === 'complete')) return '🟢 Ready';\n return '🟡 Draft';\n}\n\nexport async function statusCommand(): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir)\n .sort((a, b) => a.name.localeCompare(b.name));\n\n if (entries.length === 0) {\n log.dim(' no active changes');\n return;\n }\n\n const header = ['Change', ...ARTIFACT_TYPES.map((a) => a.slice(0, 8).padEnd(8)), 'Status'];\n const rows: string[][] = [];\n\n for (const entry of entries) {\n const changePath = join(changesDir, entry.name);\n const statuses: Record<string, string> = {};\n for (const art of ARTIFACT_TYPES) {\n statuses[art] = readStatus(changePath, art);\n }\n rows.push([\n entry.name,\n ...ARTIFACT_TYPES.map((a) => statusIcon(statuses[a])),\n overallStatus(statuses),\n ]);\n }\n\n const colWidths = header.map((h, i) => {\n const maxData = rows.reduce((max, row) => Math.max(max, stripAnsi(row[i]).length), 0);\n return Math.max(stripAnsi(h).length, maxData);\n });\n\n const divider = colWidths.map((w) => '-'.repeat(w + 2)).join('+');\n const formatRow = (row: string[]) =>\n row.map((cell, i) => ` ${cell.padEnd(colWidths[i])} `).join('|');\n\n log.info(`${symbol.start} changes`);\n console.log(formatRow(header));\n console.log(divider);\n for (const row of rows) {\n console.log(formatRow(row));\n }\n\n const archiveDir = join(changesDir, config.archive.dir);\n if (existsSync(archiveDir)) {\n const archived = readdirSync(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());\n if (archived.length > 0) {\n log.dim(`\\n ${archived.length} archived change(s)`);\n }\n }\n}\n\nfunction stripAnsi(str: string): string {\n return str.replace(/\\u001b\\[[0-9;]*m/g, '').replace(/[✅🟢🟡]/g, 'XX');\n}\n","import { existsSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { generateContext } from '../core/context.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function contextCommand(name: string | undefined): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes found`);\n return;\n }\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const content = generateContext(changePath, n);\n const destPath = join(changePath, 'context.md');\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} ${n}/context.md`);\n }\n}\n","import { existsSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { generateContext } from '../core/context.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function syncCommand(name: string | undefined, opts: { base?: string }): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes found`);\n return;\n }\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const content = generateContext(changePath, n, {\n gitDiff: true,\n baseBranch: opts.base,\n });\n const destPath = join(changePath, 'context.md');\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} synced ${n}/context.md`);\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","dirname","join","__filename","__dirname","existsSync","join","existsSync","dirname","readFileSync","readFileSync","existsSync","join","readFileSync","existsSync","join","readFileSync","existsSync","join","existsSync","writeFileSync","readdirSync","join","execSync","existsSync","readFileSync","writeFileSync","readdirSync","join","join","existsSync","readFileSync","writeFileSync","readdirSync","join","existsSync","readdirSync","writeFileSync","execSync","existsSync","join","join","existsSync","existsSync","readdirSync","join","join","existsSync","readdirSync","existsSync","join","join","existsSync","existsSync","readdirSync","join","existsSync","readdirSync","join","existsSync","readFileSync","readdirSync","join","basename","existsSync","readFileSync","writeFileSync","readdirSync","join","existsSync","readFileSync","readdirSync","join","existsSync","writeFileSync","readdirSync","join","existsSync","writeFileSync","readdirSync","join"]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@superspec/cli",
3
+ "version": "1.0.0",
4
+ "description": "Spec-driven development for AI coding assistants",
5
+ "type": "module",
6
+ "bin": {
7
+ "superspec": "./dist/cli/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist/",
19
+ "templates/",
20
+ "prompts/"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/asasugar/SuperSpec.git",
28
+ "directory": "packages/cli"
29
+ },
30
+ "homepage": "https://github.com/asasugar/SuperSpec.git#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/asasugar/SuperSpec.git/issues"
33
+ },
34
+ "keywords": [
35
+ "spec",
36
+ "sdd",
37
+ "ai",
38
+ "coding-assistant",
39
+ "cursor",
40
+ "claude",
41
+ "copilot",
42
+ "spec-driven-development"
43
+ ],
44
+ "author": "",
45
+ "license": "MIT",
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ },
49
+ "dependencies": {
50
+ "commander": "^13.1.0",
51
+ "chalk": "^5.4.1"
52
+ },
53
+ "devDependencies": {
54
+ "tsup": "^8.4.0",
55
+ "typescript": "^5.7.0",
56
+ "@types/node": "^22.0.0"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup",
60
+ "dev": "tsup --watch",
61
+ "typecheck": "tsc --noEmit"
62
+ }
63
+ }
@@ -0,0 +1,150 @@
1
+ # SuperSpec — AI Agent Instructions
2
+
3
+ ## 🚨 Before ANY Task
4
+
5
+ 1. Read `superspec.config.json` → get `lang`, `specDir`, `boost`, `strategy`, `context`
6
+ 2. Check `{specDir}/changes/` → know current state before acting
7
+ 3. Read frontmatter of current change → get `strategy` (may override config)
8
+ 4. If `strategy: follow` → read `context` files first (project rules/conventions)
9
+ 5. Never create change folders manually → use `superspec create` CLI
10
+
11
+ ---
12
+
13
+ ## 🧭 First Principles
14
+
15
+ | # | Principle | Rule |
16
+ |---|-----------|------|
17
+ | I | **Context Economy** | < 300 lines per artifact, 400 hard limit. Exceeds → split. Readable in 10 min. |
18
+ | II | **Signal-to-Noise** | Every sentence must inform a decision. If removing it changes nothing → remove it. |
19
+ | III | **Intent Over Implementation** | Focus on **why** and **what**. Let **how** emerge during `/ss-apply`. |
20
+ | IV | **Progressive Disclosure** | Start minimal. Expand only when clarification demands it. |
21
+ | V | **Required Sections** | Metadata header, Problem, Solution, Success Criteria, Trade-offs. |
22
+
23
+ ---
24
+
25
+ ## 🎯 Standard vs Boost
26
+
27
+ | | Standard (lightweight) | Boost (enhanced) |
28
+ |---|---|---|
29
+ | **场景** | Simple tasks, bug fixes, small features | Large features, breaking changes, complex designs |
30
+ | **Artifacts** | proposal + tasks | proposal + spec + tasks + checklist |
31
+ | **Task granularity** | Flexible | < 1h per task |
32
+ | **Cross-validation** | — | Auto: US↔FR↔AC↔tasks |
33
+ | **Edge cases** | Basic | Comprehensive |
34
+
35
+ **核心流程**:
36
+
37
+ ```
38
+ Standard: /ss-create → /ss-tasks → /ss-apply → [vibe: sync → /ss-resume] → /ss-archive
39
+ Boost: /ss-create -b → /ss-tasks → /ss-apply → [vibe: sync → /ss-resume] → /ss-archive
40
+ On-demand: /ss-clarify, /ss-lint, /ss-validate, /ss-search, /ss-link, /ss-deps
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 🧩 Strategy: follow vs create
46
+
47
+ | | `follow` (default) | `create` (`-c` / `--creative`) |
48
+ |---|---|---|
49
+ | **行为** | Read `context` files → strictly follow project rules/patterns | Aware of `context` but free to deviate with justification |
50
+ | **Proposal** | Solution aligns with existing architecture | May propose new architecture/patterns |
51
+ | **Spec** | Requirements fit current system design | Requirements may introduce new paradigms |
52
+ | **Tasks** | Use existing file structure, naming, dependencies | May create new structures, suggest new dependencies |
53
+ | **适用** | 常规功能、bug fix、遵循既有规范 | 架构重构、新模块设计、UX 创新 |
54
+
55
+ ### Context files
56
+
57
+ Config `context` lists files the AI should read to understand project conventions:
58
+
59
+ ```json
60
+ {
61
+ "context": [".cursor/rules/coding-style.mdc", "AGENTS.md", "docs/conventions.md"]
62
+ }
63
+ ```
64
+
65
+ - **follow**: read these files → treat as constraints (must follow)
66
+ - **create**: read these files → treat as awareness (may deviate, must justify)
67
+ - No `context` configured? AI auto-checks: `.cursor/rules/`, `AGENTS.md`, `CONTRIBUTING.md`
68
+ - Per-change override: add `context: ["src/auth/README.md"]` to frontmatter
69
+
70
+ ---
71
+
72
+ ## ⚠️ Core Rules
73
+
74
+ | Rule | Details |
75
+ |------|---------|
76
+ | Language | Follow `lang` config: `"zh"` → Chinese, `"en"` → English. All artifacts and interaction. |
77
+ | Read-first | Read existing content before writing. Preserve user edits. |
78
+ | Consistency | Boost: `US-1`, `FR-1`, `AC-1.1` must match across all artifacts. |
79
+ | Status tracking | 🟡 Draft → 🟢 Ready → ✅ Done. Update after each step. |
80
+
81
+ ---
82
+
83
+ ## 🚫 Don't / Do
84
+
85
+ | ❌ Don't | ✅ Do |
86
+ |----------|------|
87
+ | Code without planning | `/ss-create` → `/ss-tasks` → `/ss-apply` |
88
+ | Overkill simple tasks | Use standard mode. Only boost when complexity demands it. |
89
+ | Create folders manually | `superspec create <name>` or `/ss-create` |
90
+ | Ignore `clarify.md` | Read before generating/updating |
91
+ | Overwrite user edits | Merge, don't replace |
92
+
93
+ ---
94
+
95
+ ## 🔧 Commands
96
+
97
+ | Command | Mode | What it does |
98
+ |---------|------|-------------|
99
+ | `/ss-create <name>` | Both | Create change + generate proposal (boost: + spec + checklist) |
100
+ | `/ss-tasks` | Both | Generate task list from proposal (boost: from proposal + spec) |
101
+ | `/ss-apply` | Both | Implement tasks |
102
+ | `/ss-clarify` | Both | Resolve ambiguity |
103
+ | `/ss-archive` | Both | Archive completed change |
104
+ | `/ss-checklist` | Boost | Quality gate before apply |
105
+ | `/ss-status` | Both | View all changes |
106
+ | `/ss-lint` | Both | Check artifact sizes |
107
+ | `/ss-validate` | Boost | Cross-reference consistency check |
108
+ | `/ss-search <q>` | Both | Full-text search across changes |
109
+ | `/ss-link` | Both | Add spec dependency |
110
+ | `/ss-deps` | Both | View dependency graph |
111
+ | `/ss-resume` | Both | Restore spec context for vibe coding (runs sync → reads context.md) |
112
+ | `superspec sync` | Both | CLI: collect git diff into context.md (zero AI tokens) |
113
+
114
+ ---
115
+
116
+ ## 📐 Artifacts
117
+
118
+ **Standard:**
119
+ ```
120
+ {specDir}/changes/<name>/
121
+ ├── proposal.md — Why and what
122
+ └── tasks.md — Actionable steps
123
+ ```
124
+
125
+ **Boost:**
126
+ ```
127
+ {specDir}/changes/<name>/
128
+ ├── proposal.md — Why and what
129
+ ├── spec.md — Requirements (US/FR/AC)
130
+ ├── tasks.md — Phased implementation steps
131
+ ├── clarify.md — Q&A and decisions (on-demand)
132
+ └── checklist.md — Quality validation
133
+ ```
134
+
135
+ Each artifact has YAML frontmatter: `name`, `status`, `strategy`, `depends_on: []`.
136
+
137
+ ---
138
+
139
+ ## ⚙️ Config
140
+
141
+ | Field | Default | Purpose |
142
+ |-------|---------|---------|
143
+ | `lang` | `"zh"` | Artifact language |
144
+ | `specDir` | `"superspec"` | Spec folder |
145
+ | `branchPrefix` | `"spec/"` | Git branch prefix |
146
+ | `boost` | `false` | Enable boost mode |
147
+ | `strategy` | `"follow"` | `follow` = obey project rules, `create` = explore freely |
148
+ | `context` | `[]` | Files AI should read for project conventions |
149
+ | `limits.targetLines` | `300` | Target max lines per artifact |
150
+ | `limits.hardLines` | `400` | Hard max lines per artifact |
@@ -0,0 +1,154 @@
1
+ ---
2
+ description: SuperSpec - Spec-driven development slash commands
3
+ globs: ["**/*"]
4
+ alwaysApply: true
5
+ ---
6
+
7
+ # SuperSpec Slash Commands
8
+
9
+ > Principles, rules, config, workflow are defined in AGENTS.md (already in your context).
10
+ > This file only adds slash command execution details. Do not duplicate AGENTS.md content.
11
+
12
+ ---
13
+
14
+ ## /ss-create <name>
15
+
16
+ **CLI:** `superspec create <name>` (`-b` boost, `-c` creative, `--no-branch` skip branch)
17
+
18
+ **Standard steps:**
19
+ 1. Run `superspec create <name>` → creates folder + proposal.md + tasks.md templates
20
+ 2. Read frontmatter → check `strategy`
21
+ 3. If `follow`: read `context` files → constrain to project patterns
22
+ If `create`: note `context` as awareness only
23
+ 4. Collect from user: Background, Goals, Solution overview, Impact scope
24
+ 5. `follow`: solution must align with existing architecture
25
+ `create`: may propose new architecture, must explain trade-offs
26
+ 6. Apply First Principles (brevity, intent-focused, required sections)
27
+ 7. Write `proposal.md` → status 🟢 Ready
28
+
29
+ **Boost additional steps:**
30
+ 8. Read `proposal.md` → generate spec:
31
+ - User stories + acceptance criteria (AC-x.x)
32
+ - Functional requirements + priority (P0/P1/P2) + dependencies
33
+ - Non-functional requirements, Data model / API design, Edge cases
34
+ 9. Validate: every proposal goal → at least one user story
35
+ 10. Write `spec.md` → status 🟢 Ready
36
+ 11. Write `checklist.md` skeleton
37
+
38
+ ## /ss-tasks
39
+
40
+ **Steps:**
41
+ 1. Read frontmatter → check `strategy`
42
+ 2. Standard: read `proposal.md` as input
43
+ Boost: read `proposal.md` + `spec.md` as input
44
+ 3. Read `{specDir}/changes/<name>/tasks.md` template
45
+ 4. Break into phased tasks:
46
+ - Phase 1: Infrastructure / setup
47
+ - Phase 2: Core implementation
48
+ - Phase 3: Integration / verification
49
+ 5. Each task: file paths, dependencies, `[P]` for parallel
50
+ 6. `follow`: use existing file structure, naming, deps
51
+ `create`: may introduce new structures, explicitly note deviations
52
+ 7. Granularity: flexible (standard) / < 1h (boost)
53
+ 8. Checkpoints per phase
54
+ 9. Boost: validate every spec requirement → at least one task
55
+ 10. Write `tasks.md` → status 🟢 Ready
56
+
57
+ ## /ss-apply
58
+
59
+ **Steps:**
60
+ 1. Read frontmatter → check `strategy`
61
+ 2. If `follow`: read `context` files → implementation must match project conventions
62
+ If `create`: implement as designed, note any new patterns introduced
63
+ 3. Read `tasks.md` → parse task list
64
+ 4. Execute in dependency order, parallelize `[P]` where possible
65
+ 5. After each task: mark ✅ in `tasks.md`
66
+ 6. After each phase: checkpoint validation
67
+ 7. On blockers: pause and report
68
+ 8. After all tasks done: run `superspec context <name>` to refresh context.md
69
+
70
+ ## /ss-resume
71
+
72
+ **For vibe coding after `/ss-apply`.** Restores spec context in a new conversation.
73
+
74
+ **Steps:**
75
+ 1. Locate current change folder in `{specDir}/changes/`
76
+ 2. Run `superspec sync <name>` to collect latest git changes into context.md
77
+ 3. Read `context.md` (single file, minimal tokens)
78
+ 4. Cross-reference: Git Changes vs Progress → infer what's done, what's pending, what's unplanned
79
+ 5. Report: goals, progress, git changes, affected files
80
+ 6. Ask user: what needs fixing / adjusting?
81
+ 7. Fix with spec context in mind, respect `strategy`
82
+ 8. After fix: update tasks.md checkbox if applicable
83
+ 9. Run `superspec sync <name>` to refresh context.md
84
+
85
+ ## /ss-clarify
86
+
87
+ **Steps:**
88
+ 1. Read ALL existing artifacts
89
+ 2. Raise questions: ambiguous reqs, missing edge cases, undefined behaviors, technical constraints, dependencies
90
+ 3. One question at a time → wait for answer
91
+ 4. Record in `clarify.md`
92
+ 5. Propagate answers → update affected artifacts
93
+ 6. Log which docs updated
94
+
95
+ ## /ss-archive
96
+
97
+ Run CLI: `superspec archive <name>`.
98
+
99
+ ## /ss-checklist
100
+
101
+ **Boost mode only.** Quality gate before `/ss-apply`.
102
+
103
+ **Steps:**
104
+ 1. Read ALL artifacts
105
+ 2. Evaluate: requirements completeness, proposal quality, spec consistency, task executability, cross-validation, implementation readiness
106
+ 3. ✅ passing / annotate failures
107
+ 4. Score (X / 25) + recommendations
108
+ 5. Must pass before `/ss-apply`
109
+
110
+ ## /ss-status
111
+
112
+ **Steps:**
113
+ 1. List `{specDir}/changes/` (excluding archive)
114
+ 2. Read status markers per artifact
115
+ 3. Output:
116
+
117
+ ```
118
+ | Change | Proposal | Spec | Tasks | Checklist | Status |
119
+ |--------|----------|------|-------|-----------|--------|
120
+ ```
121
+
122
+ ## /ss-lint
123
+
124
+ **Steps:**
125
+ 1. Run `superspec lint [name]` or check current change
126
+ 2. Review output: ✓ ok / ⚠ warn (> target) / ✗ error (> hard limit)
127
+ 3. If error: suggest splitting into sub-specs
128
+
129
+ ## /ss-validate
130
+
131
+ **Boost mode recommended.** Checks US/FR/AC cross-references (requires spec.md).
132
+
133
+ **Steps:**
134
+ 1. Run `superspec validate [name]` (add `--check-deps` for dependency check)
135
+ 2. Review cross-reference issues
136
+ 3. Fix reported issues in corresponding artifacts
137
+
138
+ ## /ss-search <query>
139
+
140
+ **Steps:**
141
+ 1. Run `superspec search "<query>"` (add `--archived` to include archives)
142
+ 2. Optionally filter: `--artifact proposal|spec|tasks|clarify|checklist`
143
+
144
+ ## /ss-link
145
+
146
+ **Steps:**
147
+ 1. Run `superspec link <name> --depends-on <other>`
148
+ 2. Verify with `superspec deps <name>`
149
+
150
+ ## /ss-deps
151
+
152
+ **Steps:**
153
+ 1. Run `superspec deps [name]` to view dependency graph
154
+ 2. No name → show all changes and their dependencies
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: {{name}}
3
+ status: draft
4
+ strategy: {{strategy}}
5
+ depends_on: []
6
+ ---
7
+
8
+ # Quality Checklist: {{name}}
9
+
10
+ > Created: {{date}}
11
+ > Mode: Boost
12
+
13
+ ## Requirements Completeness
14
+
15
+ - [ ] All user stories have clear acceptance criteria
16
+ - [ ] Functional requirements cover all user stories
17
+ - [ ] Non-functional requirements defined (performance, security, compatibility)
18
+ - [ ] Edge cases identified and documented
19
+ - [ ] Data model changes described
20
+
21
+ ## Proposal Quality
22
+
23
+ - [ ] Background and motivation are clear
24
+ - [ ] Goals are measurable
25
+ - [ ] Non-goals are explicit
26
+ - [ ] Risks identified with mitigations
27
+ - [ ] Impact scope assessed
28
+
29
+ ## Spec Consistency
30
+
31
+ - [ ] Spec aligns with proposal goals
32
+ - [ ] User stories cover all proposal goals
33
+ - [ ] Acceptance criteria are testable
34
+ - [ ] No contradicting requirements
35
+ - [ ] Dependencies are clear
36
+
37
+ ## Task Executability
38
+
39
+ - [ ] Task granularity is reasonable (each < 2h)
40
+ - [ ] Dependencies are correct
41
+ - [ ] Parallel tasks marked correctly
42
+ - [ ] File paths specified
43
+ - [ ] Checkpoints are reasonable
44
+
45
+ ## Cross Validation
46
+
47
+ - [ ] proposal → spec: All goals have corresponding requirements
48
+ - [ ] spec → tasks: All requirements have corresponding tasks
49
+ - [ ] tasks → spec: No tasks beyond spec scope
50
+ - [ ] clarify → all: All clarifications reflected in documents
51
+
52
+ ## Implementation Readiness
53
+
54
+ - [ ] Technical approach is feasible
55
+ - [ ] No blocking dependencies
56
+ - [ ] Open questions resolved (or marked non-blocking)
57
+ - [ ] Team consensus on approach
58
+
59
+ ---
60
+
61
+ **Score**: _ / 25
62
+ **Status**: 🟡 Pending Review
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: {{name}}
3
+ status: draft
4
+ strategy: {{strategy}}
5
+ depends_on: []
6
+ ---
7
+
8
+ # Clarification Log: {{name}}
9
+
10
+ > Created: {{date}}
11
+
12
+ ## Questions
13
+
14
+ ### Q1: [Question]
15
+
16
+ - **Asked by**: AI / User
17
+ - **Answer**:
18
+ - **Impact**: Impact on spec / proposal / tasks
19
+ - **Updated**: ☐ spec ☐ proposal ☐ tasks
20
+
21
+ ### Q2: [Question]
22
+
23
+ - **Asked by**: AI / User
24
+ - **Answer**:
25
+ - **Impact**:
26
+ - **Updated**: ☐ spec ☐ proposal ☐ tasks
27
+
28
+ ## Decision Log
29
+
30
+ | # | Decision | Reason | Date |
31
+ |---|----------|--------|------|
32
+ | D1 | | | |
33
+ | D2 | | | |
34
+
35
+ ## Pending
36
+
37
+ <!-- Unresolved questions -->
38
+
39
+ 1.
40
+ 2.
41
+
42
+ ---
43
+
44
+ **Status**: 🟡 In Progress