agileflow 2.90.7 → 2.92.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/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +818 -0
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate-names.js +3 -3
- package/lib/validate.js +116 -52
- package/package.json +4 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +435 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +43 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +118 -1048
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +36 -11
- package/scripts/query-codebase.js +538 -0
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +220 -42
- package/scripts/spawn-parallel.js +651 -0
- package/scripts/tui/blessed/data/watcher.js +180 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +101 -0
- package/scripts/tui/blessed/panels/sessions.js +150 -0
- package/scripts/tui/blessed/panels/trace.js +97 -0
- package/scripts/tui/blessed/ui/help.js +77 -0
- package/scripts/tui/blessed/ui/screen.js +52 -0
- package/scripts/tui/blessed/ui/statusbar.js +47 -0
- package/scripts/tui/blessed/ui/tabbar.js +99 -0
- package/scripts/tui/index.js +38 -30
- package/scripts/validators/README.md +143 -0
- package/scripts/validators/component-validator.js +239 -0
- package/scripts/validators/json-schema-validator.js +186 -0
- package/scripts/validators/markdown-validator.js +152 -0
- package/scripts/validators/migration-validator.js +129 -0
- package/scripts/validators/security-validator.js +380 -0
- package/scripts/validators/story-format-validator.js +197 -0
- package/scripts/validators/test-result-validator.js +114 -0
- package/scripts/validators/workflow-validator.js +247 -0
- package/src/core/agents/accessibility.md +6 -0
- package/src/core/agents/adr-writer.md +6 -0
- package/src/core/agents/analytics.md +6 -0
- package/src/core/agents/api.md +6 -0
- package/src/core/agents/ci.md +6 -0
- package/src/core/agents/codebase-query.md +261 -0
- package/src/core/agents/compliance.md +6 -0
- package/src/core/agents/configuration-damage-control.md +6 -0
- package/src/core/agents/configuration-visual-e2e.md +6 -0
- package/src/core/agents/database.md +10 -0
- package/src/core/agents/datamigration.md +6 -0
- package/src/core/agents/design.md +6 -0
- package/src/core/agents/devops.md +6 -0
- package/src/core/agents/documentation.md +6 -0
- package/src/core/agents/epic-planner.md +6 -0
- package/src/core/agents/integrations.md +6 -0
- package/src/core/agents/mentor.md +6 -0
- package/src/core/agents/mobile.md +6 -0
- package/src/core/agents/monitoring.md +6 -0
- package/src/core/agents/multi-expert.md +6 -0
- package/src/core/agents/performance.md +6 -0
- package/src/core/agents/product.md +6 -0
- package/src/core/agents/qa.md +6 -0
- package/src/core/agents/readme-updater.md +6 -0
- package/src/core/agents/refactor.md +6 -0
- package/src/core/agents/research.md +6 -0
- package/src/core/agents/security.md +6 -0
- package/src/core/agents/testing.md +10 -0
- package/src/core/agents/ui.md +6 -0
- package/src/core/commands/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +205 -1
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +76 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/pr.md +96 -0
- package/src/core/commands/research/analyze.md +1 -0
- package/src/core/commands/research/ask.md +2 -0
- package/src/core/commands/research/import.md +1 -0
- package/src/core/commands/research/list.md +2 -0
- package/src/core/commands/research/synthesize.md +584 -0
- package/src/core/commands/research/view.md +2 -0
- package/src/core/commands/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +113 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +200 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/commands/story.md +143 -4
- package/src/core/experts/codebase-query/expertise.yaml +190 -0
- package/src/core/experts/codebase-query/question.md +73 -0
- package/src/core/experts/codebase-query/self-improve.md +105 -0
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +86 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +114 -1
- package/tools/cli/lib/ui.js +14 -25
|
@@ -39,6 +39,29 @@ const {
|
|
|
39
39
|
* @property {string} [source='builtin'] - Source: 'builtin' | 'plugin'
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Schema version for serialization compatibility
|
|
44
|
+
* Increment when making breaking changes to serialized format
|
|
45
|
+
*
|
|
46
|
+
* Version history:
|
|
47
|
+
* - 1: Initial schema with name, type, description, secure, cacheable, source
|
|
48
|
+
* - 2: Added validator serialization support, context metadata
|
|
49
|
+
*/
|
|
50
|
+
const SCHEMA_VERSION = 2;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Schema migrations for upgrading old configurations
|
|
54
|
+
*/
|
|
55
|
+
const SCHEMA_MIGRATIONS = {
|
|
56
|
+
// Migrate from v1 to v2
|
|
57
|
+
1: config => ({
|
|
58
|
+
...config,
|
|
59
|
+
schemaVersion: 2,
|
|
60
|
+
contextMetadata: config.contextMetadata || {},
|
|
61
|
+
validatorName: config.validatorName || null,
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
66
|
* Default resolver types with their sanitization rules
|
|
44
67
|
*/
|
|
@@ -470,6 +493,186 @@ class PlaceholderRegistry extends EventEmitter {
|
|
|
470
493
|
// Could be extended to include context hash
|
|
471
494
|
return name;
|
|
472
495
|
}
|
|
496
|
+
|
|
497
|
+
// ===========================================================================
|
|
498
|
+
// Schema Versioning and Serialization
|
|
499
|
+
// ===========================================================================
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Get current schema version
|
|
503
|
+
* @returns {number}
|
|
504
|
+
*/
|
|
505
|
+
static getSchemaVersion() {
|
|
506
|
+
return SCHEMA_VERSION;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Serialize registry configuration for persistence
|
|
511
|
+
* @returns {Object} Serializable configuration
|
|
512
|
+
*/
|
|
513
|
+
serialize() {
|
|
514
|
+
const configs = {};
|
|
515
|
+
|
|
516
|
+
for (const [name, config] of this._resolvers) {
|
|
517
|
+
configs[name] = {
|
|
518
|
+
schemaVersion: SCHEMA_VERSION,
|
|
519
|
+
name: config.name,
|
|
520
|
+
description: config.description,
|
|
521
|
+
type: config.type,
|
|
522
|
+
secure: config.secure,
|
|
523
|
+
cacheable: config.cacheable,
|
|
524
|
+
source: config.source,
|
|
525
|
+
contextMetadata: config.contextMetadata || {},
|
|
526
|
+
validatorName: config.validatorName || null,
|
|
527
|
+
// Note: resolver function cannot be serialized - must be re-registered
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
schemaVersion: SCHEMA_VERSION,
|
|
533
|
+
registryOptions: {
|
|
534
|
+
cacheEnabled: this.cacheEnabled,
|
|
535
|
+
strictMode: this.strictMode,
|
|
536
|
+
secureByDefault: this.secureByDefault,
|
|
537
|
+
},
|
|
538
|
+
placeholders: configs,
|
|
539
|
+
plugins: Array.from(this._plugins.keys()),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Export configuration as JSON string
|
|
545
|
+
* @param {number} [spaces=2] - JSON indentation
|
|
546
|
+
* @returns {string}
|
|
547
|
+
*/
|
|
548
|
+
toJSON(spaces = 2) {
|
|
549
|
+
return JSON.stringify(this.serialize(), null, spaces);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Migrate configuration to current schema version
|
|
554
|
+
* @param {Object} config - Configuration to migrate
|
|
555
|
+
* @returns {Object} Migrated configuration
|
|
556
|
+
*/
|
|
557
|
+
static migrateConfig(config) {
|
|
558
|
+
let migrated = { ...config };
|
|
559
|
+
let version = config.schemaVersion || 1;
|
|
560
|
+
|
|
561
|
+
// Apply migrations sequentially
|
|
562
|
+
while (version < SCHEMA_VERSION) {
|
|
563
|
+
const migration = SCHEMA_MIGRATIONS[version];
|
|
564
|
+
if (migration) {
|
|
565
|
+
migrated = migration(migrated);
|
|
566
|
+
}
|
|
567
|
+
version++;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return migrated;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Validate configuration against current schema
|
|
575
|
+
* @param {Object} config - Configuration to validate
|
|
576
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
577
|
+
*/
|
|
578
|
+
static validateConfig(config) {
|
|
579
|
+
const errors = [];
|
|
580
|
+
|
|
581
|
+
if (!config || typeof config !== 'object') {
|
|
582
|
+
return { valid: false, errors: ['Configuration must be an object'] };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Check schema version
|
|
586
|
+
if (config.schemaVersion && config.schemaVersion > SCHEMA_VERSION) {
|
|
587
|
+
errors.push(
|
|
588
|
+
`Configuration schema version ${config.schemaVersion} is newer than supported ${SCHEMA_VERSION}`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Validate required fields for placeholder configs
|
|
593
|
+
if (config.placeholders) {
|
|
594
|
+
for (const [name, placeholder] of Object.entries(config.placeholders)) {
|
|
595
|
+
if (!placeholder.name) {
|
|
596
|
+
errors.push(`Placeholder "${name}" missing required field: name`);
|
|
597
|
+
}
|
|
598
|
+
if (!placeholder.type) {
|
|
599
|
+
errors.push(`Placeholder "${name}" missing required field: type`);
|
|
600
|
+
}
|
|
601
|
+
if (placeholder.type && !RESOLVER_TYPES[placeholder.type]) {
|
|
602
|
+
errors.push(`Placeholder "${name}" has invalid type: ${placeholder.type}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return { valid: errors.length === 0, errors };
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Load configuration from serialized data
|
|
612
|
+
* Note: Only loads metadata - resolvers must be re-registered
|
|
613
|
+
* @param {Object} data - Serialized configuration
|
|
614
|
+
* @param {Object} [resolvers={}] - Map of resolver functions by name
|
|
615
|
+
* @returns {PlaceholderRegistry} New registry instance
|
|
616
|
+
*/
|
|
617
|
+
static deserialize(data, resolvers = {}) {
|
|
618
|
+
// Validate
|
|
619
|
+
const validation = PlaceholderRegistry.validateConfig(data);
|
|
620
|
+
if (!validation.valid) {
|
|
621
|
+
throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Migrate if needed
|
|
625
|
+
const migrated = PlaceholderRegistry.migrateConfig(data);
|
|
626
|
+
|
|
627
|
+
// Create registry with options
|
|
628
|
+
const options = migrated.registryOptions || {};
|
|
629
|
+
const registry = new PlaceholderRegistry({
|
|
630
|
+
cache: options.cacheEnabled,
|
|
631
|
+
strict: options.strictMode,
|
|
632
|
+
secure: options.secureByDefault,
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// Register placeholders with provided resolvers
|
|
636
|
+
if (migrated.placeholders) {
|
|
637
|
+
for (const [name, config] of Object.entries(migrated.placeholders)) {
|
|
638
|
+
const resolver = resolvers[name];
|
|
639
|
+
if (resolver) {
|
|
640
|
+
registry.register(name, resolver, {
|
|
641
|
+
description: config.description,
|
|
642
|
+
type: config.type,
|
|
643
|
+
secure: config.secure,
|
|
644
|
+
cacheable: config.cacheable,
|
|
645
|
+
source: config.source,
|
|
646
|
+
contextMetadata: config.contextMetadata,
|
|
647
|
+
validatorName: config.validatorName,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return registry;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Parse JSON configuration and create registry
|
|
658
|
+
* @param {string} json - JSON string
|
|
659
|
+
* @param {Object} [resolvers={}] - Map of resolver functions by name
|
|
660
|
+
* @returns {PlaceholderRegistry}
|
|
661
|
+
*/
|
|
662
|
+
static fromJSON(json, resolvers = {}) {
|
|
663
|
+
const data = JSON.parse(json);
|
|
664
|
+
return PlaceholderRegistry.deserialize(data, resolvers);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Check if configuration needs migration
|
|
669
|
+
* @param {Object} config - Configuration to check
|
|
670
|
+
* @returns {boolean}
|
|
671
|
+
*/
|
|
672
|
+
static needsMigration(config) {
|
|
673
|
+
const version = config.schemaVersion || 1;
|
|
674
|
+
return version < SCHEMA_VERSION;
|
|
675
|
+
}
|
|
473
676
|
}
|
|
474
677
|
|
|
475
678
|
// =============================================================================
|
|
@@ -608,6 +811,8 @@ function resetRegistry() {
|
|
|
608
811
|
module.exports = {
|
|
609
812
|
PlaceholderRegistry,
|
|
610
813
|
RESOLVER_TYPES,
|
|
814
|
+
SCHEMA_VERSION,
|
|
815
|
+
SCHEMA_MIGRATIONS,
|
|
611
816
|
createCountResolver,
|
|
612
817
|
createListResolver,
|
|
613
818
|
createStaticResolver,
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry Dependency Injection Module
|
|
3
|
+
*
|
|
4
|
+
* Provides dependency injection patterns for registry classes to enable:
|
|
5
|
+
* - Easier testing with mock dependencies
|
|
6
|
+
* - Configuration without modifying code
|
|
7
|
+
* - Clear separation of concerns
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const { createContainer, createPlaceholderRegistry } = require('./registry-di');
|
|
11
|
+
*
|
|
12
|
+
* // Create a container with custom dependencies
|
|
13
|
+
* const container = createContainer({
|
|
14
|
+
* sanitizer: mockSanitizer,
|
|
15
|
+
* fs: mockFs,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Create registry using container
|
|
19
|
+
* const registry = createPlaceholderRegistry(container);
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
// Default dependencies
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} IDependencyContainer
|
|
30
|
+
* @property {Object} fs - File system module
|
|
31
|
+
* @property {Object} path - Path module
|
|
32
|
+
* @property {Object} sanitizer - Content sanitizer module
|
|
33
|
+
* @property {Object} [logger] - Optional logger interface
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} ISanitizer
|
|
38
|
+
* @property {Function} sanitize.count - Sanitize count values
|
|
39
|
+
* @property {Function} sanitize.description - Sanitize description strings
|
|
40
|
+
* @property {Function} sanitize.date - Sanitize date values
|
|
41
|
+
* @property {Function} sanitize.version - Sanitize version strings
|
|
42
|
+
* @property {Function} sanitize.folderName - Sanitize folder names
|
|
43
|
+
* @property {Function} validatePlaceholderValue - Validate placeholder values
|
|
44
|
+
* @property {Function} detectInjectionAttempt - Detect injection attempts
|
|
45
|
+
* @property {Function} escapeMarkdown - Escape markdown characters
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {Object} ILogger
|
|
50
|
+
* @property {Function} debug - Debug level logging
|
|
51
|
+
* @property {Function} info - Info level logging
|
|
52
|
+
* @property {Function} warn - Warning level logging
|
|
53
|
+
* @property {Function} error - Error level logging
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Default no-op logger
|
|
58
|
+
*/
|
|
59
|
+
const noopLogger = {
|
|
60
|
+
debug: () => {},
|
|
61
|
+
info: () => {},
|
|
62
|
+
warn: () => {},
|
|
63
|
+
error: () => {},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Console logger adapter
|
|
68
|
+
*/
|
|
69
|
+
const consoleLogger = {
|
|
70
|
+
debug: (...args) => console.debug('[Registry]', ...args),
|
|
71
|
+
info: (...args) => console.log('[Registry]', ...args),
|
|
72
|
+
warn: (...args) => console.warn('[Registry]', ...args),
|
|
73
|
+
error: (...args) => console.error('[Registry]', ...args),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load default sanitizer module
|
|
78
|
+
* @returns {ISanitizer}
|
|
79
|
+
*/
|
|
80
|
+
function loadDefaultSanitizer() {
|
|
81
|
+
try {
|
|
82
|
+
return require('./content-sanitizer');
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// Return stub sanitizer if module not found
|
|
85
|
+
return {
|
|
86
|
+
sanitize: {
|
|
87
|
+
count: v => (typeof v === 'number' ? Math.max(0, Math.floor(v)) : 0),
|
|
88
|
+
description: v => String(v || ''),
|
|
89
|
+
date: v => (v instanceof Date ? v.toISOString().split('T')[0] : String(v || '')),
|
|
90
|
+
version: v => String(v || 'unknown'),
|
|
91
|
+
folderName: v => String(v || '.agileflow'),
|
|
92
|
+
},
|
|
93
|
+
validatePlaceholderValue: () => ({ valid: true }),
|
|
94
|
+
detectInjectionAttempt: () => ({ safe: true }),
|
|
95
|
+
escapeMarkdown: v => String(v || ''),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a dependency container with defaults
|
|
102
|
+
* @param {Partial<IDependencyContainer>} [overrides={}] - Override specific dependencies
|
|
103
|
+
* @returns {IDependencyContainer}
|
|
104
|
+
*/
|
|
105
|
+
function createContainer(overrides = {}) {
|
|
106
|
+
const defaultSanitizer = loadDefaultSanitizer();
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
fs: overrides.fs || fs,
|
|
110
|
+
path: overrides.path || path,
|
|
111
|
+
sanitizer: overrides.sanitizer || defaultSanitizer,
|
|
112
|
+
logger: overrides.logger || noopLogger,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a test container with mock-friendly defaults
|
|
118
|
+
* @param {Partial<IDependencyContainer>} [overrides={}] - Override specific dependencies
|
|
119
|
+
* @returns {IDependencyContainer}
|
|
120
|
+
*/
|
|
121
|
+
function createTestContainer(overrides = {}) {
|
|
122
|
+
const mockFs = {
|
|
123
|
+
readdirSync: () => [],
|
|
124
|
+
readFileSync: () => '',
|
|
125
|
+
existsSync: () => false,
|
|
126
|
+
writeFileSync: () => {},
|
|
127
|
+
mkdirSync: () => {},
|
|
128
|
+
...overrides.fs,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const mockSanitizer = {
|
|
132
|
+
sanitize: {
|
|
133
|
+
count: v => v,
|
|
134
|
+
description: v => v,
|
|
135
|
+
date: v => v,
|
|
136
|
+
version: v => v,
|
|
137
|
+
folderName: v => v,
|
|
138
|
+
},
|
|
139
|
+
validatePlaceholderValue: () => ({ valid: true }),
|
|
140
|
+
detectInjectionAttempt: () => ({ safe: true }),
|
|
141
|
+
escapeMarkdown: v => v,
|
|
142
|
+
...overrides.sanitizer,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return createContainer({
|
|
146
|
+
fs: mockFs,
|
|
147
|
+
sanitizer: mockSanitizer,
|
|
148
|
+
logger: overrides.logger || noopLogger,
|
|
149
|
+
...overrides,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Registry factory configuration
|
|
155
|
+
* @typedef {Object} RegistryFactoryConfig
|
|
156
|
+
* @property {Object} [options] - Registry options
|
|
157
|
+
* @property {IDependencyContainer} [container] - Dependency container
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Create PlaceholderRegistry with injected dependencies
|
|
162
|
+
* @param {IDependencyContainer} container - Dependency container
|
|
163
|
+
* @param {Object} [options={}] - Registry options
|
|
164
|
+
* @returns {PlaceholderRegistry}
|
|
165
|
+
*/
|
|
166
|
+
function createPlaceholderRegistry(container, options = {}) {
|
|
167
|
+
const { PlaceholderRegistry } = require('./placeholder-registry');
|
|
168
|
+
|
|
169
|
+
// Create registry with injected container
|
|
170
|
+
const registry = new PlaceholderRegistry({
|
|
171
|
+
...options,
|
|
172
|
+
// Inject dependencies via context
|
|
173
|
+
_container: container,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return registry;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Scan registry factory - creates scanner functions with injected dependencies
|
|
181
|
+
* @param {IDependencyContainer} container - Dependency container
|
|
182
|
+
* @returns {Object} Scanner functions
|
|
183
|
+
*/
|
|
184
|
+
function createScannerFactory(container) {
|
|
185
|
+
const { fs: fsModule, path: pathModule, logger } = container;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Generic file scanner
|
|
189
|
+
* @param {string} directory - Directory to scan
|
|
190
|
+
* @param {string} extension - File extension (e.g., '.md')
|
|
191
|
+
* @param {Function} parser - Frontmatter parser function
|
|
192
|
+
* @returns {Array} Parsed items
|
|
193
|
+
*/
|
|
194
|
+
function scanDirectory(directory, extension, parser) {
|
|
195
|
+
logger.debug(`Scanning directory: ${directory}`);
|
|
196
|
+
|
|
197
|
+
if (!fsModule.existsSync(directory)) {
|
|
198
|
+
logger.warn(`Directory not found: ${directory}`);
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let files;
|
|
203
|
+
try {
|
|
204
|
+
files = fsModule.readdirSync(directory);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
logger.error(`Failed to read directory: ${err.message}`);
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const items = [];
|
|
211
|
+
|
|
212
|
+
for (const file of files) {
|
|
213
|
+
if (!file.endsWith(extension)) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const filePath = pathModule.join(directory, file);
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const content = fsModule.readFileSync(filePath, 'utf8');
|
|
221
|
+
const parsed = parser(content, file, filePath);
|
|
222
|
+
|
|
223
|
+
if (parsed) {
|
|
224
|
+
items.push(parsed);
|
|
225
|
+
}
|
|
226
|
+
} catch (err) {
|
|
227
|
+
logger.warn(`Failed to parse ${file}: ${err.message}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
logger.debug(`Found ${items.length} items`);
|
|
232
|
+
return items;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
scanDirectory,
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Scan for command files
|
|
240
|
+
* @param {string} commandsDir - Commands directory path
|
|
241
|
+
* @param {Function} extractFrontmatter - Frontmatter extractor
|
|
242
|
+
* @returns {Array} Command metadata
|
|
243
|
+
*/
|
|
244
|
+
scanCommands(commandsDir, extractFrontmatter) {
|
|
245
|
+
return scanDirectory(commandsDir, '.md', (content, file, filePath) => {
|
|
246
|
+
const frontmatter = extractFrontmatter(filePath);
|
|
247
|
+
if (Object.keys(frontmatter).length === 0) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const name = file.replace('.md', '');
|
|
252
|
+
return {
|
|
253
|
+
name,
|
|
254
|
+
file,
|
|
255
|
+
path: filePath,
|
|
256
|
+
description: frontmatter.description || '',
|
|
257
|
+
argumentHint: frontmatter['argument-hint'] || '',
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Scan for agent files
|
|
264
|
+
* @param {string} agentsDir - Agents directory path
|
|
265
|
+
* @param {Function} extractFrontmatter - Frontmatter extractor
|
|
266
|
+
* @param {Function} normalizeTools - Tools normalizer
|
|
267
|
+
* @returns {Array} Agent metadata
|
|
268
|
+
*/
|
|
269
|
+
scanAgents(agentsDir, extractFrontmatter, normalizeTools) {
|
|
270
|
+
return scanDirectory(agentsDir, '.md', (content, file, filePath) => {
|
|
271
|
+
const frontmatter = extractFrontmatter(filePath);
|
|
272
|
+
if (Object.keys(frontmatter).length === 0) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const name = file.replace('.md', '');
|
|
277
|
+
const tools = normalizeTools ? normalizeTools(frontmatter.tools) : frontmatter.tools || [];
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
name,
|
|
281
|
+
file,
|
|
282
|
+
path: filePath,
|
|
283
|
+
displayName: frontmatter.name || name,
|
|
284
|
+
description: frontmatter.description || '',
|
|
285
|
+
tools,
|
|
286
|
+
model: frontmatter.model || 'haiku',
|
|
287
|
+
color: frontmatter.color || 'blue',
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Resolve paths factory - creates path resolver with injected dependencies
|
|
296
|
+
* @param {IDependencyContainer} container - Dependency container
|
|
297
|
+
* @param {string} [baseDir] - Base directory for relative paths
|
|
298
|
+
* @returns {Object} Path resolver functions
|
|
299
|
+
*/
|
|
300
|
+
function createPathResolver(container, baseDir) {
|
|
301
|
+
const { path: pathModule, fs: fsModule } = container;
|
|
302
|
+
const resolvedBaseDir = baseDir || process.cwd();
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
/**
|
|
306
|
+
* Resolve path relative to base directory
|
|
307
|
+
* @param {...string} segments - Path segments
|
|
308
|
+
* @returns {string} Resolved path
|
|
309
|
+
*/
|
|
310
|
+
resolve(...segments) {
|
|
311
|
+
return pathModule.resolve(resolvedBaseDir, ...segments);
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Join path segments
|
|
316
|
+
* @param {...string} segments - Path segments
|
|
317
|
+
* @returns {string} Joined path
|
|
318
|
+
*/
|
|
319
|
+
join(...segments) {
|
|
320
|
+
return pathModule.join(...segments);
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Check if path exists
|
|
325
|
+
* @param {string} targetPath - Path to check
|
|
326
|
+
* @returns {boolean}
|
|
327
|
+
*/
|
|
328
|
+
exists(targetPath) {
|
|
329
|
+
return fsModule.existsSync(targetPath);
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get base directory
|
|
334
|
+
* @returns {string}
|
|
335
|
+
*/
|
|
336
|
+
getBaseDir() {
|
|
337
|
+
return resolvedBaseDir;
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
module.exports = {
|
|
343
|
+
// Container factories
|
|
344
|
+
createContainer,
|
|
345
|
+
createTestContainer,
|
|
346
|
+
|
|
347
|
+
// Registry factories
|
|
348
|
+
createPlaceholderRegistry,
|
|
349
|
+
createScannerFactory,
|
|
350
|
+
createPathResolver,
|
|
351
|
+
|
|
352
|
+
// Loggers
|
|
353
|
+
noopLogger,
|
|
354
|
+
consoleLogger,
|
|
355
|
+
|
|
356
|
+
// Utilities
|
|
357
|
+
loadDefaultSanitizer,
|
|
358
|
+
};
|