spec-canon 0.1.13 → 0.1.14

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/README.md CHANGED
@@ -12,6 +12,8 @@ SpecCanon 是一套面向 AI Coding Agent(如 Claude Code)的 **Spec-Driven
12
12
 
13
13
  核心理念:**先文档后代码,人做判断题,AI 做填空题。**
14
14
 
15
+ AI 编程工具本质上是一个把结构化 Spec 转成实现代码的高效转换器。Spec 越清晰、越完整,AI 就越能稳定地产出与系统现状一致、与变更目标对齐的可运行代码。
16
+
15
17
  SpecCanon 的本质是一个**文档状态机**:每份 Spec 文档是一个状态节点,状态转移由人触发,AI 负责生成状态内容。通过持续维护 Domain Spec 作为系统行为的单一事实来源(Single Source of Truth),让 AI 在每次开发时都拥有准确的系统全景。
16
18
 
17
19
  ## 核心机制
@@ -50,14 +52,21 @@ domain_spec.md(演化更新)
50
52
 
51
53
  ## 安装
52
54
 
55
+ 你可以直接运行最新版,也可以先全局安装后再使用 `spec-canon`:
56
+
57
+ ```bash
58
+ npx spec-canon@latest init
59
+ ```
60
+
53
61
  ```bash
54
62
  npm install -g spec-canon
63
+ spec-canon init
55
64
  ```
56
65
 
57
- 或直接通过 `npx` 使用(无需全局安装):
66
+ 如果只想先查看命令,也可以运行:
58
67
 
59
68
  ```bash
60
- npx spec-canon --help
69
+ npx spec-canon@latest --help
61
70
  ```
62
71
 
63
72
  ## 快速开始
@@ -66,7 +75,7 @@ npx spec-canon --help
66
75
  → 阅读 [guide/04_new_project_sop.md](docs/guide/04_new_project_sop.md),然后使用 `spec-canon prompt guide` 查看决策树
67
76
 
68
77
  ### 我要在已有项目中开发新需求
69
- 先运行 `spec-canon change start --goal ...` 建立 active change(待命名状态),运行 `ctx` 后 AI 会建议 change 名,再用 `change start -g <goal> -c <name>` 确认命名;然后用 `spec-canon change next` 查看候选步骤
78
+ 若暂时还没想好 change 名,先运行 `spec-canon change start --goal ...` 建立 active change(待命名状态);运行 `ctx` 后 AI 会给出命名建议,再由你确认或修改后执行 `change start -g <goal> -c <name>` 记录命名。若一开始就已明确 change 名,且确认其中序号就是当前 next seq,也可直接使用 `-g -c`;然后用 `spec-canon change next` 查看候选步骤
70
79
 
71
80
  ### 我要为已有项目引入 SDD
72
81
  → 阅读 [guide/05_iterative_project_sop.md](docs/guide/05_iterative_project_sop.md) 的冷启动策略,然后使用 `spec-canon prompt list --stage iterative`
@@ -76,9 +85,11 @@ npx spec-canon --help
76
85
 
77
86
  ## 常用命令
78
87
 
88
+ 未全局安装时,把下面的 `spec-canon` 替换为 `npx spec-canon@latest`。
89
+
79
90
  ```bash
80
91
  spec-canon init
81
- spec-canon init --domain auth
92
+ spec-canon domain create auth
82
93
  spec-canon sync # 给已初始化项目安全补齐新骨架
83
94
  spec-canon change start -g @docs/prd.md
84
95
  spec-canon change status
@@ -104,7 +115,7 @@ docs/
104
115
  ├── SOP.md ← 导航索引(入口)
105
116
  ├── guide/ ← 方法论(一次性阅读)
106
117
  │ ├── 00_introduction.md SDD 框架综合介绍(对外发布用)
107
- │ ├── 01_claude_md.md CLAUDE.md 书写指南
118
+ │ ├── 01_claude_md.md AI 配置文件指南(CLAUDE.md / AGENTS.md)
108
119
  │ ├── 02_spec_overview.md 三层文档体系总览
109
120
  │ ├── 03_spec_documents.md 各文档详解
110
121
  │ ├── 04_new_project_sop.md 新项目 SOP
@@ -2,7 +2,7 @@ import { join, resolve } from 'node:path';
2
2
  import { ActiveChangeError, clearActiveChange, getActiveChangeProgress, ensureSddRoot, getActiveChangeStatus, getChangeDir, readActiveChange, writeActiveChange, } from '../utils/active-change.js';
3
3
  import { getChangeSeq, getChangeType, getNextChangeSeq, isValidChangeName, } from '../utils/change.js';
4
4
  import { buildChangeNextPlan } from '../utils/change-next.js';
5
- import { ensureDir } from '../utils/fs.js';
5
+ import { ensureDir, fileExists } from '../utils/fs.js';
6
6
  import { logger } from '../utils/logger.js';
7
7
  export function registerChangeCommand(program) {
8
8
  const change = program
@@ -68,6 +68,17 @@ async function runStart(opts, rootDir) {
68
68
  const isIdempotent = existing.goal === opts.goal &&
69
69
  existing.change === opts.change;
70
70
  if (canConfirmPendingChange) {
71
+ const requestedSeq = getChangeSeq(opts.change);
72
+ if (requestedSeq !== existing.changeSeq) {
73
+ logger.error(`当前 pending active change 的序号是 ${existing.changeSeq},请使用 <type>-${existing.changeSeq}-<slug>;若不确定 change 名,请先运行 spec-canon prompt show ctx`);
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ if (await fileExists(getChangeDir(opts.change, rootDir))) {
78
+ logger.error(`change 目录已存在:spec-canon/changes/${opts.change}。为避免复用旧 change,请改用新的 change 名,或先运行 spec-canon prompt show ctx`);
79
+ process.exitCode = 1;
80
+ return;
81
+ }
71
82
  await ensureDir(getChangeDir(opts.change, rootDir));
72
83
  await writeActiveChange({
73
84
  ...existing,
@@ -84,12 +95,23 @@ async function runStart(opts, rootDir) {
84
95
  process.exitCode = 1;
85
96
  return;
86
97
  }
98
+ const nextChangeSeq = await getNextChangeSeq(rootDir);
87
99
  const changeSeq = opts.change
88
100
  ? getChangeSeq(opts.change)
89
- : await getNextChangeSeq(rootDir);
101
+ : nextChangeSeq;
90
102
  const changesDir = join(rootDir, 'spec-canon', 'changes');
91
103
  await ensureDir(changesDir);
92
104
  if (opts.change) {
105
+ if (changeSeq !== nextChangeSeq) {
106
+ logger.error(`change 名中的序号 ${changeSeq} 不是当前可用序号 ${nextChangeSeq}。为避免复用旧 change,请先运行 spec-canon change start -g <goal>,再执行 spec-canon prompt show ctx`);
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+ if (await fileExists(getChangeDir(opts.change, rootDir))) {
111
+ logger.error(`change 目录已存在:spec-canon/changes/${opts.change}。为避免复用旧 change,请改用新的 change 名,或先运行 spec-canon change start -g <goal> 再执行 spec-canon prompt show ctx`);
112
+ process.exitCode = 1;
113
+ return;
114
+ }
93
115
  await ensureDir(getChangeDir(opts.change, rootDir));
94
116
  }
95
117
  await writeActiveChange({
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerDomainCommand(program: Command): void;
@@ -0,0 +1,38 @@
1
+ import { resolve } from 'node:path';
2
+ import { createDomainScaffold, DomainScaffoldError, } from '../utils/scaffold.js';
3
+ import { logger } from '../utils/logger.js';
4
+ export function registerDomainCommand(program) {
5
+ const domain = program
6
+ .command('domain')
7
+ .description('管理业务域骨架')
8
+ .option('-d, --dir <path>', '指定 SDD 项目根目录(默认当前目录)');
9
+ domain
10
+ .command('create <name>')
11
+ .description('创建业务域骨架')
12
+ .action(async (name, _opts, command) => {
13
+ await runCreate(name, getRootDir(command));
14
+ });
15
+ }
16
+ async function runCreate(name, rootDir) {
17
+ try {
18
+ const result = await createDomainScaffold(rootDir, name);
19
+ logger.success(`已创建业务域: ${name}`);
20
+ logger.dim(` ${result.createdPath}`);
21
+ if (result.manifestPath) {
22
+ logger.info(`已更新模板状态清单:${result.manifestPath}`);
23
+ }
24
+ }
25
+ catch (error) {
26
+ if (error instanceof DomainScaffoldError) {
27
+ logger.error(error.message);
28
+ process.exitCode = 1;
29
+ return;
30
+ }
31
+ throw error;
32
+ }
33
+ }
34
+ function getRootDir(command) {
35
+ const parent = command.parent;
36
+ const opts = parent?.opts() ?? {};
37
+ return resolve(opts.dir ?? process.cwd());
38
+ }
@@ -2,13 +2,13 @@ import { join, resolve } from 'node:path';
2
2
  import { loadTemplate } from '../templates/index.js';
3
3
  import { fileExists, } from '../utils/fs.js';
4
4
  import { logger } from '../utils/logger.js';
5
- import { SDD_ROOT, TEMPLATE_MANIFEST, buildManagedFiles, ensureBaseScaffoldDirs, reconcileManagedFile, writeTemplateManifest, } from '../utils/scaffold.js';
5
+ import { createDomainScaffold, DomainScaffoldError, SDD_ROOT, TEMPLATE_MANIFEST, buildManagedFiles, ensureBaseScaffoldDirs, reconcileManagedFile, writeTemplateManifest, } from '../utils/scaffold.js';
6
6
  export function registerInitCommand(program) {
7
7
  program
8
8
  .command('init')
9
9
  .description('初始化 SDD 文档结构')
10
10
  .option('-d, --dir <path>', '目标项目目录', process.cwd())
11
- .option('--domain <name>', '同时创建初始业务域')
11
+ .option('--domain <name>', '兼容入口:同时创建初始业务域(推荐改用 spec-canon domain create)')
12
12
  .option('--force', '覆盖已存在的 spec-canon 目录', false)
13
13
  .action(async (opts) => {
14
14
  await runInit(opts);
@@ -31,10 +31,9 @@ async function runInit(opts) {
31
31
  await ensureBaseScaffoldDirs(targetDir);
32
32
  const managedFiles = await buildManagedFiles(targetDir, {
33
33
  includeClaude: true,
34
- extraDomains: opts.domain ? [opts.domain] : [],
35
34
  });
36
35
  for (const file of managedFiles) {
37
- const overwriteExisting = file.relativePath !== 'CLAUDE.md';
36
+ const overwriteExisting = file.relativePath !== 'CLAUDE.md' && file.relativePath !== 'AGENTS.md';
38
37
  const status = await reconcileManagedFile(targetDir, file, {
39
38
  overwriteExisting,
40
39
  });
@@ -43,21 +42,44 @@ async function runInit(opts) {
43
42
  created.push(file.relativePath);
44
43
  }
45
44
  }
45
+ const AI_CONFIG_FILES = ['CLAUDE.md', 'AGENTS.md'];
46
46
  const sddSection = await loadTemplate('claude-md-section.md');
47
- const claudeStatus = statuses.find((status) => status.relativePath === 'CLAUDE.md');
48
- if (claudeStatus?.state !== 'created') {
49
- logger.warn('CLAUDE.md 已存在,请手动将以下 SDD 协议段添加到文件中:');
47
+ const createdFiles = AI_CONFIG_FILES.filter((name) => statuses.find((s) => s.relativePath === name)?.state === 'created');
48
+ const existingFiles = AI_CONFIG_FILES.filter((name) => statuses.find((s) => s.relativePath === name)?.state !== 'created');
49
+ if (existingFiles.length > 0) {
50
+ logger.warn(`${existingFiles.join(' / ')} 已存在,请手动将以下 SDD 协议段添加到文件中:`);
50
51
  logger.blank();
51
52
  logger.dim('─'.repeat(60));
52
53
  console.log(sddSection);
53
54
  logger.dim('─'.repeat(60));
54
55
  logger.blank();
55
56
  }
56
- else {
57
- logger.success('已创建 CLAUDE.md(含 SDD 协议段)');
57
+ if (createdFiles.length > 0) {
58
+ logger.success(`已创建 ${createdFiles.join(' + ')}(含 SDD 协议段)`);
58
59
  }
59
60
  if (opts.domain) {
60
- logger.success(`已创建业务域: ${opts.domain}`);
61
+ try {
62
+ const result = await createDomainScaffold(targetDir, opts.domain, {
63
+ writeManifest: false,
64
+ allowExisting: opts.force,
65
+ overwriteExisting: opts.force,
66
+ });
67
+ if (result.status.state === 'created') {
68
+ created.push(result.createdPath);
69
+ }
70
+ statuses.push(result.status);
71
+ logger.success(result.status.state === 'created'
72
+ ? `已创建业务域: ${opts.domain}`
73
+ : `已刷新业务域: ${opts.domain}`);
74
+ }
75
+ catch (error) {
76
+ if (error instanceof DomainScaffoldError) {
77
+ logger.error(error.message);
78
+ process.exitCode = 1;
79
+ return;
80
+ }
81
+ throw error;
82
+ }
61
83
  }
62
84
  const manifestAbsolutePath = join(targetDir, SDD_ROOT, TEMPLATE_MANIFEST);
63
85
  const manifestExisted = await fileExists(manifestAbsolutePath);
@@ -75,5 +97,16 @@ async function runInit(opts) {
75
97
  logger.dim(` ${file}`);
76
98
  }
77
99
  logger.blank();
78
- logger.info('下一步:编辑 CLAUDE.md 填写项目信息,然后开始你的第一个变更');
100
+ logger.info(opts.domain
101
+ ? '下一步:编辑 CLAUDE.md(及 AGENTS.md)填写项目信息,然后开始你的第一个变更'
102
+ : `下一步:编辑 CLAUDE.md(及 AGENTS.md)填写项目信息;如需先建业务域,运行 ${formatDomainCreateCommand(targetDir)}`);
103
+ }
104
+ function formatDomainCreateCommand(targetDir) {
105
+ if (targetDir === process.cwd()) {
106
+ return 'spec-canon domain create <name>';
107
+ }
108
+ return `spec-canon domain -d ${quoteShellArg(targetDir)} create <name>`;
109
+ }
110
+ function quoteShellArg(value) {
111
+ return JSON.stringify(value);
79
112
  }
@@ -97,7 +97,7 @@ export function formatManualReviewItem(item, targetDir) {
97
97
  lines.push(` 建议命令: cp ${quotedTemplatePath} ${quotedTargetPath}`);
98
98
  }
99
99
  if (item.compareMode === 'merge-section') {
100
- lines.push(' CLAUDE.md 只需按需合并 SDD 协议段,无需整文件覆盖');
100
+ lines.push(` ${item.path} 只需按需合并 SDD 协议段,无需整文件覆盖`);
101
101
  }
102
102
  return lines;
103
103
  }
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import updateNotifier from 'update-notifier';
3
3
  import { registerChangeCommand } from './commands/change.js';
4
+ import { registerDomainCommand } from './commands/domain.js';
4
5
  import { registerInitCommand } from './commands/init.js';
5
6
  import { registerPromptCommand } from './commands/prompt.js';
6
7
  import { registerSyncCommand } from './commands/sync.js';
@@ -12,6 +13,7 @@ program
12
13
  .description('CLI toolkit for Spec-Driven Development (SDD)')
13
14
  .version(CLI_VERSION);
14
15
  registerInitCommand(program);
16
+ registerDomainCommand(program);
15
17
  registerChangeCommand(program);
16
18
  registerPromptCommand(program);
17
19
  registerSyncCommand(program);
@@ -47,6 +47,8 @@ export interface SyncReport {
47
47
  }
48
48
  export declare class ScaffoldSyncError extends Error {
49
49
  }
50
+ export declare class DomainScaffoldError extends Error {
51
+ }
50
52
  export declare function ensureBaseScaffoldDirs(rootDir: string): Promise<void>;
51
53
  export declare function loadClaudeSection(): Promise<string>;
52
54
  export declare function buildManagedFiles(rootDir: string, opts?: {
@@ -60,3 +62,12 @@ export declare function reconcileManagedFile(rootDir: string, file: ManagedFileD
60
62
  export declare function inspectManagedFile(rootDir: string, file: ManagedFileDefinition): Promise<ManagedFileStatus>;
61
63
  export declare function writeTemplateManifest(rootDir: string, files: ManagedFileDefinition[], statuses: ManagedFileStatus[]): Promise<string>;
62
64
  export declare function syncScaffold(rootDir: string): Promise<SyncReport>;
65
+ export declare function createDomainScaffold(rootDir: string, domain: string, opts?: {
66
+ writeManifest?: boolean;
67
+ allowExisting?: boolean;
68
+ overwriteExisting?: boolean;
69
+ }): Promise<{
70
+ createdPath: string;
71
+ status: ManagedFileStatus;
72
+ manifestPath?: string;
73
+ }>;
@@ -25,6 +25,8 @@ const LEGACY_RULES_H1 = '# SKILLS.md — 团队规则库';
25
25
  const RULES_H1 = '# RULES.md — 团队规则库';
26
26
  export class ScaffoldSyncError extends Error {
27
27
  }
28
+ export class DomainScaffoldError extends Error {
29
+ }
28
30
  export async function ensureBaseScaffoldDirs(rootDir) {
29
31
  for (const dir of BASE_DIRS) {
30
32
  await ensureDir(join(rootDir, dir));
@@ -71,10 +73,17 @@ export async function buildManagedFiles(rootDir, opts = {}) {
71
73
  manualReviewReason: 'spec-canon/domains/README.md 是持续演化的活文档,当前内容与 CLI 初始基线存在差异,已保留,可按需参考基线结构手动比对',
72
74
  });
73
75
  if (opts.includeClaude) {
76
+ const aiConfigContent = await buildClaudeMdContent();
77
+ addManagedFile(files, {
78
+ relativePath: 'AGENTS.md',
79
+ sourceId: 'generated:agents-md',
80
+ desiredContent: aiConfigContent,
81
+ manualReviewReason: 'AGENTS.md 是用户自定义项目说明文件,CLI 未自动改写;如需吸收新版 SDD 协议,请手动合并当前 CLI 提供的协议段基线',
82
+ });
74
83
  addManagedFile(files, {
75
84
  relativePath: 'CLAUDE.md',
76
85
  sourceId: 'generated:claude-md',
77
- desiredContent: await buildClaudeMdContent(),
86
+ desiredContent: aiConfigContent,
78
87
  manualReviewReason: 'CLAUDE.md 是用户自定义项目说明文件,CLI 未自动改写;如需吸收新版 SDD 协议,请手动合并当前 CLI 提供的协议段基线',
79
88
  });
80
89
  }
@@ -114,6 +123,16 @@ export async function reconcileManagedFile(rootDir, file, opts = {}) {
114
123
  }
115
124
  export async function inspectManagedFile(rootDir, file) {
116
125
  const absolutePath = join(rootDir, file.relativePath);
126
+ if (!(await fileExists(absolutePath))) {
127
+ return {
128
+ relativePath: file.relativePath,
129
+ sourceId: file.sourceId,
130
+ templateHash: hashContent(file.desiredContent),
131
+ fileHash: hashContent(''),
132
+ state: 'preserved',
133
+ manualReviewReason: file.manualReviewReason,
134
+ };
135
+ }
117
136
  const currentContent = await readFileSafe(absolutePath);
118
137
  const state = currentContent === file.desiredContent
119
138
  ? 'unchanged'
@@ -188,6 +207,35 @@ export async function syncScaffold(rootDir) {
188
207
  manifestPath,
189
208
  };
190
209
  }
210
+ export async function createDomainScaffold(rootDir, domain, opts = {}) {
211
+ const sddDir = join(rootDir, SDD_ROOT);
212
+ if (!(await fileExists(sddDir))) {
213
+ throw new DomainScaffoldError('未找到 spec-canon/ 目录,请先运行 spec-canon init');
214
+ }
215
+ const domainDir = join(rootDir, SDD_ROOT, 'domains', domain);
216
+ const domainExists = await fileExists(domainDir);
217
+ if (domainExists && !opts.allowExisting) {
218
+ throw new DomainScaffoldError(`业务域已存在:${domain}`);
219
+ }
220
+ const createdPath = join(SDD_ROOT, 'domains', domain, 'domain_spec.md');
221
+ const files = await buildManagedFiles(rootDir, {
222
+ includeClaude: true,
223
+ includeExistingDomains: true,
224
+ extraDomains: [domain],
225
+ });
226
+ const domainFile = files.find((file) => file.relativePath === createdPath);
227
+ if (!domainFile) {
228
+ throw new DomainScaffoldError(`未找到业务域骨架定义:${createdPath}`);
229
+ }
230
+ const status = await reconcileManagedFile(rootDir, domainFile, {
231
+ overwriteExisting: opts.overwriteExisting ?? false,
232
+ });
233
+ if (opts.writeManifest === false) {
234
+ return { createdPath, status };
235
+ }
236
+ const manifestPath = await writeTemplateManifest(rootDir, files, [status]);
237
+ return { createdPath, status, manifestPath };
238
+ }
191
239
  async function buildClaudeMdContent() {
192
240
  const sddSection = await loadClaudeSection();
193
241
  return `${DEFAULT_CLAUDE_MD}\n${sddSection}`;
@@ -220,7 +268,7 @@ function buildManualReviewItem(status) {
220
268
  item.compareMode = 'full-file';
221
269
  return item;
222
270
  }
223
- if (status.sourceId === 'generated:claude-md') {
271
+ if (status.sourceId === 'generated:claude-md' || status.sourceId === 'generated:agents-md') {
224
272
  item.sourceKind = 'baseline';
225
273
  item.templatePath = getTemplatePath('claude-md-section.md');
226
274
  item.compareMode = 'merge-section';
@@ -254,21 +302,26 @@ async function applyLegacyRulesCompatibility(rootDir) {
254
302
  compareMode: 'full-file',
255
303
  });
256
304
  }
257
- const claudePath = join(rootDir, 'CLAUDE.md');
258
- if (!(await fileExists(claudePath))) {
259
- return { migrated, manualReview };
260
- }
261
- const claudeContent = await readFileSafe(claudePath);
262
- if (!containsLegacyRulesReference(claudeContent)) {
263
- return { migrated, manualReview };
305
+ const aiConfigFiles = [
306
+ { path: 'CLAUDE.md', name: 'CLAUDE.md' },
307
+ { path: 'AGENTS.md', name: 'AGENTS.md' },
308
+ ];
309
+ for (const file of aiConfigFiles) {
310
+ const filePath = join(rootDir, file.path);
311
+ if (!(await fileExists(filePath))) {
312
+ continue;
313
+ }
314
+ const content = await readFileSafe(filePath);
315
+ if (containsLegacyRulesReference(content)) {
316
+ manualReview.push({
317
+ path: file.name,
318
+ reason: `${file.name} 中仍引用旧规则路径,请将 spec-canon/skills/SKILL.md 更新为 spec-canon/rules/RULES.md`,
319
+ sourceKind: 'baseline',
320
+ templatePath: getTemplatePath('claude-md-section.md'),
321
+ compareMode: 'merge-section',
322
+ });
323
+ }
264
324
  }
265
- manualReview.push({
266
- path: 'CLAUDE.md',
267
- reason: 'CLAUDE.md 中仍引用旧规则路径,请将 spec-canon/skills/SKILL.md 更新为 spec-canon/rules/RULES.md',
268
- sourceKind: 'baseline',
269
- templatePath: getTemplatePath('claude-md-section.md'),
270
- compareMode: 'merge-section',
271
- });
272
325
  return { migrated, manualReview };
273
326
  }
274
327
  function containsLegacyRulesReference(content) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-canon",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "CLI toolkit for Spec-Driven Development (SDD)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,6 +3,7 @@
3
3
  ### 角色定义
4
4
  你是一名遵循 Spec-Driven Development 协议的高级工程师。
5
5
  核心原则:**先文档后代码,人做判断题,AI 做填空题。**
6
+ AI 编程工具本质上是把结构化 Spec 转成实现代码的高效转换器。编码时应以 Spec 为主输入,不应以零散对话替代;若 Spec 不清晰,先补齐或澄清,再进入实现。
6
7
 
7
8
  ### 核心规则
8
9
  - **Context First**:编码前,必须先阅读 `spec-canon/` 下对应的 Spec 文件
@@ -65,9 +66,10 @@ domain_spec 生命周期:变更启动时由 AI 从 change goal 出发,结合
65
66
 
66
67
  ```bash
67
68
  spec-canon sync # 给已初始化项目安全补齐 SDD 骨架
69
+ spec-canon domain create checkin # 显式创建业务域骨架
68
70
  spec-canon change start -g @docs/prd.md # 建立当前 active change(待命名)
69
- # → 运行 ctx 后 AI 建议 change 名
70
- spec-canon change start -g @docs/prd.md -c feat-001-checkin # 确认命名
71
+ # → 运行 ctx 后 AI 会给出命名建议;由你确认或修改后再记录
72
+ spec-canon change start -g @docs/prd.md -c feat-001-checkin # 确认命名;若名称已明确且序号就是当前 next seq,也可首次直接使用
71
73
  spec-canon change status # 查看当前进度与推荐下一步
72
74
  spec-canon change next # 查看当前候选 next prompts
73
75
  spec-canon change -d /path status # 指定目标项目目录
@@ -21,12 +21,13 @@
21
21
 
22
22
  ## 全新项目
23
23
 
24
- 1. 运行 `spec-canon init --domain <首个业务域名>` 初始化项目结构
25
- 2. 使用 `change start --goal -c <name>` 建立 active change(新项目跳过 ctx,直接命名)
26
- 3. 可先用 `change next` 查看当前候选步骤
27
- 4. 使用 `req` 生成 01_requirement.md(人工审阅 AC 并 Sign-off)
28
- 5. 依次使用 `iface` `impl-spec` `test-spec` → `impl` → `review` 完成首个变更
29
- 6. 使用 `domain-sync` 创建首份 domain_spec(人工审阅后写入;可省略 `-d` 自动发现受影响域)
24
+ 1. 运行 `spec-canon init` 初始化项目结构
25
+ 2. 运行 `spec-canon domain create <首个业务域名>` 创建首个业务域
26
+ 3. 使用 `change start --goal -c <name>` 建立 active change(新项目跳过 ctx,首个 change 通常可直接命名)
27
+ 4. 可先用 `change next` 查看当前候选步骤
28
+ 5. 使用 `req` 生成 01_requirement.md(人工审阅 AC 并 Sign-off)
29
+ 6. 依次使用 `iface` → `impl-spec` `test-spec` `impl` → `review` 完成首个变更
30
+ 7. 使用 `domain-sync` 创建首份 domain_spec(人工审阅后写入;可省略 `-d` 自动发现受影响域)
30
31
 
31
32
  > 新项目没有既有系统,00_context(系统现状)无需生成。架构决策自然归入 `impl-spec`(03_implementation)的 Design Decision 段。
32
33
 
@@ -39,8 +40,8 @@
39
40
  1. 运行 `spec-canon init` 初始化结构
40
41
  2. 使用 `change start --goal` 建立 active change
41
42
  3. 可先用 `change next` 查看当前候选步骤
42
- 4. 使用 `ctx` 生成 00_context.md,AI 自动识别主域并建议 change
43
- 5. 用 `change start -g <goal> -c <name>` 确认命名
43
+ 4. 使用 `ctx` 生成 00_context.md,AI 自动识别主域并给出 change 名建议
44
+ 5. 用 `change start -g <goal> -c <name>` 记录确认后的 change 名(可采纳或修改 AI 建议)
44
45
  6. 按日常流程完成变更(req → iface → impl-spec → test-spec → impl → review)
45
46
  7. 在 Archive 步骤使用 `domain-sync` 创建首份 domain_spec(可省略 `-d` 自动发现受影响域)
46
47
 
@@ -70,7 +71,7 @@
70
71
  | Step -1 | `change start --goal` | 建立 active change | 确认 goal 描述准确;若未确认 change 名,先接受待命名状态 |
71
72
  | Step -0.5 | `change next` | 查看候选 next prompts | 关注默认宽松模式下保留的 `iface` / `domain-sync` 候选,自己做判断 |
72
73
  | Step 0 | `ctx` | 生成 00_context.md | 审阅系统现状快照,确认 AI 自动识别的域、模块和待补充项 |
73
- | Step 0.5 | `change start -g <goal> -c <name>` | 确认 change 名 | AI ctx 中建议的名称是否准确 |
74
+ | Step 0.5 | `change start -g <goal> -c <name>` | 确认 change 名 | 采纳或修改 ctx 给出的建议;若名称已明确且序号就是当前 next seq,也可一开始直接用 `-g -c` |
74
75
  | Step 1 | `req` | 生成 01_requirement.md | 审阅 AC,Sign-off |
75
76
  | Step 2 | `iface` → `impl-spec` → `test-spec` | 生成 02/03/04 文档 | 审阅文件路径、步骤顺序 |
76
77
  | Step 3 | `impl` | 分步编码 | 逐步确认 |