ai-native-core 0.2.0 → 0.2.2
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/config/ai-native.config.toml +9 -2
- package/package.json +1 -1
- package/src/commands/init.js +91 -30
- package/src/commands/sync.js +32 -2
|
@@ -18,7 +18,9 @@ description = ""
|
|
|
18
18
|
# =============================================================================
|
|
19
19
|
[stack]
|
|
20
20
|
# 适配器 ID,决定默认模板和规则
|
|
21
|
-
#
|
|
21
|
+
# 单栈:adapter = "react-spa"
|
|
22
|
+
# 多栈(monorepo):adapter = ["react-spa", "backend-java"]
|
|
23
|
+
# 可选值:react-spa | nextjs | vue | backend-go | backend-python | backend-java
|
|
22
24
|
adapter = "react-spa"
|
|
23
25
|
|
|
24
26
|
# 包管理器:pnpm | npm | yarn | bun
|
|
@@ -30,7 +32,7 @@ css = "tailwind-v4"
|
|
|
30
32
|
# UI 组件库(前端项目):shadcn | radix | mui | antd | none
|
|
31
33
|
ui_library = "shadcn"
|
|
32
34
|
|
|
33
|
-
# 测试框架:vitest | jest | playwright | go-test | pytest
|
|
35
|
+
# 测试框架:vitest | jest | playwright | go-test | pytest | junit
|
|
34
36
|
test_framework = "vitest"
|
|
35
37
|
|
|
36
38
|
# TypeScript(前端/Node 项目)
|
|
@@ -130,6 +132,11 @@ manifest = ".ai-native/memory/MANIFEST.yaml"
|
|
|
130
132
|
# 记忆文件输出目录
|
|
131
133
|
output_dir = ".ai-native/memory/"
|
|
132
134
|
|
|
135
|
+
# 自定义架构规则(覆盖适配器内置规则)
|
|
136
|
+
# 如果此文件存在,蒸馏时优先使用;框架内置规则作为 fallback
|
|
137
|
+
# 格式与 adapters/{stack}/immutable-rules.md 相同
|
|
138
|
+
# custom_rules = ".ai-native/custom-rules.md"
|
|
139
|
+
|
|
133
140
|
# LLM provider(用于蒸馏):openai | anthropic | local
|
|
134
141
|
# 如果不配置,使用 AI Agent 当前会话的模型
|
|
135
142
|
# llm_provider = "anthropic"
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
11
|
+
const isForce = args.includes('--force');
|
|
11
12
|
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
40
|
-
if (transform)
|
|
41
|
-
fs.writeFileSync(dst,
|
|
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
|
|
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
|
|
51
|
-
if (!fs.existsSync(
|
|
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.
|
|
58
|
-
console.log(' 2.
|
|
59
|
-
console.log(' 3.
|
|
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 };
|
package/src/commands/sync.js
CHANGED
|
@@ -49,8 +49,25 @@ function run(args) {
|
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// Load adapter
|
|
53
|
-
const
|
|
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 };
|