ai-codebase-registry 1.0.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 +137 -0
- package/bin/add-headers.mjs +74 -0
- package/bin/cli.mjs +82 -0
- package/bin/generate.mjs +153 -0
- package/bin/init.mjs +198 -0
- package/bin/query.mjs +62 -0
- package/bin/validate.mjs +102 -0
- package/lib/core/ai-meta-parser.mjs +196 -0
- package/lib/core/config-loader.mjs +85 -0
- package/lib/core/domain-detector.mjs +79 -0
- package/lib/core/file-discovery.mjs +60 -0
- package/lib/core/registry.mjs +222 -0
- package/lib/core/semantic-id.mjs +56 -0
- package/lib/core/type-detector.mjs +114 -0
- package/lib/generators/deps.mjs +55 -0
- package/lib/generators/features.mjs +58 -0
- package/lib/generators/index.mjs +20 -0
- package/lib/generators/manifest.mjs +128 -0
- package/lib/generators/semantic-ids.mjs +43 -0
- package/lib/generators/side-effects.mjs +65 -0
- package/lib/index.mjs +19 -0
- package/lib/parsers/imports-typescript.mjs +104 -0
- package/lib/parsers/index.mjs +16 -0
- package/lib/query/engine.mjs +254 -0
- package/lib/schemas/config.schema.json +88 -0
- package/package.json +57 -0
- package/templates/config-swift.mjs +27 -0
- package/templates/config.mjs +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# ai-codebase-registry
|
|
2
|
+
|
|
3
|
+
Registry-driven AI-agent infrastructure for codebases. Auto-generates machine-readable metadata, dependency graphs, side-effects tracking, and query APIs for AI coding agents.
|
|
4
|
+
|
|
5
|
+
Supports **TypeScript**, **Swift**, **Kotlin**, and **Python** projects.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev ai-codebase-registry
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Initialize config file
|
|
17
|
+
npx ai-registry init
|
|
18
|
+
|
|
19
|
+
# Generate registry from source
|
|
20
|
+
npx ai-registry generate
|
|
21
|
+
|
|
22
|
+
# Validate @ai-meta coverage
|
|
23
|
+
npx ai-registry validate
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## CLI Commands
|
|
27
|
+
|
|
28
|
+
| Command | Description |
|
|
29
|
+
|---------|-------------|
|
|
30
|
+
| `ai-registry init` | Scaffold `ai-registry.config.mjs` for a new project |
|
|
31
|
+
| `ai-registry generate` | Rebuild all registry files from source |
|
|
32
|
+
| `ai-registry validate` | Check `@ai-meta` coverage and report gaps |
|
|
33
|
+
| `ai-registry query` | Query registry data (files, deps, impact, coverage) |
|
|
34
|
+
| `ai-registry add-headers` | Auto-add `@ai-meta` headers to files missing them |
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
Create `ai-registry.config.mjs` in your project root:
|
|
39
|
+
|
|
40
|
+
### TypeScript / JavaScript
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
export default {
|
|
44
|
+
name: 'my-project',
|
|
45
|
+
scanDirs: [{ dir: 'src', prefix: 'src' }],
|
|
46
|
+
extensions: ['.ts', '.tsx'],
|
|
47
|
+
skipDirs: ['node_modules', 'dist'],
|
|
48
|
+
aliases: { '@/': 'src/' },
|
|
49
|
+
registryDir: '_registry',
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Swift (iOS / macOS)
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
export default {
|
|
57
|
+
name: 'my-ios-app',
|
|
58
|
+
scanDirs: [
|
|
59
|
+
{ dir: 'Sources', prefix: 'Sources' },
|
|
60
|
+
{ dir: 'Tests', prefix: 'Tests' },
|
|
61
|
+
],
|
|
62
|
+
extensions: ['.swift'],
|
|
63
|
+
skipDirs: ['build', 'Pods', '.build', 'DerivedData'],
|
|
64
|
+
language: 'swift',
|
|
65
|
+
fieldMap: { mutations: 'side-effects', dependencies: 'depends-on' },
|
|
66
|
+
generators: ['manifest', 'semantic-ids', 'side-effects', 'features'],
|
|
67
|
+
registryDir: '_registry',
|
|
68
|
+
};
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Config Reference
|
|
72
|
+
|
|
73
|
+
| Field | Type | Default | Description |
|
|
74
|
+
|-------|------|---------|-------------|
|
|
75
|
+
| `name` | string | `'my-project'` | Project name |
|
|
76
|
+
| `description` | string | `''` | Project description |
|
|
77
|
+
| `scanDirs` | array | `[{ dir: 'src', prefix: 'src' }]` | Directories to scan |
|
|
78
|
+
| `extensions` | array | `['.ts', '.tsx']` | File extensions to include |
|
|
79
|
+
| `skipDirs` | array | `['node_modules', ...]` | Directories to skip |
|
|
80
|
+
| `language` | string | `'typescript'` | `'typescript'` \| `'swift'` \| `'kotlin'` \| `'python'` |
|
|
81
|
+
| `fieldMap` | object | `{}` | Field name normalization map |
|
|
82
|
+
| `generators` | array\|null | `null` (all) | Which generators to run |
|
|
83
|
+
| `aliases` | object | `{}` | Import alias map (TS only) |
|
|
84
|
+
| `router` | string | `'none'` | Router type for route extraction |
|
|
85
|
+
| `dataStore` | string | `'none'` | Data store type |
|
|
86
|
+
| `registryDir` | string | `'_registry'` | Output directory |
|
|
87
|
+
| `agent` | string | `'claude-code'` | Target AI agent |
|
|
88
|
+
| `claudemd` | boolean | `true` | Generate CLAUDE.md references |
|
|
89
|
+
|
|
90
|
+
## Multi-Language Support
|
|
91
|
+
|
|
92
|
+
The parser auto-detects the comment style from file content:
|
|
93
|
+
|
|
94
|
+
| Language | Comment Style | Field Syntax |
|
|
95
|
+
|----------|--------------|--------------|
|
|
96
|
+
| TypeScript/JS | `/** @ai-meta ... */` | `@field value` |
|
|
97
|
+
| Swift | `/// @ai-meta ...` | `field: value` |
|
|
98
|
+
| Kotlin/Go | `// @ai-meta ...` | `field: value` or `@field value` |
|
|
99
|
+
|
|
100
|
+
### Field Normalization
|
|
101
|
+
|
|
102
|
+
Use `fieldMap` to map project-specific field names to canonical ones:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
fieldMap: {
|
|
106
|
+
mutations: 'side-effects', // Swift uses "mutations:", canonical is "side-effects"
|
|
107
|
+
dependencies: 'depends-on', // Swift uses "dependencies:", canonical is "depends-on"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Generated Registry Files
|
|
112
|
+
|
|
113
|
+
| File | Description |
|
|
114
|
+
|------|-------------|
|
|
115
|
+
| `manifest.json` | Every source file with domain, type, purpose, imports |
|
|
116
|
+
| `semantic-ids.json` | Stable entity ID to file path mapping |
|
|
117
|
+
| `deps.json` | Import dependency graph (TypeScript only) |
|
|
118
|
+
| `side-effects.json` | Mutation/trigger to downstream effects graph |
|
|
119
|
+
| `features.json` | Feature inventory by domain |
|
|
120
|
+
|
|
121
|
+
## Deep Import Paths
|
|
122
|
+
|
|
123
|
+
| Import Path | Purpose |
|
|
124
|
+
|-------------|---------|
|
|
125
|
+
| `ai-codebase-registry` | Main entry (re-exports) |
|
|
126
|
+
| `ai-codebase-registry/core/file-discovery` | Recursive file scanning |
|
|
127
|
+
| `ai-codebase-registry/core/ai-meta-parser` | `@ai-meta` header parsing |
|
|
128
|
+
| `ai-codebase-registry/core/domain-detector` | Domain detection from paths |
|
|
129
|
+
| `ai-codebase-registry/core/type-detector` | File type detection |
|
|
130
|
+
| `ai-codebase-registry/core/semantic-id` | Semantic ID generation |
|
|
131
|
+
| `ai-codebase-registry/parsers` | Parser barrel export |
|
|
132
|
+
| `ai-codebase-registry/generators` | Generator barrel export |
|
|
133
|
+
| `ai-codebase-registry/query` | Registry query engine |
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @ai-meta
|
|
4
|
+
* @id module:ai-agent-infra:cli-add-headers
|
|
5
|
+
* @domain ai-agent-infra
|
|
6
|
+
* @type module
|
|
7
|
+
* @side-effects writes:filesystem:source-files
|
|
8
|
+
* @stability medium
|
|
9
|
+
* @complexity complex
|
|
10
|
+
* @token-budget 300
|
|
11
|
+
* @purpose CLI command: auto-add @ai-meta headers to source files missing them
|
|
12
|
+
*
|
|
13
|
+
* @filechangelog
|
|
14
|
+
* @changelog 2026-02-22T14:00 [feat] [Claude Opus 4.6] Created add-headers CLI stub
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync } from 'fs';
|
|
18
|
+
import { resolve } from 'path';
|
|
19
|
+
import { loadConfig } from '../lib/core/config-loader.mjs';
|
|
20
|
+
import { collectFiles } from '../lib/core/file-discovery.mjs';
|
|
21
|
+
|
|
22
|
+
const C = {
|
|
23
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
24
|
+
green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const isDryRun = process.argv.includes('--dry-run');
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
const rootDir = resolve('.');
|
|
31
|
+
const config = await loadConfig(rootDir);
|
|
32
|
+
|
|
33
|
+
console.log(`${C.bold}ai-codebase-registry add-headers${C.reset}`);
|
|
34
|
+
console.log(isDryRun ? `${C.yellow}DRY RUN — no files will be modified${C.reset}\n` : '\n');
|
|
35
|
+
|
|
36
|
+
// Discover files
|
|
37
|
+
const allFiles = [];
|
|
38
|
+
const extensions = new Set(config.extensions);
|
|
39
|
+
const skipDirs = new Set(config.skipDirs);
|
|
40
|
+
|
|
41
|
+
for (const { dir, prefix } of config.scanDirs) {
|
|
42
|
+
if (existsSync(dir)) {
|
|
43
|
+
allFiles.push(...collectFiles(dir, prefix, { extensions, skipDirs }));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`Scanned ${allFiles.length} files`);
|
|
48
|
+
console.log(`${C.dim}Note: For full header injection, use the project's scripts/add-ai-meta-headers.mjs${C.reset}`);
|
|
49
|
+
console.log(`${C.dim}This command provides the framework; the actual injection logic is in the project script.${C.reset}`);
|
|
50
|
+
|
|
51
|
+
// Count files without @ai-meta
|
|
52
|
+
let missing = 0;
|
|
53
|
+
for (const { fullPath } of allFiles) {
|
|
54
|
+
try {
|
|
55
|
+
const { readFileSync } = await import('fs');
|
|
56
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
57
|
+
if (!content.includes('@ai-meta')) missing++;
|
|
58
|
+
} catch {
|
|
59
|
+
// skip
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(`\nFiles without @ai-meta: ${missing}`);
|
|
64
|
+
if (missing === 0) {
|
|
65
|
+
console.log(`${C.green}All files have @ai-meta headers!${C.reset}`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log(`${C.yellow}Run 'node scripts/add-ai-meta-headers.mjs' to add headers${C.reset}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main().catch(err => {
|
|
72
|
+
console.error(err);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @ai-meta
|
|
4
|
+
* @id module:ai-agent-infra:cli
|
|
5
|
+
* @domain ai-agent-infra
|
|
6
|
+
* @type module
|
|
7
|
+
* @side-effects none
|
|
8
|
+
* @stability medium
|
|
9
|
+
* @complexity moderate
|
|
10
|
+
* @token-budget 200
|
|
11
|
+
* @purpose Unified CLI entry point for ai-codebase-registry — dispatches to init, generate, query, validate, add-headers
|
|
12
|
+
*
|
|
13
|
+
* @filechangelog
|
|
14
|
+
* @changelog 2026-02-22T14:00 [feat] [Claude Opus 4.6] Created CLI dispatcher
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const COMMANDS = {
|
|
18
|
+
init: './init.mjs',
|
|
19
|
+
generate: './generate.mjs',
|
|
20
|
+
query: './query.mjs',
|
|
21
|
+
validate: './validate.mjs',
|
|
22
|
+
'add-headers': './add-headers.mjs',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function printUsage() {
|
|
26
|
+
console.log(`
|
|
27
|
+
\x1b[1mai-codebase-registry\x1b[0m — Registry-driven AI agent infrastructure
|
|
28
|
+
|
|
29
|
+
\x1b[1mUsage:\x1b[0m
|
|
30
|
+
ai-registry <command> [options]
|
|
31
|
+
|
|
32
|
+
\x1b[1mCommands:\x1b[0m
|
|
33
|
+
init Scaffold registry for a new project
|
|
34
|
+
generate Rebuild all registry files from source
|
|
35
|
+
query Query registry data (files, deps, impact, coverage)
|
|
36
|
+
validate Check @ai-meta coverage and completeness
|
|
37
|
+
add-headers Auto-add @ai-meta headers to files missing them
|
|
38
|
+
|
|
39
|
+
\x1b[1mExamples:\x1b[0m
|
|
40
|
+
ai-registry init
|
|
41
|
+
ai-registry generate
|
|
42
|
+
ai-registry query --domain trip --type hook
|
|
43
|
+
ai-registry query --impact functions/src/services/projectionService.ts
|
|
44
|
+
ai-registry query --coverage
|
|
45
|
+
ai-registry validate
|
|
46
|
+
ai-registry add-headers --dry-run
|
|
47
|
+
|
|
48
|
+
Run \x1b[1mai-registry <command> --help\x1b[0m for command-specific help.
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
const command = args[0];
|
|
55
|
+
|
|
56
|
+
if (!command || command === '--help' || command === '-h') {
|
|
57
|
+
printUsage();
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!COMMANDS[command]) {
|
|
62
|
+
console.error(`Unknown command: ${command}`);
|
|
63
|
+
console.error(`Run 'ai-registry --help' for available commands.`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Forward remaining args
|
|
68
|
+
process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await import(COMMANDS[command]);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
74
|
+
console.error(`Command '${command}' is not yet implemented.`);
|
|
75
|
+
console.error(`Module not found: ${COMMANDS[command]}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main();
|
package/bin/generate.mjs
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @ai-meta
|
|
4
|
+
* @id module:ai-agent-infra:cli-generate
|
|
5
|
+
* @domain ai-agent-infra
|
|
6
|
+
* @type module
|
|
7
|
+
* @side-effects writes:filesystem:_registry
|
|
8
|
+
* @stability medium
|
|
9
|
+
* @complexity complex
|
|
10
|
+
* @token-budget 300
|
|
11
|
+
* @purpose CLI command: rebuild all registry files from source files — supports multi-language projects
|
|
12
|
+
*
|
|
13
|
+
* @filechangelog
|
|
14
|
+
* @changelog 2026-02-23T10:00 [feat] [Claude Opus 4.6] Add fieldMap pass-through, language-aware deps skip, conditional generators
|
|
15
|
+
* @changelog 2026-02-22T14:00 [feat] [Claude Opus 4.6] Created generate CLI command
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
19
|
+
import { resolve } from 'path';
|
|
20
|
+
import { loadConfig } from '../lib/core/config-loader.mjs';
|
|
21
|
+
import { collectFiles } from '../lib/core/file-discovery.mjs';
|
|
22
|
+
import { createDomainDetector } from '../lib/core/domain-detector.mjs';
|
|
23
|
+
import { generateManifest } from '../lib/generators/manifest.mjs';
|
|
24
|
+
import { generateDeps } from '../lib/generators/deps.mjs';
|
|
25
|
+
import { generateSemanticIds } from '../lib/generators/semantic-ids.mjs';
|
|
26
|
+
import { generateSideEffects } from '../lib/generators/side-effects.mjs';
|
|
27
|
+
import { generateFeatures } from '../lib/generators/features.mjs';
|
|
28
|
+
|
|
29
|
+
const C = {
|
|
30
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
31
|
+
green: '\x1b[32m', cyan: '\x1b[36m', yellow: '\x1b[33m',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Languages that support textual import parsing */
|
|
35
|
+
const IMPORT_GRAPH_LANGUAGES = new Set(['typescript', 'javascript']);
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
const rootDir = resolve('.');
|
|
39
|
+
const config = await loadConfig(rootDir);
|
|
40
|
+
|
|
41
|
+
const enabledGenerators = config.generators
|
|
42
|
+
? new Set(config.generators)
|
|
43
|
+
: null; // null = all
|
|
44
|
+
|
|
45
|
+
const shouldGenerate = (name) => !enabledGenerators || enabledGenerators.has(name);
|
|
46
|
+
const canGenerateDeps = IMPORT_GRAPH_LANGUAGES.has(config.language || 'typescript');
|
|
47
|
+
|
|
48
|
+
console.log(`${C.bold}ai-codebase-registry generate${C.reset}`);
|
|
49
|
+
console.log(`Project: ${config.name}`);
|
|
50
|
+
if (config.language && config.language !== 'typescript') {
|
|
51
|
+
console.log(`Language: ${config.language}`);
|
|
52
|
+
}
|
|
53
|
+
console.log('');
|
|
54
|
+
|
|
55
|
+
// 1. Discover files
|
|
56
|
+
console.log('Scanning codebase...');
|
|
57
|
+
const allFiles = [];
|
|
58
|
+
const extensions = new Set(config.extensions);
|
|
59
|
+
const skipDirs = new Set(config.skipDirs);
|
|
60
|
+
|
|
61
|
+
for (const { dir, prefix } of config.scanDirs) {
|
|
62
|
+
if (existsSync(dir)) {
|
|
63
|
+
const files = collectFiles(dir, prefix, { extensions, skipDirs });
|
|
64
|
+
allFiles.push(...files);
|
|
65
|
+
console.log(` ${prefix}: ${files.length} files`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log(` ${prefix}: directory not found, skipping`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
console.log(` Total: ${allFiles.length} files\n`);
|
|
71
|
+
|
|
72
|
+
// 2. Build manifest
|
|
73
|
+
console.log('Building manifest...');
|
|
74
|
+
const detectDomain = createDomainDetector();
|
|
75
|
+
const { manifest, importGraph, stats } = generateManifest(allFiles, {
|
|
76
|
+
aliasMap: config.aliases,
|
|
77
|
+
detectDomain,
|
|
78
|
+
fieldMap: config.fieldMap,
|
|
79
|
+
skipImportParsing: !canGenerateDeps,
|
|
80
|
+
});
|
|
81
|
+
console.log(` @ai-meta coverage: ${stats.coverage}`);
|
|
82
|
+
console.log(` With @purpose: ${stats.purposeCount}\n`);
|
|
83
|
+
|
|
84
|
+
// 3. Generate registry files
|
|
85
|
+
console.log('Generating registry files...');
|
|
86
|
+
|
|
87
|
+
const outputs = [];
|
|
88
|
+
|
|
89
|
+
if (shouldGenerate('manifest')) {
|
|
90
|
+
const manifestOutput = {
|
|
91
|
+
generated: new Date().toISOString(),
|
|
92
|
+
fileCount: Object.keys(manifest).length,
|
|
93
|
+
aiMetaCoverage: stats.coverage,
|
|
94
|
+
files: manifest,
|
|
95
|
+
};
|
|
96
|
+
outputs.push(['manifest.json', manifestOutput]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (shouldGenerate('deps') && canGenerateDeps) {
|
|
100
|
+
const importGraphSets = {};
|
|
101
|
+
for (const [key, value] of Object.entries(importGraph)) {
|
|
102
|
+
importGraphSets[key] = value;
|
|
103
|
+
}
|
|
104
|
+
const deps = generateDeps(importGraphSets);
|
|
105
|
+
outputs.push(['deps.json', deps]);
|
|
106
|
+
} else if (shouldGenerate('deps') && !canGenerateDeps) {
|
|
107
|
+
console.log(` ${C.yellow}skip${C.reset} deps.json (no import graph for ${config.language})`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (shouldGenerate('semantic-ids')) {
|
|
111
|
+
const semanticIds = generateSemanticIds(manifest);
|
|
112
|
+
outputs.push(['semantic-ids.json', semanticIds]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (shouldGenerate('side-effects')) {
|
|
116
|
+
const sideEffects = generateSideEffects(manifest);
|
|
117
|
+
outputs.push(['side-effects.json', sideEffects]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (shouldGenerate('features')) {
|
|
121
|
+
const features = generateFeatures(manifest);
|
|
122
|
+
outputs.push(['features.json', features]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 4. Write output
|
|
126
|
+
const registryDir = config.registryDir;
|
|
127
|
+
if (!existsSync(registryDir)) {
|
|
128
|
+
mkdirSync(registryDir, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const [filename, data] of outputs) {
|
|
132
|
+
const filePath = resolve(registryDir, filename);
|
|
133
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
134
|
+
console.log(` ${C.green}wrote${C.reset} ${filename}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`\n${C.bold}Registry generation complete!${C.reset}`);
|
|
138
|
+
console.log(` Files: ${Object.keys(manifest).length}`);
|
|
139
|
+
|
|
140
|
+
// Find generated data for summary
|
|
141
|
+
const semanticIds = outputs.find(([n]) => n === 'semantic-ids.json')?.[1];
|
|
142
|
+
const sideEffects = outputs.find(([n]) => n === 'side-effects.json')?.[1];
|
|
143
|
+
const features = outputs.find(([n]) => n === 'features.json')?.[1];
|
|
144
|
+
|
|
145
|
+
if (semanticIds) console.log(` Semantic IDs: ${semanticIds.explicitCount} explicit, ${semanticIds.derivedCount} derived`);
|
|
146
|
+
if (sideEffects) console.log(` Side-effects: ${sideEffects.documentedCount} documented, ${sideEffects.missingCount} missing`);
|
|
147
|
+
if (features) console.log(` Domains: ${features.domainCount}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
main().catch(err => {
|
|
151
|
+
console.error(err);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
package/bin/init.mjs
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @ai-meta
|
|
4
|
+
* @id module:ai-agent-infra:cli-init
|
|
5
|
+
* @domain ai-agent-infra
|
|
6
|
+
* @type module
|
|
7
|
+
* @side-effects writes:filesystem:_registry,writes:filesystem:ai-registry.config.mjs
|
|
8
|
+
* @stability medium
|
|
9
|
+
* @complexity complex
|
|
10
|
+
* @token-budget 300
|
|
11
|
+
* @purpose CLI command: scaffold registry infrastructure for a new project (config, directories, starter files)
|
|
12
|
+
*
|
|
13
|
+
* @filechangelog
|
|
14
|
+
* @changelog 2026-02-22T14:00 [feat] [Claude Opus 4.6] Created init CLI command with interactive prompts
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createInterface } from 'readline';
|
|
18
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
19
|
+
import { resolve, join } from 'path';
|
|
20
|
+
|
|
21
|
+
const C = {
|
|
22
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
23
|
+
green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
27
|
+
const ask = (q) => new Promise((res) => rl.question(q, res));
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
console.log(`\n${C.bold}ai-codebase-registry init${C.reset}`);
|
|
31
|
+
console.log(`${C.dim}Scaffold AI agent infrastructure for your project${C.reset}\n`);
|
|
32
|
+
|
|
33
|
+
const rootDir = resolve('.');
|
|
34
|
+
|
|
35
|
+
// Check for existing config
|
|
36
|
+
if (existsSync(join(rootDir, 'ai-registry.config.mjs'))) {
|
|
37
|
+
console.log(`${C.yellow}ai-registry.config.mjs already exists.${C.reset}`);
|
|
38
|
+
const overwrite = await ask('Overwrite? (y/N) ');
|
|
39
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
40
|
+
console.log('Aborted.');
|
|
41
|
+
rl.close();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Gather info
|
|
47
|
+
const name = await ask(`${C.cyan}Project name${C.reset} [${rootDir.split('/').pop()}]: `) || rootDir.split('/').pop();
|
|
48
|
+
const description = await ask(`${C.cyan}Description${C.reset}: `);
|
|
49
|
+
const srcDir = await ask(`${C.cyan}Source directory${C.reset} [src]: `) || 'src';
|
|
50
|
+
const extraDirs = await ask(`${C.cyan}Additional source directories${C.reset} (comma-separated, or empty): `);
|
|
51
|
+
const router = await ask(`${C.cyan}Router framework${C.reset} (react-router/nextjs/expo-router/express/none) [none]: `) || 'none';
|
|
52
|
+
const dataStore = await ask(`${C.cyan}Data store${C.reset} (firestore/postgres/mongodb/none) [none]: `) || 'none';
|
|
53
|
+
|
|
54
|
+
rl.close();
|
|
55
|
+
|
|
56
|
+
// Build scanDirs
|
|
57
|
+
const scanDirs = [{ dir: srcDir, prefix: srcDir }];
|
|
58
|
+
if (extraDirs.trim()) {
|
|
59
|
+
for (const d of extraDirs.split(',').map(s => s.trim()).filter(Boolean)) {
|
|
60
|
+
scanDirs.push({ dir: d, prefix: d });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Generate config file
|
|
65
|
+
const configContent = `/** @type {import('ai-codebase-registry').Config} */
|
|
66
|
+
export default {
|
|
67
|
+
name: ${JSON.stringify(name)},
|
|
68
|
+
description: ${JSON.stringify(description)},
|
|
69
|
+
|
|
70
|
+
scanDirs: ${JSON.stringify(scanDirs, null, 4).replace(/\n/g, '\n ')},
|
|
71
|
+
extensions: ['.ts', '.tsx'],
|
|
72
|
+
skipDirs: ['node_modules', 'dist', '.next', 'coverage', '__tests__', '__mocks__'],
|
|
73
|
+
|
|
74
|
+
aliases: {
|
|
75
|
+
'@/': '${srcDir}/',
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
router: '${router}',
|
|
79
|
+
routerEntryFile: ${router === 'react-router' ? `'${srcDir}/App.tsx'` : 'null'},
|
|
80
|
+
routeBuilderFile: ${router === 'react-router' ? `'${srcDir}/routes.ts'` : 'null'},
|
|
81
|
+
|
|
82
|
+
dataStore: '${dataStore}',
|
|
83
|
+
|
|
84
|
+
registryDir: '_registry',
|
|
85
|
+
agent: 'claude-code',
|
|
86
|
+
claudemd: true,
|
|
87
|
+
};
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
writeFileSync(join(rootDir, 'ai-registry.config.mjs'), configContent);
|
|
91
|
+
console.log(`\n ${C.green}created${C.reset} ai-registry.config.mjs`);
|
|
92
|
+
|
|
93
|
+
// Create _registry directory
|
|
94
|
+
const registryDir = join(rootDir, '_registry');
|
|
95
|
+
if (!existsSync(registryDir)) {
|
|
96
|
+
mkdirSync(registryDir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
console.log(` ${C.green}created${C.reset} _registry/`);
|
|
99
|
+
|
|
100
|
+
// Create starter relationships.json
|
|
101
|
+
const relationships = {
|
|
102
|
+
_meta: {
|
|
103
|
+
description: `Cross-cutting relationship graph for ${name}`,
|
|
104
|
+
generated: new Date().toISOString().split('T')[0],
|
|
105
|
+
version: '1.0.0',
|
|
106
|
+
},
|
|
107
|
+
firestore_paths: {},
|
|
108
|
+
change_impact_rules: [],
|
|
109
|
+
};
|
|
110
|
+
writeFileSync(join(registryDir, 'relationships.json'), JSON.stringify(relationships, null, 2));
|
|
111
|
+
console.log(` ${C.green}created${C.reset} _registry/relationships.json`);
|
|
112
|
+
|
|
113
|
+
// Create starter patterns.json
|
|
114
|
+
const patterns = {
|
|
115
|
+
version: '1.0',
|
|
116
|
+
generated: new Date().toISOString(),
|
|
117
|
+
patternCount: 0,
|
|
118
|
+
patterns: {},
|
|
119
|
+
};
|
|
120
|
+
writeFileSync(join(registryDir, 'patterns.json'), JSON.stringify(patterns, null, 2));
|
|
121
|
+
console.log(` ${C.green}created${C.reset} _registry/patterns.json`);
|
|
122
|
+
|
|
123
|
+
// Create starter nav-exceptions.json
|
|
124
|
+
const navExceptions = {
|
|
125
|
+
_meta: { description: 'Routes that intentionally have no nav entry point' },
|
|
126
|
+
exceptions: {},
|
|
127
|
+
};
|
|
128
|
+
writeFileSync(join(registryDir, 'nav-exceptions.json'), JSON.stringify(navExceptions, null, 2));
|
|
129
|
+
console.log(` ${C.green}created${C.reset} _registry/nav-exceptions.json`);
|
|
130
|
+
|
|
131
|
+
// Generate CLAUDE.md section
|
|
132
|
+
const claudeMdSection = generateClaudeMdSection(name, scanDirs);
|
|
133
|
+
const claudeMdPath = join(rootDir, 'CLAUDE.md');
|
|
134
|
+
if (existsSync(claudeMdPath)) {
|
|
135
|
+
console.log(`\n ${C.yellow}CLAUDE.md already exists — appending AI agent infrastructure section${C.reset}`);
|
|
136
|
+
const existing = (await import('fs')).readFileSync(claudeMdPath, 'utf-8');
|
|
137
|
+
if (!existing.includes('AI-Agent-First Architecture')) {
|
|
138
|
+
writeFileSync(claudeMdPath, existing + '\n\n' + claudeMdSection);
|
|
139
|
+
console.log(` ${C.green}updated${C.reset} CLAUDE.md`);
|
|
140
|
+
} else {
|
|
141
|
+
console.log(` ${C.dim}CLAUDE.md already has agent infrastructure section${C.reset}`);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
writeFileSync(claudeMdPath, `# CLAUDE.md\n\n${claudeMdSection}`);
|
|
145
|
+
console.log(` ${C.green}created${C.reset} CLAUDE.md`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(`
|
|
149
|
+
${C.bold}Next steps:${C.reset}
|
|
150
|
+
1. Run ${C.cyan}ai-registry generate${C.reset} to build initial registry
|
|
151
|
+
2. Run ${C.cyan}ai-registry add-headers${C.reset} to add @ai-meta headers to source files
|
|
152
|
+
3. Run ${C.cyan}ai-registry validate${C.reset} to check coverage
|
|
153
|
+
4. Add to pre-commit hook: ${C.cyan}ai-registry generate${C.reset}
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function generateClaudeMdSection(name) {
|
|
158
|
+
return `## AI-Agent-First Architecture Mandate
|
|
159
|
+
|
|
160
|
+
${name} uses a registry-driven architecture where every source file carries machine-readable metadata.
|
|
161
|
+
AI agents MUST consult the registry before searching the filesystem.
|
|
162
|
+
|
|
163
|
+
### Registry Loading Order
|
|
164
|
+
\`semantic-ids.json\` -> \`manifest.json\` -> \`side-effects.json\` -> \`patterns.json\`
|
|
165
|
+
|
|
166
|
+
### \`@ai-meta\` Headers (MANDATORY)
|
|
167
|
+
|
|
168
|
+
Every source file MUST have an \`@ai-meta\` JSDoc header with these mandatory fields:
|
|
169
|
+
- \`@id\` — format: \`type:domain:kebab-name\`
|
|
170
|
+
- \`@domain\` — business domain this file belongs to
|
|
171
|
+
- \`@type\` — file type (hook, component, page, service, util, etc.)
|
|
172
|
+
- \`@side-effects\` — what this file mutates (\`none\` if pure)
|
|
173
|
+
- \`@stability\` — how often this changes (low/medium/high)
|
|
174
|
+
- \`@complexity\` — code complexity (trivial/moderate/complex)
|
|
175
|
+
- \`@token-budget\` — estimated tokens for AI agent to process
|
|
176
|
+
- \`@purpose\` — one-line description
|
|
177
|
+
|
|
178
|
+
### Side-Effect Documentation
|
|
179
|
+
|
|
180
|
+
Before modifying any mutation, trigger, or data write:
|
|
181
|
+
1. Query \`_registry/side-effects.json\` for the entity
|
|
182
|
+
2. If NOT listed, add it before writing code
|
|
183
|
+
3. After modifying: update \`@side-effects\` AND \`side-effects.json\`
|
|
184
|
+
|
|
185
|
+
### Agent Session Protocol
|
|
186
|
+
|
|
187
|
+
At session start:
|
|
188
|
+
1. Load: \`_registry/semantic-ids.json\` -> \`manifest.json\` -> \`side-effects.json\` -> \`patterns.json\`
|
|
189
|
+
2. Check \`TODO.md\` for open items
|
|
190
|
+
3. Auto-select highest-severity unblocked item
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main().catch(err => {
|
|
195
|
+
console.error(err);
|
|
196
|
+
rl.close();
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|