@tongsh6/aief-init 0.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/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # aief-init
2
+
3
+ Optional bootstrap CLI for AI Engineering Framework (AIEF).
4
+
5
+ Run without copying any AIEF files into your repo:
6
+
7
+ npx --yes @tongsh6/aief-init@latest new
8
+
9
+ npx --yes @tongsh6/aief-init@latest retrofit --level L0
10
+
11
+ npx --yes @tongsh6/aief-init@latest retrofit --level L0+
12
+
13
+ Notes:
14
+ - It only writes AIEF entry files (AGENTS.md + context/*). It does not modify your existing code structure.
15
+ - By default it will not overwrite existing files. Use --force to overwrite.
@@ -0,0 +1,355 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import process from 'node:process'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = path.dirname(__filename)
10
+
11
+ function printHelp() {
12
+ process.stdout.write(
13
+ [
14
+ 'aief-init (optional bootstrap)',
15
+ '',
16
+ 'Usage:',
17
+ ' aief-init new',
18
+ ' aief-init retrofit --level L0',
19
+ ' aief-init retrofit --level L0+',
20
+ '',
21
+ 'Options:',
22
+ ' --level <L0|L0+|L1> Migration level for retrofit (default: L0+)',
23
+ ' --force Overwrite existing files',
24
+ ' --dry-run Print actions without writing',
25
+ ' -h, --help Show help',
26
+ '',
27
+ ].join('\n')
28
+ )
29
+ }
30
+
31
+ function parseArgs(argv) {
32
+ const args = argv.slice(2)
33
+
34
+ if (args.includes('-h') || args.includes('--help')) {
35
+ return { help: true }
36
+ }
37
+
38
+ const command = args[0]
39
+ const opts = {
40
+ command,
41
+ level: 'L0+',
42
+ force: false,
43
+ dryRun: false,
44
+ }
45
+
46
+ for (let i = 1; i < args.length; i += 1) {
47
+ const a = args[i]
48
+ if (a === '--force') opts.force = true
49
+ else if (a === '--dry-run') opts.dryRun = true
50
+ else if (a === '--level') {
51
+ const v = args[i + 1]
52
+ if (!v) throw new Error('Missing value for --level')
53
+ opts.level = v
54
+ i += 1
55
+ } else {
56
+ throw new Error(`Unknown option: ${a}`)
57
+ }
58
+ }
59
+
60
+ return opts
61
+ }
62
+
63
+ function exists(p) {
64
+ try {
65
+ fs.accessSync(p, fs.constants.F_OK)
66
+ return true
67
+ } catch {
68
+ return false
69
+ }
70
+ }
71
+
72
+ function ensureDir(dirPath, { dryRun } = {}) {
73
+ if (dryRun) {
74
+ process.stdout.write(`[dry-run] mkdir -p ${dirPath}\n`)
75
+ return
76
+ }
77
+ fs.mkdirSync(dirPath, { recursive: true })
78
+ }
79
+
80
+ function writeFile(filePath, content, { force, dryRun } = {}) {
81
+ if (dryRun) {
82
+ process.stdout.write(`[dry-run] write ${filePath}\n`)
83
+ return
84
+ }
85
+
86
+ ensureDir(path.dirname(filePath))
87
+ const flags = force ? 'w' : 'wx'
88
+ fs.writeFileSync(filePath, content, { encoding: 'utf8', flag: flags })
89
+ }
90
+
91
+ function readText(filePath) {
92
+ return fs.readFileSync(filePath, 'utf8')
93
+ }
94
+
95
+ function copyFile(src, dst, { force, dryRun } = {}) {
96
+ if (dryRun) {
97
+ process.stdout.write(`[dry-run] copy ${src} -> ${dst}\n`)
98
+ return
99
+ }
100
+ ensureDir(path.dirname(dst))
101
+ const flags = force ? 0 : fs.constants.COPYFILE_EXCL
102
+ fs.copyFileSync(src, dst, flags)
103
+ }
104
+
105
+ function listTopLevel(rootDir) {
106
+ const ignore = new Set([
107
+ '.git',
108
+ 'node_modules',
109
+ 'dist',
110
+ 'build',
111
+ 'out',
112
+ 'target',
113
+ '.venv',
114
+ 'venv',
115
+ '__pycache__',
116
+ '.idea',
117
+ '.vscode',
118
+ ])
119
+
120
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true })
121
+ return entries
122
+ .filter((e) => !ignore.has(e.name))
123
+ .map((e) => ({ name: e.name, isDir: e.isDirectory() }))
124
+ .sort((a, b) => a.name.localeCompare(b.name))
125
+ }
126
+
127
+ function detectTech(rootDir) {
128
+ const f = (name) => exists(path.join(rootDir, name))
129
+ const d = (name) => exists(path.join(rootDir, name)) && fs.statSync(path.join(rootDir, name)).isDirectory()
130
+
131
+ const tech = {
132
+ languages: new Set(),
133
+ frameworks: new Set(),
134
+ buildTools: new Set(),
135
+ runtimes: new Set(),
136
+ packageManagers: new Set(),
137
+ ci: new Set(),
138
+ docker: new Set(),
139
+ }
140
+
141
+ if (f('package.json')) {
142
+ tech.languages.add('JavaScript/TypeScript')
143
+ tech.buildTools.add('npm-compatible')
144
+ tech.runtimes.add('Node.js')
145
+ tech.packageManagers.add('npm')
146
+
147
+ if (f('pnpm-lock.yaml')) tech.packageManagers.add('pnpm')
148
+ if (f('yarn.lock')) tech.packageManagers.add('yarn')
149
+ if (f('bun.lockb')) tech.packageManagers.add('bun')
150
+
151
+ try {
152
+ const pkg = JSON.parse(readText(path.join(rootDir, 'package.json')))
153
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
154
+ const has = (k) => Boolean(deps && Object.prototype.hasOwnProperty.call(deps, k))
155
+ if (has('react')) tech.frameworks.add('React')
156
+ if (has('next')) tech.frameworks.add('Next.js')
157
+ if (has('vue')) tech.frameworks.add('Vue')
158
+ if (has('nuxt')) tech.frameworks.add('Nuxt')
159
+ if (has('@nestjs/core')) tech.frameworks.add('NestJS')
160
+ if (has('express')) tech.frameworks.add('Express')
161
+ if (has('fastify')) tech.frameworks.add('Fastify')
162
+ } catch {
163
+ // best-effort only
164
+ }
165
+ }
166
+
167
+ if (f('pyproject.toml') || f('requirements.txt') || f('poetry.lock')) {
168
+ tech.languages.add('Python')
169
+ tech.buildTools.add('pip/poetry')
170
+ if (f('poetry.lock')) tech.packageManagers.add('poetry')
171
+ }
172
+
173
+ if (f('go.mod')) {
174
+ tech.languages.add('Go')
175
+ tech.buildTools.add('go')
176
+ }
177
+
178
+ if (f('Cargo.toml')) {
179
+ tech.languages.add('Rust')
180
+ tech.buildTools.add('cargo')
181
+ }
182
+
183
+ if (f('pom.xml')) {
184
+ tech.languages.add('Java')
185
+ tech.buildTools.add('maven')
186
+ }
187
+ if (f('build.gradle') || f('build.gradle.kts')) {
188
+ tech.languages.add('Java/Kotlin')
189
+ tech.buildTools.add('gradle')
190
+ }
191
+
192
+ if (d('.github') && d('.github/workflows')) tech.ci.add('GitHub Actions')
193
+ if (f('.gitlab-ci.yml')) tech.ci.add('GitLab CI')
194
+ if (f('Jenkinsfile')) tech.ci.add('Jenkins')
195
+ if (d('.circleci')) tech.ci.add('CircleCI')
196
+ if (f('azure-pipelines.yml')) tech.ci.add('Azure Pipelines')
197
+
198
+ if (f('Dockerfile')) tech.docker.add('Dockerfile')
199
+ if (f('docker-compose.yml') || f('compose.yml')) tech.docker.add('Docker Compose')
200
+
201
+ return tech
202
+ }
203
+
204
+ function detectModules(rootDir) {
205
+ const candidates = ['apps', 'services', 'packages', 'modules']
206
+ const modules = []
207
+
208
+ for (const base of candidates) {
209
+ const basePath = path.join(rootDir, base)
210
+ if (!exists(basePath)) continue
211
+ if (!fs.statSync(basePath).isDirectory()) continue
212
+
213
+ const children = fs
214
+ .readdirSync(basePath, { withFileTypes: true })
215
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
216
+ .map((e) => e.name)
217
+ .sort((a, b) => a.localeCompare(b))
218
+
219
+ for (const name of children) {
220
+ modules.push({ name, path: `./${base}/${name}` })
221
+ }
222
+ }
223
+
224
+ return modules
225
+ }
226
+
227
+ function formatRepoSnapshot(rootDir) {
228
+ const tech = detectTech(rootDir)
229
+ const topLevel = listTopLevel(rootDir)
230
+ const modules = detectModules(rootDir)
231
+
232
+ const fmtList = (set) => {
233
+ const arr = Array.from(set)
234
+ return arr.length ? arr.join(', ') : ''
235
+ }
236
+
237
+ const lines = []
238
+ lines.push('# Repo Snapshot')
239
+ lines.push('')
240
+ lines.push('This file is generated as a retrofit starter draft. Edit freely.')
241
+ lines.push('')
242
+ lines.push('## Tech Stack (Detected)')
243
+ lines.push(`- Language: ${fmtList(tech.languages) || 'unknown'}`)
244
+ lines.push(`- Framework: ${fmtList(tech.frameworks) || 'unknown'}`)
245
+ lines.push(`- Build Tool: ${fmtList(tech.buildTools) || 'unknown'}`)
246
+ lines.push(`- Runtime: ${fmtList(tech.runtimes) || 'unknown'}`)
247
+ if (tech.packageManagers.size) lines.push(`- Package Manager: ${fmtList(tech.packageManagers)}`)
248
+ lines.push('')
249
+ lines.push('## Repo Layout (Top Level)')
250
+ for (const e of topLevel) {
251
+ lines.push(`- ${e.isDir ? e.name + '/' : e.name}`)
252
+ }
253
+ lines.push('')
254
+ lines.push('## Modules / Services (Heuristics)')
255
+ if (!modules.length) {
256
+ lines.push('- (none detected)')
257
+ } else {
258
+ for (const m of modules) {
259
+ lines.push(`- ${m.name} (${m.path})`)
260
+ }
261
+ }
262
+ lines.push('')
263
+ lines.push('## Infra & CI (Detected)')
264
+ lines.push(`- CI: ${fmtList(tech.ci) || 'unknown'}`)
265
+ lines.push(`- Docker: ${fmtList(tech.docker) || 'unknown'}`)
266
+ lines.push('')
267
+ lines.push('## Commands (Fill In)')
268
+ lines.push('- build:')
269
+ lines.push('- test:')
270
+ lines.push('- run:')
271
+ lines.push('')
272
+
273
+ return lines.join('\n')
274
+ }
275
+
276
+ function repoPath(rel) {
277
+ return path.resolve(process.cwd(), rel)
278
+ }
279
+
280
+ function templatePath(rel) {
281
+ return path.resolve(__dirname, '..', rel)
282
+ }
283
+
284
+ function initMinimal({ force, dryRun } = {}) {
285
+ const agentsSrc = templatePath('templates/minimal/AGENTS.md')
286
+ const indexSrc = templatePath('templates/minimal/context/INDEX.md')
287
+ const bKeep = templatePath('templates/minimal/context/business/.gitkeep')
288
+ const tKeep = templatePath('templates/minimal/context/tech/.gitkeep')
289
+ const eKeep = templatePath('templates/minimal/context/experience/.gitkeep')
290
+
291
+ if (exists(agentsSrc)) copyFile(agentsSrc, repoPath('AGENTS.md'), { force, dryRun })
292
+ else writeFile(repoPath('AGENTS.md'), '# AI Guide\n', { force, dryRun })
293
+
294
+ if (exists(indexSrc)) copyFile(indexSrc, repoPath('context/INDEX.md'), { force, dryRun })
295
+ else writeFile(repoPath('context/INDEX.md'), '# Context Index\n', { force, dryRun })
296
+
297
+ ensureDir(repoPath('context/business'), { dryRun })
298
+ ensureDir(repoPath('context/tech'), { dryRun })
299
+ ensureDir(repoPath('context/experience'), { dryRun })
300
+
301
+ if (exists(bKeep)) copyFile(bKeep, repoPath('context/business/.gitkeep'), { force, dryRun })
302
+ if (exists(tKeep)) copyFile(tKeep, repoPath('context/tech/.gitkeep'), { force, dryRun })
303
+ if (exists(eKeep)) copyFile(eKeep, repoPath('context/experience/.gitkeep'), { force, dryRun })
304
+ }
305
+
306
+ function initRetrofit({ level, force, dryRun } = {}) {
307
+ initMinimal({ force, dryRun })
308
+
309
+ if (level === 'L0') return
310
+ if (level !== 'L0+' && level !== 'L1') {
311
+ throw new Error(`Unsupported level: ${level}`)
312
+ }
313
+
314
+ const snapshotPath = repoPath('context/tech/REPO_SNAPSHOT.md')
315
+ const snapshotContent = formatRepoSnapshot(process.cwd())
316
+ writeFile(snapshotPath, snapshotContent, { force, dryRun })
317
+ }
318
+
319
+ function main() {
320
+ let opts
321
+ try {
322
+ opts = parseArgs(process.argv)
323
+ } catch (err) {
324
+ process.stderr.write(`${err.message}\n`)
325
+ printHelp()
326
+ process.exit(1)
327
+ }
328
+
329
+ if (opts.help || !opts.command) {
330
+ printHelp()
331
+ process.exit(0)
332
+ }
333
+
334
+ try {
335
+ if (opts.command === 'new') {
336
+ initMinimal({ force: opts.force, dryRun: opts.dryRun })
337
+ process.stdout.write('Done. Created minimal AIEF entry (AGENTS.md + context/).\n')
338
+ return
339
+ }
340
+
341
+ if (opts.command === 'retrofit') {
342
+ initRetrofit({ level: opts.level, force: opts.force, dryRun: opts.dryRun })
343
+ process.stdout.write(`Done. Retrofit init at level ${opts.level}.\n`)
344
+ return
345
+ }
346
+
347
+ throw new Error(`Unknown command: ${opts.command}`)
348
+ } catch (err) {
349
+ const msg = err && err.message ? err.message : String(err)
350
+ process.stderr.write(`${msg}\n`)
351
+ process.exit(1)
352
+ }
353
+ }
354
+
355
+ main()
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@tongsh6/aief-init",
3
+ "version": "0.1.0",
4
+ "description": "Optional bootstrap CLI for AI Engineering Framework (AIEF)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "aief-init": "./bin/aief-init.mjs"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "files": [
17
+ "bin/",
18
+ "templates/",
19
+ "README.md"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/tongsh6/ai-engineering-framework.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/tongsh6/ai-engineering-framework/issues"
27
+ },
28
+ "homepage": "https://github.com/tongsh6/ai-engineering-framework#readme"
29
+ }
@@ -0,0 +1,19 @@
1
+ # [Project Name] AI Guide
2
+
3
+ This is the project-level entry for AI-assisted engineering.
4
+
5
+ Language:
6
+ - Use Chinese for communication by default
7
+ - Keep code/commands/identifiers in English
8
+
9
+ Project:
10
+ - One-liner:
11
+ - Core value:
12
+
13
+ Quick Commands:
14
+ - build:
15
+ - test:
16
+ - run:
17
+
18
+ Context Entry:
19
+ - context/INDEX.md
@@ -0,0 +1,15 @@
1
+ # Context Index
2
+
3
+ This is the navigation entry for long-term project context.
4
+
5
+ Directories:
6
+
7
+ context/
8
+ business/
9
+ tech/
10
+ experience/
11
+
12
+ Suggested next documents (optional):
13
+ - context/business/DOMAIN.md
14
+ - context/tech/ARCHITECTURE.md
15
+ - context/experience/INDEX.md
File without changes
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ # Repo Snapshot
2
+
3
+ ## Tech Stack
4
+ - Language:
5
+ - Framework:
6
+ - Build Tool:
7
+ - Runtime:
8
+
9
+ ## Repo Layout (Top Level)
10
+ - /
11
+
12
+ ## Modules / Services
13
+ - name:
14
+ - path:
15
+ - responsibility:
16
+ - owner (optional):
17
+
18
+ ## Infra & CI
19
+ - CI:
20
+ - Docker:
21
+ - Deploy:
22
+
23
+ ## Commands (If Known)
24
+ - build:
25
+ - test:
26
+ - run: