agileflow 2.99.8 → 3.0.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -3
  3. package/lib/cache-provider.js +155 -0
  4. package/lib/codebase-indexer.js +1 -1
  5. package/lib/content-sanitizer.js +1 -0
  6. package/lib/dashboard-protocol.js +25 -0
  7. package/lib/dashboard-server.js +282 -150
  8. package/lib/errors.js +18 -0
  9. package/lib/file-cache.js +1 -1
  10. package/lib/flag-detection.js +11 -20
  11. package/lib/git-operations.js +15 -33
  12. package/lib/merge-operations.js +40 -34
  13. package/lib/process-executor.js +199 -0
  14. package/lib/registry-cache.js +13 -47
  15. package/lib/skill-loader.js +206 -0
  16. package/lib/smart-json-file.js +2 -4
  17. package/package.json +1 -1
  18. package/scripts/agileflow-configure.js +13 -12
  19. package/scripts/agileflow-statusline.sh +30 -0
  20. package/scripts/agileflow-welcome.js +181 -212
  21. package/scripts/archive-completed-stories.sh +3 -0
  22. package/scripts/auto-self-improve.js +3 -3
  23. package/scripts/ci-summary.js +294 -0
  24. package/scripts/claude-smart.sh +85 -0
  25. package/scripts/claude-tmux.sh +272 -161
  26. package/scripts/damage-control-multi-agent.js +227 -0
  27. package/scripts/lib/bus-utils.js +471 -0
  28. package/scripts/lib/configure-detect.js +87 -10
  29. package/scripts/lib/configure-features.js +110 -4
  30. package/scripts/lib/configure-repair.js +5 -6
  31. package/scripts/lib/configure-utils.js +2 -3
  32. package/scripts/lib/context-formatter.js +87 -8
  33. package/scripts/lib/damage-control-utils.js +37 -3
  34. package/scripts/lib/file-lock.js +392 -0
  35. package/scripts/lib/ideation-index.js +2 -5
  36. package/scripts/lib/lifecycle-detector.js +123 -0
  37. package/scripts/lib/process-cleanup.js +55 -81
  38. package/scripts/lib/scale-detector.js +357 -0
  39. package/scripts/lib/signal-detectors.js +779 -0
  40. package/scripts/lib/story-state-machine.js +1 -1
  41. package/scripts/lib/sync-ideation-status.js +2 -3
  42. package/scripts/lib/task-registry.js +7 -1
  43. package/scripts/lib/team-events.js +357 -0
  44. package/scripts/messaging-bridge.js +79 -36
  45. package/scripts/migrate-ideation-index.js +37 -14
  46. package/scripts/obtain-context.js +37 -19
  47. package/scripts/precompact-context.sh +3 -0
  48. package/scripts/ralph-loop.js +3 -4
  49. package/scripts/smart-detect.js +390 -0
  50. package/scripts/team-manager.js +174 -30
  51. package/src/core/commands/audit.md +13 -11
  52. package/src/core/commands/babysit.md +162 -115
  53. package/src/core/commands/changelog.md +21 -4
  54. package/src/core/commands/configure.md +141 -21
  55. package/src/core/commands/debt.md +12 -2
  56. package/src/core/commands/feedback.md +7 -6
  57. package/src/core/commands/ideate/history.md +1 -1
  58. package/src/core/commands/ideate/new.md +5 -5
  59. package/src/core/commands/logic/audit.md +2 -2
  60. package/src/core/commands/pr.md +7 -6
  61. package/src/core/commands/research/analyze.md +28 -20
  62. package/src/core/commands/research/ask.md +43 -0
  63. package/src/core/commands/research/import.md +29 -21
  64. package/src/core/commands/research/list.md +8 -7
  65. package/src/core/commands/research/synthesize.md +356 -20
  66. package/src/core/commands/research/view.md +8 -5
  67. package/src/core/commands/review.md +24 -6
  68. package/src/core/commands/skill/create.md +34 -0
  69. package/tools/cli/lib/docs-setup.js +4 -0
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * skill-loader.js
4
+ *
5
+ * Skill metadata parser and loader for enhanced skills (EP-0033 Story 4).
6
+ *
7
+ * Skills can now include frontmatter metadata:
8
+ * ---
9
+ * type: skill
10
+ * name: my-skill
11
+ * model: haiku
12
+ * category: database
13
+ * version: 1.0.0
14
+ * ---
15
+ *
16
+ * This module:
17
+ * - Parses skill frontmatter from SKILL.md files
18
+ * - Discovers all installed skills with metadata
19
+ * - Filters skills by category or model
20
+ * - Provides skill recommendations based on context
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ /**
27
+ * Parse frontmatter from a SKILL.md file.
28
+ * Supports YAML-like frontmatter between --- delimiters.
29
+ *
30
+ * @param {string} content - File content
31
+ * @returns {Object} { metadata: {...}, body: "..." }
32
+ */
33
+ function parseSkillFrontmatter(content) {
34
+ const result = { metadata: {}, body: content };
35
+
36
+ if (!content || !content.startsWith('---')) {
37
+ return result;
38
+ }
39
+
40
+ const endIndex = content.indexOf('---', 3);
41
+ if (endIndex === -1) {
42
+ return result;
43
+ }
44
+
45
+ const frontmatterBlock = content.substring(3, endIndex).trim();
46
+ result.body = content.substring(endIndex + 3).trim();
47
+
48
+ // Simple YAML-like parsing (no dependency on js-yaml)
49
+ for (const line of frontmatterBlock.split('\n')) {
50
+ const trimmed = line.trim();
51
+ if (!trimmed || trimmed.startsWith('#')) continue;
52
+
53
+ const colonIndex = trimmed.indexOf(':');
54
+ if (colonIndex === -1) continue;
55
+
56
+ const key = trimmed.substring(0, colonIndex).trim();
57
+ let value = trimmed.substring(colonIndex + 1).trim();
58
+
59
+ // Remove surrounding quotes
60
+ if ((value.startsWith('"') && value.endsWith('"')) ||
61
+ (value.startsWith("'") && value.endsWith("'"))) {
62
+ value = value.slice(1, -1);
63
+ }
64
+
65
+ // Type coercion for known fields
66
+ if (key === 'version' || key === 'model' || key === 'category' || key === 'name' || key === 'type') {
67
+ result.metadata[key] = value;
68
+ } else if (value === 'true') {
69
+ result.metadata[key] = true;
70
+ } else if (value === 'false') {
71
+ result.metadata[key] = false;
72
+ } else {
73
+ result.metadata[key] = value;
74
+ }
75
+ }
76
+
77
+ return result;
78
+ }
79
+
80
+ /**
81
+ * Load a single skill from its directory.
82
+ *
83
+ * @param {string} skillDir - Path to skill directory
84
+ * @returns {Object|null} Skill info or null if invalid
85
+ */
86
+ function loadSkill(skillDir) {
87
+ const skillMdPath = path.join(skillDir, 'SKILL.md');
88
+
89
+ if (!fs.existsSync(skillMdPath)) {
90
+ return null;
91
+ }
92
+
93
+ try {
94
+ const content = fs.readFileSync(skillMdPath, 'utf8');
95
+ const { metadata, body } = parseSkillFrontmatter(content);
96
+
97
+ // Extract description from frontmatter or first paragraph
98
+ let description = metadata.description || '';
99
+ if (!description && body) {
100
+ // Try to get description from first non-empty line after title
101
+ const lines = body.split('\n');
102
+ for (const line of lines) {
103
+ const trimmed = line.trim();
104
+ if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('---')) {
105
+ description = trimmed;
106
+ break;
107
+ }
108
+ }
109
+ }
110
+
111
+ return {
112
+ name: metadata.name || path.basename(skillDir),
113
+ type: metadata.type || 'skill',
114
+ model: metadata.model || null,
115
+ category: metadata.category || null,
116
+ version: metadata.version || null,
117
+ description,
118
+ path: skillDir,
119
+ hasReferences: fs.existsSync(path.join(skillDir, 'references.md')),
120
+ hasCookbook: fs.existsSync(path.join(skillDir, 'cookbook')),
121
+ hasMcp: fs.existsSync(path.join(skillDir, '.mcp.json')),
122
+ metadata,
123
+ };
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Discover all installed skills.
131
+ *
132
+ * @param {string} rootDir - Project root directory
133
+ * @returns {Object[]} Array of skill info objects
134
+ */
135
+ function discoverSkills(rootDir) {
136
+ const skills = [];
137
+ const skillsDirs = [
138
+ path.join(rootDir, '.claude', 'skills'),
139
+ path.join(rootDir, '.agileflow', 'skills'),
140
+ ];
141
+
142
+ for (const skillsDir of skillsDirs) {
143
+ if (!fs.existsSync(skillsDir)) continue;
144
+
145
+ try {
146
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
147
+ for (const entry of entries) {
148
+ if (!entry.isDirectory()) continue;
149
+
150
+ const skill = loadSkill(path.join(skillsDir, entry.name));
151
+ if (skill) {
152
+ skills.push(skill);
153
+ }
154
+ }
155
+ } catch {
156
+ // Silently continue
157
+ }
158
+ }
159
+
160
+ return skills;
161
+ }
162
+
163
+ /**
164
+ * Filter skills by category.
165
+ *
166
+ * @param {Object[]} skills - Array of skill info objects
167
+ * @param {string} category - Category to filter by
168
+ * @returns {Object[]} Filtered skills
169
+ */
170
+ function filterByCategory(skills, category) {
171
+ return skills.filter(s => s.category === category);
172
+ }
173
+
174
+ /**
175
+ * Filter skills by model preference.
176
+ *
177
+ * @param {Object[]} skills - Array of skill info objects
178
+ * @param {string} model - Model to filter by (haiku, sonnet, opus)
179
+ * @returns {Object[]} Filtered skills
180
+ */
181
+ function filterByModel(skills, model) {
182
+ return skills.filter(s => s.model === model);
183
+ }
184
+
185
+ /**
186
+ * Get a formatted skill summary for display.
187
+ *
188
+ * @param {Object} skill - Skill info object
189
+ * @returns {string} Formatted summary line
190
+ */
191
+ function formatSkillSummary(skill) {
192
+ const parts = [skill.name];
193
+ if (skill.category) parts.push(`[${skill.category}]`);
194
+ if (skill.model) parts.push(`(${skill.model})`);
195
+ if (skill.version) parts.push(`v${skill.version}`);
196
+ return parts.join(' ');
197
+ }
198
+
199
+ module.exports = {
200
+ parseSkillFrontmatter,
201
+ loadSkill,
202
+ discoverSkills,
203
+ filterByCategory,
204
+ filterByModel,
205
+ formatSkillSummary,
206
+ };
@@ -427,13 +427,11 @@ class SmartJsonFile {
427
427
  debugLog('modify', { filePath: this.filePath });
428
428
 
429
429
  // Read current data
430
- const readResult = await this.read();
430
+ let readResult = await this.read();
431
431
  if (!readResult.ok) {
432
432
  // If file doesn't exist but we have a default value, use that
433
433
  if (readResult.error?.errorCode === 'ENOENT' && this.defaultValue !== undefined) {
434
- readResult.ok = true;
435
- readResult.data = this.defaultValue;
436
- delete readResult.error;
434
+ readResult = success(this.defaultValue);
437
435
  } else {
438
436
  return readResult;
439
437
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.99.8",
3
+ "version": "3.0.1",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -45,6 +45,7 @@ const {
45
45
  } = require('./lib/configure-features');
46
46
  const { listScripts, showVersionInfo, repairScripts } = require('./lib/configure-repair');
47
47
  const { feedback } = require('../lib/feedback');
48
+ const { tryOptional } = require('../lib/errors');
48
49
 
49
50
  // ============================================================================
50
51
  // VERSION
@@ -52,31 +53,31 @@ const { feedback } = require('../lib/feedback');
52
53
 
53
54
  function getVersion() {
54
55
  // Try agileflow-metadata.json first
55
- try {
56
+ const metaVersion = tryOptional(() => {
56
57
  const metaPath = path.join(process.cwd(), 'docs/00-meta/agileflow-metadata.json');
57
58
  if (fs.existsSync(metaPath)) {
58
- const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
59
- if (meta.version) return meta.version;
59
+ return JSON.parse(fs.readFileSync(metaPath, 'utf8')).version;
60
60
  }
61
- } catch {}
61
+ }, 'read metadata version');
62
+ if (metaVersion) return metaVersion;
62
63
 
63
64
  // Try .agileflow/package.json
64
- try {
65
+ const agileflowVersion = tryOptional(() => {
65
66
  const pkgPath = path.join(process.cwd(), '.agileflow/package.json');
66
67
  if (fs.existsSync(pkgPath)) {
67
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
68
- if (pkg.version) return pkg.version;
68
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
69
69
  }
70
- } catch {}
70
+ }, 'read agileflow package version');
71
+ if (agileflowVersion) return agileflowVersion;
71
72
 
72
73
  // Fallback to script's own package.json
73
- try {
74
+ const pkgVersion = tryOptional(() => {
74
75
  const pkgPath = path.join(__dirname, '..', 'package.json');
75
76
  if (fs.existsSync(pkgPath)) {
76
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
77
- if (pkg.version) return pkg.version;
77
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
78
78
  }
79
- } catch {}
79
+ }, 'read package version');
80
+ if (pkgVersion) return pkgVersion;
80
81
 
81
82
  return 'unknown';
82
83
  }
@@ -87,6 +87,7 @@ SHOW_CONTEXT_BAR=true
87
87
  SHOW_SESSION_TIME=false
88
88
  SHOW_COST=false
89
89
  SHOW_GIT=true
90
+ SHOW_SCALE=true
90
91
 
91
92
  # Check agileflow-metadata.json for component settings
92
93
  if [ -f "docs/00-meta/agileflow-metadata.json" ]; then
@@ -104,6 +105,7 @@ if [ -f "docs/00-meta/agileflow-metadata.json" ]; then
104
105
  SHOW_SESSION_TIME=$(echo "$COMPONENTS" | jq -r '.session_time | if . == null then true else . end')
105
106
  SHOW_COST=$(echo "$COMPONENTS" | jq -r '.cost | if . == null then true else . end')
106
107
  SHOW_GIT=$(echo "$COMPONENTS" | jq -r '.git | if . == null then true else . end')
108
+ SHOW_SCALE=$(echo "$COMPONENTS" | jq -r '.scale | if . == null then true else . end')
107
109
  fi
108
110
  fi
109
111
 
@@ -645,6 +647,28 @@ if [ "$SHOW_SESSION" = "true" ]; then
645
647
  fi
646
648
  fi
647
649
 
650
+ # ============================================================================
651
+ # Scale Detection (EP-0033: Scale-Adaptive Workflows)
652
+ # ============================================================================
653
+ # Reads cached scale from session-state.json (written by welcome hook)
654
+ SCALE_DISPLAY=""
655
+ if [ "$SHOW_SCALE" = "true" ] && [ -f "docs/09-agents/session-state.json" ]; then
656
+ DETECTED_SCALE=$(jq -r '.scale_detection.scale // empty' docs/09-agents/session-state.json 2>/dev/null)
657
+ if [ -n "$DETECTED_SCALE" ] && [ "$DETECTED_SCALE" != "null" ]; then
658
+ case "$DETECTED_SCALE" in
659
+ micro) SCALE_COLOR="$CYAN"; SCALE_ICON="◦" ;;
660
+ small) SCALE_COLOR="$CYAN"; SCALE_ICON="○" ;;
661
+ medium) SCALE_COLOR="$GREEN"; SCALE_ICON="◎" ;;
662
+ large) SCALE_COLOR="$YELLOW"; SCALE_ICON="●" ;;
663
+ enterprise) SCALE_COLOR="$RED"; SCALE_ICON="◉" ;;
664
+ *) SCALE_COLOR="$DIM"; SCALE_ICON="◎" ;;
665
+ esac
666
+ # Capitalize first letter
667
+ SCALE_LABEL="$(echo "$DETECTED_SCALE" | sed 's/^./\U&/')"
668
+ SCALE_DISPLAY="${SCALE_COLOR}${SCALE_ICON}${SCALE_LABEL}${RESET}"
669
+ fi
670
+ fi
671
+
648
672
  # ============================================================================
649
673
  # Build Status Line
650
674
  # ============================================================================
@@ -729,6 +753,12 @@ if [ "$SHOW_GIT" = "true" ] && [ -n "$GIT_DISPLAY" ]; then
729
753
  OUTPUT="${OUTPUT}${GIT_DISPLAY}"
730
754
  fi
731
755
 
756
+ # Add scale indicator (if enabled)
757
+ if [ "$SHOW_SCALE" = "true" ] && [ -n "$SCALE_DISPLAY" ]; then
758
+ [ -n "$OUTPUT" ] && OUTPUT="${OUTPUT}${SEP}"
759
+ OUTPUT="${OUTPUT}${SCALE_DISPLAY}"
760
+ fi
761
+
732
762
  # Session health indicator (next to git branch)
733
763
  if [ "$SHOW_SESSION" = "true" ]; then
734
764
  SCRIPTS_DIR="$(dirname "$0")"