agileflow 2.91.0 → 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.
Files changed (99) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +3 -3
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +31 -23
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate.js +116 -52
  22. package/package.json +1 -1
  23. package/scripts/af +34 -0
  24. package/scripts/agent-loop.js +63 -9
  25. package/scripts/agileflow-configure.js +2 -2
  26. package/scripts/agileflow-welcome.js +435 -23
  27. package/scripts/archive-completed-stories.sh +57 -11
  28. package/scripts/claude-tmux.sh +102 -0
  29. package/scripts/damage-control-bash.js +3 -70
  30. package/scripts/damage-control-edit.js +3 -20
  31. package/scripts/damage-control-write.js +3 -20
  32. package/scripts/dependency-check.js +310 -0
  33. package/scripts/get-env.js +11 -4
  34. package/scripts/lib/configure-detect.js +23 -1
  35. package/scripts/lib/configure-features.js +43 -2
  36. package/scripts/lib/context-formatter.js +771 -0
  37. package/scripts/lib/context-loader.js +699 -0
  38. package/scripts/lib/damage-control-utils.js +107 -0
  39. package/scripts/lib/json-utils.sh +162 -0
  40. package/scripts/lib/state-migrator.js +353 -0
  41. package/scripts/lib/story-state-machine.js +437 -0
  42. package/scripts/obtain-context.js +80 -1248
  43. package/scripts/pre-push-check.sh +46 -0
  44. package/scripts/precompact-context.sh +23 -10
  45. package/scripts/query-codebase.js +122 -14
  46. package/scripts/ralph-loop.js +5 -5
  47. package/scripts/session-manager.js +220 -42
  48. package/scripts/spawn-parallel.js +651 -0
  49. package/scripts/tui/blessed/data/watcher.js +20 -15
  50. package/scripts/tui/blessed/index.js +2 -2
  51. package/scripts/tui/blessed/panels/output.js +14 -8
  52. package/scripts/tui/blessed/panels/sessions.js +22 -15
  53. package/scripts/tui/blessed/panels/trace.js +14 -8
  54. package/scripts/tui/blessed/ui/help.js +3 -3
  55. package/scripts/tui/blessed/ui/screen.js +4 -4
  56. package/scripts/tui/blessed/ui/statusbar.js +5 -9
  57. package/scripts/tui/blessed/ui/tabbar.js +11 -11
  58. package/scripts/validators/component-validator.js +41 -14
  59. package/scripts/validators/json-schema-validator.js +11 -4
  60. package/scripts/validators/markdown-validator.js +1 -2
  61. package/scripts/validators/migration-validator.js +17 -5
  62. package/scripts/validators/security-validator.js +137 -33
  63. package/scripts/validators/story-format-validator.js +31 -10
  64. package/scripts/validators/test-result-validator.js +19 -4
  65. package/scripts/validators/workflow-validator.js +12 -5
  66. package/src/core/agents/codebase-query.md +24 -0
  67. package/src/core/commands/adr.md +114 -0
  68. package/src/core/commands/agent.md +120 -0
  69. package/src/core/commands/assign.md +145 -0
  70. package/src/core/commands/babysit.md +32 -5
  71. package/src/core/commands/changelog.md +118 -0
  72. package/src/core/commands/configure.md +42 -6
  73. package/src/core/commands/diagnose.md +114 -0
  74. package/src/core/commands/epic.md +113 -0
  75. package/src/core/commands/handoff.md +128 -0
  76. package/src/core/commands/help.md +75 -0
  77. package/src/core/commands/pr.md +96 -0
  78. package/src/core/commands/roadmap/analyze.md +400 -0
  79. package/src/core/commands/session/new.md +113 -6
  80. package/src/core/commands/session/spawn.md +197 -0
  81. package/src/core/commands/sprint.md +22 -0
  82. package/src/core/commands/status.md +74 -0
  83. package/src/core/commands/story.md +143 -4
  84. package/src/core/templates/agileflow-metadata.json +55 -2
  85. package/src/core/templates/plan-template.md +125 -0
  86. package/src/core/templates/story-lifecycle.md +213 -0
  87. package/src/core/templates/story-template.md +4 -0
  88. package/src/core/templates/tdd-test-template.js +241 -0
  89. package/tools/cli/commands/setup.js +86 -0
  90. package/tools/cli/installers/core/installer.js +94 -0
  91. package/tools/cli/installers/ide/_base-ide.js +20 -11
  92. package/tools/cli/installers/ide/codex.js +29 -47
  93. package/tools/cli/lib/config-manager.js +17 -2
  94. package/tools/cli/lib/content-transformer.js +271 -0
  95. package/tools/cli/lib/error-handler.js +14 -22
  96. package/tools/cli/lib/ide-error-factory.js +421 -0
  97. package/tools/cli/lib/ide-health-monitor.js +364 -0
  98. package/tools/cli/lib/ide-registry.js +114 -1
  99. package/tools/cli/lib/ui.js +14 -25
package/lib/paths.js CHANGED
@@ -2,11 +2,43 @@
2
2
  * AgileFlow CLI - Shared Path Utilities
3
3
  *
4
4
  * Centralized path resolution functions used across scripts.
5
+ *
6
+ * NOTE: This module delegates to PathResolver for the implementation.
7
+ * Functions accept optional rootDir for backwards compatibility, but
8
+ * if not provided, use PathResolver's manifest-aware auto-detection.
9
+ *
10
+ * For new code, prefer using PathResolver directly:
11
+ * const { PathResolver, getDefaultResolver } = require('./path-resolver');
12
+ * const resolver = getDefaultResolver();
13
+ * const statusPath = resolver.getStatusPath();
5
14
  */
6
15
 
7
- const fs = require('fs');
16
+ const { PathResolver, getDefaultResolver } = require('./path-resolver');
8
17
  const path = require('path');
9
18
 
19
+ // Cache for resolvers by rootDir
20
+ const resolverCache = new Map();
21
+
22
+ /**
23
+ * Get a PathResolver for the given rootDir.
24
+ * Uses singleton for default (no rootDir) case, caches others.
25
+ *
26
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
27
+ * @returns {PathResolver}
28
+ * @private
29
+ */
30
+ function getResolver(rootDir) {
31
+ if (!rootDir) {
32
+ return getDefaultResolver();
33
+ }
34
+
35
+ // Cache resolvers by rootDir to avoid repeated construction
36
+ if (!resolverCache.has(rootDir)) {
37
+ resolverCache.set(rootDir, new PathResolver(rootDir, { autoDetect: false }));
38
+ }
39
+ return resolverCache.get(rootDir);
40
+ }
41
+
10
42
  /**
11
43
  * Find the project root by looking for .agileflow directory.
12
44
  * Walks up from current directory until .agileflow is found.
@@ -15,11 +47,8 @@ const path = require('path');
15
47
  * @returns {string} Project root path, or startDir if not found
16
48
  */
17
49
  function getProjectRoot(startDir = process.cwd()) {
18
- let dir = startDir;
19
- while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
20
- dir = path.dirname(dir);
21
- }
22
- return dir !== '/' ? dir : startDir;
50
+ const resolver = new PathResolver(startDir, { autoDetect: true });
51
+ return resolver.getProjectRoot();
23
52
  }
24
53
 
25
54
  /**
@@ -29,8 +58,7 @@ function getProjectRoot(startDir = process.cwd()) {
29
58
  * @returns {string} Path to .agileflow directory
30
59
  */
31
60
  function getAgileflowDir(rootDir) {
32
- const root = rootDir || getProjectRoot();
33
- return path.join(root, '.agileflow');
61
+ return getResolver(rootDir).getAgileflowDir();
34
62
  }
35
63
 
36
64
  /**
@@ -40,8 +68,7 @@ function getAgileflowDir(rootDir) {
40
68
  * @returns {string} Path to .claude directory
41
69
  */
42
70
  function getClaudeDir(rootDir) {
43
- const root = rootDir || getProjectRoot();
44
- return path.join(root, '.claude');
71
+ return getResolver(rootDir).getClaudeDir();
45
72
  }
46
73
 
47
74
  /**
@@ -51,8 +78,7 @@ function getClaudeDir(rootDir) {
51
78
  * @returns {string} Path to docs directory
52
79
  */
53
80
  function getDocsDir(rootDir) {
54
- const root = rootDir || getProjectRoot();
55
- return path.join(root, 'docs');
81
+ return getResolver(rootDir).getDocsDir();
56
82
  }
57
83
 
58
84
  /**
@@ -62,8 +88,7 @@ function getDocsDir(rootDir) {
62
88
  * @returns {string} Path to status.json
63
89
  */
64
90
  function getStatusPath(rootDir) {
65
- const root = rootDir || getProjectRoot();
66
- return path.join(root, 'docs', '09-agents', 'status.json');
91
+ return getResolver(rootDir).getStatusPath();
67
92
  }
68
93
 
69
94
  /**
@@ -73,27 +98,194 @@ function getStatusPath(rootDir) {
73
98
  * @returns {string} Path to session-state.json
74
99
  */
75
100
  function getSessionStatePath(rootDir) {
76
- const root = rootDir || getProjectRoot();
77
- return path.join(root, 'docs', '09-agents', 'session-state.json');
101
+ return getResolver(rootDir).getSessionStatePath();
102
+ }
103
+
104
+ /**
105
+ * Get the agileflow-metadata.json path.
106
+ *
107
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
108
+ * @returns {string} Path to agileflow-metadata.json
109
+ */
110
+ function getMetadataPath(rootDir) {
111
+ return getResolver(rootDir).getMetadataPath();
112
+ }
113
+
114
+ /**
115
+ * Get the message bus log path.
116
+ *
117
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
118
+ * @returns {string} Path to bus/log.jsonl
119
+ */
120
+ function getBusLogPath(rootDir) {
121
+ return getResolver(rootDir).getBusLogPath();
122
+ }
123
+
124
+ /**
125
+ * Get the epics directory path.
126
+ *
127
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
128
+ * @returns {string} Path to epics directory
129
+ */
130
+ function getEpicsDir(rootDir) {
131
+ return getResolver(rootDir).getEpicsDir();
132
+ }
133
+
134
+ /**
135
+ * Get the stories directory path.
136
+ *
137
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
138
+ * @returns {string} Path to stories directory
139
+ */
140
+ function getStoriesDir(rootDir) {
141
+ return getResolver(rootDir).getStoriesDir();
142
+ }
143
+
144
+ /**
145
+ * Get the archive directory path for completed stories.
146
+ *
147
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
148
+ * @returns {string} Path to archive directory
149
+ */
150
+ function getArchiveDir(rootDir) {
151
+ return getResolver(rootDir).getArchiveDir();
152
+ }
153
+
154
+ /**
155
+ * Get the agents directory path (docs/09-agents).
156
+ *
157
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
158
+ * @returns {string} Path to agents directory (docs/09-agents)
159
+ */
160
+ function getAgentsDir(rootDir) {
161
+ return getResolver(rootDir).getDocsAgentsDir();
162
+ }
163
+
164
+ /**
165
+ * Get the decisions (ADR) directory path.
166
+ *
167
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
168
+ * @returns {string} Path to decisions directory
169
+ */
170
+ function getDecisionsDir(rootDir) {
171
+ return getResolver(rootDir).getDecisionsDir();
172
+ }
173
+
174
+ /**
175
+ * Get the research directory path.
176
+ *
177
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
178
+ * @returns {string} Path to research directory
179
+ */
180
+ function getResearchDir(rootDir) {
181
+ return getResolver(rootDir).getResearchDir();
78
182
  }
79
183
 
80
184
  /**
81
185
  * Check if we're in an AgileFlow project.
186
+ * Walks up from dir to find .agileflow directory.
82
187
  *
83
- * @param {string} [dir=process.cwd()] - Directory to check
84
- * @returns {boolean} True if .agileflow directory exists
188
+ * @param {string} [dir=process.cwd()] - Directory to check from
189
+ * @returns {boolean} True if .agileflow directory exists in dir or any parent
85
190
  */
86
191
  function isAgileflowProject(dir = process.cwd()) {
87
- const root = getProjectRoot(dir);
88
- return fs.existsSync(path.join(root, '.agileflow'));
192
+ // Use auto-detection to walk up and find project root
193
+ const resolver = new PathResolver(dir, { autoDetect: true });
194
+ return resolver.isAgileflowProject();
195
+ }
196
+
197
+ // ============================================================================
198
+ // New functions (added in ConfigResolver consolidation)
199
+ // ============================================================================
200
+
201
+ /**
202
+ * Get the .claude/settings.json path.
203
+ *
204
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
205
+ * @returns {string} Path to settings.json
206
+ */
207
+ function getSettingsPath(rootDir) {
208
+ return getResolver(rootDir).getSettingsPath();
209
+ }
210
+
211
+ /**
212
+ * Get the .claude/settings.local.json path.
213
+ *
214
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
215
+ * @returns {string} Path to settings.local.json
216
+ */
217
+ function getSettingsLocalPath(rootDir) {
218
+ return getResolver(rootDir).getSettingsLocalPath();
219
+ }
220
+
221
+ /**
222
+ * Get the skills directory path.
223
+ *
224
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
225
+ * @returns {string} Path to skills directory
226
+ */
227
+ function getSkillsDir(rootDir) {
228
+ return getResolver(rootDir).getSkillsDir();
229
+ }
230
+
231
+ /**
232
+ * Get the scripts directory path.
233
+ *
234
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
235
+ * @returns {string} Path to scripts directory
236
+ */
237
+ function getScriptsDir(rootDir) {
238
+ return getResolver(rootDir).getScriptsDir();
239
+ }
240
+
241
+ /**
242
+ * Get the commands directory path.
243
+ *
244
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
245
+ * @returns {string} Path to commands directory
246
+ */
247
+ function getCommandsDir(rootDir) {
248
+ return getResolver(rootDir).getCommandsDir();
249
+ }
250
+
251
+ /**
252
+ * Clear the resolver cache (useful for testing or after config changes).
253
+ */
254
+ function clearResolverCache() {
255
+ resolverCache.clear();
89
256
  }
90
257
 
91
258
  module.exports = {
259
+ // Root and base directories
92
260
  getProjectRoot,
93
261
  getAgileflowDir,
94
262
  getClaudeDir,
95
263
  getDocsDir,
264
+
265
+ // Status and session files
96
266
  getStatusPath,
97
267
  getSessionStatePath,
268
+ getMetadataPath,
269
+ getBusLogPath,
270
+
271
+ // Documentation directories
272
+ getEpicsDir,
273
+ getStoriesDir,
274
+ getArchiveDir,
275
+ getAgentsDir,
276
+ getDecisionsDir,
277
+ getResearchDir,
278
+
279
+ // Configuration files (new in ConfigResolver consolidation)
280
+ getSettingsPath,
281
+ getSettingsLocalPath,
282
+
283
+ // AgileFlow subdirectories (new in ConfigResolver consolidation)
284
+ getSkillsDir,
285
+ getScriptsDir,
286
+ getCommandsDir,
287
+
288
+ // Utilities
98
289
  isAgileflowProject,
290
+ clearResolverCache,
99
291
  };
@@ -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,