@magic-ingredients/tiny-brain-local 0.20.1 → 0.21.1
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/dist/core/mcp-server.d.ts.map +1 -1
- package/dist/core/mcp-server.js +11 -8
- package/dist/core/repo-registration.d.ts +30 -0
- package/dist/core/repo-registration.d.ts.map +1 -0
- package/dist/core/repo-registration.js +34 -0
- package/dist/prompts/persona/persona.prompt.js +1 -1
- package/dist/prompts/planning/planning.prompt.d.ts +0 -8
- package/dist/prompts/planning/planning.prompt.d.ts.map +1 -1
- package/dist/prompts/planning/planning.prompt.js +0 -193
- package/dist/prompts/prompt-registry.d.ts.map +1 -1
- package/dist/prompts/prompt-registry.js +0 -2
- package/dist/services/analyse-service.d.ts +15 -29
- package/dist/services/analyse-service.d.ts.map +1 -1
- package/dist/services/analyse-service.js +99 -236
- package/dist/services/repo-service.d.ts +5 -0
- package/dist/services/repo-service.d.ts.map +1 -1
- package/dist/services/repo-service.js +67 -42
- package/dist/storage/local-filesystem-adapter.d.ts.map +1 -1
- package/dist/storage/local-filesystem-adapter.js +0 -3
- package/dist/tools/dashboard/dashboard.tool.d.ts +4 -3
- package/dist/tools/dashboard/dashboard.tool.d.ts.map +1 -1
- package/dist/tools/dashboard/dashboard.tool.js +53 -6
- package/dist/tools/plan/plan.tool.d.ts +0 -1
- package/dist/tools/plan/plan.tool.d.ts.map +1 -1
- package/dist/tools/plan/plan.tool.js +10 -56
- package/dist/tools/quality/quality.tool.js +1 -1
- package/dist/tools/recommendations/recommendations.tool.d.ts +13 -0
- package/dist/tools/recommendations/recommendations.tool.d.ts.map +1 -0
- package/dist/tools/recommendations/recommendations.tool.js +178 -0
- package/dist/tools/tool-registry.d.ts.map +1 -1
- package/dist/tools/tool-registry.js +4 -0
- package/package.json +4 -10
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Handles repository tech stack analysis.
|
|
5
5
|
* Focused solely on analysis - no context file management.
|
|
6
6
|
*/
|
|
7
|
-
import { analyseRepository, formatTechStackChanges, detectTechStackChanges, ConfigService, RepoConfigService, LibraryClient, TechContextService, AgentsMdService, } from '@magic-ingredients/tiny-brain-core';
|
|
7
|
+
import { analyseRepository, formatTechStackChanges, detectTechStackChanges, ConfigService, ConfigSetupService, RepoConfigService, LibraryClient, TechContextService, AgentsMdService, RecommendationService, } from '@magic-ingredients/tiny-brain-core';
|
|
8
8
|
import { RepoService } from './repo-service.js';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import { dirname, join } from 'path';
|
|
11
11
|
import { existsSync } from 'fs';
|
|
12
|
-
import {
|
|
12
|
+
import { readFile, readdir, writeFile } from 'fs/promises';
|
|
13
13
|
import { getPackageVersion } from '../utils/package-version.js';
|
|
14
14
|
/**
|
|
15
15
|
* Service for managing repository analysis
|
|
@@ -21,12 +21,14 @@ export class AnalyseService {
|
|
|
21
21
|
libraryClient;
|
|
22
22
|
configService;
|
|
23
23
|
techContextService;
|
|
24
|
+
recommendationService;
|
|
24
25
|
constructor(context) {
|
|
25
26
|
this.context = context;
|
|
26
27
|
this.configService = new ConfigService(context);
|
|
27
28
|
this.repoService = new RepoService(context);
|
|
28
29
|
this.libraryClient = new LibraryClient();
|
|
29
30
|
this.techContextService = new TechContextService(process.cwd());
|
|
31
|
+
this.recommendationService = new RecommendationService(process.cwd());
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Perform repository analysis with unified behavior for initialized/uninitialized repos
|
|
@@ -38,15 +40,14 @@ export class AnalyseService {
|
|
|
38
40
|
this.context.logger.info(`Repository analysis complete: ${currentAnalysis.languages.join(', ')}`);
|
|
39
41
|
// Register repository in dashboard
|
|
40
42
|
await this.registerRepository();
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const gitHooksInstalled =
|
|
48
|
-
|
|
49
|
-
const skillPermissionsAdded = await this.injectSkillPermissions();
|
|
43
|
+
// Perform config setup (directories, hooks, permissions, CLAUDE.md)
|
|
44
|
+
const configSetupResult = await this.performConfigSetup();
|
|
45
|
+
const prdInitialized = configSetupResult?.prdInitialized ?? false;
|
|
46
|
+
const adrInitialized = configSetupResult?.adrInitialized ?? false;
|
|
47
|
+
const qualityInitialized = configSetupResult?.qualityInitialized ?? false;
|
|
48
|
+
const fixesInitialized = configSetupResult?.fixesInitialized ?? false;
|
|
49
|
+
const gitHooksInstalled = configSetupResult?.gitHooksInstalled ?? false;
|
|
50
|
+
const skillPermissionsAdded = configSetupResult?.skillPermissionsAdded ?? [];
|
|
50
51
|
// Check if stack has changed using hash-based comparison
|
|
51
52
|
const analysisInput = {
|
|
52
53
|
languages: currentAnalysis.languages,
|
|
@@ -80,6 +81,11 @@ export class AnalyseService {
|
|
|
80
81
|
if (!dryRun) {
|
|
81
82
|
writtenTechContexts = await this.fetchAndWriteTechContexts(currentAnalysis);
|
|
82
83
|
}
|
|
84
|
+
// Fetch and write recommendations from TBR
|
|
85
|
+
let recommendations = [];
|
|
86
|
+
if (!dryRun) {
|
|
87
|
+
recommendations = await this.fetchAndWriteRecommendations(currentAnalysis);
|
|
88
|
+
}
|
|
83
89
|
// Get enableAgenticCoding preference and sync tech agents
|
|
84
90
|
// When enabled, copies tech contexts from .tiny-brain/tech/ to .claude/agents/
|
|
85
91
|
const enableAgentic = await this.configService.isAgenticCodingEnabled();
|
|
@@ -92,10 +98,6 @@ export class AnalyseService {
|
|
|
92
98
|
if (!dryRun && manageAgentsMd) {
|
|
93
99
|
agentsMdStatus = await this.generateAgentsMd(currentAnalysis);
|
|
94
100
|
}
|
|
95
|
-
// Write workflow sections to CLAUDE.md based on config flags (SDD, TDD)
|
|
96
|
-
if (!dryRun) {
|
|
97
|
-
await this.repoService.writeWorkflowSections({ contextPath });
|
|
98
|
-
}
|
|
99
101
|
// Check context block version (for backwards compatibility during migration)
|
|
100
102
|
const currentVersion = getPackageVersion();
|
|
101
103
|
const existingContent = await this.repoService.readRepoBlockFromContextFile(contextPath);
|
|
@@ -120,6 +122,7 @@ export class AnalyseService {
|
|
|
120
122
|
skillPermissionsAdded: skillPermissionsAdded.length > 0 ? skillPermissionsAdded : undefined,
|
|
121
123
|
writtenTechContexts,
|
|
122
124
|
agentsMdStatus,
|
|
125
|
+
recommendations,
|
|
123
126
|
};
|
|
124
127
|
}
|
|
125
128
|
if (!stackChanged) {
|
|
@@ -141,6 +144,7 @@ export class AnalyseService {
|
|
|
141
144
|
skillPermissionsAdded: skillPermissionsAdded.length > 0 ? skillPermissionsAdded : undefined,
|
|
142
145
|
writtenTechContexts,
|
|
143
146
|
agentsMdStatus,
|
|
147
|
+
recommendations,
|
|
144
148
|
};
|
|
145
149
|
}
|
|
146
150
|
// Changes detected - compute diff for display purposes
|
|
@@ -180,6 +184,7 @@ export class AnalyseService {
|
|
|
180
184
|
skillPermissionsAdded: skillPermissionsAdded.length > 0 ? skillPermissionsAdded : undefined,
|
|
181
185
|
writtenTechContexts,
|
|
182
186
|
agentsMdStatus,
|
|
187
|
+
recommendations,
|
|
183
188
|
};
|
|
184
189
|
}
|
|
185
190
|
/**
|
|
@@ -319,239 +324,52 @@ export class AnalyseService {
|
|
|
319
324
|
}
|
|
320
325
|
}
|
|
321
326
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
|
|
325
|
-
async initializePrdDirectory() {
|
|
326
|
-
try {
|
|
327
|
-
const enableSDD = await this.configService.isSDDEnabled();
|
|
328
|
-
if (!enableSDD) {
|
|
329
|
-
this.context.logger.debug('SDD disabled, skipping PRD directory initialization');
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
const repoRoot = process.cwd();
|
|
333
|
-
const prdDir = join(repoRoot, 'docs', 'prd');
|
|
334
|
-
if (existsSync(prdDir)) {
|
|
335
|
-
return false; // Already exists
|
|
336
|
-
}
|
|
337
|
-
await mkdir(prdDir, { recursive: true });
|
|
338
|
-
this.context.logger.info('✅ Created docs/prd/ directory');
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
catch (error) {
|
|
342
|
-
this.context.logger.warn(`Failed to initialize PRD directory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Initialize ADR directory structure
|
|
348
|
-
* @returns true if directory was created
|
|
327
|
+
* Perform config setup using core ConfigSetupService.
|
|
328
|
+
* Delegates directory initialization, git hooks, skill permissions, and CLAUDE.md
|
|
329
|
+
* context block writing to the shared service.
|
|
349
330
|
*/
|
|
350
|
-
async
|
|
331
|
+
async performConfigSetup() {
|
|
351
332
|
try {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return
|
|
365
|
-
}
|
|
366
|
-
catch (error) {
|
|
367
|
-
this.context.logger.warn(`Failed to initialize ADR directory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Initialize Quality directory structure
|
|
373
|
-
* @returns true if directory was created
|
|
374
|
-
*/
|
|
375
|
-
async initializeQualityDirectory() {
|
|
376
|
-
try {
|
|
377
|
-
const enableQuality = await this.configService.isQualityEnabled();
|
|
378
|
-
if (!enableQuality) {
|
|
379
|
-
this.context.logger.debug('Quality disabled, skipping Quality directory initialization');
|
|
380
|
-
return false;
|
|
381
|
-
}
|
|
382
|
-
const repoRoot = process.cwd();
|
|
383
|
-
const qualityDir = join(repoRoot, 'docs', 'quality');
|
|
384
|
-
if (existsSync(qualityDir)) {
|
|
385
|
-
return false; // Already exists
|
|
386
|
-
}
|
|
387
|
-
await mkdir(qualityDir, { recursive: true });
|
|
388
|
-
this.context.logger.info('✅ Created docs/quality/ directory');
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
catch (error) {
|
|
392
|
-
this.context.logger.warn(`Failed to initialize Quality directory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Initialize fixes tracking directory
|
|
398
|
-
* @returns true if directory was created
|
|
399
|
-
*/
|
|
400
|
-
async initializeFixesDirectory() {
|
|
401
|
-
try {
|
|
402
|
-
const enableSDD = await this.configService.isSDDEnabled();
|
|
403
|
-
if (!enableSDD) {
|
|
404
|
-
this.context.logger.debug('SDD disabled, skipping fixes directory initialization');
|
|
405
|
-
return false;
|
|
406
|
-
}
|
|
407
|
-
const repoRoot = process.cwd();
|
|
408
|
-
const fixesDir = join(repoRoot, '.tiny-brain', 'fixes');
|
|
409
|
-
if (existsSync(fixesDir)) {
|
|
410
|
-
return false; // Already exists
|
|
411
|
-
}
|
|
412
|
-
await mkdir(fixesDir, { recursive: true });
|
|
413
|
-
this.context.logger.info('✅ Created .tiny-brain/fixes/ directory');
|
|
414
|
-
return true;
|
|
333
|
+
const hooksSourceDir = this.resolveHooksDir();
|
|
334
|
+
const version = getPackageVersion();
|
|
335
|
+
const configSetup = new ConfigSetupService({
|
|
336
|
+
repoPath: process.cwd(),
|
|
337
|
+
version,
|
|
338
|
+
hooksSourceDir,
|
|
339
|
+
logger: {
|
|
340
|
+
info: (msg) => this.context.logger.info(msg),
|
|
341
|
+
debug: (msg) => this.context.logger.debug(msg),
|
|
342
|
+
warn: (msg) => this.context.logger.warn(msg),
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
return await configSetup.performSetup();
|
|
415
346
|
}
|
|
416
347
|
catch (error) {
|
|
417
|
-
this.context.logger.warn(`
|
|
418
|
-
return
|
|
348
|
+
this.context.logger.warn(`Config setup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
349
|
+
return undefined;
|
|
419
350
|
}
|
|
420
351
|
}
|
|
421
352
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
353
|
+
* Resolve hooks template directory from the core package.
|
|
354
|
+
* Tries the core package's templates first (monorepo layout), then falls back
|
|
355
|
+
* to the MCP package's own templates for backward compatibility.
|
|
425
356
|
*/
|
|
426
|
-
|
|
357
|
+
resolveHooksDir() {
|
|
427
358
|
try {
|
|
428
|
-
const repoRoot = process.cwd();
|
|
429
|
-
const gitHooksDir = join(repoRoot, '.git', 'hooks');
|
|
430
|
-
// Check if .git directory exists (is this a git repo?)
|
|
431
|
-
if (!existsSync(join(repoRoot, '.git'))) {
|
|
432
|
-
this.context.logger.debug('Not a git repository, skipping git hooks initialization');
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
// Find the source hook templates in the installed package
|
|
436
359
|
const __filename = fileURLToPath(import.meta.url);
|
|
437
360
|
const __dirname = dirname(__filename);
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const hooks = ['commit-msg', 'post-commit', 'pre-commit'];
|
|
448
|
-
let anyInstalled = false;
|
|
449
|
-
for (const hookName of hooks) {
|
|
450
|
-
const srcHook = join(srcHooksDir, hookName);
|
|
451
|
-
const destHook = join(gitHooksDir, hookName);
|
|
452
|
-
// Skip if source doesn't exist
|
|
453
|
-
if (!existsSync(srcHook)) {
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
// Skip if hook already exists (don't overwrite user customizations)
|
|
457
|
-
if (existsSync(destHook)) {
|
|
458
|
-
// Check if it's our hook by looking for our signature comment
|
|
459
|
-
try {
|
|
460
|
-
const existingContent = await readFile(destHook, 'utf-8');
|
|
461
|
-
if (!existingContent.includes('Installed by: tiny-brain')) {
|
|
462
|
-
this.context.logger.debug(`Skipping ${hookName} - custom hook already exists`);
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
// Our hook already installed, check if source is newer
|
|
466
|
-
const srcStat = await stat(srcHook);
|
|
467
|
-
const destStat = await stat(destHook);
|
|
468
|
-
if (srcStat.mtime <= destStat.mtime) {
|
|
469
|
-
continue; // Already up to date
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
catch {
|
|
473
|
-
continue; // Can't read, skip
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Copy hook
|
|
477
|
-
await copyFile(srcHook, destHook);
|
|
478
|
-
// Make executable (755)
|
|
479
|
-
await chmod(destHook, 0o755);
|
|
480
|
-
anyInstalled = true;
|
|
481
|
-
this.context.logger.info(`✅ Installed git hook: ${hookName}`);
|
|
482
|
-
}
|
|
483
|
-
if (anyInstalled) {
|
|
484
|
-
this.context.logger.info('Git hooks initialized in .git/hooks/');
|
|
485
|
-
}
|
|
486
|
-
return anyInstalled;
|
|
487
|
-
}
|
|
488
|
-
catch (error) {
|
|
489
|
-
this.context.logger.warn(`Failed to initialize git hooks: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Inject skill permissions into .claude/settings.json
|
|
495
|
-
* These permissions allow skills to read/write to specific directories without prompts
|
|
496
|
-
* @returns Array of permissions that were added (empty if none added)
|
|
497
|
-
*/
|
|
498
|
-
async injectSkillPermissions() {
|
|
499
|
-
const SKILL_PERMISSIONS = [
|
|
500
|
-
'Write(.tiny-brain/**)',
|
|
501
|
-
'Write(docs/prd/**)',
|
|
502
|
-
'Write(docs/adr/**)',
|
|
503
|
-
'Write(docs/quality/**)',
|
|
504
|
-
'Read(.tiny-brain/**)',
|
|
505
|
-
'Read(docs/prd/**)',
|
|
506
|
-
'Read(docs/adr/**)',
|
|
507
|
-
'Read(docs/quality/**)',
|
|
508
|
-
'Read(.claude/skills/**/templates/**)',
|
|
509
|
-
];
|
|
510
|
-
try {
|
|
511
|
-
const repoRoot = process.cwd();
|
|
512
|
-
const settingsDir = join(repoRoot, '.claude');
|
|
513
|
-
const settingsPath = join(settingsDir, 'settings.json');
|
|
514
|
-
// Ensure .claude directory exists
|
|
515
|
-
await mkdir(settingsDir, { recursive: true });
|
|
516
|
-
// Read existing settings or start with empty object
|
|
517
|
-
let settings = {};
|
|
518
|
-
if (existsSync(settingsPath)) {
|
|
519
|
-
try {
|
|
520
|
-
const content = await readFile(settingsPath, 'utf-8');
|
|
521
|
-
settings = JSON.parse(content);
|
|
522
|
-
}
|
|
523
|
-
catch {
|
|
524
|
-
this.context.logger.warn('Failed to parse existing settings.json, will create new one');
|
|
525
|
-
settings = {};
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
// Ensure permissions.allow array exists
|
|
529
|
-
if (!settings.permissions) {
|
|
530
|
-
settings.permissions = {};
|
|
531
|
-
}
|
|
532
|
-
const permissions = settings.permissions;
|
|
533
|
-
if (!Array.isArray(permissions.allow)) {
|
|
534
|
-
permissions.allow = [];
|
|
535
|
-
}
|
|
536
|
-
const allowList = permissions.allow;
|
|
537
|
-
// Add missing permissions
|
|
538
|
-
const addedPermissions = [];
|
|
539
|
-
for (const perm of SKILL_PERMISSIONS) {
|
|
540
|
-
if (!allowList.includes(perm)) {
|
|
541
|
-
allowList.push(perm);
|
|
542
|
-
addedPermissions.push(perm);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
// Only write if we added new permissions
|
|
546
|
-
if (addedPermissions.length > 0) {
|
|
547
|
-
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
548
|
-
this.context.logger.info(`Added ${addedPermissions.length} skill permissions to .claude/settings.json`);
|
|
549
|
-
}
|
|
550
|
-
return addedPermissions;
|
|
361
|
+
// First try: core package's templates (via monorepo workspace link)
|
|
362
|
+
const coreHooksDir = join(__dirname, '..', '..', '..', 'tiny-brain-core', 'templates', 'hooks');
|
|
363
|
+
if (existsSync(coreHooksDir))
|
|
364
|
+
return coreHooksDir;
|
|
365
|
+
// Second try: the MCP package's own templates (backward compatibility)
|
|
366
|
+
const mcpHooksDir = join(__dirname, '..', '..', 'templates', 'hooks');
|
|
367
|
+
if (existsSync(mcpHooksDir))
|
|
368
|
+
return mcpHooksDir;
|
|
369
|
+
return undefined;
|
|
551
370
|
}
|
|
552
|
-
catch
|
|
553
|
-
|
|
554
|
-
return [];
|
|
371
|
+
catch {
|
|
372
|
+
return undefined;
|
|
555
373
|
}
|
|
556
374
|
}
|
|
557
375
|
/**
|
|
@@ -585,6 +403,30 @@ export class AnalyseService {
|
|
|
585
403
|
return [];
|
|
586
404
|
}
|
|
587
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Fetch recommendations from TBR and write them via RecommendationService
|
|
408
|
+
*/
|
|
409
|
+
async fetchAndWriteRecommendations(analysis) {
|
|
410
|
+
try {
|
|
411
|
+
const localContext = this.context;
|
|
412
|
+
if (!localContext.authToken?.token) {
|
|
413
|
+
this.context.logger.debug('No auth token available for recommendations API call');
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
const response = await this.libraryClient.getRecommendations(analysis, localContext.authToken.token);
|
|
417
|
+
if (!response?.recommendations || response.recommendations.length === 0) {
|
|
418
|
+
this.context.logger.debug('No recommendations returned from TBR');
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
await this.recommendationService.writeRecommendations(response.recommendations, response.fetchedAt);
|
|
422
|
+
this.context.logger.info(`Fetched ${response.recommendations.length} recommendation(s) from TBR`);
|
|
423
|
+
return response.recommendations;
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
this.context.logger.warn(`Failed to fetch recommendations from TBR: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
588
430
|
/**
|
|
589
431
|
* Format analysis output for display
|
|
590
432
|
*/
|
|
@@ -704,6 +546,27 @@ export class AnalyseService {
|
|
|
704
546
|
: 'Up to date';
|
|
705
547
|
lines.push(`\nAGENTS.md: ${statusLabel}`);
|
|
706
548
|
}
|
|
549
|
+
// Recommendations section (shown when recommendations exist)
|
|
550
|
+
if (result.recommendations && result.recommendations.length > 0) {
|
|
551
|
+
lines.push('\n🎯 Recommendations:');
|
|
552
|
+
const groupLabels = { skill: 'Skills', agent: 'Agents', hook: 'Hooks' };
|
|
553
|
+
const grouped = new Map();
|
|
554
|
+
for (const rec of result.recommendations) {
|
|
555
|
+
const existing = grouped.get(rec.type) ?? [];
|
|
556
|
+
grouped.set(rec.type, [...existing, rec]);
|
|
557
|
+
}
|
|
558
|
+
for (const [type, label] of Object.entries(groupLabels)) {
|
|
559
|
+
const recs = grouped.get(type);
|
|
560
|
+
if (recs && recs.length > 0) {
|
|
561
|
+
lines.push(`\n ${label}:`);
|
|
562
|
+
for (const rec of recs) {
|
|
563
|
+
lines.push(` • ${rec.name} — ${rec.description}`);
|
|
564
|
+
lines.push(` Reason: ${rec.reason}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
lines.push('\n Use `recommendations operation=accept` to install');
|
|
569
|
+
}
|
|
707
570
|
return lines.join('\n');
|
|
708
571
|
}
|
|
709
572
|
/**
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { RequestContext } from '../types/request-context.js';
|
|
9
9
|
export interface WriteWorkflowSectionsOptions {
|
|
10
10
|
contextPath?: string;
|
|
11
|
+
isMonorepo?: boolean;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
14
|
* Service for repository context operations
|
|
@@ -75,6 +76,10 @@ export declare class RepoService {
|
|
|
75
76
|
* Format the Progress Tracking Auto-Commit Configuration section
|
|
76
77
|
*/
|
|
77
78
|
private formatProgressTrackingSection;
|
|
79
|
+
/**
|
|
80
|
+
* Format the Worktree Merge Workflow section
|
|
81
|
+
*/
|
|
82
|
+
private formatWorktreeMergeSection;
|
|
78
83
|
/**
|
|
79
84
|
* Format the Operational Tracking Directory section
|
|
80
85
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-service.d.ts","sourceRoot":"","sources":["../../src/services/repo-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIlE,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,WAAW;IAMV,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAA2B;IACnE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAyB;IAE/D,OAAO,CAAC,aAAa,CAAgB;gBAEjB,OAAO,EAAE,cAAc;IAS3C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;OAEG;IACG,4BAA4B,CAChC,eAAe,GAAE,MAAoB,GACpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmCzB;;;;OAIG;IACH,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOvD;;OAEG;IACH,cAAc,IAAI,OAAO;IAUzB;;;OAGG;IACG,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAcjD;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAa5B;;;;;;;;OAQG;IACG,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"repo-service.d.ts","sourceRoot":"","sources":["../../src/services/repo-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIlE,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,WAAW;IAMV,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAA2B;IACnE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAyB;IAE/D,OAAO,CAAC,aAAa,CAAgB;gBAEjB,OAAO,EAAE,cAAc;IAS3C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;OAEG;IACG,4BAA4B,CAChC,eAAe,GAAE,MAAoB,GACpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmCzB;;;;OAIG;IACH,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOvD;;OAEG;IACH,cAAc,IAAI,OAAO;IAUzB;;;OAGG;IACG,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAcjD;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAa5B;;;;;;;;OAQG;IACG,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkGtF;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA6BhC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAgGlC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA4ChC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAiErC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA6BlC;;OAEG;IACH,OAAO,CAAC,gCAAgC;CAyCzC"}
|
|
@@ -155,7 +155,7 @@ export class RepoService {
|
|
|
155
155
|
// Build the workflow sections content
|
|
156
156
|
let workflowContent = '';
|
|
157
157
|
// Repository Context (always when workflow sections are enabled)
|
|
158
|
-
workflowContent += this.formatRepoContextSection(enableAgentic);
|
|
158
|
+
workflowContent += this.formatRepoContextSection(enableAgentic, options.isMonorepo);
|
|
159
159
|
// Commit Message Format (when SDD enabled)
|
|
160
160
|
if (enableSDD) {
|
|
161
161
|
workflowContent += this.formatCommitMessageSection();
|
|
@@ -168,6 +168,10 @@ export class RepoService {
|
|
|
168
168
|
if (enableSDD) {
|
|
169
169
|
workflowContent += this.formatProgressTrackingSection();
|
|
170
170
|
}
|
|
171
|
+
// Worktree Merge Workflow (when SDD enabled)
|
|
172
|
+
if (enableSDD) {
|
|
173
|
+
workflowContent += this.formatWorktreeMergeSection();
|
|
174
|
+
}
|
|
171
175
|
// Operational Tracking Directory (when SDD enabled)
|
|
172
176
|
if (enableSDD) {
|
|
173
177
|
workflowContent += this.formatOperationalTrackingSection();
|
|
@@ -226,13 +230,15 @@ export class RepoService {
|
|
|
226
230
|
* Format the Repository Context section
|
|
227
231
|
* When agentic coding is enabled, includes a mandatory agent delegation section
|
|
228
232
|
*/
|
|
229
|
-
formatRepoContextSection(enableAgenticCoding) {
|
|
230
|
-
let content = `## Repository Context
|
|
231
|
-
|
|
232
|
-
Before starting work, read \`AGENTS.md\` for comprehensive project context (tech stack, commands, structure).
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
`;
|
|
233
|
+
formatRepoContextSection(enableAgenticCoding, isMonorepo) {
|
|
234
|
+
let content = `## Repository Context\n\n`;
|
|
235
|
+
if (isMonorepo) {
|
|
236
|
+
content += `Before starting work, read the root \`AGENTS.md\` for comprehensive project context (tech stack, commands, structure).\nWhen working in a specific package, also read that package's \`AGENTS.md\` for package-specific details.\n`;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
content += `Before starting work, read \`AGENTS.md\` for comprehensive project context (tech stack, commands, structure).\n`;
|
|
240
|
+
}
|
|
241
|
+
content += `Also read \`.tiny-brain/analysis.json\` for detailed detection data and test patterns.\n\n`;
|
|
236
242
|
if (enableAgenticCoding) {
|
|
237
243
|
content += `## Agent Delegation (MANDATORY)
|
|
238
244
|
|
|
@@ -297,19 +303,6 @@ Description of changes...
|
|
|
297
303
|
|
|
298
304
|
**WARNING:** The commit-msg hook will reject commits missing required headers.
|
|
299
305
|
|
|
300
|
-
### Updating Markdown After Commits
|
|
301
|
-
|
|
302
|
-
**For PRD tasks:**
|
|
303
|
-
1. Open the feature file: \`docs/prd/{prd-id}/features/{feature-id}.md\`
|
|
304
|
-
2. Update the task with status and commitSha:
|
|
305
|
-
\`\`\`markdown
|
|
306
|
-
### 1. Task description
|
|
307
|
-
status: completed
|
|
308
|
-
commitSha: abc1234
|
|
309
|
-
\`\`\`
|
|
310
|
-
3. Run: \`npx tiny-brain sync-file docs/prd/{prd-id}/features/{feature-id}.md\`
|
|
311
|
-
4. If ALL tasks in the feature are complete, update the PRD status
|
|
312
|
-
|
|
313
306
|
### Fix Status Workflow
|
|
314
307
|
|
|
315
308
|
Fix documents have three statuses: \`documented\` → \`in_progress\` → \`resolved\`
|
|
@@ -317,7 +310,7 @@ Fix documents have three statuses: \`documented\` → \`in_progress\` → \`reso
|
|
|
317
310
|
**When starting work on a fix:**
|
|
318
311
|
1. Open the fix file: \`.tiny-brain/fixes/{fix-id}.md\`
|
|
319
312
|
2. Update frontmatter: \`status: in_progress\`
|
|
320
|
-
3. Run: \`npx tiny-brain sync-
|
|
313
|
+
3. Run: \`npx tiny-brain sync-progress .tiny-brain/fixes/{fix-id}.md\`
|
|
321
314
|
|
|
322
315
|
**After each commit:**
|
|
323
316
|
1. Update the completed task(s) in the markdown:
|
|
@@ -333,7 +326,7 @@ Fix documents have three statuses: \`documented\` → \`in_progress\` → \`reso
|
|
|
333
326
|
status: superseded
|
|
334
327
|
commitSha: null
|
|
335
328
|
\`\`\`
|
|
336
|
-
4. Run: \`npx tiny-brain sync-
|
|
329
|
+
4. Run: \`npx tiny-brain sync-progress .tiny-brain/fixes/{fix-id}.md\`
|
|
337
330
|
|
|
338
331
|
**When all tasks are complete:**
|
|
339
332
|
1. **ONLY set \`status: resolved\`** when ALL tasks are accounted for:
|
|
@@ -354,9 +347,9 @@ Fix documents have three statuses: \`documented\` → \`in_progress\` → \`reso
|
|
|
354
347
|
- path/to/file1.ts
|
|
355
348
|
- path/to/file2.ts
|
|
356
349
|
\`\`\`
|
|
357
|
-
3. Run: \`npx tiny-brain sync-
|
|
350
|
+
3. Run: \`npx tiny-brain sync-progress .tiny-brain/fixes/{fix-id}.md\`
|
|
358
351
|
|
|
359
|
-
**Note:** The markdown file is the source of truth. The \`sync-
|
|
352
|
+
**Note:** The markdown file is the source of truth. The \`sync-progress\` command updates \`progress.json\` from the markdown.
|
|
360
353
|
|
|
361
354
|
`;
|
|
362
355
|
}
|
|
@@ -371,30 +364,38 @@ IMPORTANT: This repository follows strict Test-Driven Development (TDD) with a 3
|
|
|
371
364
|
**Red → Green → Refactor Cycle:**
|
|
372
365
|
|
|
373
366
|
1. **Red Phase** (\`test:\` or \`test(scope):\` commits):
|
|
367
|
+
- **CRITICAL — you MUST run before writing tests:**
|
|
368
|
+
\`npx tiny-brain update-phase --phase red --event start --task '...' [--fix ID | --prd ID --feature ID]\`
|
|
374
369
|
- Write failing tests first
|
|
375
370
|
- Tests SHOULD fail (that's the point!)
|
|
376
371
|
- Use: \`git commit -m "test: ..."\` or \`git commit -m "test(api): ..."\`
|
|
377
372
|
- Git hook automatically runs typecheck + lint but SKIPS tests
|
|
378
373
|
- Tracked in: \`testCommitSha\` field
|
|
374
|
+
- **After commit:** post-commit hook automatically transitions to \`green:start\`
|
|
379
375
|
|
|
380
376
|
2. **Green Phase** (\`feat:\` or \`feat(scope):\` commits):
|
|
377
|
+
- Phase started automatically by post-commit hook after \`test:\` commit (no manual step needed)
|
|
381
378
|
- Implement minimum code to make tests pass
|
|
382
379
|
- Use: \`git commit -m "feat: ..."\` or \`git commit -m "feat(auth): ..."\`
|
|
383
380
|
- Git hook automatically runs full checks (typecheck + lint + test)
|
|
384
381
|
- Tracked in: \`commitSha\` field
|
|
385
382
|
- **Marks task as COMPLETED**
|
|
383
|
+
- **After commit:** adversarial review hook sets \`adversarial:start\` and outputs review instructions
|
|
386
384
|
|
|
387
385
|
3. **Refactor Phase** (\`refactor:\` or \`refactor(scope):\` commits - optional):
|
|
386
|
+
- Triggered by adversarial review suggestions
|
|
388
387
|
- Improve code quality without changing behavior
|
|
389
388
|
- Use: \`git commit -m "refactor: ..."\` or \`git commit -m "refactor(plan): ..."\`
|
|
390
389
|
- Git hook automatically runs full checks (typecheck + lint + test)
|
|
391
|
-
-
|
|
390
|
+
- **After commit:** post-commit hook automatically transitions \`adversarial-refactor:complete\`
|
|
391
|
+
|
|
392
|
+
4. **Progress Commit** (end of cycle):
|
|
393
|
+
- After the full RED→GREEN→REFACTOR cycle is complete
|
|
394
|
+
- Run: \`npx tiny-brain commit-progress\`
|
|
395
|
+
|
|
396
|
+
**What you must do manually:** Only \`update-phase --phase red --event start\` before writing tests. Everything else is automated by hooks.
|
|
392
397
|
|
|
393
|
-
**
|
|
394
|
-
- Git hooks automatically detect commit type and run appropriate checks
|
|
395
|
-
- Separate commit tracking enables Feature 9 (TDD phase tracking in dashboard)
|
|
396
|
-
- Provides audit trail of development process
|
|
397
|
-
- Only \`feat:\` or \`feat(scope):\` commits mark tasks as completed
|
|
398
|
+
**What you must NOT do:** Do not manually call \`update-phase\` for green:start, adversarial:start, or adversarial-refactor:complete — the hooks handle these.
|
|
398
399
|
|
|
399
400
|
`;
|
|
400
401
|
}
|
|
@@ -410,18 +411,11 @@ By default, tiny-brain uses **manual mode** for progress.json commits. This mean
|
|
|
410
411
|
|
|
411
412
|
When you complete a task with a \`feat:\` or \`test:\` commit:
|
|
412
413
|
1. The post-commit hook updates progress.json automatically
|
|
413
|
-
2. You'll see a friendly message with instructions
|
|
414
|
+
2. You'll see a friendly message with instructions
|
|
415
|
+
3. Commit the progress.json changes by running:
|
|
416
|
+
\`\`\`bash
|
|
417
|
+
npx tiny-brain commit-progress
|
|
414
418
|
\`\`\`
|
|
415
|
-
📝 Progress tracking updated!
|
|
416
|
-
|
|
417
|
-
To commit progress.json changes, run:
|
|
418
|
-
git add .tiny-brain/progress/*.json
|
|
419
|
-
git commit -m "chore: update progress tracking"
|
|
420
|
-
|
|
421
|
-
💡 Tip: Enable auto-commit to skip this step:
|
|
422
|
-
npx tiny-brain config preferences set autoCommitProgress true
|
|
423
|
-
\`\`\`
|
|
424
|
-
3. Commit the progress.json changes when you're ready
|
|
425
419
|
|
|
426
420
|
### Enabling Auto-Commit
|
|
427
421
|
|
|
@@ -470,6 +464,37 @@ Settings follow this precedence (highest to lowest):
|
|
|
470
464
|
|
|
471
465
|
This allows you to set a global preference but override it per repository as needed.
|
|
472
466
|
|
|
467
|
+
`;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Format the Worktree Merge Workflow section
|
|
471
|
+
*/
|
|
472
|
+
formatWorktreeMergeSection() {
|
|
473
|
+
return `## Worktree Merge Workflow
|
|
474
|
+
|
|
475
|
+
When merging a worktree branch back into the main branch, \`progress.json\` files may conflict because both branches tracked different task completions. The \`post-merge\` git hook handles this automatically.
|
|
476
|
+
|
|
477
|
+
### How It Works
|
|
478
|
+
|
|
479
|
+
1. After a merge, the hook detects if any \`.tiny-brain/progress/*.json\` or \`.tiny-brain/fixes/progress.json\` files changed
|
|
480
|
+
2. If so, it re-runs \`npx tiny-brain sync-progress\` on all markdown sources to regenerate progress.json
|
|
481
|
+
3. The regenerated files are staged and committed as \`chore: re-sync progress after merge\`
|
|
482
|
+
|
|
483
|
+
### Handling Merge Conflicts in progress.json
|
|
484
|
+
|
|
485
|
+
If you encounter a merge conflict in progress.json:
|
|
486
|
+
1. Accept either version (it doesn't matter which)
|
|
487
|
+
2. Complete the merge
|
|
488
|
+
3. The post-merge hook will regenerate progress.json from the markdown sources automatically
|
|
489
|
+
|
|
490
|
+
### Manual Re-sync
|
|
491
|
+
|
|
492
|
+
If the hook doesn't run or you need to re-sync manually:
|
|
493
|
+
\`\`\`bash
|
|
494
|
+
npx tiny-brain sync-progress docs/prd/*/features/*.md
|
|
495
|
+
npx tiny-brain sync-progress .tiny-brain/fixes/*.md
|
|
496
|
+
\`\`\`
|
|
497
|
+
|
|
473
498
|
`;
|
|
474
499
|
}
|
|
475
500
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-filesystem-adapter.d.ts","sourceRoot":"","sources":["../../src/storage/local-filesystem-adapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAOnF;;GAEG;AACH,qBAAa,6BAA8B,YAAW,wBAAwB;IAC5E,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,UAAU,CAAU;IACrB,WAAW,EAAE,MAAM,CAAC;gBAEf,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE;IAMvD,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtD,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAYV,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAanB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvF,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAqCxE,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBjD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAajE,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"local-filesystem-adapter.d.ts","sourceRoot":"","sources":["../../src/storage/local-filesystem-adapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAOnF;;GAEG;AACH,qBAAa,6BAA8B,YAAW,wBAAwB;IAC5E,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,UAAU,CAAU;IACrB,WAAW,EAAE,MAAM,CAAC;gBAEf,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE;IAMvD,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtD,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAYV,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAanB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvF,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAqCxE,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBjD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAajE,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5E,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5E,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAalE,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5D,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAiB/B,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDhG,iBAAiB,IAAI;QACnB,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;QACzB,WAAW,CAAC,EAAE,OAAO,oCAAoC,EAAE,iBAAiB,CAAC;QAC7E,iBAAiB,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB;IAQD;;OAEG;YACW,qBAAqB;IAWnC;;OAEG;IACH,WAAW,IAAI,MAAM;CAGtB"}
|