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.
|
@@ -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
package/src/commands/accept.js
CHANGED
|
@@ -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);
|
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 };
|