ai-native-core 0.2.1 → 0.2.3

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.
@@ -33,8 +33,10 @@
33
33
 
34
34
  ## SDD 流程(不可变)
35
35
 
36
+ - 完整流程:explore(复杂需求推荐)→ propose → confirm → apply
36
37
  - 禁止跳过 spec 阶段直接写实现代码
37
38
  - 禁止 design.md 缺少 ASCII 页面骨架图
39
+ - 复杂需求(新模块、架构变更)应先 explore 再 propose,禁止跳过 explore 直接 propose
38
40
 
39
41
  ## 技术栈一致性(不可变)
40
42
 
@@ -18,6 +18,8 @@ description = ""
18
18
  # =============================================================================
19
19
  [stack]
20
20
  # 适配器 ID,决定默认模板和规则
21
+ # 单栈:adapter = "react-spa"
22
+ # 多栈(monorepo):adapter = ["react-spa", "backend-java"]
21
23
  # 可选值:react-spa | nextjs | vue | backend-go | backend-python | backend-java
22
24
  adapter = "react-spa"
23
25
 
@@ -104,9 +106,14 @@ paradigm_detection = true
104
106
  # SDD 门禁(Spec-Driven Development)
105
107
  # =============================================================================
106
108
  [sdd]
109
+ # 完整流程:explore(可选)→ propose → confirm → apply
107
110
  # 是否强制 spec-before-code
108
111
  enforced = true
109
112
 
113
+ # 复杂需求是否推荐先 explore(探索需求、澄清边界)
114
+ # 设为 true 时,AI 会在 propose 前主动建议 explore
115
+ recommend_explore = true
116
+
110
117
  # 是否要求 design.md 包含 ASCII wireframe
111
118
  require_ascii_wireframe = true
112
119
 
@@ -130,6 +137,11 @@ manifest = ".ai-native/memory/MANIFEST.yaml"
130
137
  # 记忆文件输出目录
131
138
  output_dir = ".ai-native/memory/"
132
139
 
140
+ # 自定义架构规则(覆盖适配器内置规则)
141
+ # 如果此文件存在,蒸馏时优先使用;框架内置规则作为 fallback
142
+ # 格式与 adapters/{stack}/immutable-rules.md 相同
143
+ # custom_rules = ".ai-native/custom-rules.md"
144
+
133
145
  # LLM provider(用于蒸馏):openai | anthropic | local
134
146
  # 如果不配置,使用 AI Agent 当前会话的模型
135
147
  # llm_provider = "anthropic"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-native-core",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "让任何项目拥有 AI Native 开发能力的运行时框架",
5
5
  "keywords": [
6
6
  "ai-native",
@@ -32,6 +32,21 @@ function run(args) {
32
32
  return { pass: fs.existsSync(p), detail: fs.existsSync(p) ? 'initialized' : 'NOT INITIALIZED' };
33
33
  }, results);
34
34
 
35
+ // Phase 1.5 — SDD check
36
+ const sddTool = config.sdd?.tool || config.ai_tools?.sdd_tool;
37
+ if (sddTool && sddTool !== 'none') {
38
+ console.log('Phase 1.5 — SDD 门禁');
39
+ check('sdd-tool-configured', () => {
40
+ const dir = path.join(root, 'openspec', 'changes');
41
+ const ok = fs.existsSync(dir) && fs.readdirSync(dir).length > 0;
42
+ return { pass: ok || sddTool !== 'openspec', detail: sddTool + (ok ? ' ✓' : ' (spec 目录为空)') };
43
+ }, results);
44
+ check('sdd-gate', () => {
45
+ const enforced = config.sdd?.enforced !== false;
46
+ return { pass: enforced, detail: enforced ? 'enforced' : '⚠ disabled' };
47
+ }, results);
48
+ }
49
+
35
50
  // Phase 2
36
51
  console.log('Phase 2 — 代码质量');
37
52
  check('lint', () => tryExec('pnpm lint || npm run lint || true', root), results);
@@ -1,62 +1,123 @@
1
- // ai-native init
1
+ // ai-native init — interactive mode when no --stack provided
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const readline = require('readline');
5
6
 
6
7
  const VALID_STACKS = ['react-spa', 'nextjs', 'vue', 'backend-go', 'backend-python', 'backend-java'];
7
8
 
8
9
  function run(args) {
9
10
  const idx = args.indexOf('--stack');
10
- const stack = idx >= 0 ? args[idx + 1] : 'react-spa';
11
+ const isForce = args.includes('--force');
11
12
 
12
- if (!VALID_STACKS.includes(stack)) {
13
- console.error(`Invalid stack: ${stack}. Valid: ${VALID_STACKS.join(', ')}`);
14
- process.exit(1);
13
+ if (idx >= 0) {
14
+ const stackArg = args[idx + 1];
15
+ const stacks = stackArg.split(',').map(s => s.trim());
16
+ for (const s of stacks) {
17
+ if (!VALID_STACKS.includes(s)) {
18
+ console.error(`Invalid: ${s}. Valid: ${VALID_STACKS.join(', ')}`);
19
+ process.exit(1);
20
+ }
21
+ }
22
+ doInit(stacks, isForce);
23
+ } else {
24
+ interactiveInit(isForce);
15
25
  }
26
+ }
16
27
 
17
- const root = process.cwd();
18
- const dirs = [
19
- '.ai-native/memory', '.ai-native/hooks', '.ai-native/reports',
20
- 'docs/.ai-native/memory', 'docs/decisions'
21
- ];
28
+ function interactiveInit(isForce) {
29
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
30
+ const answers = {};
31
+ const ask = (q, d) => new Promise(r => rl.question(`${q} [${d}]: `, a => r(a.trim() || d)));
32
+
33
+ (async () => {
34
+ console.log('\n✨ AI Native Core — 交互式初始化\n');
35
+
36
+ const type = await ask('项目类型 (frontend/backend/fullstack/cli)', 'frontend');
37
+ answers.type = type;
38
+ const stacks = [];
39
+
40
+ if (type === 'frontend' || type === 'fullstack') {
41
+ const fw = await ask('前端框架 (react/vue/nextjs)', 'react');
42
+ stacks.push({ react: 'react-spa', vue: 'vue', nextjs: 'nextjs' }[fw] || 'react-spa');
43
+ answers.css = await ask('CSS (tailwind-v4/tailwind-v3/css-modules/none)', 'tailwind-v4');
44
+ answers.ui = await ask('UI 组件库 (shadcn/radix/mui/antd/none)', 'shadcn');
45
+ answers.ts = await ask('TypeScript? (yes/no)', 'yes');
46
+ }
47
+ if (type === 'backend' || type === 'fullstack') {
48
+ const lang = await ask('后端语言 (java/go/python)', 'java');
49
+ stacks.push({ java: 'backend-java', go: 'backend-go', python: 'backend-python' }[lang] || 'backend-java');
50
+ if (type === 'backend') answers.ts = await ask('TypeScript? (yes/no)', 'no');
51
+ }
52
+ if (type === 'cli') answers.ts = await ask('TypeScript? (yes/no)', 'yes');
53
+
54
+ answers.pm = await ask('包管理器 (pnpm/npm/yarn)', 'pnpm');
55
+
56
+ if (type === 'backend') {
57
+ const lang = stacks[0].replace('backend-', '');
58
+ answers.test = { java: 'junit', go: 'go-test', python: 'pytest' }[lang];
59
+ } else {
60
+ answers.test = await ask('测试框架 (vitest/jest/junit)', 'vitest');
61
+ }
22
62
 
23
- console.log(`[ai-native] Initializing with stack: ${stack}\n`);
63
+ rl.close();
24
64
 
65
+ console.log(`\n📋 预览:`);
66
+ console.log(` 类型: ${type} | 适配器: ${stacks.join(', ')}`);
67
+ console.log(` 包管理: ${answers.pm} | 测试: ${answers.test} | TS: ${answers.ts}`);
68
+ if (answers.css) console.log(` CSS: ${answers.css} | UI: ${answers.ui}`);
69
+
70
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
71
+ const confirm = await new Promise(r => rl2.question('\n确认? (yes/no) [yes]: ', a => { rl2.close(); r(a.trim() || 'yes'); }));
72
+ if (confirm !== 'yes' && confirm !== 'y') { console.log('已取消'); process.exit(0); }
73
+
74
+ doInit(stacks, isForce, answers);
75
+ })();
76
+ }
77
+
78
+ function doInit(stacks, isForce, answers = {}) {
79
+ const root = process.cwd();
80
+ const dirs = ['.ai-native/memory', '.ai-native/hooks', '.ai-native/reports', 'docs/.ai-native/memory', 'docs/decisions'];
81
+
82
+ console.log(`\n[ai-native] Initializing...\n`);
25
83
  dirs.forEach(d => {
26
84
  const full = path.join(root, d);
27
- if (!fs.existsSync(full)) {
28
- fs.mkdirSync(full, { recursive: true });
29
- console.log(` created: ${d}`);
30
- }
85
+ if (!fs.existsSync(full)) { fs.mkdirSync(full, { recursive: true }); console.log(` created: ${d}`); }
31
86
  });
32
87
 
33
- // Copy config
34
88
  const copyFile = (srcRel, dstRel, transform) => {
35
89
  const src = path.join(__dirname, '..', '..', srcRel);
36
90
  const dst = path.join(root, dstRel);
37
- if (fs.existsSync(dst)) { console.log(` exists: ${dstRel} (skipped)`); return; }
91
+ if (fs.existsSync(dst) && !isForce) { console.log(` exists: ${dstRel} (skipped)`); return; }
38
92
  if (!fs.existsSync(src)) return;
39
- let content = fs.readFileSync(src, 'utf-8');
40
- if (transform) content = transform(content);
41
- fs.writeFileSync(dst, content);
93
+ let c = fs.readFileSync(src, 'utf-8');
94
+ if (transform) c = transform(c);
95
+ fs.writeFileSync(dst, c);
42
96
  console.log(` created: ${dstRel}`);
43
97
  };
44
98
 
45
- copyFile('config/ai-native.config.toml', '.ai-native/config.toml',
46
- c => c.replace(/adapter = "react-spa"/, `adapter = "${stack}"`));
99
+ copyFile('config/ai-native.config.toml', '.ai-native/config.toml', c => {
100
+ c = c.replace(/adapter = "react-spa"/, stacks.length > 1
101
+ ? `adapter = [${stacks.map(s => `"${s}"`).join(', ')}]`
102
+ : `adapter = "${stacks[0]}"`);
103
+ if (answers.pm) c = c.replace(/package_manager = "pnpm"/, `package_manager = "${answers.pm}"`);
104
+ if (answers.css) c = c.replace(/css = "tailwind-v4"/, `css = "${answers.css}"`);
105
+ if (answers.ui) c = c.replace(/ui_library = "shadcn"/, `ui_library = "${answers.ui}"`);
106
+ if (answers.test) c = c.replace(/test_framework = "vitest"/, `test_framework = "${answers.test}"`);
107
+ if (answers.ts) c = c.replace(/typescript = true/, `typescript = ${answers.ts === 'yes'}`);
108
+ return c;
109
+ });
110
+
47
111
  copyFile('config/acceptance.yaml', '.ai-native/acceptance.yaml');
48
112
  copyFile('config/memory-manifest.yaml', '.ai-native/memory/MANIFEST.yaml');
49
113
 
50
- const selfUpdate = path.join(root, 'docs', 'self-update.md');
51
- if (!fs.existsSync(selfUpdate)) {
52
- fs.writeFileSync(selfUpdate, '# AI Native 范式变更日志\n\n记录范式文件的系统性变更。\n每次范式变更检测后由 AI 追加。\n\n---\n');
53
- console.log(' created: docs/self-update.md');
54
- }
114
+ const su = path.join(root, 'docs', 'self-update.md');
115
+ if (!fs.existsSync(su)) fs.writeFileSync(su, '# AI Native 范式变更日志\n\n记录范式文件的系统性变更。\n\n---\n');
55
116
 
56
117
  console.log('\n[ai-native] Done. Next:');
57
- console.log(' 1. Edit .ai-native/config.toml');
58
- console.log(' 2. Run ai-native sync');
59
- console.log(' 3. Run ai-native hooks install');
118
+ console.log(' 1. ai-native sync');
119
+ console.log(' 2. ai-native hooks install');
120
+ console.log(' 3. ai-native accept');
60
121
  }
61
122
 
62
123
  module.exports = { run };
@@ -49,8 +49,25 @@ function run(args) {
49
49
  return;
50
50
  }
51
51
 
52
- // Load adapter rules
53
- const builtin = loadAdapterRules(config.stack?.adapter || 'react-spa');
52
+ // Load rules: custom > adapter builtin
53
+ const adapters = Array.isArray(config.stack?.adapter)
54
+ ? config.stack.adapter
55
+ : [config.stack?.adapter || 'react-spa'];
56
+
57
+ let builtin = mergeAdapterRules(adapters);
58
+
59
+ // Check for project-level custom rules (overrides adapter)
60
+ const customPath = config.distiller?.custom_rules;
61
+ if (customPath) {
62
+ const fullCustomPath = path.join(root, customPath);
63
+ if (fs.existsSync(fullCustomPath)) {
64
+ const customRules = loadAdapterRules(fullCustomPath);
65
+ if (Object.keys(customRules).length > 0) {
66
+ console.log(`[ai-native] Using custom rules: ${customPath}`);
67
+ builtin = customRules; // Override entirely
68
+ }
69
+ }
70
+ }
54
71
 
55
72
  // Process
56
73
  const factors = targetFactor ? manifest.factors.filter(f => f.name === targetFactor) : manifest.factors;
@@ -116,4 +133,17 @@ ${builtinContent || '\n- (蒸馏引擎将在完整实现中从源文件提取因
116
133
  `;
117
134
  }
118
135
 
136
+ function mergeAdapterRules(adapters) {
137
+ const merged = {};
138
+ for (const name of adapters) {
139
+ const rules = loadAdapterRules(name);
140
+ for (const [key, content] of Object.entries(rules)) {
141
+ if (content) {
142
+ merged[key] = (merged[key] || '') + '\n' + content;
143
+ }
144
+ }
145
+ }
146
+ return merged;
147
+ }
148
+
119
149
  module.exports = { run };