ai-engineering-loop 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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +272 -0
  3. package/adapters/dot/README.md +55 -0
  4. package/adapters/dot/coreview.md +88 -0
  5. package/adapters/dot/gitlab.md +128 -0
  6. package/adapters/dot/mattermost.md +102 -0
  7. package/adapters/dot/multi-branch.md +89 -0
  8. package/agents/devil-advocate.md +111 -0
  9. package/agents/judge.md +69 -0
  10. package/agents/maker.md +66 -0
  11. package/bin/ai-engineering-loop.js +633 -0
  12. package/core/configuration-precedence.md +102 -0
  13. package/core/context-impact-assessment.md +127 -0
  14. package/core/context-refresh-policy.md +116 -0
  15. package/core/definition-of-done.md +79 -0
  16. package/core/escalation-policy.md +99 -0
  17. package/core/goal-contract.md +88 -0
  18. package/core/iteration-policy.md +108 -0
  19. package/core/judge-policy.md +123 -0
  20. package/core/project-initialization.md +97 -0
  21. package/core/repo-config-schema.md +72 -0
  22. package/core/verification-loop.md +97 -0
  23. package/docs/antigravity-feasibility.md +90 -0
  24. package/docs/migration-plan.md +70 -0
  25. package/examples/backend-api/payment-idempotency/README.md +33 -0
  26. package/examples/backend-api/payment-idempotency/goal-contract.md +33 -0
  27. package/examples/backend-api/payment-idempotency/judge-verdict.md +28 -0
  28. package/examples/backend-api/payment-idempotency/review-findings.md +50 -0
  29. package/examples/dot/attendance-confirmation/README.md +22 -0
  30. package/examples/dot/attendance-confirmation/delivery-report.md +51 -0
  31. package/examples/dot/attendance-confirmation/goal-contract.md +39 -0
  32. package/examples/dot/attendance-confirmation/judge-verdict.md +43 -0
  33. package/examples/dot/attendance-confirmation/review-findings.md +57 -0
  34. package/examples/initialization/README.md +19 -0
  35. package/examples/initialization/discovery-trace.md +63 -0
  36. package/examples/initialization/generated-context.md +110 -0
  37. package/examples/mobile-app/offline-sync-queue/README.md +33 -0
  38. package/examples/mobile-app/offline-sync-queue/goal-contract.md +32 -0
  39. package/examples/mobile-app/offline-sync-queue/judge-verdict.md +27 -0
  40. package/examples/mobile-app/offline-sync-queue/review-findings.md +30 -0
  41. package/package.json +31 -0
  42. package/policies/discovery-safety-policy.md +51 -0
  43. package/policies/evidence-policy.md +70 -0
  44. package/policies/finding-policy.md +102 -0
  45. package/policies/no-progress-policy.md +92 -0
  46. package/profiles/README.md +42 -0
  47. package/profiles/backend-api.md +64 -0
  48. package/profiles/library.md +51 -0
  49. package/profiles/mobile-app.md +59 -0
  50. package/profiles/monorepo.md +46 -0
  51. package/profiles/web-app.md +65 -0
  52. package/scripts/init.sh +85 -0
  53. package/templates/repo-config/adapter.md +11 -0
  54. package/templates/repo-config/architecture.md +15 -0
  55. package/templates/repo-config/config.md +11 -0
  56. package/templates/repo-config/conventions.md +16 -0
  57. package/templates/repo-config/verification.md +13 -0
@@ -0,0 +1,633 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * AI Engineering Loop — Deterministic CLI Bootstrap & Living Context Engine
5
+ * Repository: https://github.com/egagofur/ai-engineering-loop
6
+ *
7
+ * Architecture Principle:
8
+ * The CLI handles deterministic repository discovery, context initialization,
9
+ * baseline metadata tracking (metadata.json), status checks, and non-destructive drift refreshes.
10
+ * The AI Agent handles task reasoning, RCA, implementation, review, judging, and impact assessment.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const crypto = require('crypto');
16
+ const { execSync } = require('child_process');
17
+
18
+ const VERSION = '1.0.0';
19
+ const CWD = process.cwd();
20
+ const CONTEXT_DIR = path.join(CWD, '.ai-engineering-loop');
21
+
22
+ // Core files in .ai-engineering-loop/
23
+ const REQUIRED_FILES = [
24
+ 'metadata.json',
25
+ 'config.md',
26
+ 'architecture.md',
27
+ 'conventions.md',
28
+ 'verification.md',
29
+ 'adapter.md'
30
+ ];
31
+
32
+ /**
33
+ * Colorized console helpers
34
+ */
35
+ const log = {
36
+ info: (msg) => console.log('\x1b[36m%s\x1b[0m', msg),
37
+ success: (msg) => console.log('\x1b[32m%s\x1b[0m', msg),
38
+ warn: (msg) => console.log('\x1b[33m%s\x1b[0m', msg),
39
+ error: (msg) => console.log('\x1b[31m%s\x1b[0m', msg),
40
+ bold: (msg) => console.log('\x1b[1m%s\x1b[0m', msg),
41
+ dim: (msg) => console.log('\x1b[2m%s\x1b[0m', msg)
42
+ };
43
+
44
+ /**
45
+ * Safe file reader
46
+ */
47
+ function readFileSafe(filePath) {
48
+ try {
49
+ return fs.readFileSync(filePath, 'utf8');
50
+ } catch (e) {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Compute SHA256 checksum of a file
57
+ */
58
+ function getFileChecksum(filePath) {
59
+ const content = readFileSafe(filePath);
60
+ if (!content) return null;
61
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
62
+ }
63
+
64
+ /**
65
+ * Get current git HEAD revision (Level 0 signal)
66
+ */
67
+ function getGitRevision(rootDir) {
68
+ try {
69
+ return execSync('git rev-parse HEAD', { cwd: rootDir, stdio: ['pipe', 'pipe', 'ignore'] })
70
+ .toString()
71
+ .trim();
72
+ } catch (e) {
73
+ return 'untracked';
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Repository Discovery Engine
79
+ */
80
+ function analyzeRepository(rootDir) {
81
+ const discovery = {
82
+ projectName: path.basename(rootDir),
83
+ isMonorepo: false,
84
+ profile: 'backend-api',
85
+ languages: [],
86
+ frameworks: [],
87
+ packageManager: 'npm',
88
+ scripts: {
89
+ testUnit: 'npm test',
90
+ testAll: 'npm test',
91
+ typecheck: 'npx tsc --noEmit',
92
+ lint: 'npx eslint --fix',
93
+ build: 'npm run build',
94
+ e2e: null
95
+ },
96
+ adapter: {
97
+ type: 'standard',
98
+ repoSlug: null,
99
+ defaultBranch: 'main',
100
+ ciProvider: 'none'
101
+ },
102
+ topLevelDirs: [],
103
+ manifestChecksums: {},
104
+ evidence: []
105
+ };
106
+
107
+ // Inspect directory structure
108
+ try {
109
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
110
+ discovery.topLevelDirs = entries
111
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules')
112
+ .map((e) => e.name);
113
+ } catch (e) {}
114
+
115
+ // 1. Monorepo & Topology Detection
116
+ const hasApps = fs.existsSync(path.join(rootDir, 'apps'));
117
+ const hasPackages = fs.existsSync(path.join(rootDir, 'packages'));
118
+ const hasPnpmWorkspace = fs.existsSync(path.join(rootDir, 'pnpm-workspace.yaml'));
119
+ const hasTurbo = fs.existsSync(path.join(rootDir, 'turbo.json'));
120
+ const hasNx = fs.existsSync(path.join(rootDir, 'nx.json'));
121
+ const hasLerna = fs.existsSync(path.join(rootDir, 'lerna.json'));
122
+ const hasGoWork = fs.existsSync(path.join(rootDir, 'go.work'));
123
+
124
+ if (hasPnpmWorkspace || hasTurbo || hasNx || hasLerna || hasGoWork || (hasApps && hasPackages)) {
125
+ discovery.isMonorepo = true;
126
+ discovery.profile = 'monorepo';
127
+ discovery.evidence.push('Topology: Monorepo workspace detected');
128
+ }
129
+
130
+ // 2. Package Managers & Manifests
131
+ if (fs.existsSync(path.join(rootDir, 'pnpm-lock.yaml')) || hasPnpmWorkspace) {
132
+ discovery.packageManager = 'pnpm';
133
+ } else if (fs.existsSync(path.join(rootDir, 'yarn.lock'))) {
134
+ discovery.packageManager = 'yarn';
135
+ } else if (fs.existsSync(path.join(rootDir, 'bun.lockb')) || fs.existsSync(path.join(rootDir, 'bun.lock'))) {
136
+ discovery.packageManager = 'bun';
137
+ }
138
+
139
+ // Track manifest checksums
140
+ const pkgPath = path.join(rootDir, 'package.json');
141
+ if (fs.existsSync(pkgPath)) {
142
+ discovery.manifestChecksums['package.json'] = getFileChecksum(pkgPath);
143
+ discovery.languages.push('TypeScript / JavaScript');
144
+ discovery.evidence.push('Manifest: package.json');
145
+ try {
146
+ const pkg = JSON.parse(readFileSafe(pkgPath) || '{}');
147
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
148
+
149
+ if (allDeps['next']) { discovery.frameworks.push('Next.js'); if (!discovery.isMonorepo) discovery.profile = 'web-app'; }
150
+ if (allDeps['react']) { discovery.frameworks.push('React'); if (!discovery.isMonorepo) discovery.profile = 'web-app'; }
151
+ if (allDeps['vue']) { discovery.frameworks.push('Vue'); if (!discovery.isMonorepo) discovery.profile = 'web-app'; }
152
+ if (allDeps['svelte'] || allDeps['@sveltejs/kit']) { discovery.frameworks.push('Svelte'); if (!discovery.isMonorepo) discovery.profile = 'web-app'; }
153
+ if (allDeps['@nestjs/core']) { discovery.frameworks.push('NestJS'); if (!discovery.isMonorepo) discovery.profile = 'backend-api'; }
154
+ if (allDeps['express']) { discovery.frameworks.push('Express'); if (!discovery.isMonorepo) discovery.profile = 'backend-api'; }
155
+ if (allDeps['fastify']) { discovery.frameworks.push('Fastify'); if (!discovery.isMonorepo) discovery.profile = 'backend-api'; }
156
+ if (allDeps['@prisma/client'] || allDeps['prisma']) { discovery.frameworks.push('Prisma ORM'); }
157
+
158
+ // Script mapping
159
+ const scripts = pkg.scripts || {};
160
+ const pm = discovery.packageManager;
161
+ if (scripts['test:unit']) discovery.scripts.testUnit = `${pm} run test:unit`;
162
+ else if (scripts['test']) discovery.scripts.testUnit = `${pm} test`;
163
+
164
+ if (scripts['typecheck']) discovery.scripts.typecheck = `${pm} run typecheck`;
165
+ else if (scripts['type-check']) discovery.scripts.typecheck = `${pm} run type-check`;
166
+ else if (scripts['check']) discovery.scripts.typecheck = `${pm} run check`;
167
+
168
+ if (scripts['lint']) discovery.scripts.lint = `${pm} run lint`;
169
+ if (scripts['build']) discovery.scripts.build = `${pm} run build`;
170
+ if (scripts['test:e2e']) discovery.scripts.e2e = `${pm} run test:e2e`;
171
+ } catch (e) {}
172
+ }
173
+
174
+ // Go
175
+ const goModPath = path.join(rootDir, 'go.mod');
176
+ if (fs.existsSync(goModPath)) {
177
+ discovery.manifestChecksums['go.mod'] = getFileChecksum(goModPath);
178
+ discovery.languages.push('Go');
179
+ if (!discovery.isMonorepo) discovery.profile = 'backend-api';
180
+ discovery.scripts.testUnit = 'go test -v ./...';
181
+ discovery.scripts.build = 'go build ./...';
182
+ discovery.scripts.typecheck = 'go vet ./...';
183
+ discovery.evidence.push('Manifest: go.mod');
184
+ }
185
+
186
+ // Rust
187
+ const cargoPath = path.join(rootDir, 'Cargo.toml');
188
+ if (fs.existsSync(cargoPath)) {
189
+ discovery.manifestChecksums['Cargo.toml'] = getFileChecksum(cargoPath);
190
+ discovery.languages.push('Rust');
191
+ if (!discovery.isMonorepo) discovery.profile = 'library';
192
+ discovery.scripts.testUnit = 'cargo test';
193
+ discovery.scripts.build = 'cargo build';
194
+ discovery.scripts.typecheck = 'cargo check';
195
+ discovery.scripts.lint = 'cargo clippy';
196
+ discovery.evidence.push('Manifest: Cargo.toml');
197
+ }
198
+
199
+ // Python
200
+ const pyprojPath = path.join(rootDir, 'pyproject.toml');
201
+ const reqPath = path.join(rootDir, 'requirements.txt');
202
+ if (fs.existsSync(pyprojPath) || fs.existsSync(reqPath)) {
203
+ if (fs.existsSync(pyprojPath)) discovery.manifestChecksums['pyproject.toml'] = getFileChecksum(pyprojPath);
204
+ if (fs.existsSync(reqPath)) discovery.manifestChecksums['requirements.txt'] = getFileChecksum(reqPath);
205
+ discovery.languages.push('Python');
206
+ if (!discovery.isMonorepo) discovery.profile = 'backend-api';
207
+ discovery.scripts.testUnit = 'pytest';
208
+ discovery.scripts.typecheck = 'mypy .';
209
+ discovery.scripts.lint = 'ruff check .';
210
+ discovery.evidence.push('Manifest: pyproject.toml / requirements.txt');
211
+ }
212
+
213
+ // Flutter / Mobile
214
+ const pubspecPath = path.join(rootDir, 'pubspec.yaml');
215
+ if (fs.existsSync(pubspecPath)) {
216
+ discovery.manifestChecksums['pubspec.yaml'] = getFileChecksum(pubspecPath);
217
+ discovery.languages.push('Dart / Flutter');
218
+ discovery.profile = 'mobile-app';
219
+ discovery.scripts.testUnit = 'flutter test';
220
+ discovery.scripts.typecheck = 'dart analyze';
221
+ discovery.scripts.build = 'flutter build bundle';
222
+ discovery.evidence.push('Manifest: pubspec.yaml');
223
+ }
224
+
225
+ // 3. Adapter / CI Detection
226
+ if (fs.existsSync(path.join(rootDir, '.github', 'workflows'))) {
227
+ discovery.adapter.ciProvider = 'GitHub Actions';
228
+ discovery.adapter.type = 'github';
229
+ } else if (fs.existsSync(path.join(rootDir, '.gitlab-ci.yml'))) {
230
+ discovery.adapter.ciProvider = 'GitLab CI';
231
+ discovery.adapter.type = 'gitlab';
232
+ }
233
+
234
+ // Check git remote if git folder exists
235
+ const gitConfigStr = readFileSafe(path.join(rootDir, '.git', 'config'));
236
+ if (gitConfigStr) {
237
+ const urlMatch = gitConfigStr.match(/url\s*=\s*(.*)/);
238
+ if (urlMatch) {
239
+ discovery.adapter.repoSlug = urlMatch[1].trim();
240
+ if (discovery.adapter.repoSlug.includes('gitlab.dot.co.id')) {
241
+ discovery.adapter.type = 'dot';
242
+ }
243
+ }
244
+ }
245
+
246
+ return discovery;
247
+ }
248
+
249
+ /**
250
+ * Generate Context Files (Including metadata.json Baseline)
251
+ */
252
+ function generateContextFiles(rootDir, discovery, trigger = 'init', impact = 'INITIAL_BOOTSTRAP') {
253
+ const targetDir = path.join(rootDir, '.ai-engineering-loop');
254
+ fs.mkdirSync(targetDir, { recursive: true });
255
+
256
+ const currentRevision = getGitRevision(rootDir);
257
+
258
+ // 0. metadata.json (Baseline)
259
+ const metadataJson = {
260
+ contextVersion: '1.0.0',
261
+ generatedAt: new Date().toISOString(),
262
+ repositoryRevision: currentRevision,
263
+ projectProfile: discovery.profile,
264
+ manifestChecksums: discovery.manifestChecksums,
265
+ lastReconciliation: {
266
+ timestamp: new Date().toISOString(),
267
+ trigger,
268
+ impact
269
+ }
270
+ };
271
+ fs.writeFileSync(path.join(targetDir, 'metadata.json'), JSON.stringify(metadataJson, null, 2) + '\n');
272
+
273
+ // 1. config.md
274
+ const configMd = `# Project Configuration
275
+
276
+ ## Metadata
277
+ - **project_name**: "${discovery.projectName}"
278
+ - **project_profile**: "${discovery.profile}" # Archetype from profiles/${discovery.profile}.md
279
+ - **languages**:
280
+ ${discovery.languages.map((l) => ` - ${l}`).join('\n') || ' - Unspecified'}
281
+ - **frameworks**:
282
+ ${discovery.frameworks.map((f) => ` - ${f}`).join('\n') || ' - Standard'}
283
+ - **package_manager**: "${discovery.packageManager}"
284
+ - **default_base_branch**: "${discovery.adapter.defaultBranch}"
285
+
286
+ ## Observed Evidence
287
+ ${discovery.evidence.map((e) => `- ${e}`).join('\n')}
288
+ `;
289
+ fs.writeFileSync(path.join(targetDir, 'config.md'), configMd);
290
+
291
+ // 2. architecture.md
292
+ const archMd = `# Project Architecture
293
+
294
+ ## System Overview
295
+ Discovered architecture for ${discovery.projectName} (${discovery.profile}).
296
+
297
+ ## Discovered Top-Level Directories
298
+ ${discovery.topLevelDirs.map((d) => `- \`${d}/\``).join('\n') || '- Flat directory layout'}
299
+
300
+ ## Boundary Invariants
301
+ - Preserve existing component boundaries and modular encapsulation.
302
+ - Zero circular dependencies across packages or modules.
303
+ - Changes must be surgical and adhere to existing architecture patterns.
304
+
305
+ ## Evidence & Confidence
306
+ - Observed from: Directory scan, package manifests
307
+ - Confidence: HIGH
308
+ `;
309
+ fs.writeFileSync(path.join(targetDir, 'architecture.md'), archMd);
310
+
311
+ // 3. conventions.md
312
+ const convMd = `# Project Conventions
313
+
314
+ ## Code Standards
315
+ - File naming: kebab-case or established repository convention.
316
+ - Error handling: Use domain-specific errors; zero empty catch blocks.
317
+ - Types: Strict typing; zero unnecessary \`any\` types.
318
+
319
+ ## Forbidden Anti-Patterns
320
+ - Zero speculative TODOs or orphan dead code in production pull requests.
321
+ - Never commit private secrets, passwords, or API keys.
322
+ - Do not make unsolicited renovations outside the active Goal Contract scope.
323
+ `;
324
+ fs.writeFileSync(path.join(targetDir, 'conventions.md'), convMd);
325
+
326
+ // 4. verification.md
327
+ const verifyMd = `# Project Verification Commands
328
+
329
+ ## Discovered Verification Commands
330
+ - **test_unit**: \`${discovery.scripts.testUnit}\`
331
+ - **typecheck**: \`${discovery.scripts.typecheck}\`
332
+ - **lint**: \`${discovery.scripts.lint}\`
333
+ - **build**: \`${discovery.scripts.build}\`
334
+ ${discovery.scripts.e2e ? `- **e2e**: \`${discovery.scripts.e2e}\`` : ''}
335
+
336
+ ## Verification Protocol
337
+ - 100% deterministic checks must pass before Devil's Advocate review.
338
+ - Unit tests must cover boundary cases, null safety, and error paths.
339
+ `;
340
+ fs.writeFileSync(path.join(targetDir, 'verification.md'), verifyMd);
341
+
342
+ // 5. adapter.md
343
+ const adapterMd = `# Project Delivery Adapter Configuration
344
+
345
+ ## Delivery Pipeline
346
+ - **adapter_type**: "${discovery.adapter.type}" # dot | github | gitlab | standard
347
+ ${discovery.adapter.repoSlug ? `- **remote_repository**: "${discovery.adapter.repoSlug}"` : ''}
348
+ - **default_target_branch**: "${discovery.adapter.defaultBranch}"
349
+ - **ci_provider**: "${discovery.adapter.ciProvider}"
350
+ `;
351
+ fs.writeFileSync(path.join(targetDir, 'adapter.md'), adapterMd);
352
+ }
353
+
354
+ /**
355
+ * Validate Context Integrity
356
+ */
357
+ function validateContext(targetDir) {
358
+ if (!fs.existsSync(targetDir)) return { valid: false, reason: 'Directory missing' };
359
+ for (const f of REQUIRED_FILES) {
360
+ const fullPath = path.join(targetDir, f);
361
+ if (!fs.existsSync(fullPath) || fs.statSync(fullPath).size === 0) {
362
+ return { valid: false, reason: `Missing or empty ${f}` };
363
+ }
364
+ }
365
+ return { valid: true };
366
+ }
367
+
368
+ /**
369
+ * Evaluate Drift Against Baseline (Progressive Level 0 & Level 1)
370
+ */
371
+ function evaluateDrift(rootDir, targetDir) {
372
+ const metadataPath = path.join(targetDir, 'metadata.json');
373
+ const metadataStr = readFileSafe(metadataPath);
374
+ if (!metadataStr) return { status: 'STALE', reason: 'Missing metadata.json baseline' };
375
+
376
+ let metadata;
377
+ try {
378
+ metadata = JSON.parse(metadataStr);
379
+ } catch (e) {
380
+ return { status: 'STALE', reason: 'Corrupt metadata.json' };
381
+ }
382
+
383
+ const currentRevision = getGitRevision(rootDir);
384
+ const baselineRevision = metadata.repositoryRevision || 'unknown';
385
+
386
+ // Check manifest checksums (Level 0)
387
+ const currentManifests = {};
388
+ for (const manifest of ['package.json', 'go.mod', 'Cargo.toml', 'pyproject.toml', 'requirements.txt', 'pubspec.yaml']) {
389
+ const p = path.join(rootDir, manifest);
390
+ if (fs.existsSync(p)) {
391
+ currentManifests[manifest] = getFileChecksum(p);
392
+ }
393
+ }
394
+
395
+ const baselineManifests = metadata.manifestChecksums || {};
396
+ let manifestDrift = false;
397
+ const changedManifests = [];
398
+
399
+ for (const [m, hash] of Object.entries(currentManifests)) {
400
+ if (baselineManifests[m] !== hash) {
401
+ manifestDrift = true;
402
+ changedManifests.push(m);
403
+ }
404
+ }
405
+
406
+ if (manifestDrift) {
407
+ return {
408
+ status: 'STALE',
409
+ reason: `Manifest drift detected in: ${changedManifests.join(', ')}`,
410
+ level: 'LEVEL_2'
411
+ };
412
+ }
413
+
414
+ if (currentRevision === baselineRevision || currentRevision === 'untracked') {
415
+ return { status: 'CURRENT', reason: 'Git HEAD and manifest checksums match baseline (Level 0)', level: 'LEVEL_0' };
416
+ }
417
+
418
+ // Inspect touched files if HEAD advanced (Level 1)
419
+ try {
420
+ const diffFiles = execSync(`git diff --name-only ${baselineRevision}..HEAD`, { cwd: rootDir, stdio: ['pipe', 'pipe', 'ignore'] })
421
+ .toString()
422
+ .trim()
423
+ .split('\n')
424
+ .filter(Boolean);
425
+
426
+ const architecturalFiles = diffFiles.filter(
427
+ (f) =>
428
+ f.endsWith('.json') ||
429
+ f.endsWith('.toml') ||
430
+ f.endsWith('.yaml') ||
431
+ f.endsWith('.yml') ||
432
+ f.startsWith('.github/') ||
433
+ f.startsWith('.gitlab-ci')
434
+ );
435
+
436
+ if (architecturalFiles.length === 0) {
437
+ return {
438
+ status: 'CURRENT',
439
+ reason: `HEAD advanced but only non-architectural files modified (${diffFiles.length} files)`,
440
+ level: 'LEVEL_1'
441
+ };
442
+ } else {
443
+ return {
444
+ status: 'POSSIBLE_DRIFT',
445
+ reason: `Architectural files modified: ${architecturalFiles.join(', ')}`,
446
+ level: 'LEVEL_2'
447
+ };
448
+ }
449
+ } catch (e) {
450
+ return { status: 'CURRENT', reason: 'Unable to compute git diff; manifests match', level: 'LEVEL_0' };
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Command Handlers
456
+ */
457
+
458
+ // Command: init
459
+ function handleInit() {
460
+ log.info('AI Engineering Loop — Project Context Bootstrap (init)');
461
+ log.dim(`Target directory: ${CWD}`);
462
+
463
+ if (fs.existsSync(CONTEXT_DIR)) {
464
+ const validation = validateContext(CONTEXT_DIR);
465
+ if (validation.valid) {
466
+ log.success('✓ .ai-engineering-loop/ already exists and is valid.');
467
+ const drift = evaluateDrift(CWD, CONTEXT_DIR);
468
+ console.log(`- Baseline Status: ${drift.status} (${drift.reason})`);
469
+ log.dim('Run "npx ai-engineering-loop status" to inspect health, or "refresh" to update.');
470
+ process.exit(0);
471
+ } else {
472
+ log.warn(`! Existing .ai-engineering-loop/ found but incomplete: ${validation.reason}. Repairing...`);
473
+ }
474
+ }
475
+
476
+ log.info('Analyzing repository topology and manifests...');
477
+ const discovery = analyzeRepository(CWD);
478
+
479
+ log.dim(`> Profile Bound: ${discovery.profile}`);
480
+ log.dim(`> Languages: ${discovery.languages.join(', ') || 'Unspecified'}`);
481
+ log.dim(`> Package Manager: ${discovery.packageManager}`);
482
+ log.dim(`> Unit Test Command: ${discovery.scripts.testUnit}`);
483
+
484
+ generateContextFiles(CWD, discovery, 'init', 'INITIAL_BOOTSTRAP');
485
+
486
+ const validation = validateContext(CONTEXT_DIR);
487
+ if (validation.valid) {
488
+ log.success('\n✓ Successfully initialized .ai-engineering-loop/ with:');
489
+ REQUIRED_FILES.forEach((f) => console.log(` - .ai-engineering-loop/${f}`));
490
+ log.dim('\nRecommendation: Commit .ai-engineering-loop/ to version control so team agents share context.');
491
+ } else {
492
+ log.error(`\n✗ Initialization validation failed: ${validation.reason}`);
493
+ process.exit(1);
494
+ }
495
+ }
496
+
497
+ // Command: status
498
+ function handleStatus() {
499
+ log.info('AI Engineering Loop — Project Context Status (status)');
500
+ log.dim(`Target directory: ${CWD}`);
501
+
502
+ if (!fs.existsSync(CONTEXT_DIR)) {
503
+ log.warn('Status: NOT INITIALIZED');
504
+ log.dim('Run "npx ai-engineering-loop init" to bootstrap context.');
505
+ process.exit(1);
506
+ }
507
+
508
+ const validation = validateContext(CONTEXT_DIR);
509
+ if (!validation.valid) {
510
+ log.error(`Status: INCOMPLETE (${validation.reason})`);
511
+ log.dim('Run "npx ai-engineering-loop refresh" or "init" to repair.');
512
+ process.exit(1);
513
+ }
514
+
515
+ const metadataStr = readFileSafe(path.join(CONTEXT_DIR, 'metadata.json'));
516
+ let metadata = {};
517
+ try { metadata = JSON.parse(metadataStr || '{}'); } catch (e) {}
518
+
519
+ const drift = evaluateDrift(CWD, CONTEXT_DIR);
520
+
521
+ log.success('Status: READY & VALID');
522
+ console.log(`- Project Name: ${path.basename(CWD)}`);
523
+ console.log(`- Project Profile: ${metadata.projectProfile || 'unspecified'}`);
524
+ console.log(`- Context Baseline Git: ${metadata.repositoryRevision ? metadata.repositoryRevision.slice(0, 8) : 'unknown'}`);
525
+ console.log(`- Living Freshness: \x1b[32m${drift.status}\x1b[0m (${drift.reason})`);
526
+ console.log('- Context Files: 6/6 verified (including metadata.json)');
527
+ }
528
+
529
+ // Command: refresh
530
+ function handleRefresh() {
531
+ log.info('AI Engineering Loop — Context Drift Refresh (refresh)');
532
+ log.dim(`Target directory: ${CWD}`);
533
+
534
+ if (!fs.existsSync(CONTEXT_DIR)) {
535
+ log.warn('Context not found. Initializing fresh context...');
536
+ handleInit();
537
+ return;
538
+ }
539
+
540
+ const drift = evaluateDrift(CWD, CONTEXT_DIR);
541
+ if (drift.status === 'CURRENT') {
542
+ log.success('✓ Context is already CURRENT. No changes required.');
543
+ log.dim(`Reason: ${drift.reason}`);
544
+ process.exit(0);
545
+ }
546
+
547
+ log.info(`Drift detected: ${drift.reason}. Reconciling context...`);
548
+ const discovery = analyzeRepository(CWD);
549
+
550
+ generateContextFiles(CWD, discovery, 'refresh', 'DRIFT_RECONCILIATION');
551
+
552
+ log.success('✓ Context reconciled non-destructively.');
553
+ handleStatus();
554
+ }
555
+
556
+ // Command: run
557
+ function handleRun() {
558
+ log.info('AI Engineering Loop — Task Execution Entrypoint (run)');
559
+
560
+ if (!fs.existsSync(CONTEXT_DIR)) {
561
+ log.warn('Project context missing. Auto-initializing before task execution...');
562
+ handleInit();
563
+ } else {
564
+ handleStatus();
565
+ }
566
+
567
+ console.log('\n------------------------------------------------------------');
568
+ log.bold('AI Agent Ready:');
569
+ console.log('1. Formulate Goal Contract (core/goal-contract.md)');
570
+ console.log('2. Execute Root Cause Analysis & Plan');
571
+ console.log('3. Maker Agent implements surgical code and tests');
572
+ console.log('4. Run Deterministic Verification');
573
+ console.log('5. Execute Devil\'s Advocate Adversarial Review');
574
+ console.log('6. Judge Agent evaluates DoD and issues PASS verdict');
575
+ console.log('7. Context Impact Assessment (NONE / TARGETED / MAJOR)');
576
+ console.log('8. Delivery Adapter creates MR/PR');
577
+ console.log('------------------------------------------------------------\n');
578
+ }
579
+
580
+ // Help Menu
581
+ function printHelp() {
582
+ console.log(`
583
+ AI Engineering Loop CLI (v${VERSION})
584
+ A reusable, framework-agnostic AI Engineering Operating System.
585
+
586
+ Usage:
587
+ npx ai-engineering-loop <command>
588
+
589
+ Commands:
590
+ init Bootstrap .ai-engineering-loop/ context from repository discovery
591
+ status Check the validity, readiness, and baseline freshness of context
592
+ refresh Reconcile drifted context against repository non-destructively
593
+ run Verify context readiness and instruct AI agent to begin loop
594
+
595
+ Options:
596
+ -h, --help Show this help menu
597
+ -v, --version Show version number
598
+
599
+ Documentation:
600
+ https://github.com/egagofur/ai-engineering-loop
601
+ `);
602
+ }
603
+
604
+ // CLI Router
605
+ const args = process.argv.slice(2);
606
+ const command = args[0] || 'init';
607
+
608
+ switch (command) {
609
+ case 'init':
610
+ handleInit();
611
+ break;
612
+ case 'status':
613
+ handleStatus();
614
+ break;
615
+ case 'refresh':
616
+ handleRefresh();
617
+ break;
618
+ case 'run':
619
+ handleRun();
620
+ break;
621
+ case '-v':
622
+ case '--version':
623
+ console.log(`ai-engineering-loop v${VERSION}`);
624
+ break;
625
+ case '-h':
626
+ case '--help':
627
+ default:
628
+ if (command && command !== '-h' && command !== '--help') {
629
+ log.error(`Unknown command: ${command}`);
630
+ }
631
+ printHelp();
632
+ break;
633
+ }