@superspec/cli 1.1.0 → 1.2.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/index.js.map CHANGED
@@ -1 +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/utils/template.ts","../src/commands/archive.ts","../src/commands/update.ts","../src/commands/lint.ts","../src/commands/validate.ts","../src/commands/search.ts","../src/commands/deps.ts","../src/commands/status.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 changeNameTemplate: 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: 'en',\n aiEditor: 'cursor',\n specDir: 'superspec',\n branchPrefix: '',\n branchTemplate: '{prefix}{intentType}-{date}-{feature}-{user}',\n changeNameTemplate: '{prefix}{intentType}-{date}-{feature}-{user}',\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 design: 'design.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', 'design', 'tasks', 'checklist'],\n};\n\nexport function loadConfig(projectRoot: string = process.cwd(), silent: boolean = false): 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 if (!silent) {\n console.warn(`⚠ Config file parsing failed: ${e.message}`);\n }\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 const val = source[key];\n if (val === null || val === undefined) continue;\n if (\n typeof val === 'object' &&\n !Array.isArray(val) &&\n target[key] &&\n typeof target[key] === 'object'\n ) {\n result[key] = deepMerge(target[key], val);\n } else {\n result[key] = val;\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 fallbackLang = lang === 'zh' ? 'en' : 'zh';\n const fallback = join(root, 'templates', fallbackLang, 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 { existsSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\nexport function getPackageRoot(): string {\n const __filename = fileURLToPath(import.meta.url);\n let dir = dirname(__filename);\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'templates'))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return join(dirname(__filename), '..', '..');\n}\n","import { mkdirSync, existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport function ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nexport function resolveChangeNames(changesDir: string, name: string | undefined, archiveDirName: string): string[] {\n if (!existsSync(changesDir)) return [];\n if (name) return [name];\n return readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== archiveDirName)\n .map((e) => e.name);\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 === true) {\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\nconst GIT_TIMEOUT = 10_000;\n\nexport function isGitRepo(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore', timeout: GIT_TIMEOUT });\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', timeout: GIT_TIMEOUT }).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', timeout: GIT_TIMEOUT });\n}\n\nexport function getDefaultBranch(): string {\n try {\n const ref = execSync('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf-8', timeout: GIT_TIMEOUT }).trim();\n return ref.replace('refs/remotes/origin/', '');\n } catch {\n try {\n execSync('git rev-parse --verify main', { stdio: 'ignore', timeout: GIT_TIMEOUT });\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', timeout: GIT_TIMEOUT }).trim();\n const output = execSync(`git diff --name-status ${mergeBase}`, { encoding: 'utf-8', timeout: 30_000 }).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, t, setLang } 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} ${t('superspec.config.json already exists, use --force to overwrite', 'superspec.config.json 已存在,使用 --force 覆盖')}`);\n return;\n }\n\n const lang = options.lang || 'zh';\n setLang(lang as 'zh' | 'en');\n\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 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 const existingFiles = readdirSync(cwd).filter(f => !f.startsWith('.') && f !== 'node_modules');\n if (existingFiles.length > 0 && !options.force) {\n log.warn(`${symbol.warn} ${t(`current directory is not empty (${existingFiles.length} items)`, `当前目录非空(${existingFiles.length} 项)`)}`);\n log.dim(` ${t('template files will be merged with existing content', '模板文件将与现有内容合并')}`);\n console.log();\n }\n\n log.section(t('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(t('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(t('Installing Templates', '安装模板'));\n const templateNames = Object.values(config.templates).map((v) => (v.endsWith('.md') ? v : `${v}.md`));\n for (const tpl of templateNames) {\n try {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n } catch {\n // skip missing templates\n }\n }\n log.success(`${symbol.ok} ${templateNames.length} ${t('templates', '个模板')} (${lang})`);\n\n log.section(t('Installing AI Agent Files', '安装 AI Agent 文件'));\n installAgentsMd(cwd);\n\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 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(t('SuperSpec initialized successfully!', 'SuperSpec 初始化成功!'));\n log.dim(`${t('Next', '下一步')}: superspec create <feature>`);\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', '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 info: theme.info('ℹ'),\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\nlet _lang: 'zh' | 'en' = 'en';\n\nexport function setLang(lang: 'zh' | 'en'): void {\n _lang = lang;\n}\n\nexport function t(en: string, zh: string): string {\n return _lang === 'zh' ? zh : en;\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+(?:;\\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, getDateString, renderNameTemplate, detectLang, type NameTemplateVars } from '../utils/index.js';\nimport { isGitRepo, createBranch } from '../utils/git.js';\nimport { log, symbol, t } from '../ui/index.js';\n\nexport interface CreateOptions {\n boost?: boolean;\n creative?: boolean;\n user?: string;\n lang?: string;\n branch?: boolean;\n specDir?: string;\n branchPrefix?: string;\n branchTemplate?: string;\n changeNameTemplate?: string;\n description?: string;\n intentType?: string;\n}\n\nexport async function createCommand(feature: 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 description = options.description || '';\n const lang = options.lang || config.lang || 'en';\n\n const templateVars: NameTemplateVars = {\n prefix: branchPrefix,\n intentType: options.intentType,\n feature,\n date: getDateString(),\n user: options.user,\n };\n\n const changeNameTemplate = options.changeNameTemplate || config.changeNameTemplate || '{date}-{feature}';\n const changeFolderName = renderNameTemplate(changeNameTemplate, templateVars, false);\n const changePath = join(cwd, specDir, 'changes', changeFolderName);\n\n if (existsSync(changePath)) {\n log.warn(`${symbol.warn} ${t(`change \"${changeFolderName}\" already exists`, `变更 \"${changeFolderName}\" 已存在`)}: ${changePath}`);\n return;\n }\n\n log.title(`${t('Creating Change', '创建变更')}: ${changeFolderName}`);\n if (options.intentType) {\n log.info(`${t('Intent Type', '意图类型')}: ${options.intentType}`);\n }\n\n if (boost) {\n log.boost(`${symbol.bolt} ${t('Boost mode enabled', '增强模式已启用')}`);\n }\n if (strategy === 'create') {\n log.boost(`${symbol.bolt} ${t('Creative mode: exploring new solutions', '创造模式:探索新方案')}`);\n }\n\n ensureDir(changePath);\n\n const vars: Record<string, string> = {\n name: changeFolderName,\n date: templateVars.date,\n boost: boost ? 'true' : 'false',\n strategy,\n description,\n };\n\n const artifacts = boost ? config.boostArtifacts : config.artifacts;\n\n log.section(t('Generating Artifacts', '生成 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 = options.branchTemplate || config.branchTemplate || '{prefix}{date}-{feature}';\n const branchName = renderNameTemplate(branchTemplate, templateVars, true);\n try {\n createBranch(branchName);\n log.success(`${symbol.ok} Branch: ${branchName}`);\n } catch (e: any) {\n log.warn(`${symbol.warn} ${t('branch creation failed', '分支创建失败')}: ${e.message}`);\n }\n }\n\n log.done(t('Change created successfully!', '变更创建成功!'));\n log.dim(`${t('Path', '路径')}: ${specDir}/changes/${changeFolderName}/`);\n\n if (boost) {\n log.dim(`${t('Workflow', '工作流')}: /ss-create → /ss-tasks → /ss-apply (boost)`);\n } else {\n log.dim(`${t('Workflow', '工作流')}: /ss-tasks → /ss-apply`);\n }\n\n log.dim(`${t('Next', '下一步')}: superspec lint ${changeFolderName}`);\n}\n","const CJK_REGEX = /[\\u4e00-\\u9fff\\u3400-\\u4dbf\\u{20000}-\\u{2a6df}\\u{2a700}-\\u{2b73f}]/u;\n\nexport function detectLang(...inputs: (string | undefined)[]): 'zh' | 'en' | undefined {\n for (const text of inputs) {\n if (text && CJK_REGEX.test(text)) return 'zh';\n }\n return undefined;\n}\n\nexport interface NameTemplateVars {\n prefix?: string;\n intentType?: string;\n feature: string;\n date: string;\n user?: string;\n [key: string]: string | undefined;\n}\n\n\nexport function renderNameTemplate(template: string, vars: NameTemplateVars, strict = false): string {\n let result = template;\n\n for (const [key, value] of Object.entries(vars)) {\n if (value !== undefined) {\n result = result.replace(new RegExp(`\\\\{${key}\\\\}`, 'g'), value);\n }\n }\n\n result = result.replace(/\\{[^}]+\\}/g, '');\n\n if (strict) {\n return result.replace(/[^a-zA-Z0-9._\\-/]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');\n }\n return result.replace(/[^\\p{L}\\p{N}._\\-/]/gu, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');\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, t } 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} ${t('no changes directory found', '未找到 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} ${t('no changes to archive', '没有可归档的变更')}`);\n return;\n }\n\n log.info(`${symbol.start} ${t('archiving all changes...', '归档所有变更...')}`);\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} ${t(`change \"${name}\" not found`, `变更 \"${name}\" 不存在`)}`);\n return;\n }\n log.info(`${symbol.start} ${t(`archiving: ${name}`, `归档变更: ${name}`)}`);\n archiveOne(name, changesDir, archiveDir, config);\n } else {\n log.warn(`${symbol.warn} ${t('specify a name or use --all', '请指定变更名称或使用 --all')}`);\n return;\n }\n\n log.info(`${symbol.start} ${t('archive done!', '归档完成!')}`);\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} ${t('archive target exists', '归档目标已存在')}: ${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, t } 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} ${t('not initialized, run superspec init first', '当前目录未初始化 SuperSpec,请先运行 superspec init')}`);\n return;\n }\n\n log.info(`${symbol.start} ${t('updating SuperSpec...', '更新 SuperSpec...')}`);\n\n const templateNames = Object.values(config.templates).map((v) => (v.endsWith('.md') ? v : `${v}.md`));\n ensureDir(join(specDir, 'templates'));\n for (const tpl of templateNames) {\n try {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n } catch {\n // skip missing templates\n }\n }\n log.success(` ${symbol.ok} ${t('templates updated', '模板更新')} (${lang})`);\n\n installAgentsMd(cwd);\n\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} ${t('update done!', '更新完成!')}`);\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { lintChange } from '../core/lint.js';\nimport { resolveChangeNames } from '../utils/fs.js';\nimport { log, symbol, t } 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 const names = resolveChangeNames(changesDir, name, config.archive.dir);\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} ${t('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}\" ${t('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} ${t('all artifacts within limits', '所有 artifact 均在限制范围内')}`);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { validateChange, type ValidationIssue } from '../core/validate.js';\nimport { resolveChangeNames } from '../utils/fs.js';\nimport { log, symbol, t } 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 const names = resolveChangeNames(changesDir, name, config.archive.dir);\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} ${t('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}\" ${t('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} ${t('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} ${t('all validations passed', '所有验证通过')}`);\n } else {\n log.warn(`${symbol.warn} ${totalIssues} ${t('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, t } from '../ui/index.js';\n\nconst DEFAULT_LIMIT = 50;\n\nexport interface SearchOptions {\n archived?: boolean;\n artifact?: string;\n limit?: string;\n regex?: boolean;\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} ${t('no changes directory found', '未找到 changes 目录')}`);\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 let matcher: (line: string) => boolean;\n if (options.regex) {\n try {\n const re = new RegExp(query, 'i');\n matcher = (line) => re.test(line);\n } catch (e: any) {\n log.error(`${symbol.fail} ${t('invalid regex', '无效正则')}: ${e.message}`);\n return;\n }\n } else {\n const queryLower = query.toLowerCase();\n matcher = (line) => line.toLowerCase().includes(queryLower);\n }\n\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 (matcher(lines[i])) {\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} ${t(`no results for \"${query}\"`, `\"${query}\" 无结果`)}`);\n return;\n }\n\n const limit = options.limit ? parseInt(options.limit, 10) : DEFAULT_LIMIT;\n const shown = hits.slice(0, limit);\n\n log.info(`${symbol.start} ${hits.length} ${t('result(s) for', '条结果,搜索')} \"${query}\"`);\n for (const hit of shown) {\n log.dim(` ${hit.change}/${hit.artifact}:${hit.line} ${hit.text}`);\n }\n if (hits.length > limit) {\n log.dim(` ... ${hits.length - limit} ${t('more result(s), use --limit to show more', '条更多结果,使用 --limit 显示更多')}`);\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, t } from '../ui/index.js';\n\nexport async function depsAddCommand(name: string, options: { on: string }): 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}\" ${t('not found', '未找到')}`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = addDependency(content, options.on);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → depends_on: ${options.on}`);\n}\n\nexport async function depsRemoveCommand(name: string, options: { on: string }): 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}\" ${t('not found', '未找到')}`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = removeDependency(content, options.on);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → ${t('removed dependency', '移除依赖')}: ${options.on}`);\n}\n\nexport async function depsListCommand(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 (name) {\n const proposalPath = join(changesDir, name, 'proposal.md');\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" ${t('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(` ${t('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} ${t('no changes directory', '未找到 changes 目录')}`);\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} ${t('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, t } 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} ${t('no changes directory found', '未找到 changes 目录')}`);\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(` ${t('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} ${t('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} ${t('archived change(s)', '个已归档变更')}`);\n }\n }\n}\n\nexport async function listCommand(options: { archived?: boolean }): 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)) return;\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 for (const e of entries) {\n console.log(e.name);\n }\n\n if (options.archived) {\n const archiveDir = join(changesDir, config.archive.dir);\n if (existsSync(archiveDir)) {\n const archived = readdirSync(archiveDir, { withFileTypes: true })\n .filter((e) => e.isDirectory());\n for (const e of archived) {\n console.log(`${config.archive.dir}/${e.name}`);\n }\n }\n }\n}\n\nfunction stripAnsi(str: string): string {\n return str.replace(/\\u001b\\[\\d+(?:;\\d+)*m/g, '').replace(/[✅🟢🟡—]/g, 'XX');\n}\n","import { existsSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { generateContext } from '../core/context.js';\nimport { resolveChangeNames } from '../utils/fs.js';\nimport { log, symbol, t } from '../ui/index.js';\n\nexport async function syncCommand(name: string | undefined, opts: { base?: string; git?: boolean }): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n const names = resolveChangeNames(changesDir, name, config.archive.dir);\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} ${t('no changes found', '未找到变更')}`);\n return;\n }\n\n const useGit = opts.git !== false;\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" ${t('not found', '未找到')}`);\n continue;\n }\n\n const content = generateContext(changePath, n, {\n gitDiff: useGit,\n baseBranch: opts.base,\n });\n const destPath = join(changePath, 'context.md');\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} ${t('synced', '已同步')} ${n}/context.md`);\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","dirname","join","existsSync","__filename","existsSync","join","existsSync","dirname","readFileSync","readFileSync","existsSync","readdirSync","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","join","existsSync","join","existsSync","readFileSync","readdirSync","join","basename","existsSync","readFileSync","writeFileSync","readdirSync","join","existsSync","readFileSync","readdirSync","join","existsSync","writeFileSync","join"]}
1
+ {"version":3,"sources":["../src/commands/archive.ts","../src/core/config.ts","../src/ui/index.ts","../src/utils/date.ts","../src/utils/fs.ts","../src/commands/create.ts","../src/utils/git.ts","../src/utils/paths.ts","../src/utils/template.ts","../src/commands/deps.ts","../src/commands/init.ts","../src/core/template.ts","../src/prompts/index.ts","../src/commands/lint.ts","../src/core/lint.ts","../src/commands/search.ts","../src/commands/status.ts","../src/commands/sync.ts","../src/core/context.ts","../src/commands/update.ts","../src/commands/validate.ts","../src/core/validate.ts"],"sourcesContent":["import { existsSync, readdirSync, renameSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig, type SuperSpecConfig } from '../core/config.js';\nimport { log, symbol, t } from '../ui/index.js';\nimport { getDateString } from '../utils/date.js';\nimport { ensureDir } from '../utils/fs.js';\n\nexport interface ArchiveOptions {\n all?: boolean;\n}\n\nexport async function archiveCommand(\n name: string | undefined,\n options: ArchiveOptions\n): 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} ${t('no changes directory found', '未找到 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} ${t('no changes to archive', '没有可归档的变更')}`);\n return;\n }\n\n log.info(`${symbol.start} ${t('archiving all changes...', '归档所有变更...')}`);\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} ${t(`change \"${name}\" not found`, `变更 \"${name}\" 不存在`)}`);\n return;\n }\n log.info(`${symbol.start} ${t(`archiving: ${name}`, `归档变更: ${name}`)}`);\n archiveOne(name, changesDir, archiveDir, config);\n } else {\n log.warn(`${symbol.warn} ${t('specify a name or use --all', '请指定变更名称或使用 --all')}`);\n return;\n }\n\n log.info(`${symbol.start} ${t('archive done!', '归档完成!')}`);\n}\n\nfunction archiveOne(\n name: string,\n changesDir: string,\n archiveDir: string,\n config: SuperSpecConfig\n): 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} ${t('archive target exists', '归档目标已存在')}: ${dest}`);\n return;\n }\n\n renameSync(src, dest);\n log.success(` ${symbol.ok} ${name} → archive/${dateStr}${name}`);\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Strategy = 'follow' | 'create';\nexport type AIEditorType =\n | 'claude'\n | 'cursor'\n | 'qwen'\n | 'opencode'\n | 'codex'\n | 'codebuddy'\n | 'qoder';\n\nexport interface SuperSpecConfig {\n lang: 'zh' | 'en';\n aiEditor: AIEditorType;\n specDir: string;\n branchPrefix: string;\n branchTemplate: string;\n changeNameTemplate: 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: 'en',\n aiEditor: 'cursor',\n specDir: 'superspec',\n branchPrefix: '',\n branchTemplate: '{prefix}{intentType}-{date}-{feature}-{user}',\n changeNameTemplate: '{prefix}{intentType}-{date}-{feature}-{user}',\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 design: 'design.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', 'design', 'tasks', 'checklist']\n};\n\nexport function loadConfig(\n projectRoot: string = process.cwd(),\n silent: boolean = false\n): 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 if (!silent) {\n console.warn(`⚠ Config file parsing failed: ${e.message}`);\n }\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 const val = source[key];\n if (val === null || val === undefined) continue;\n if (\n typeof val === 'object' &&\n !Array.isArray(val) &&\n target[key] &&\n typeof target[key] === 'object'\n ) {\n result[key] = deepMerge(target[key], val);\n } else {\n result[key] = val;\n }\n }\n return result;\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(\n `${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`\n );\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 info: theme.info('ℹ')\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\nlet _lang: 'zh' | 'en' = 'en';\n\nexport function setLang(lang: 'zh' | 'en'): void {\n _lang = lang;\n}\n\nexport function t(en: string, zh: string): string {\n return _lang === 'zh' ? zh : en;\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+(?:;\\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","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 { existsSync, mkdirSync, readdirSync } from 'node:fs';\n\nexport function ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nexport function resolveChangeNames(\n changesDir: string,\n name: string | undefined,\n archiveDirName: string\n): string[] {\n if (!existsSync(changesDir)) return [];\n if (name) return [name];\n return readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== archiveDirName)\n .map((e) => e.name);\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { log, symbol, t } from '../ui/index.js';\nimport { createBranch, isGitRepo } from '../utils/git.js';\nimport {\n ensureDir,\n getDateString,\n type NameTemplateVars,\n renderNameTemplate\n} from '../utils/index.js';\n\nexport interface CreateOptions {\n boost?: boolean;\n creative?: boolean;\n user?: string;\n lang?: string;\n branch?: boolean;\n specDir?: string;\n branchPrefix?: string;\n branchTemplate?: string;\n changeNameTemplate?: string;\n description?: string;\n intentType?: string;\n}\n\nexport async function createCommand(feature: 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 templateVars: NameTemplateVars = {\n prefix: branchPrefix,\n intentType: options.intentType,\n feature,\n date: getDateString(),\n user: options.user\n };\n\n const changeNameTemplate =\n options.changeNameTemplate || config.changeNameTemplate || '{date}-{feature}';\n const changeFolderName = renderNameTemplate(changeNameTemplate, templateVars, false);\n const changePath = join(cwd, specDir, 'changes', changeFolderName);\n\n if (existsSync(changePath)) {\n log.warn(\n `${symbol.warn} ${t(`change \"${changeFolderName}\" already exists`, `变更 \"${changeFolderName}\" 已存在`)}: ${changePath}`\n );\n return;\n }\n\n log.title(`${t('Creating Change', '创建变更')}: ${changeFolderName}`);\n if (options.intentType) {\n log.info(`${t('Intent Type', '意图类型')}: ${options.intentType}`);\n }\n\n if (boost) {\n log.boost(`${symbol.bolt} ${t('Boost mode enabled', '增强模式已启用')}`);\n }\n if (strategy === 'create') {\n log.boost(\n `${symbol.bolt} ${t('Creative mode: exploring new solutions', '创造模式:探索新方案')}`\n );\n }\n\n ensureDir(changePath);\n\n if (options.branch !== false && isGitRepo()) {\n const branchTemplate =\n options.branchTemplate || config.branchTemplate || '{prefix}{date}-{feature}';\n const branchName = renderNameTemplate(branchTemplate, templateVars, true);\n try {\n createBranch(branchName);\n log.success(`${symbol.ok} Branch: ${branchName}`);\n } catch (e: any) {\n log.warn(`${symbol.warn} ${t('branch creation failed', '分支创建失败')}: ${e.message}`);\n }\n }\n\n log.done(t('Change created successfully!', '变更创建成功!'));\n log.dim(`${t('Path', '路径')}: ${specDir}/changes/${changeFolderName}/`);\n log.dim(`${t('Templates', '模板参考')}: ${specDir}/templates/`);\n\n const expectedArtifacts = boost ? config.boostArtifacts : config.artifacts;\n log.dim(`${t('Expected artifacts', '预期 Artifacts')}: ${expectedArtifacts.join(', ')}`);\n log.dim(\n `${t('Next', '下一步')}: ${t('AI generates artifacts on demand via /ss-create', 'AI 按需通过 /ss-create 生成 artifacts')}`\n );\n}\n","import { execSync } from 'node:child_process';\n\nconst GIT_TIMEOUT = 10_000;\n\nexport function isGitRepo(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore', timeout: GIT_TIMEOUT });\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', {\n encoding: 'utf-8',\n timeout: GIT_TIMEOUT\n }).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', timeout: GIT_TIMEOUT });\n}\n\nexport function getDefaultBranch(): string {\n try {\n const ref = execSync('git symbolic-ref refs/remotes/origin/HEAD', {\n encoding: 'utf-8',\n timeout: GIT_TIMEOUT\n }).trim();\n return ref.replace('refs/remotes/origin/', '');\n } catch {\n try {\n execSync('git rev-parse --verify main', { stdio: 'ignore', timeout: GIT_TIMEOUT });\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`, {\n encoding: 'utf-8',\n timeout: GIT_TIMEOUT\n }).trim();\n const output = execSync(`git diff --name-status ${mergeBase}`, {\n encoding: 'utf-8',\n timeout: 30_000\n }).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 } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport function getPackageRoot(): string {\n const __filename = fileURLToPath(import.meta.url);\n let dir = dirname(__filename);\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'templates'))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return join(dirname(__filename), '..', '..');\n}\n","export interface NameTemplateVars {\n prefix?: string;\n intentType?: string;\n feature: string;\n date: string;\n user?: string;\n [key: string]: string | undefined;\n}\n\nexport function renderNameTemplate(\n template: string,\n vars: NameTemplateVars,\n strict = false\n): string {\n let result = template;\n\n for (const [key, value] of Object.entries(vars)) {\n if (value !== undefined) {\n result = result.replace(new RegExp(`\\\\{${key}\\\\}`, 'g'), value);\n }\n }\n\n result = result.replace(/\\{[^}]+\\}/g, '');\n\n if (strict) {\n return result\n .replace(/[^a-zA-Z0-9._\\-/]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n }\n return result\n .replace(/[^\\p{L}\\p{N}._\\-/]/gu, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n","import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { addDependency, parseFrontmatter, removeDependency } from '../core/frontmatter.js';\nimport { log, symbol, t } from '../ui/index.js';\n\nexport async function depsAddCommand(name: string, options: { on: string }): 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}\" ${t('not found', '未找到')}`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = addDependency(content, options.on);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → depends_on: ${options.on}`);\n}\n\nexport async function depsRemoveCommand(name: string, options: { on: string }): 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}\" ${t('not found', '未找到')}`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = removeDependency(content, options.on);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → ${t('removed dependency', '移除依赖')}: ${options.on}`);\n}\n\nexport async function depsListCommand(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 (name) {\n const proposalPath = join(changesDir, name, 'proposal.md');\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" ${t('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(` ${t('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} ${t('no changes directory', '未找到 changes 目录')}`);\n return;\n }\n\n const entries = readdirSync(changesDir, { withFileTypes: true }).filter(\n (e) => e.isDirectory() && e.name !== config.archive.dir\n );\n\n log.info(`${symbol.start} ${t('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 { execSync } from 'node:child_process';\nimport { existsSync, readdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getDefaultConfig } from '../core/config.js';\nimport { copyTemplate } from '../core/template.js';\nimport {\n AI_EDITORS,\n type AIEditor,\n installAgentsMd,\n installCommands,\n installRules\n} from '../prompts/index.js';\nimport { log, printLogo, printSummary, setLang, symbol, t, theme } from '../ui/index.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { isGitRepo } from '../utils/git.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(\n `${symbol.warn} ${t('superspec.config.json already exists, use --force to overwrite', 'superspec.config.json 已存在,使用 --force 覆盖')}`\n );\n return;\n }\n\n const lang = options.lang || 'zh';\n setLang(lang as 'zh' | 'en');\n\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 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 const existingFiles = readdirSync(cwd).filter((f) => !f.startsWith('.') && f !== 'node_modules');\n if (existingFiles.length > 0 && !options.force) {\n log.warn(\n `${symbol.warn} ${t(`current directory is not empty (${existingFiles.length} items)`, `当前目录非空(${existingFiles.length} 项)`)}`\n );\n log.dim(\n ` ${t('template files will be merged with existing content', '模板文件将与现有内容合并')}`\n );\n console.log();\n }\n\n log.section(t('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(t('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(t('Installing Templates', '安装模板'));\n const templateNames = Object.values(config.templates).map((v) =>\n v.endsWith('.md') ? v : `${v}.md`\n );\n for (const tpl of templateNames) {\n try {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n } catch {\n // skip missing templates\n }\n }\n log.success(`${symbol.ok} ${templateNames.length} ${t('templates', '个模板')} (${lang})`);\n\n log.section(t('Installing AI Agent Files', '安装 AI Agent 文件'));\n installAgentsMd(cwd);\n\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 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(t('SuperSpec initialized successfully!', 'SuperSpec 初始化成功!'));\n log.dim(`${t('Next', '下一步')}: superspec create <feature>`);\n}\n","import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { ensureDir } from '../utils/fs.js';\nimport { getPackageRoot } from '../utils/paths.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 fallbackLang = lang === 'zh' ? 'en' : 'zh';\n const fallback = join(root, 'templates', fallbackLang, 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(\n templateName: string,\n vars: Record<string, string> = {},\n lang: string = 'zh'\n): 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 { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { log, symbol } from '../ui/index.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { getPackageRoot } from '../utils/paths.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', '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 { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { lintChange } from '../core/lint.js';\nimport { log, symbol, t } from '../ui/index.js';\nimport { resolveChangeNames } from '../utils/fs.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 const names = resolveChangeNames(changesDir, name, config.archive.dir);\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} ${t('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}\" ${t('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} ${t('all artifacts within limits', '所有 artifact 均在限制范围内')}`);\n }\n}\n","import { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } 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 {\n artifact,\n lines,\n status: 'error',\n message: `${lines} lines exceeds hard limit (${hardLines}). Must split.`\n };\n }\n if (lines > targetLines) {\n return {\n artifact,\n lines,\n status: 'warn',\n message: `${lines} lines exceeds target (${targetLines}). Consider splitting.`\n };\n }\n return { artifact, lines, status: 'ok', message: `${lines} lines` };\n}\n\nexport function lintChange(\n changePath: string,\n targetLines: number,\n hardLines: number\n): 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 { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { log, symbol, t } from '../ui/index.js';\n\nconst DEFAULT_LIMIT = 50;\n\nexport interface SearchOptions {\n archived?: boolean;\n artifact?: string;\n limit?: string;\n regex?: boolean;\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} ${t('no changes directory found', '未找到 changes 目录')}`);\n return;\n }\n\n const dirs: { name: string; path: string }[] = [];\n\n const activeEntries = readdirSync(changesDir, { withFileTypes: true }).filter(\n (e) => e.isDirectory() && e.name !== config.archive.dir\n );\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 }).filter((e) =>\n e.isDirectory()\n );\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 let matcher: (line: string) => boolean;\n if (options.regex) {\n try {\n const re = new RegExp(query, 'i');\n matcher = (line) => re.test(line);\n } catch (e: any) {\n log.error(`${symbol.fail} ${t('invalid regex', '无效正则')}: ${e.message}`);\n return;\n }\n } else {\n const queryLower = query.toLowerCase();\n matcher = (line) => line.toLowerCase().includes(queryLower);\n }\n\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 (matcher(lines[i])) {\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} ${t(`no results for \"${query}\"`, `\"${query}\" 无结果`)}`);\n return;\n }\n\n const limit = options.limit ? parseInt(options.limit, 10) : DEFAULT_LIMIT;\n const shown = hits.slice(0, limit);\n\n log.info(`${symbol.start} ${hits.length} ${t('result(s) for', '条结果,搜索')} \"${query}\"`);\n for (const hit of shown) {\n log.dim(` ${hit.change}/${hit.artifact}:${hit.line} ${hit.text}`);\n }\n if (hits.length > limit) {\n log.dim(\n ` ... ${hits.length - limit} ${t('more result(s), use --limit to show more', '条更多结果,使用 --limit 显示更多')}`\n );\n }\n}\n","import { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { parseFrontmatter } from '../core/frontmatter.js';\nimport { log, symbol, t } 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} ${t('no changes directory found', '未找到 changes 目录')}`);\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(` ${t('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} ${t('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) =>\n e.isDirectory()\n );\n if (archived.length > 0) {\n log.dim(`\\n ${archived.length} ${t('archived change(s)', '个已归档变更')}`);\n }\n }\n}\n\nexport async function listCommand(options: { archived?: boolean }): 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)) return;\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 for (const e of entries) {\n console.log(e.name);\n }\n\n if (options.archived) {\n const archiveDir = join(changesDir, config.archive.dir);\n if (existsSync(archiveDir)) {\n const archived = readdirSync(archiveDir, { withFileTypes: true }).filter((e) =>\n e.isDirectory()\n );\n for (const e of archived) {\n console.log(`${config.archive.dir}/${e.name}`);\n }\n }\n }\n}\n\nfunction stripAnsi(str: string): string {\n return str.replace(/\\u001b\\[\\d+(?:;\\d+)*m/g, '').replace(/[✅🟢🟡—]/gu, 'XX');\n}\n","import { existsSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { generateContext } from '../core/context.js';\nimport { log, symbol, t } from '../ui/index.js';\nimport { resolveChangeNames } from '../utils/fs.js';\n\nexport async function syncCommand(\n name: string | undefined,\n opts: { base?: string; git?: boolean }\n): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n const names = resolveChangeNames(changesDir, name, config.archive.dir);\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} ${t('no changes found', '未找到变更')}`);\n return;\n }\n\n const useGit = opts.git !== false;\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" ${t('not found', '未找到')}`);\n continue;\n }\n\n const content = generateContext(changePath, n, {\n gitDiff: useGit,\n baseBranch: opts.base\n });\n const destPath = join(changePath, 'context.md');\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} ${t('synced', '已同步')} ${n}/context.md`);\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getDateString } from '../utils/date.js';\nimport { type GitChange, getDiffFiles } from '../utils/git.js';\nimport { parseFrontmatter, serializeFrontmatter } from './frontmatter.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 = regex.exec(body);\n while (match !== null) {\n const p = match[1];\n if (p.includes('/') && !p.startsWith('http') && !p.includes(' ')) {\n paths.add(p);\n }\n match = regex.exec(body);\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\n .split('|')\n .map((c) => c.trim())\n .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 =\n 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(\n changePath: string,\n changeName: string,\n options: GenerateContextOptions = {}\n): 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 const mode = spec ? 'boost' : 'standard';\n const proposalMeta = proposal ? parseFrontmatter(proposal).meta : {};\n const strategy = proposalMeta.strategy || 'follow';\n const status = proposalMeta.status || 'in-progress';\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 fmData: Record<string, string> = {\n name: changeName,\n status,\n strategy,\n mode,\n updated: getDateString()\n };\n if (proposalMeta.input) fmData.input = proposalMeta.input;\n const fm = serializeFrontmatter(fmData);\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 === true) {\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","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { copyTemplate } from '../core/template.js';\nimport {\n AI_EDITORS,\n type AIEditor,\n installAgentsMd,\n installCommands,\n installRules\n} from '../prompts/index.js';\nimport { log, symbol, t } from '../ui/index.js';\nimport { ensureDir } from '../utils/fs.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(\n `${symbol.warn} ${t('not initialized, run superspec init first', '当前目录未初始化 SuperSpec,请先运行 superspec init')}`\n );\n return;\n }\n\n log.info(`${symbol.start} ${t('updating SuperSpec...', '更新 SuperSpec...')}`);\n\n const templateNames = Object.values(config.templates).map((v) =>\n v.endsWith('.md') ? v : `${v}.md`\n );\n ensureDir(join(specDir, 'templates'));\n for (const tpl of templateNames) {\n try {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n } catch {\n // skip missing templates\n }\n }\n log.success(` ${symbol.ok} ${t('templates updated', '模板更新')} (${lang})`);\n\n installAgentsMd(cwd);\n\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} ${t('update done!', '更新完成!')}`);\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { validateChange } from '../core/validate.js';\nimport { log, symbol, t } from '../ui/index.js';\nimport { resolveChangeNames } from '../utils/fs.js';\n\nexport interface ValidateOptions {\n checkDeps?: boolean;\n}\n\nexport async function validateCommand(\n name: string | undefined,\n options: ValidateOptions\n): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n const names = resolveChangeNames(changesDir, name, config.archive.dir);\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} ${t('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}\" ${t('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} ${t('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} ${t('all validations passed', '所有验证通过')}`);\n } else {\n log.warn(`${symbol.warn} ${totalIssues} ${t('issue(s) found', '个问题')}`);\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } 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({\n level: 'error',\n artifact: 'spec.md',\n message: `${id} from proposal not found in spec`\n });\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({\n level: 'warn',\n artifact: 'tasks.md',\n message: `${id} from spec not referenced in tasks`\n });\n }\n }\n\n for (const id of tasksFR) {\n if (!specFR.includes(id)) {\n issues.push({\n level: 'error',\n artifact: 'tasks.md',\n message: `${id} referenced but not defined in spec`\n });\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({\n level: 'warn',\n artifact: 'spec.md',\n message: `${ac} has no parent ${parentFR}`\n });\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({\n level: 'warn',\n artifact: 'proposal.md',\n message: `content mentions \"${dep}\" but not in frontmatter depends_on`\n });\n }\n }\n\n const changesDir = join(changePath, '..');\n for (const dep of fmDeps) {\n if (!existsSync(join(changesDir, dep))) {\n issues.push({\n level: 'error',\n artifact: 'proposal.md',\n message: `depends_on \"${dep}\" not found in changes`\n });\n }\n }\n }\n\n return issues;\n}\n"],"mappings":"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","names":["existsSync","readdirSync","join","existsSync","join","existsSync","readdirSync","existsSync","join","existsSync","join","__filename","join","existsSync","existsSync","readdirSync","readFileSync","join","execSync","existsSync","readdirSync","writeFileSync","join","existsSync","readFileSync","writeFileSync","dirname","join","join","existsSync","dirname","existsSync","readdirSync","readFileSync","writeFileSync","join","join","existsSync","readFileSync","writeFileSync","readdirSync","join","existsSync","readdirSync","writeFileSync","execSync","existsSync","join","existsSync","readdirSync","readFileSync","join","existsSync","readdirSync","readFileSync","basename","join","existsSync","readdirSync","readFileSync","join","existsSync","writeFileSync","join","existsSync","readFileSync","join","existsSync","join","join","existsSync","existsSync","join","existsSync","readFileSync","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superspec/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Spec-driven development for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/prompts/agents.md CHANGED
@@ -12,7 +12,8 @@
12
12
  - Run `/ss-status` or check `{specDir}/changes/` → know active changes
13
13
  - Review related changes via `depends_on` to avoid duplication
14
14
  4. **Read current change context**:
15
- - Read frontmatter get `strategy` (may override config)
15
+ - Determine `strategy` by priority: user input `-c` > config default
16
+ - Read frontmatter `input` field → understand original user intent
16
17
  - If `strategy: follow` → treat context files as constraints (must follow)
17
18
  - If `strategy: create` → treat context files as awareness (may deviate, must justify)
18
19
  5. **Never create change folders manually** → use `superspec create` CLI or `/ss-create`
@@ -36,7 +37,10 @@
36
37
  | | Standard (lightweight) | Boost (enhanced) |
37
38
  |---|---|---|
38
39
  | **场景** | Simple tasks, bug fixes, small features | Large features, breaking changes, complex designs |
39
- | **Artifacts** | proposal + tasks | proposal + spec + tasks + checklist |
40
+ | **Artifacts** | proposal + checklist + tasks | proposal + spec + checklist + tasks (+ design optional) |
41
+ | **proposal 定位** | 需求 + 技术方案(自含,可直接拆 task) | 需求背景(Goals, Risks, Impact) |
42
+ | **spec 定位** | — | 需求细节 + 交互(US/FR/AC/Edge Cases) |
43
+ | **Checklist 时机** | proposal 后自动检查(/ 10) | spec 后自动检查(/ 25) |
40
44
  | **Task granularity** | Flexible | < 1h per task |
41
45
  | **Cross-validation** | — | Auto: US↔FR↔AC↔tasks |
42
46
  | **Edge cases** | Basic | Comprehensive |
@@ -44,9 +48,9 @@
44
48
  **核心流程**:
45
49
 
46
50
  ```
47
- Standard: /ss-create → /ss-tasks → /ss-apply → [vibe: sync → /ss-resume] → /ss-archive
48
- Boost: /ss-create -b /ss-tasks /ss-apply → [vibe: sync → /ss-resume] → /ss-archive
49
- On-demand: /ss-clarify, /ss-lint, /ss-validate, /ss-search, /ss-link, /ss-deps
51
+ Standard: /ss-create (proposal checklist ✓) → /ss-tasks → /ss-apply → [vibe: sync → /ss-resume] → /ss-archive
52
+ Boost: /ss-create -b (proposalspec → [auto: split? design?] checklist ✓) → /ss-tasks → /ss-apply → ...
53
+ On-demand: /ss-clarify, /ss-checklist, /ss-lint, /ss-validate, /ss-search, /ss-link, /ss-deps
50
54
  ```
51
55
 
52
56
  ---
@@ -105,12 +109,12 @@ Config `context` lists files the AI should read to understand project convention
105
109
 
106
110
  | Command | Mode | What it does |
107
111
  |---------|------|-------------|
108
- | `/ss-create <feature>` | Both | Create change + generate proposal (boost: + spec + checklist) |
109
- | `/ss-tasks` | Both | Generate task list from proposal (boost: from proposal + spec) |
112
+ | `/ss-create <feature>` | Both | Create folder + branch, generate proposal (+ spec in boost), auto-run checklist gate |
113
+ | `/ss-tasks` | Both | AI generates task list from proposal (boost: from proposal + spec) |
110
114
  | `/ss-apply` | Both | Implement tasks |
111
115
  | `/ss-clarify` | Both | Resolve ambiguity |
112
116
  | `/ss-archive` | Both | Archive completed change |
113
- | `/ss-checklist` | Boost | Quality gate before apply |
117
+ | `/ss-checklist` | Both | Quality gate: Standard (/ 10 after proposal) or Boost (/ 25 after spec). Auto-invoked by /ss-create, also callable manually |
114
118
  | `/ss-status` | Both | View all changes |
115
119
  | `/ss-lint` | Both | Check artifact sizes |
116
120
  | `/ss-validate` | Boost | Cross-reference consistency check |
@@ -124,22 +128,34 @@ Config `context` lists files the AI should read to understand project convention
124
128
 
125
129
  ## 📐 Artifacts
126
130
 
131
+ **On-demand generation**: CLI `superspec create` only creates the folder + git branch. AI reads templates from `{specDir}/templates/` as structural reference, then generates each artifact with real content when needed — never pre-creates empty template files.
132
+
133
+ | Artifact | Generated by | When |
134
+ |----------|-------------|------|
135
+ | proposal.md | `/ss-create` | Always (Standard: requirements + tech solution; Boost: requirements background) |
136
+ | spec.md | `/ss-create -b` | Boost mode (requirement details + interactions) |
137
+ | design.md | `/ss-create -b` | Boost mode, auto-detected when needed |
138
+ | checklist.md | `/ss-create` (auto) | Always, after proposal (Standard) or after spec (Boost) |
139
+ | tasks.md | `/ss-tasks` | On demand, after checklist passes |
140
+ | clarify.md | `/ss-clarify` | On demand |
141
+
127
142
  **Standard:**
128
143
  ```
129
144
  {specDir}/changes/<name>/
130
- ├── proposal.md — Why and what
131
- └── tasks.md Actionable steps
145
+ ├── proposal.md — Requirements + technical solution (generated by /ss-create)
146
+ ├── checklist.md Quality gate / 10 (auto-generated by /ss-create)
147
+ └── tasks.md — Actionable steps (generated by /ss-tasks)
132
148
  ```
133
149
 
134
150
  **Boost:**
135
151
  ```
136
152
  {specDir}/changes/<name>/
137
- ├── proposal.md — Why and what
138
- ├── spec.md — Requirements (US/FR/AC)
139
- ├── design.md — Architecture decisions (optional, for complex changes)
140
- ├── tasks.md Phased implementation steps
141
- ├── clarify.md Q&A and decisions (on-demand)
142
- └── checklist.md Quality validation
153
+ ├── proposal.md — Requirements background (generated by /ss-create -b)
154
+ ├── spec.md — Requirement details + interactions (generated by /ss-create -b)
155
+ ├── design.md — Architecture decisions (optional, auto-detected by /ss-create -b)
156
+ ├── checklist.md Quality gate / 25 (auto-generated by /ss-create -b)
157
+ ├── tasks.md Phased implementation steps (generated by /ss-tasks)
158
+ └── clarify.md Q&A and decisions (generated by /ss-clarify)
143
159
  ```
144
160
 
145
161
  **When to use design.md** (optional in boost mode):
@@ -172,7 +188,9 @@ When a change involves multiple distinct capabilities, split specs by capability
172
188
  - Easier parallel review and implementation
173
189
  - Better traceability for cross-references
174
190
 
175
- Each artifact has YAML frontmatter: `name`, `status`, `strategy`, `depends_on: []`.
191
+ Each artifact has YAML frontmatter: `name`, `status`, `strategy`, `depends_on: []`, `input` (proposal.md only, records user's original input).
192
+
193
+ **Strategy priority** (highest to lowest): user input `-c` > `superspec.config.json` default.
176
194
 
177
195
  ---
178
196