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.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/lib/cache-provider.js +155 -0
- package/lib/codebase-indexer.js +1 -1
- package/lib/content-sanitizer.js +1 -0
- package/lib/dashboard-protocol.js +25 -0
- package/lib/dashboard-server.js +282 -150
- package/lib/errors.js +18 -0
- package/lib/file-cache.js +1 -1
- package/lib/flag-detection.js +11 -20
- package/lib/git-operations.js +15 -33
- package/lib/merge-operations.js +40 -34
- package/lib/process-executor.js +199 -0
- package/lib/registry-cache.js +13 -47
- package/lib/skill-loader.js +206 -0
- package/lib/smart-json-file.js +2 -4
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +13 -12
- package/scripts/agileflow-statusline.sh +30 -0
- package/scripts/agileflow-welcome.js +181 -212
- package/scripts/archive-completed-stories.sh +3 -0
- package/scripts/auto-self-improve.js +3 -3
- package/scripts/ci-summary.js +294 -0
- package/scripts/claude-smart.sh +85 -0
- package/scripts/claude-tmux.sh +272 -161
- package/scripts/damage-control-multi-agent.js +227 -0
- package/scripts/lib/bus-utils.js +471 -0
- package/scripts/lib/configure-detect.js +87 -10
- package/scripts/lib/configure-features.js +110 -4
- package/scripts/lib/configure-repair.js +5 -6
- package/scripts/lib/configure-utils.js +2 -3
- package/scripts/lib/context-formatter.js +87 -8
- package/scripts/lib/damage-control-utils.js +37 -3
- package/scripts/lib/file-lock.js +392 -0
- package/scripts/lib/ideation-index.js +2 -5
- package/scripts/lib/lifecycle-detector.js +123 -0
- package/scripts/lib/process-cleanup.js +55 -81
- package/scripts/lib/scale-detector.js +357 -0
- package/scripts/lib/signal-detectors.js +779 -0
- package/scripts/lib/story-state-machine.js +1 -1
- package/scripts/lib/sync-ideation-status.js +2 -3
- package/scripts/lib/task-registry.js +7 -1
- package/scripts/lib/team-events.js +357 -0
- package/scripts/messaging-bridge.js +79 -36
- package/scripts/migrate-ideation-index.js +37 -14
- package/scripts/obtain-context.js +37 -19
- package/scripts/precompact-context.sh +3 -0
- package/scripts/ralph-loop.js +3 -4
- package/scripts/smart-detect.js +390 -0
- package/scripts/team-manager.js +174 -30
- package/src/core/commands/audit.md +13 -11
- package/src/core/commands/babysit.md +162 -115
- package/src/core/commands/changelog.md +21 -4
- package/src/core/commands/configure.md +141 -21
- package/src/core/commands/debt.md +12 -2
- package/src/core/commands/feedback.md +7 -6
- package/src/core/commands/ideate/history.md +1 -1
- package/src/core/commands/ideate/new.md +5 -5
- package/src/core/commands/logic/audit.md +2 -2
- package/src/core/commands/pr.md +7 -6
- package/src/core/commands/research/analyze.md +28 -20
- package/src/core/commands/research/ask.md +43 -0
- package/src/core/commands/research/import.md +29 -21
- package/src/core/commands/research/list.md +8 -7
- package/src/core/commands/research/synthesize.md +356 -20
- package/src/core/commands/research/view.md +8 -5
- package/src/core/commands/review.md +24 -6
- package/src/core/commands/skill/create.md +34 -0
- 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
|
+
};
|
package/lib/smart-json-file.js
CHANGED
|
@@ -427,13 +427,11 @@ class SmartJsonFile {
|
|
|
427
427
|
debugLog('modify', { filePath: this.filePath });
|
|
428
428
|
|
|
429
429
|
// Read current data
|
|
430
|
-
|
|
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
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
59
|
-
if (meta.version) return meta.version;
|
|
59
|
+
return JSON.parse(fs.readFileSync(metaPath, 'utf8')).version;
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
}, 'read metadata version');
|
|
62
|
+
if (metaVersion) return metaVersion;
|
|
62
63
|
|
|
63
64
|
// Try .agileflow/package.json
|
|
64
|
-
|
|
65
|
+
const agileflowVersion = tryOptional(() => {
|
|
65
66
|
const pkgPath = path.join(process.cwd(), '.agileflow/package.json');
|
|
66
67
|
if (fs.existsSync(pkgPath)) {
|
|
67
|
-
|
|
68
|
-
if (pkg.version) return pkg.version;
|
|
68
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
|
69
69
|
}
|
|
70
|
-
}
|
|
70
|
+
}, 'read agileflow package version');
|
|
71
|
+
if (agileflowVersion) return agileflowVersion;
|
|
71
72
|
|
|
72
73
|
// Fallback to script's own package.json
|
|
73
|
-
|
|
74
|
+
const pkgVersion = tryOptional(() => {
|
|
74
75
|
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
75
76
|
if (fs.existsSync(pkgPath)) {
|
|
76
|
-
|
|
77
|
-
if (pkg.version) return pkg.version;
|
|
77
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
|
78
78
|
}
|
|
79
|
-
}
|
|
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")"
|