@superspec/cli 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +295 -258
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +159 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/prompts/agents.md +51 -9
- package/prompts/{cursor-rules.md → rules.md} +7 -7
- package/templates/en/commands/ss-create.md +90 -8
- package/templates/en/commands/ss-link.md +2 -1
- package/templates/en/commands/ss-specs.md +54 -0
- package/templates/en/design.md +126 -0
- package/templates/zh/commands/ss-create.md +90 -8
- package/templates/zh/commands/ss-link.md +2 -1
- package/templates/zh/commands/ss-specs.md +54 -0
- package/templates/zh/design.md +126 -0
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/commands/archive.ts","../src/commands/update.ts","../src/commands/lint.ts","../src/commands/validate.ts","../src/commands/search.ts","../src/commands/link.ts","../src/commands/status.ts","../src/commands/context.ts","../src/commands/sync.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Strategy = 'follow' | 'create';\nexport type AIEditorType = 'claude' | 'cursor' | 'qwen' | 'opencode' | 'codex' | 'codebuddy' | 'qoder';\n\nexport interface SuperSpecConfig {\n lang: 'zh' | 'en';\n aiEditor: AIEditorType;\n specDir: string;\n branchPrefix: string;\n branchTemplate: string;\n boost: boolean;\n strategy: Strategy;\n context: string[];\n templates: Record<string, string>;\n archive: {\n dir: string;\n datePrefix: boolean;\n };\n limits: {\n targetLines: number;\n hardLines: number;\n };\n artifacts: string[];\n boostArtifacts: string[];\n}\n\nconst DEFAULT_CONFIG: SuperSpecConfig = {\n lang: 'zh',\n aiEditor: 'cursor',\n specDir: 'superspec',\n branchPrefix: 'spec/',\n branchTemplate: '{prefix}{name}',\n boost: false,\n strategy: 'follow',\n context: [],\n templates: {\n spec: 'spec.md',\n proposal: 'proposal.md',\n tasks: 'tasks.md',\n clarify: 'clarify.md',\n checklist: 'checklist.md',\n },\n archive: {\n dir: 'archive',\n datePrefix: true,\n },\n limits: {\n targetLines: 300,\n hardLines: 400,\n },\n artifacts: ['proposal'],\n boostArtifacts: ['proposal', 'spec', 'tasks', 'checklist'],\n};\n\nexport function loadConfig(projectRoot: string = process.cwd()): SuperSpecConfig {\n const configPath = join(projectRoot, 'superspec.config.json');\n let userConfig: Partial<SuperSpecConfig> = {};\n\n if (existsSync(configPath)) {\n try {\n userConfig = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch (e: any) {\n console.warn(`⚠ 配置文件解析失败: ${e.message}`);\n }\n }\n\n return deepMerge(DEFAULT_CONFIG, userConfig) as SuperSpecConfig;\n}\n\nexport function getDefaultConfig(): SuperSpecConfig {\n return structuredClone(DEFAULT_CONFIG);\n}\n\nfunction deepMerge(target: Record<string, any>, source: Record<string, any>): Record<string, any> {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === 'object' &&\n !Array.isArray(source[key]) &&\n target[key] &&\n typeof target[key] === 'object'\n ) {\n result[key] = deepMerge(target[key], source[key]);\n } else {\n result[key] = source[key];\n }\n }\n return result;\n}\n","import { existsSync, copyFileSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { getPackageRoot } from '../utils/paths.js';\nimport { ensureDir } from '../utils/fs.js';\n\nexport function resolveTemplatePath(templateName: string, lang: string = 'zh'): string {\n const root = getPackageRoot();\n const langPath = join(root, 'templates', lang, templateName);\n if (existsSync(langPath)) return langPath;\n const fallback = join(root, 'templates', 'zh', templateName);\n if (existsSync(fallback)) return fallback;\n throw new Error(`Template not found: ${templateName} (lang: ${lang})`);\n}\n\nexport function copyTemplate(templateName: string, destPath: string, lang: string = 'zh'): void {\n const srcPath = resolveTemplatePath(templateName, lang);\n ensureDir(dirname(destPath));\n copyFileSync(srcPath, destPath);\n}\n\n/**\n * Simple template engine supporting:\n * - {{variable}} - variable substitution\n * - {{#if variable}}...{{/if}} - conditional blocks (shown if variable is truthy)\n */\nexport function renderTemplate(templateName: string, vars: Record<string, string> = {}, lang: string = 'zh'): string {\n const srcPath = resolveTemplatePath(templateName, lang);\n let content = readFileSync(srcPath, 'utf-8');\n\n // Process conditionals first: {{#if variable}}...{{/if}}\n content = processConditionals(content, vars);\n\n // Process simple variable substitution: {{variable}}\n for (const [key, value] of Object.entries(vars)) {\n content = content.replaceAll(`{{${key}}}`, value);\n }\n\n return content;\n}\n\nfunction processConditionals(content: string, vars: Record<string, string>): string {\n // Match {{#if variable}}...{{/if}} patterns\n const ifRegex = /\\{\\{#if\\s+(\\w+)\\}\\}([\\s\\S]*?)\\{\\{\\/if\\}\\}/g;\n\n return content.replace(ifRegex, (match, varName, innerContent) => {\n const value = vars[varName];\n // Show content if variable exists and is not empty\n if (value && value.trim() !== '') {\n return innerContent;\n }\n return '';\n });\n}\n\nexport function writeRenderedTemplate(\n templateName: string,\n destPath: string,\n vars: Record<string, string> = {},\n lang: string = 'zh',\n): void {\n const content = renderTemplate(templateName, vars, lang);\n ensureDir(dirname(destPath));\n writeFileSync(destPath, content, 'utf-8');\n}\n","import { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport function getPackageRoot(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, '..', '..');\n}\n","import { mkdirSync, existsSync } from 'node:fs';\n\nexport function ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n","import { readFileSync, existsSync, readdirSync } from 'node:fs';\nimport { join, basename } from 'node:path';\n\nexport interface LintResult {\n artifact: string;\n lines: number;\n status: 'ok' | 'warn' | 'error';\n message: string;\n}\n\nexport function lintArtifact(filePath: string, targetLines: number, hardLines: number): LintResult {\n const artifact = basename(filePath);\n if (!existsSync(filePath)) {\n return { artifact, lines: 0, status: 'ok', message: 'not found' };\n }\n\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').length;\n\n if (lines > hardLines) {\n return { artifact, lines, status: 'error', message: `${lines} lines exceeds hard limit (${hardLines}). Must split.` };\n }\n if (lines > targetLines) {\n return { artifact, lines, status: 'warn', message: `${lines} lines exceeds target (${targetLines}). Consider splitting.` };\n }\n return { artifact, lines, status: 'ok', message: `${lines} lines` };\n}\n\nexport function lintChange(changePath: string, targetLines: number, hardLines: number): LintResult[] {\n if (!existsSync(changePath)) return [];\n\n const files = readdirSync(changePath).filter((f) => f.endsWith('.md'));\n return files.map((f) => lintArtifact(join(changePath, f), targetLines, hardLines));\n}\n","import { readFileSync, existsSync, readdirSync } from 'node:fs';\nimport { join, basename } from 'node:path';\nimport { parseFrontmatter } from './frontmatter.js';\n\nexport interface ValidationIssue {\n level: 'error' | 'warn' | 'info';\n artifact: string;\n message: string;\n}\n\nfunction extractIds(content: string, pattern: RegExp): string[] {\n const matches = content.match(pattern);\n return matches ? [...new Set(matches)] : [];\n}\n\nexport function validateChange(changePath: string, checkDeps = false): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n const read = (name: string): string | null => {\n const p = join(changePath, name);\n return existsSync(p) ? readFileSync(p, 'utf-8') : null;\n };\n\n const proposal = read('proposal.md');\n const spec = read('spec.md');\n const tasks = read('tasks.md');\n\n if (!proposal) issues.push({ level: 'warn', artifact: 'proposal.md', message: 'missing' });\n if (!tasks) issues.push({ level: 'warn', artifact: 'tasks.md', message: 'missing' });\n\n if (proposal && spec) {\n const proposalGoals = extractIds(proposal, /US-\\d+/g);\n const specUS = extractIds(spec, /US-\\d+/g);\n for (const id of proposalGoals) {\n if (!specUS.includes(id)) {\n issues.push({ level: 'error', artifact: 'spec.md', message: `${id} from proposal not found in spec` });\n }\n }\n }\n\n if (spec && tasks) {\n const specFR = extractIds(spec, /FR-\\d+/g);\n const tasksFR = extractIds(tasks, /FR-\\d+/g);\n\n for (const id of specFR) {\n if (!tasksFR.includes(id)) {\n issues.push({ level: 'warn', artifact: 'tasks.md', message: `${id} from spec not referenced in tasks` });\n }\n }\n\n for (const id of tasksFR) {\n if (!specFR.includes(id)) {\n issues.push({ level: 'error', artifact: 'tasks.md', message: `${id} referenced but not defined in spec` });\n }\n }\n }\n\n if (spec) {\n const acIds = extractIds(spec, /AC-\\d+\\.\\d+/g);\n const frIds = extractIds(spec, /FR-\\d+/g);\n for (const ac of acIds) {\n const frNum = ac.replace('AC-', '').split('.')[0];\n const parentFR = `FR-${frNum}`;\n if (!frIds.includes(parentFR)) {\n issues.push({ level: 'warn', artifact: 'spec.md', message: `${ac} has no parent ${parentFR}` });\n }\n }\n }\n\n if (checkDeps && proposal) {\n const { meta, body } = parseFrontmatter(proposal);\n const fmDeps: string[] = Array.isArray(meta.depends_on) ? meta.depends_on : [];\n\n const contentRefs = extractIds(body, /depends[_ ]on[:\\s]+(\\S+)/gi);\n const mentioned = contentRefs.map((m) => m.replace(/depends[_ ]on[:\\s]+/i, ''));\n\n for (const dep of mentioned) {\n if (!fmDeps.includes(dep)) {\n issues.push({ level: 'warn', artifact: 'proposal.md', message: `content mentions \"${dep}\" but not in frontmatter depends_on` });\n }\n }\n\n const changesDir = join(changePath, '..');\n for (const dep of fmDeps) {\n if (!existsSync(join(changesDir, dep))) {\n issues.push({ level: 'error', artifact: 'proposal.md', message: `depends_on \"${dep}\" not found in changes` });\n }\n }\n }\n\n return issues;\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { parseFrontmatter, serializeFrontmatter } from './frontmatter.js';\nimport { getDateString } from '../utils/date.js';\nimport { getDiffFiles, type GitChange } from '../utils/git.js';\n\nexport interface ContextData {\n name: string;\n status: string;\n strategy: string;\n mode: string;\n updated: string;\n goals: string[];\n progress: { total: number; done: number; items: string[] };\n decisions: string[];\n files: string[];\n}\n\nfunction extractSection(body: string, heading: string): string[] {\n const regex = new RegExp(`^##\\\\s+${heading}[\\\\s\\\\S]*?$`, 'im');\n const match = body.match(regex);\n if (!match) return [];\n\n const startIdx = body.indexOf(match[0]) + match[0].length;\n const rest = body.slice(startIdx);\n const nextSection = rest.match(/^##\\s+/m);\n const sectionContent = nextSection ? rest.slice(0, nextSection.index) : rest;\n\n return sectionContent\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.length > 0 && !l.startsWith('<!--'));\n}\n\nfunction parseTaskItems(body: string): { total: number; done: number; items: string[] } {\n const lines = body.split('\\n');\n const items: string[] = [];\n let done = 0;\n let total = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n const checked = trimmed.match(/^-\\s*\\[x\\]\\s+(.*)/i);\n const unchecked = trimmed.match(/^-\\s*\\[\\s\\]\\s+(.*)/);\n\n if (checked) {\n total++;\n done++;\n const desc = checked[1].trim();\n items.push(`- [x] ${desc}`);\n } else if (unchecked) {\n total++;\n const desc = unchecked[1].trim();\n items.push(`- [ ] ${desc}`);\n }\n }\n\n return { total, done, items };\n}\n\nfunction extractFilePaths(body: string): string[] {\n const paths = new Set<string>();\n const regex = /`([^`]+\\.[a-zA-Z]+)`/g;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(body)) !== null) {\n const p = match[1];\n if (p.includes('/') && !p.startsWith('http') && !p.includes(' ')) {\n paths.add(p);\n }\n }\n return [...paths];\n}\n\nfunction extractDecisions(body: string): string[] {\n const lines = body.split('\\n');\n const decisions: string[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (/^\\|?\\s*D\\d+\\s*\\|/.test(trimmed)) {\n const cells = trimmed.split('|').map((c) => c.trim()).filter(Boolean);\n if (cells.length >= 2) {\n decisions.push(`- ${cells[0]}: ${cells[1]}`);\n }\n }\n }\n return decisions;\n}\n\nexport interface GenerateContextOptions {\n gitDiff?: boolean;\n baseBranch?: string;\n}\n\nconst STATUS_LABELS: Record<string, string> = {\n A: 'added',\n M: 'modified',\n D: 'deleted',\n R: 'renamed',\n};\n\nfunction classifyGitChanges(gitChanges: GitChange[], taskFiles: string[]): string[] {\n const taskFileSet = new Set(taskFiles);\n const lines: string[] = [];\n for (const { status, file } of gitChanges) {\n const label = STATUS_LABELS[status] || status;\n const inTasks = taskFileSet.has(file) || taskFiles.some((tf) => file.endsWith(tf) || tf.endsWith(file));\n const tag = inTasks ? '' : ' (unplanned)';\n lines.push(`- ${label}: ${file}${tag}`);\n }\n return lines;\n}\n\nexport function generateContext(changePath: string, changeName: string, options: GenerateContextOptions = {}): string {\n const read = (name: string): string | null => {\n const p = join(changePath, name);\n return existsSync(p) ? readFileSync(p, 'utf-8') : null;\n };\n\n const proposal = read('proposal.md');\n const spec = read('spec.md');\n const tasks = read('tasks.md');\n const clarify = read('clarify.md');\n\n let strategy = 'follow';\n let status = 'in-progress';\n const mode = spec ? 'boost' : 'standard';\n\n if (proposal) {\n const { meta } = parseFrontmatter(proposal);\n if (meta.strategy) strategy = meta.strategy;\n if (meta.status) status = meta.status;\n }\n\n const goals: string[] = [];\n if (proposal) {\n const { body } = parseFrontmatter(proposal);\n const goalLines = extractSection(body, '目标|Goals');\n for (const line of goalLines) {\n const cleaned = line.replace(/^-\\s*\\[.\\]\\s*/, '- ').replace(/^-\\s*/, '');\n if (cleaned) goals.push(`- ${cleaned}`);\n }\n }\n\n let progress = { total: 0, done: 0, items: [] as string[] };\n let files: string[] = [];\n if (tasks) {\n const { body } = parseFrontmatter(tasks);\n progress = parseTaskItems(body);\n files = extractFilePaths(body);\n }\n\n const decisions: string[] = [];\n if (clarify) {\n const { body } = parseFrontmatter(clarify);\n decisions.push(...extractDecisions(body));\n }\n\n const existingContext = read('context.md');\n let notes = '';\n if (existingContext) {\n const { body } = parseFrontmatter(existingContext);\n const notesSection = extractSection(body, 'Notes');\n if (notesSection.length > 0) {\n notes = notesSection.join('\\n');\n }\n }\n\n const fm = serializeFrontmatter({\n name: changeName,\n status,\n strategy,\n mode,\n updated: getDateString(),\n });\n\n const lines: string[] = [fm, ''];\n\n if (goals.length > 0) {\n lines.push('## Goals', ...goals, '');\n }\n\n if (progress.total > 0) {\n lines.push(`## Progress (${progress.done}/${progress.total} tasks)`);\n lines.push(...progress.items, '');\n }\n\n if (decisions.length > 0) {\n lines.push('## Decisions', ...decisions, '');\n }\n\n if (files.length > 0) {\n lines.push('## Affected Files');\n for (const f of files) {\n lines.push(`- ${f}`);\n }\n lines.push('');\n }\n\n if (options.gitDiff !== false) {\n const gitChanges = getDiffFiles(options.baseBranch);\n if (gitChanges.length > 0) {\n const classified = classifyGitChanges(gitChanges, files);\n lines.push('## Git Changes', ...classified, '');\n }\n }\n\n lines.push('## Notes');\n if (notes) {\n lines.push(notes);\n }\n lines.push('');\n\n return lines.join('\\n');\n}\n","export function getDateString(): string {\n const d = new Date();\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n}\n","import { execSync } from 'node:child_process';\n\nexport function isGitRepo(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getCurrentBranch(): string | null {\n try {\n return execSync('git branch --show-current', { encoding: 'utf-8' }).trim();\n } catch {\n return null;\n }\n}\n\nconst SAFE_BRANCH_RE = /^[a-zA-Z0-9._\\-/]+$/;\n\nexport function createBranch(branchName: string): void {\n if (!SAFE_BRANCH_RE.test(branchName)) {\n throw new Error(`invalid branch name: ${branchName}`);\n }\n execSync(`git checkout -b ${branchName}`, { stdio: 'inherit' });\n}\n\nexport function getDefaultBranch(): string {\n try {\n const ref = execSync('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf-8' }).trim();\n return ref.replace('refs/remotes/origin/', '');\n } catch {\n try {\n execSync('git rev-parse --verify main', { stdio: 'ignore' });\n return 'main';\n } catch {\n return 'master';\n }\n }\n}\n\nexport interface GitChange {\n status: string;\n file: string;\n}\n\nexport function getDiffFiles(base?: string): GitChange[] {\n const baseBranch = base || getDefaultBranch();\n try {\n const mergeBase = execSync(`git merge-base ${baseBranch} HEAD`, { encoding: 'utf-8' }).trim();\n const output = execSync(`git diff --name-status ${mergeBase}`, { encoding: 'utf-8' }).trim();\n if (!output) return [];\n return output.split('\\n').map((line) => {\n const [status, ...parts] = line.split('\\t');\n return { status: status.charAt(0), file: parts.join('\\t') };\n });\n } catch {\n return [];\n }\n}\n","import { existsSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { getDefaultConfig } from '../core/config.js';\nimport { copyTemplate } from '../core/template.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { isGitRepo } from '../utils/git.js';\nimport { installRules, installAgentsMd, installCommands, AI_EDITORS, type AIEditor } from '../prompts/index.js';\nimport { log, symbol, printLogo, printSummary, theme } from '../ui/index.js';\n\nexport interface InitOptions {\n ai: string;\n lang: string;\n force?: boolean;\n git?: boolean;\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n const cwd = process.cwd();\n const configPath = join(cwd, 'superspec.config.json');\n\n if (existsSync(configPath) && !options.force) {\n log.warn(`${symbol.warn} superspec.config.json already exists, use --force to overwrite`);\n return;\n }\n\n const lang = options.lang || 'zh';\n\n // Print logo\n printLogo('small');\n console.log(theme.dim(' Spec-Driven Development Toolkit\\n'));\n\n const config = getDefaultConfig();\n config.lang = lang as 'zh' | 'en';\n\n // Set AI editor in config\n const aiEditor = options.ai as AIEditor;\n if (aiEditor && AI_EDITORS[aiEditor]) {\n config.aiEditor = aiEditor;\n }\n\n const specDir = join(cwd, config.specDir);\n\n // Check if directory is not empty\n const existingFiles = readdirSync(cwd).filter(f => !f.startsWith('.') && f !== 'node_modules');\n if (existingFiles.length > 0 && !options.force) {\n log.warn(`${symbol.warn} Current directory is not empty (${existingFiles.length} items)`);\n log.dim(' Template files will be merged with existing content');\n console.log();\n }\n\n log.section('Creating Configuration');\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n log.success(`${symbol.file} superspec.config.json`);\n\n log.section('Creating Directory Structure');\n ensureDir(join(specDir, 'changes'));\n ensureDir(join(specDir, 'templates'));\n log.success(`${symbol.folder} ${config.specDir}/changes/`);\n log.success(`${symbol.folder} ${config.specDir}/templates/`);\n\n log.section('Installing Templates');\n const templates = ['spec.md', 'proposal.md', 'tasks.md', 'clarify.md', 'checklist.md'];\n for (const tpl of templates) {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n }\n log.success(`${symbol.ok} ${templates.length} templates (${lang})`);\n\n log.section('Installing AI Agent Files');\n installAgentsMd(cwd);\n\n // Install rules and commands for the selected AI editor\n if (aiEditor && AI_EDITORS[aiEditor]) {\n installRules(cwd, aiEditor);\n installCommands(cwd, aiEditor, lang);\n }\n\n if (options.git !== false && !isGitRepo()) {\n execSync('git init', { cwd, stdio: 'inherit' });\n log.success(`${symbol.git} git init`);\n }\n\n // Print summary\n console.log();\n printSummary([\n { label: 'Config', value: 'superspec.config.json' },\n { label: 'Spec dir', value: `${config.specDir}/` },\n { label: 'AI agent', value: options.ai },\n { label: 'Language', value: lang },\n ]);\n\n log.done('SuperSpec initialized successfully!');\n log.dim('Next: Run superspec create <name> to create a change');\n}\n","import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getPackageRoot } from '../utils/paths.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { log, symbol } from '../ui/index.js';\n\n// Supported AI editors configuration\nexport const AI_EDITORS = {\n claude: {\n commands: '.claude/commands',\n rules: null, // Claude doesn't use rules files\n },\n cursor: {\n commands: '.cursor/commands',\n rules: '.cursor/rules',\n rulesFile: 'superspec.mdc',\n },\n qwen: {\n commands: '.qwen/commands',\n rules: '.qwen/rules',\n rulesFile: 'superspec.md',\n },\n opencode: {\n commands: '.opencode/commands',\n rules: null,\n },\n codex: {\n commands: '.codex/commands',\n rules: null,\n },\n codebuddy: {\n commands: '.codebuddy/commands',\n rules: '.codebuddy/rules',\n rulesFile: 'superspec.md',\n },\n qoder: {\n commands: '.qoder/commands',\n rules: '.qoder/rules',\n rulesFile: 'superspec.md',\n },\n} as const;\n\nexport type AIEditor = keyof typeof AI_EDITORS;\n\n/**\n * Install rules file for a specific AI editor\n */\nexport function installRules(cwd: string, editor: AIEditor): void {\n const config = AI_EDITORS[editor];\n\n // Skip if this editor doesn't use rules files\n if (!config.rules) {\n return;\n }\n\n const rulesDir = join(cwd, config.rules);\n ensureDir(rulesDir);\n\n const promptSrc = join(getPackageRoot(), 'prompts', 'cursor-rules.md');\n if (existsSync(promptSrc)) {\n const content = readFileSync(promptSrc, 'utf-8');\n const rulesFile = 'rulesFile' in config ? config.rulesFile : 'superspec.md';\n const destPath = join(rulesDir, rulesFile as string);\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} ${config.rules}/${rulesFile}`);\n }\n}\n\nconst SS_START = '<!-- superspec:start -->';\nconst SS_END = '<!-- superspec:end -->';\n\nexport function installAgentsMd(cwd: string): void {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n const agentPromptSrc = join(getPackageRoot(), 'prompts', 'agents.md');\n\n if (!existsSync(agentPromptSrc)) return;\n\n const newContent = readFileSync(agentPromptSrc, 'utf-8');\n const wrapped = `${SS_START}\\n${newContent}\\n${SS_END}`;\n\n if (existsSync(agentsMdPath)) {\n const existing = readFileSync(agentsMdPath, 'utf-8');\n const startIdx = existing.indexOf(SS_START);\n const endIdx = existing.indexOf(SS_END);\n\n if (startIdx !== -1 && endIdx !== -1) {\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + SS_END.length);\n writeFileSync(agentsMdPath, before + wrapped + after, 'utf-8');\n } else if (existing.includes('SuperSpec')) {\n writeFileSync(agentsMdPath, existing, 'utf-8');\n } else {\n writeFileSync(agentsMdPath, existing + '\\n\\n' + wrapped, 'utf-8');\n }\n } else {\n writeFileSync(agentsMdPath, wrapped, 'utf-8');\n }\n log.success(`${symbol.ok} AGENTS.md`);\n}\n\n/**\n * Install commands for a specific AI editor\n */\nexport function installCommands(cwd: string, editor: AIEditor, lang: string = 'zh'): void {\n const config = AI_EDITORS[editor];\n const commandsDir = join(cwd, config.commands);\n ensureDir(commandsDir);\n\n // Copy command templates from templates/{lang}/commands/\n const templatesDir = join(getPackageRoot(), 'templates', lang, 'commands');\n const fallbackDir = join(getPackageRoot(), 'templates', 'zh', 'commands');\n const sourceDir = existsSync(templatesDir) ? templatesDir : fallbackDir;\n\n if (!existsSync(sourceDir)) {\n log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);\n return;\n }\n\n const commandFiles = readdirSync(sourceDir).filter(f => f.endsWith('.md'));\n\n for (const file of commandFiles) {\n const srcPath = join(sourceDir, file);\n const destPath = join(commandsDir, file);\n const content = readFileSync(srcPath, 'utf-8');\n writeFileSync(destPath, content, 'utf-8');\n }\n\n log.success(`${symbol.ok} ${config.commands}/ (${commandFiles.length} commands)`);\n}\n\n/**\n * Install commands for all supported AI editors\n */\nexport function installAllCommands(cwd: string, lang: string = 'zh'): void {\n for (const editor of Object.keys(AI_EDITORS) as AIEditor[]) {\n installCommands(cwd, editor, lang);\n }\n}\n","import chalk from 'chalk';\n\nexport const theme = {\n primary: chalk.hex('#6366f1'),\n success: chalk.hex('#22c55e'),\n warning: chalk.hex('#f59e0b'),\n error: chalk.hex('#ef4444'),\n info: chalk.hex('#3b82f6'),\n boost: chalk.hex('#a855f7'),\n dim: chalk.hex('#6b7280'),\n highlight: chalk.hex('#f472b6'),\n border: chalk.hex('#374151'),\n gradient1: chalk.hex('#818cf8'),\n gradient2: chalk.hex('#6366f1'),\n gradient3: chalk.hex('#4f46e5'),\n};\n\n// ASCII Art Logo for SuperSpec (S-U-P-E-R-S-P-E-C)\nexport const logo = {\n small: `\n${theme.gradient1(' ███████╗██╗ ██╗██████╗ ███████╗██████╗ ███████╗██████╗ ███████╗ ██████╗')}\n${theme.gradient2(' ██╔════╝██║ ██║██╔══██╗██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝')}\n${theme.gradient3(' ███████╗██║ ██║██████╔╝█████╗ ██████╔╝███████╗██████╔╝█████╗ ██║ ')}\n${theme.gradient2(' ╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗╚════██║██╔═══╝ ██╔══╝ ██║ ')}\n${theme.gradient1(' ███████║╚██████╔╝██║ ███████╗██║ ██║███████║██║ ███████╗╚██████╗')}\n${theme.gradient1(' ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝')}\n `,\n tiny: `\n${theme.gradient1(' ███████╗██╗ ██╗██████╗ ███████╗██████╗ ███████╗██████╗ ███████╗ ██████╗')}\n${theme.gradient2(' ╚═════╗██║ ██║██╔══██║██╔════╝██╔══██╗╚═════╗██╔══██║██╔════╝██╔════╝')}\n${theme.gradient3(' ███████║██║ ██║██████╔╝█████╗ ██████╔╝███████║██████╔╝█████╗ ██║ ')} ${theme.highlight('Spec-Driven Development')}\n${theme.gradient2(' ╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗╚════██║██╔═══╝ ██╔══╝ ██║ ')}\n${theme.gradient1(' ███████║╚██████╔╝██║ ███████╗██║ ██║███████║██║ ███████╗╚██████╗')}\n${theme.gradient1(' ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝')}\n `,\n};\n\nconst box = {\n topLeft: '╭',\n topRight: '╮',\n bottomLeft: '╰',\n bottomRight: '╯',\n horizontal: '─',\n vertical: '│',\n};\n\nfunction boxText(text: string, width: number = 50): string {\n const padding = ' '.repeat(Math.max(0, width - text.length - 4));\n return `${theme.border(box.vertical)} ${theme.highlight(text)}${padding} ${theme.border(box.vertical)}`;\n}\n\nfunction createBox(lines: string[], width: number = 52): string {\n const top = theme.border(`${box.topLeft}${box.horizontal.repeat(width - 2)}${box.topRight}`);\n const bottom = theme.border(`${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`);\n const middle = lines.map(line => boxText(line, width));\n return [top, ...middle, bottom].join('\\n');\n}\n\nexport const log = {\n info: (msg: string) => console.log(theme.info(msg)),\n success: (msg: string) => console.log(theme.success(msg)),\n warn: (msg: string) => console.log(theme.warning(msg)),\n error: (msg: string) => console.log(theme.error(msg)),\n dim: (msg: string) => console.log(theme.dim(msg)),\n boost: (msg: string) => console.log(theme.boost(msg)),\n highlight: (msg: string) => console.log(theme.highlight(msg)),\n title: (msg: string) => {\n console.log();\n console.log(createBox([msg]));\n console.log();\n },\n section: (msg: string) => {\n console.log();\n console.log(theme.primary(`◆ ${msg}`));\n console.log(theme.border('─'.repeat(50)));\n },\n done: (msg: string) => {\n console.log();\n console.log(theme.success(`✨ ${msg}`));\n console.log();\n },\n};\n\nexport const symbol = {\n start: theme.primary('◆'),\n ok: theme.success('✓'),\n fail: theme.error('✗'),\n warn: theme.warning('⚠'),\n bolt: theme.boost('⚡'),\n arrow: theme.dim('→'),\n bullet: theme.dim('•'),\n sparkle: theme.highlight('✨'),\n folder: theme.primary('📁'),\n file: theme.info('📄'),\n git: theme.warning('🌿'),\n ai: theme.boost('🤖'),\n} as const;\n\n// Helper to print the logo\nexport function printLogo(size: 'small' | 'tiny' = 'small'): void {\n console.log(logo[size]);\n}\n\n// Helper to print a summary box\nexport function printSummary(items: { label: string; value: string }[]): void {\n const maxLabel = Math.max(...items.map(i => i.label.length));\n const width = 50;\n\n console.log(theme.border('╭' + '─'.repeat(width - 2) + '╮'));\n for (const { label, value } of items) {\n const padding = ' '.repeat(maxLabel - label.length);\n const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;\n // Strip ANSI codes for length calculation\n const plainLine = line.replace(/\\u001b\\[\\d+m/g, '');\n const rightPad = ' '.repeat(Math.max(0, width - plainLine.length - 4));\n console.log(theme.border('│ ') + line + rightPad + theme.border(' │'));\n }\n console.log(theme.border('╰' + '─'.repeat(width - 2) + '╯'));\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { writeRenderedTemplate } from '../core/template.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { isGitRepo, createBranch } from '../utils/git.js';\nimport { getDateString } from '../utils/date.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface CreateOptions {\n boost?: boolean;\n creative?: boolean;\n branch?: boolean;\n specDir?: string;\n branchPrefix?: string;\n description?: string;\n}\n\nexport async function createCommand(name: string, options: CreateOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n\n const specDir = options.specDir || config.specDir;\n const branchPrefix = options.branchPrefix || config.branchPrefix;\n const boost = options.boost || config.boost;\n const strategy = options.creative ? 'create' : config.strategy;\n const lang = config.lang || 'zh';\n const description = options.description || '';\n\n const changePath = join(cwd, specDir, 'changes', name);\n\n if (existsSync(changePath)) {\n log.warn(`${symbol.warn} Change \"${name}\" already exists: ${changePath}`);\n return;\n }\n\n log.title(`Creating Change: ${name}`);\n\n if (boost) {\n log.boost(`${symbol.bolt} Boost mode enabled`);\n }\n if (strategy === 'create') {\n log.boost(`${symbol.bolt} Creative mode: exploring new solutions`);\n }\n\n ensureDir(changePath);\n\n const vars: Record<string, string> = {\n name,\n date: getDateString(),\n boost: boost ? 'true' : 'false',\n strategy,\n description,\n };\n\n const artifacts = boost ? config.boostArtifacts : config.artifacts;\n\n log.section('Generating Artifacts');\n for (const artifact of artifacts) {\n const templateFile = config.templates[artifact] || `${artifact}.md`;\n const destPath = join(changePath, `${artifact}.md`);\n try {\n writeRenderedTemplate(templateFile, destPath, vars, lang);\n log.success(`${symbol.ok} ${artifact}.md`);\n } catch (e: any) {\n log.error(`${symbol.fail} ${artifact}.md: ${e.message}`);\n }\n }\n\n if (options.branch !== false && isGitRepo()) {\n const branchTemplate = config.branchTemplate || '{prefix}{name}';\n const branchName = branchTemplate.replace('{prefix}', branchPrefix).replace('{name}', name);\n try {\n createBranch(branchName);\n log.success(`${symbol.ok} Branch: ${branchName}`);\n } catch (e: any) {\n log.warn(`${symbol.warn} Branch creation failed: ${e.message}`);\n }\n }\n\n log.done('Change created successfully!');\n log.dim(`Path: ${specDir}/changes/${name}/`);\n\n if (boost) {\n log.dim(`Workflow: /ss-create → /ss-tasks → /ss-apply (boost)`);\n } else {\n log.dim(`Workflow: /ss-tasks → /ss-apply`);\n }\n}\n","import { existsSync, readdirSync, renameSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig, type SuperSpecConfig } from '../core/config.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { getDateString } from '../utils/date.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface ArchiveOptions {\n all?: boolean;\n}\n\nexport async function archiveCommand(name: string | undefined, options: ArchiveOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n const archiveDir = join(cwd, config.specDir, 'changes', config.archive.dir);\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} 没有找到 changes 目录`);\n return;\n }\n\n if (options.all) {\n const entries = readdirSync(changesDir, { withFileTypes: true }).filter(\n (e) => e.isDirectory() && e.name !== config.archive.dir,\n );\n\n if (entries.length === 0) {\n log.warn(`${symbol.warn} 没有可归档的变更`);\n return;\n }\n\n log.info(`${symbol.start} 归档所有变更...`);\n for (const entry of entries) {\n archiveOne(entry.name, changesDir, archiveDir, config);\n }\n } else if (name) {\n const changePath = join(changesDir, name);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} 变更 \"${name}\" 不存在`);\n return;\n }\n log.info(`${symbol.start} 归档变更: ${name}`);\n archiveOne(name, changesDir, archiveDir, config);\n } else {\n log.warn(`${symbol.warn} 请指定变更名称或使用 --all`);\n return;\n }\n\n log.info(`${symbol.start} 归档完成!`);\n}\n\nfunction archiveOne(name: string, changesDir: string, archiveDir: string, config: SuperSpecConfig): void {\n ensureDir(archiveDir);\n const src = join(changesDir, name);\n const dateStr = config.archive.datePrefix ? `${getDateString()}-` : '';\n const dest = join(archiveDir, `${dateStr}${name}`);\n\n if (existsSync(dest)) {\n log.warn(` ${symbol.warn} 归档目标已存在: ${dest}`);\n return;\n }\n\n renameSync(src, dest);\n log.success(` ${symbol.ok} ${name} → archive/${dateStr}${name}`);\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { copyTemplate } from '../core/template.js';\nimport { ensureDir } from '../utils/fs.js';\nimport { installRules, installAgentsMd, installCommands, AI_EDITORS, type AIEditor } from '../prompts/index.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function updateCommand(): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const specDir = join(cwd, config.specDir);\n const lang = config.lang || 'zh';\n\n if (!existsSync(join(cwd, 'superspec.config.json'))) {\n log.warn(`${symbol.warn} 当前目录未初始化 SuperSpec,请先运行 superspec init`);\n return;\n }\n\n log.info(`${symbol.start} 更新 SuperSpec...`);\n\n const templates = ['spec.md', 'proposal.md', 'tasks.md', 'clarify.md', 'checklist.md'];\n ensureDir(join(specDir, 'templates'));\n for (const tpl of templates) {\n copyTemplate(tpl, join(specDir, 'templates', tpl), lang);\n }\n log.success(` ${symbol.ok} 模板更新 (${lang})`);\n\n installAgentsMd(cwd);\n\n // Update rules and commands for the configured AI editor\n const aiEditor = config.aiEditor as AIEditor;\n if (aiEditor && AI_EDITORS[aiEditor]) {\n installRules(cwd, aiEditor);\n installCommands(cwd, aiEditor, lang);\n }\n\n log.info(`${symbol.start} 更新完成!`);\n}\n","import { existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { lintChange } from '../core/lint.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function lintCommand(name: string | undefined): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n const { targetLines, hardLines } = config.limits;\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes to lint`);\n return;\n }\n\n let hasIssues = false;\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const results = lintChange(changePath, targetLines, hardLines);\n log.info(`${symbol.start} ${n}`);\n\n for (const r of results) {\n if (r.status === 'error') {\n log.error(` ${symbol.fail} ${r.artifact}: ${r.message}`);\n hasIssues = true;\n } else if (r.status === 'warn') {\n log.warn(` ${symbol.warn} ${r.artifact}: ${r.message}`);\n hasIssues = true;\n } else {\n log.success(` ${symbol.ok} ${r.artifact}: ${r.message}`);\n }\n }\n }\n\n if (!hasIssues) {\n log.info(`${symbol.start} all artifacts within limits`);\n }\n}\n","import { existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { validateChange, type ValidationIssue } from '../core/validate.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface ValidateOptions {\n checkDeps?: boolean;\n}\n\nexport async function validateCommand(name: string | undefined, options: ValidateOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes to validate`);\n return;\n }\n\n let totalIssues = 0;\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const issues = validateChange(changePath, options.checkDeps);\n log.info(`${symbol.start} ${n}`);\n\n if (issues.length === 0) {\n log.success(` ${symbol.ok} all checks passed`);\n } else {\n for (const issue of issues) {\n totalIssues++;\n if (issue.level === 'error') {\n log.error(` ${symbol.fail} [${issue.artifact}] ${issue.message}`);\n } else if (issue.level === 'warn') {\n log.warn(` ${symbol.warn} [${issue.artifact}] ${issue.message}`);\n } else {\n log.dim(` ℹ [${issue.artifact}] ${issue.message}`);\n }\n }\n }\n }\n\n if (totalIssues === 0) {\n log.success(`${symbol.ok} all validations passed`);\n } else {\n log.warn(`${symbol.warn} ${totalIssues} issue(s) found`);\n }\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join, basename } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface SearchOptions {\n archived?: boolean;\n artifact?: string;\n}\n\ninterface SearchHit {\n change: string;\n artifact: string;\n line: number;\n text: string;\n}\n\nexport async function searchCommand(query: string, options: SearchOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const dirs: { name: string; path: string }[] = [];\n\n const activeEntries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n for (const e of activeEntries) {\n dirs.push({ name: e.name, path: join(changesDir, e.name) });\n }\n\n if (options.archived) {\n const archiveDir = join(changesDir, config.archive.dir);\n if (existsSync(archiveDir)) {\n const archivedEntries = readdirSync(archiveDir, { withFileTypes: true })\n .filter((e) => e.isDirectory());\n for (const e of archivedEntries) {\n dirs.push({ name: `${config.archive.dir}/${e.name}`, path: join(archiveDir, e.name) });\n }\n }\n }\n\n const queryLower = query.toLowerCase();\n const hits: SearchHit[] = [];\n\n for (const dir of dirs) {\n if (!existsSync(dir.path)) continue;\n const files = readdirSync(dir.path).filter((f) => f.endsWith('.md'));\n\n for (const file of files) {\n if (options.artifact) {\n const artType = basename(file, '.md');\n if (artType !== options.artifact) continue;\n }\n\n const filePath = join(dir.path, file);\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n');\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].toLowerCase().includes(queryLower)) {\n hits.push({\n change: dir.name,\n artifact: file,\n line: i + 1,\n text: lines[i].trim(),\n });\n }\n }\n }\n }\n\n if (hits.length === 0) {\n log.warn(`${symbol.warn} no results for \"${query}\"`);\n return;\n }\n\n log.info(`${symbol.start} ${hits.length} result(s) for \"${query}\"`);\n for (const hit of hits) {\n log.dim(` ${hit.change}/${hit.artifact}:${hit.line} ${hit.text}`);\n }\n}\n","import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { addDependency, removeDependency, parseFrontmatter } from '../core/frontmatter.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport interface LinkOptions {\n dependsOn: string;\n}\n\nexport async function linkCommand(name: string, options: LinkOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const proposalPath = join(cwd, config.specDir, 'changes', name, 'proposal.md');\n\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" not found`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = addDependency(content, options.dependsOn);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → depends_on: ${options.dependsOn}`);\n}\n\nexport async function unlinkCommand(name: string, options: LinkOptions): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const proposalPath = join(cwd, config.specDir, 'changes', name, 'proposal.md');\n\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" not found`);\n return;\n }\n\n const content = readFileSync(proposalPath, 'utf-8');\n const updated = removeDependency(content, options.dependsOn);\n writeFileSync(proposalPath, updated, 'utf-8');\n log.success(`${symbol.ok} ${name} → removed dependency: ${options.dependsOn}`);\n}\n\nexport async function depsCommand(name: string): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (name) {\n const proposalPath = join(changesDir, name, 'proposal.md');\n if (!existsSync(proposalPath)) {\n log.warn(`${symbol.warn} \"${name}\" not found`);\n return;\n }\n const content = readFileSync(proposalPath, 'utf-8');\n const { meta } = parseFrontmatter(content);\n const deps: string[] = Array.isArray(meta.depends_on) ? meta.depends_on : [];\n log.info(`${symbol.start} ${name}`);\n if (deps.length === 0) {\n log.dim(' no dependencies');\n } else {\n for (const d of deps) {\n log.dim(` → ${d}`);\n }\n }\n return;\n }\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory`);\n return;\n }\n\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n\n log.info(`${symbol.start} dependency graph`);\n for (const entry of entries) {\n const proposalPath = join(changesDir, entry.name, 'proposal.md');\n if (!existsSync(proposalPath)) continue;\n const content = readFileSync(proposalPath, 'utf-8');\n const { meta } = parseFrontmatter(content);\n const deps: string[] = Array.isArray(meta.depends_on) ? meta.depends_on : [];\n if (deps.length > 0) {\n log.dim(` ${entry.name} → [${deps.join(', ')}]`);\n } else {\n log.dim(` ${entry.name}`);\n }\n }\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { parseFrontmatter } from '../core/frontmatter.js';\nimport { log, symbol } from '../ui/index.js';\n\nconst ARTIFACT_TYPES = ['proposal', 'spec', 'tasks', 'clarify', 'checklist'] as const;\n\nfunction readStatus(changePath: string, artifact: string): string {\n const filePath = join(changePath, `${artifact}.md`);\n if (!existsSync(filePath)) return '—';\n const content = readFileSync(filePath, 'utf-8');\n const { meta } = parseFrontmatter(content);\n if (meta.status) return meta.status;\n if (content.includes('✅')) return 'done';\n if (content.includes('🟢')) return 'ready';\n return 'draft';\n}\n\nfunction statusIcon(s: string): string {\n if (s === '—') return '—';\n if (s === 'done' || s === 'complete') return '✅';\n if (s === 'ready') return '🟢';\n return '🟡';\n}\n\nfunction overallStatus(statuses: Record<string, string>): string {\n const vals = Object.values(statuses).filter((v) => v !== '—');\n if (vals.length === 0) return 'empty';\n if (vals.every((v) => v === 'done' || v === 'complete')) return '✅ Done';\n if (vals.every((v) => v === 'ready' || v === 'done' || v === 'complete')) return '🟢 Ready';\n return '🟡 Draft';\n}\n\nexport async function statusCommand(): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir)\n .sort((a, b) => a.name.localeCompare(b.name));\n\n if (entries.length === 0) {\n log.dim(' no active changes');\n return;\n }\n\n const header = ['Change', ...ARTIFACT_TYPES.map((a) => a.slice(0, 8).padEnd(8)), 'Status'];\n const rows: string[][] = [];\n\n for (const entry of entries) {\n const changePath = join(changesDir, entry.name);\n const statuses: Record<string, string> = {};\n for (const art of ARTIFACT_TYPES) {\n statuses[art] = readStatus(changePath, art);\n }\n rows.push([\n entry.name,\n ...ARTIFACT_TYPES.map((a) => statusIcon(statuses[a])),\n overallStatus(statuses),\n ]);\n }\n\n const colWidths = header.map((h, i) => {\n const maxData = rows.reduce((max, row) => Math.max(max, stripAnsi(row[i]).length), 0);\n return Math.max(stripAnsi(h).length, maxData);\n });\n\n const divider = colWidths.map((w) => '-'.repeat(w + 2)).join('+');\n const formatRow = (row: string[]) =>\n row.map((cell, i) => ` ${cell.padEnd(colWidths[i])} `).join('|');\n\n log.info(`${symbol.start} changes`);\n console.log(formatRow(header));\n console.log(divider);\n for (const row of rows) {\n console.log(formatRow(row));\n }\n\n const archiveDir = join(changesDir, config.archive.dir);\n if (existsSync(archiveDir)) {\n const archived = readdirSync(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());\n if (archived.length > 0) {\n log.dim(`\\n ${archived.length} archived change(s)`);\n }\n }\n}\n\nfunction stripAnsi(str: string): string {\n return str.replace(/\\u001b\\[[0-9;]*m/g, '').replace(/[✅🟢🟡]/g, 'XX');\n}\n","import { existsSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { generateContext } from '../core/context.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function contextCommand(name: string | undefined): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes found`);\n return;\n }\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const content = generateContext(changePath, n);\n const destPath = join(changePath, 'context.md');\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} ${n}/context.md`);\n }\n}\n","import { existsSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from '../core/config.js';\nimport { generateContext } from '../core/context.js';\nimport { log, symbol } from '../ui/index.js';\n\nexport async function syncCommand(name: string | undefined, opts: { base?: string }): Promise<void> {\n const cwd = process.cwd();\n const config = loadConfig(cwd);\n const changesDir = join(cwd, config.specDir, 'changes');\n\n if (!existsSync(changesDir)) {\n log.warn(`${symbol.warn} no changes directory found`);\n return;\n }\n\n const names: string[] = [];\n if (name) {\n names.push(name);\n } else {\n const entries = readdirSync(changesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== config.archive.dir);\n names.push(...entries.map((e) => e.name));\n }\n\n if (names.length === 0) {\n log.warn(`${symbol.warn} no changes found`);\n return;\n }\n\n for (const n of names) {\n const changePath = join(changesDir, n);\n if (!existsSync(changePath)) {\n log.warn(`${symbol.warn} \"${n}\" not found`);\n continue;\n }\n\n const content = generateContext(changePath, n, {\n gitDiff: true,\n baseBranch: opts.base,\n });\n const destPath = join(changePath, 'context.md');\n writeFileSync(destPath, content, 'utf-8');\n log.success(`${symbol.ok} synced ${n}/context.md`);\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","dirname","join","__filename","__dirname","existsSync","join","existsSync","dirname","readFileSync","readFileSync","existsSync","join","readFileSync","existsSync","join","readFileSync","existsSync","join","existsSync","writeFileSync","readdirSync","join","execSync","existsSync","readFileSync","writeFileSync","readdirSync","join","join","existsSync","readFileSync","writeFileSync","readdirSync","join","existsSync","readdirSync","writeFileSync","execSync","existsSync","join","join","existsSync","existsSync","readdirSync","join","join","existsSync","readdirSync","existsSync","join","join","existsSync","existsSync","readdirSync","join","existsSync","readdirSync","join","existsSync","readFileSync","readdirSync","join","basename","existsSync","readFileSync","writeFileSync","readdirSync","join","existsSync","readFileSync","readdirSync","join","existsSync","writeFileSync","readdirSync","join","existsSync","writeFileSync","readdirSync","join"]}
|
|
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"]}
|
package/package.json
CHANGED
package/prompts/agents.md
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
<!-- superspec:start -->
|
|
1
2
|
# SuperSpec — AI Agent Instructions
|
|
2
3
|
|
|
3
4
|
## 🚨 Before ANY Task
|
|
4
5
|
|
|
5
|
-
1. Read `superspec.config.json` → get `lang`, `specDir`, `boost`, `strategy`, `context`
|
|
6
|
-
2.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
1. **Read configuration**: `superspec.config.json` → get `lang`, `specDir`, `boost`, `strategy`, `context`
|
|
7
|
+
2. **Review project context**:
|
|
8
|
+
- Read `context` files (project rules/conventions)
|
|
9
|
+
- Check project README, architecture docs, CONTRIBUTING.md
|
|
10
|
+
- If no `context` configured, auto-check: `.cursor/rules/`, `AGENTS.md`, `CONTRIBUTING.md`
|
|
11
|
+
3. **Inspect current state**:
|
|
12
|
+
- Run `/ss-status` or check `{specDir}/changes/` → know active changes
|
|
13
|
+
- Review related changes via `depends_on` to avoid duplication
|
|
14
|
+
4. **Read current change context**:
|
|
15
|
+
- Read frontmatter → get `strategy` (may override config)
|
|
16
|
+
- If `strategy: follow` → treat context files as constraints (must follow)
|
|
17
|
+
- If `strategy: create` → treat context files as awareness (may deviate, must justify)
|
|
18
|
+
5. **Never create change folders manually** → use `superspec create` CLI or `/ss-create`
|
|
10
19
|
|
|
11
20
|
---
|
|
12
21
|
|
|
@@ -86,7 +95,7 @@ Config `context` lists files the AI should read to understand project convention
|
|
|
86
95
|
|----------|------|
|
|
87
96
|
| Code without planning | `/ss-create` → `/ss-tasks` → `/ss-apply` |
|
|
88
97
|
| Overkill simple tasks | Use standard mode. Only boost when complexity demands it. |
|
|
89
|
-
| Create folders manually | `superspec create <
|
|
98
|
+
| Create folders manually | `superspec create <feature>` or `/ss-create` |
|
|
90
99
|
| Ignore `clarify.md` | Read before generating/updating |
|
|
91
100
|
| Overwrite user edits | Merge, don't replace |
|
|
92
101
|
|
|
@@ -96,7 +105,7 @@ Config `context` lists files the AI should read to understand project convention
|
|
|
96
105
|
|
|
97
106
|
| Command | Mode | What it does |
|
|
98
107
|
|---------|------|-------------|
|
|
99
|
-
| `/ss-create <
|
|
108
|
+
| `/ss-create <feature>` | Both | Create change + generate proposal (boost: + spec + checklist) |
|
|
100
109
|
| `/ss-tasks` | Both | Generate task list from proposal (boost: from proposal + spec) |
|
|
101
110
|
| `/ss-apply` | Both | Implement tasks |
|
|
102
111
|
| `/ss-clarify` | Both | Resolve ambiguity |
|
|
@@ -106,8 +115,8 @@ Config `context` lists files the AI should read to understand project convention
|
|
|
106
115
|
| `/ss-lint` | Both | Check artifact sizes |
|
|
107
116
|
| `/ss-validate` | Boost | Cross-reference consistency check |
|
|
108
117
|
| `/ss-search <q>` | Both | Full-text search across changes |
|
|
109
|
-
| `/ss-link` | Both | Add spec dependency |
|
|
110
|
-
| `/ss-deps` | Both | View dependency graph |
|
|
118
|
+
| `/ss-link` | Both | Add spec dependency (`deps add`) |
|
|
119
|
+
| `/ss-deps` | Both | View dependency graph (`deps list`) |
|
|
111
120
|
| `/ss-resume` | Both | Restore spec context for vibe coding (runs sync → reads context.md) |
|
|
112
121
|
| `superspec sync` | Both | CLI: collect git diff into context.md (zero AI tokens) |
|
|
113
122
|
|
|
@@ -127,11 +136,42 @@ Config `context` lists files the AI should read to understand project convention
|
|
|
127
136
|
{specDir}/changes/<name>/
|
|
128
137
|
├── proposal.md — Why and what
|
|
129
138
|
├── spec.md — Requirements (US/FR/AC)
|
|
139
|
+
├── design.md — Architecture decisions (optional, for complex changes)
|
|
130
140
|
├── tasks.md — Phased implementation steps
|
|
131
141
|
├── clarify.md — Q&A and decisions (on-demand)
|
|
132
142
|
└── checklist.md — Quality validation
|
|
133
143
|
```
|
|
134
144
|
|
|
145
|
+
**When to use design.md** (optional in boost mode):
|
|
146
|
+
- Solution spans multiple systems or introduces new architectural patterns
|
|
147
|
+
- Major architectural decisions with significant trade-offs
|
|
148
|
+
- Need to document decision rationale before committing to specs
|
|
149
|
+
- Cross-team architectural alignment required
|
|
150
|
+
|
|
151
|
+
**Spec deltas - Multi-capability structure** (recommended for large changes):
|
|
152
|
+
When a change involves multiple distinct capabilities, split specs by capability domain:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
{specDir}/changes/<name>/
|
|
156
|
+
├── proposal.md
|
|
157
|
+
├── design.md
|
|
158
|
+
├── specs/
|
|
159
|
+
│ ├── auth/ — Authentication capability
|
|
160
|
+
│ │ └── spec.md
|
|
161
|
+
│ ├── api/ — API layer capability
|
|
162
|
+
│ │ └── spec.md
|
|
163
|
+
│ └── ui/ — UI components capability
|
|
164
|
+
│ └── spec.md
|
|
165
|
+
├── tasks.md
|
|
166
|
+
└── checklist.md
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Benefits of capability-based splitting**:
|
|
170
|
+
- Each spec.md stays under 300-line target
|
|
171
|
+
- Clear separation of concerns
|
|
172
|
+
- Easier parallel review and implementation
|
|
173
|
+
- Better traceability for cross-references
|
|
174
|
+
|
|
135
175
|
Each artifact has YAML frontmatter: `name`, `status`, `strategy`, `depends_on: []`.
|
|
136
176
|
|
|
137
177
|
---
|
|
@@ -148,3 +188,5 @@ Each artifact has YAML frontmatter: `name`, `status`, `strategy`, `depends_on: [
|
|
|
148
188
|
| `context` | `[]` | Files AI should read for project conventions |
|
|
149
189
|
| `limits.targetLines` | `300` | Target max lines per artifact |
|
|
150
190
|
| `limits.hardLines` | `400` | Hard max lines per artifact |
|
|
191
|
+
|
|
192
|
+
<!-- superspec:end -->
|
|
@@ -11,12 +11,12 @@ alwaysApply: true
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
## /ss-create <
|
|
14
|
+
## /ss-create <feature>
|
|
15
15
|
|
|
16
|
-
**CLI:** `superspec create <
|
|
16
|
+
**CLI:** `superspec create <feature>` (`-b` boost, `-c` creative, `--no-branch` skip branch)
|
|
17
17
|
|
|
18
18
|
**Standard steps:**
|
|
19
|
-
1. Run `superspec create <
|
|
19
|
+
1. Run `superspec create <feature>` → creates folder + proposal.md + tasks.md templates
|
|
20
20
|
2. Read frontmatter → check `strategy`
|
|
21
21
|
3. If `follow`: read `context` files → constrain to project patterns
|
|
22
22
|
If `create`: note `context` as awareness only
|
|
@@ -65,7 +65,7 @@ alwaysApply: true
|
|
|
65
65
|
5. After each task: mark ✅ in `tasks.md`
|
|
66
66
|
6. After each phase: checkpoint validation
|
|
67
67
|
7. On blockers: pause and report
|
|
68
|
-
8. After all tasks done: run `superspec
|
|
68
|
+
8. After all tasks done: run `superspec sync <name>` to refresh context.md
|
|
69
69
|
|
|
70
70
|
## /ss-resume
|
|
71
71
|
|
|
@@ -144,11 +144,11 @@ Run CLI: `superspec archive <name>`.
|
|
|
144
144
|
## /ss-link
|
|
145
145
|
|
|
146
146
|
**Steps:**
|
|
147
|
-
1. Run `superspec
|
|
148
|
-
2. Verify with `superspec deps <name>`
|
|
147
|
+
1. Run `superspec deps add <name> --on <other>`
|
|
148
|
+
2. Verify with `superspec deps list <name>`
|
|
149
149
|
|
|
150
150
|
## /ss-deps
|
|
151
151
|
|
|
152
152
|
**Steps:**
|
|
153
|
-
1. Run `superspec deps [name]` to view dependency graph
|
|
153
|
+
1. Run `superspec deps list [name]` to view dependency graph
|
|
154
154
|
2. No name → show all changes and their dependencies
|