@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.
- package/dist/cli/index.js +1329 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +607 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/prompts/agents.md +150 -0
- package/prompts/cursor-rules.md +154 -0
- package/templates/en/checklist.md +62 -0
- package/templates/en/clarify.md +44 -0
- package/templates/en/commands/ss-apply.md +19 -0
- package/templates/en/commands/ss-archive.md +15 -0
- package/templates/en/commands/ss-checklist.md +16 -0
- package/templates/en/commands/ss-clarify.md +17 -0
- package/templates/en/commands/ss-create.md +19 -0
- package/templates/en/commands/ss-deps.md +11 -0
- package/templates/en/commands/ss-link.md +11 -0
- package/templates/en/commands/ss-lint.md +15 -0
- package/templates/en/commands/ss-resume.md +17 -0
- package/templates/en/commands/ss-search.md +12 -0
- package/templates/en/commands/ss-status.md +12 -0
- package/templates/en/commands/ss-tasks.md +19 -0
- package/templates/en/commands/ss-validate.md +15 -0
- package/templates/en/proposal.md +53 -0
- package/templates/en/spec.md +73 -0
- package/templates/en/tasks.md +52 -0
- package/templates/zh/checklist.md +62 -0
- package/templates/zh/clarify.md +44 -0
- package/templates/zh/commands/ss-apply.md +19 -0
- package/templates/zh/commands/ss-archive.md +15 -0
- package/templates/zh/commands/ss-checklist.md +16 -0
- package/templates/zh/commands/ss-clarify.md +17 -0
- package/templates/zh/commands/ss-create.md +19 -0
- package/templates/zh/commands/ss-deps.md +11 -0
- package/templates/zh/commands/ss-link.md +11 -0
- package/templates/zh/commands/ss-lint.md +15 -0
- package/templates/zh/commands/ss-resume.md +17 -0
- package/templates/zh/commands/ss-search.md +12 -0
- package/templates/zh/commands/ss-status.md +12 -0
- package/templates/zh/commands/ss-tasks.md +19 -0
- package/templates/zh/commands/ss-validate.md +15 -0
- package/templates/zh/proposal.md +59 -0
- package/templates/zh/spec.md +73 -0
- 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
|