agileflow 2.51.0 → 2.56.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/README.md +80 -460
- package/package.json +18 -3
- package/scripts/agileflow-configure.js +134 -63
- package/scripts/agileflow-welcome.js +161 -31
- package/scripts/generators/agent-registry.js +45 -57
- package/scripts/generators/command-registry.js +48 -32
- package/scripts/generators/index.js +2 -6
- package/scripts/generators/inject-babysit.js +9 -2
- package/scripts/generators/inject-help.js +3 -1
- package/scripts/generators/inject-readme.js +7 -3
- package/scripts/generators/skill-registry.js +60 -33
- package/scripts/get-env.js +13 -12
- package/scripts/lib/frontmatter-parser.js +82 -0
- package/scripts/obtain-context.js +79 -26
- package/scripts/session-coordinator.sh +232 -0
- package/scripts/session-manager.js +512 -0
- package/src/core/agents/orchestrator.md +275 -0
- package/src/core/commands/adr.md +38 -16
- package/src/core/commands/agent.md +39 -22
- package/src/core/commands/assign.md +17 -0
- package/src/core/commands/auto.md +60 -46
- package/src/core/commands/babysit.md +302 -637
- package/src/core/commands/baseline.md +20 -0
- package/src/core/commands/blockers.md +33 -48
- package/src/core/commands/board.md +19 -0
- package/src/core/commands/changelog.md +20 -0
- package/src/core/commands/ci.md +17 -0
- package/src/core/commands/context.md +43 -40
- package/src/core/commands/debt.md +76 -45
- package/src/core/commands/deploy.md +20 -0
- package/src/core/commands/deps.md +40 -46
- package/src/core/commands/diagnose.md +24 -18
- package/src/core/commands/docs.md +18 -0
- package/src/core/commands/epic.md +31 -0
- package/src/core/commands/feedback.md +33 -21
- package/src/core/commands/handoff.md +29 -0
- package/src/core/commands/help.md +16 -7
- package/src/core/commands/impact.md +31 -61
- package/src/core/commands/metrics.md +17 -35
- package/src/core/commands/packages.md +21 -0
- package/src/core/commands/pr.md +15 -0
- package/src/core/commands/readme-sync.md +42 -9
- package/src/core/commands/research.md +58 -11
- package/src/core/commands/retro.md +42 -50
- package/src/core/commands/review.md +22 -27
- package/src/core/commands/session/end.md +53 -297
- package/src/core/commands/session/history.md +38 -257
- package/src/core/commands/session/init.md +44 -446
- package/src/core/commands/session/new.md +152 -0
- package/src/core/commands/session/resume.md +51 -447
- package/src/core/commands/session/status.md +32 -244
- package/src/core/commands/sprint.md +33 -0
- package/src/core/commands/status.md +18 -0
- package/src/core/commands/story-validate.md +32 -0
- package/src/core/commands/story.md +21 -6
- package/src/core/commands/template.md +18 -0
- package/src/core/commands/tests.md +22 -0
- package/src/core/commands/update.md +72 -58
- package/src/core/commands/validate-expertise.md +25 -37
- package/src/core/commands/velocity.md +33 -74
- package/src/core/commands/verify.md +16 -0
- package/src/core/experts/documentation/expertise.yaml +16 -2
- package/src/core/skills/agileflow-retro-facilitator/SKILL.md +57 -219
- package/src/core/skills/agileflow-retro-facilitator/cookbook/4ls.md +86 -0
- package/src/core/skills/agileflow-retro-facilitator/cookbook/glad-sad-mad.md +79 -0
- package/src/core/skills/agileflow-retro-facilitator/cookbook/start-stop-continue.md +142 -0
- package/src/core/skills/agileflow-retro-facilitator/prompts/action-items.md +83 -0
- package/src/core/skills/writing-skills/SKILL.md +352 -0
- package/src/core/skills/writing-skills/testing-skills-with-subagents.md +232 -0
- package/tools/cli/agileflow-cli.js +4 -2
- package/tools/cli/commands/config.js +20 -13
- package/tools/cli/commands/doctor.js +25 -9
- package/tools/cli/commands/list.js +10 -6
- package/tools/cli/commands/setup.js +54 -3
- package/tools/cli/commands/status.js +6 -8
- package/tools/cli/commands/uninstall.js +5 -5
- package/tools/cli/commands/update.js +51 -7
- package/tools/cli/installers/core/installer.js +8 -4
- package/tools/cli/installers/ide/_base-ide.js +58 -1
- package/tools/cli/installers/ide/claude-code.js +3 -61
- package/tools/cli/installers/ide/codex.js +440 -0
- package/tools/cli/installers/ide/cursor.js +21 -51
- package/tools/cli/installers/ide/manager.js +2 -6
- package/tools/cli/installers/ide/windsurf.js +20 -50
- package/tools/cli/lib/content-injector.js +26 -49
- package/tools/cli/lib/docs-setup.js +3 -2
- package/tools/cli/lib/npm-utils.js +39 -12
- package/tools/cli/lib/ui.js +31 -10
- package/tools/cli/lib/version-checker.js +3 -3
- package/tools/postinstall.js +2 -3
|
@@ -91,7 +91,9 @@ function injectContentByMarker(content, markerName, generated) {
|
|
|
91
91
|
const timestamp = new Date().toISOString().split('T')[0];
|
|
92
92
|
const injectedContent = `${startMarker}\n<!-- Auto-generated on ${timestamp}. Do not edit manually. -->\n\n${generated}\n${endMarker}`;
|
|
93
93
|
|
|
94
|
-
return
|
|
94
|
+
return (
|
|
95
|
+
content.substring(0, startIdx) + injectedContent + content.substring(endIdx + endMarker.length)
|
|
96
|
+
);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
/**
|
|
@@ -117,7 +119,9 @@ function main() {
|
|
|
117
119
|
const agents = scanAgents(agentsDir);
|
|
118
120
|
const skills = scanSkills(skillsDir);
|
|
119
121
|
|
|
120
|
-
console.log(
|
|
122
|
+
console.log(
|
|
123
|
+
`Found: ${commands.length} commands, ${agents.length} agents, ${skills.length} skills`
|
|
124
|
+
);
|
|
121
125
|
|
|
122
126
|
// Read README
|
|
123
127
|
let readmeContent = fs.readFileSync(readmeFile, 'utf-8');
|
|
@@ -127,7 +131,7 @@ function main() {
|
|
|
127
131
|
const stats = generateStats({
|
|
128
132
|
commands: commands.length,
|
|
129
133
|
agents: agents.length,
|
|
130
|
-
skills: skills.length
|
|
134
|
+
skills: skills.length,
|
|
131
135
|
});
|
|
132
136
|
|
|
133
137
|
console.log('Generating agent table...');
|
|
@@ -5,37 +5,25 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Scans skills/ directory and extracts metadata from SKILL.md frontmatter.
|
|
7
7
|
* Returns structured skill registry for use in generators.
|
|
8
|
+
*
|
|
9
|
+
* Set DEBUG_REGISTRY=1 for verbose logging of skipped files.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
const fs = require('fs');
|
|
11
13
|
const path = require('path');
|
|
14
|
+
const { extractFrontmatter } = require('../lib/frontmatter-parser');
|
|
15
|
+
|
|
16
|
+
// Debug mode: set DEBUG_REGISTRY=1 to see why files are skipped
|
|
17
|
+
const DEBUG = process.env.DEBUG_REGISTRY === '1';
|
|
12
18
|
|
|
13
19
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {string}
|
|
16
|
-
* @returns {object} Frontmatter object
|
|
20
|
+
* Log debug messages when DEBUG_REGISTRY=1
|
|
21
|
+
* @param {string} message - Message to log
|
|
17
22
|
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!frontmatterMatch) {
|
|
23
|
-
return {};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const frontmatter = {};
|
|
27
|
-
const lines = frontmatterMatch[1].split('\n');
|
|
28
|
-
|
|
29
|
-
for (const line of lines) {
|
|
30
|
-
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
31
|
-
if (match) {
|
|
32
|
-
const [, key, value] = match;
|
|
33
|
-
// Remove quotes if present
|
|
34
|
-
frontmatter[key] = value.replace(/^["']|["']$/g, '');
|
|
35
|
-
}
|
|
23
|
+
function debugLog(message) {
|
|
24
|
+
if (DEBUG) {
|
|
25
|
+
console.error(`[skill-registry] ${message}`);
|
|
36
26
|
}
|
|
37
|
-
|
|
38
|
-
return frontmatter;
|
|
39
27
|
}
|
|
40
28
|
|
|
41
29
|
/**
|
|
@@ -48,10 +36,10 @@ function categorizeSkill(name, description) {
|
|
|
48
36
|
const categories = {
|
|
49
37
|
'Story & Planning': ['story', 'epic', 'sprint', 'acceptance-criteria'],
|
|
50
38
|
'Code Generation': ['type-definitions', 'validation-schema', 'error-handler'],
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
Testing: ['test-case'],
|
|
40
|
+
Documentation: ['adr', 'api-documentation', 'changelog', 'pr-description'],
|
|
41
|
+
Architecture: ['sql-schema', 'diagram'],
|
|
42
|
+
Deployment: ['deployment-guide', 'migration-checklist'],
|
|
55
43
|
};
|
|
56
44
|
|
|
57
45
|
const lowerName = name.toLowerCase();
|
|
@@ -73,32 +61,65 @@ function categorizeSkill(name, description) {
|
|
|
73
61
|
*/
|
|
74
62
|
function scanSkills(skillsDir) {
|
|
75
63
|
const skills = [];
|
|
64
|
+
const skipped = [];
|
|
65
|
+
|
|
66
|
+
let skillDirs;
|
|
67
|
+
try {
|
|
68
|
+
skillDirs = fs.readdirSync(skillsDir);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
debugLog(`Failed to read directory: ${err.message}`);
|
|
71
|
+
return skills;
|
|
72
|
+
}
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
const skillDirs = fs.readdirSync(skillsDir);
|
|
74
|
+
debugLog(`Scanning ${skillDirs.length} entries in ${skillsDir}`);
|
|
79
75
|
|
|
80
76
|
for (const skillDir of skillDirs) {
|
|
81
77
|
const skillPath = path.join(skillsDir, skillDir);
|
|
82
78
|
|
|
83
79
|
// Skip if not a directory
|
|
84
|
-
|
|
80
|
+
let stat;
|
|
81
|
+
try {
|
|
82
|
+
stat = fs.statSync(skillPath);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
debugLog(`Failed to stat ${skillDir}: ${err.message}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!stat.isDirectory()) {
|
|
89
|
+
debugLog(`Skipping non-directory: ${skillDir}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
85
92
|
|
|
86
93
|
const skillFile = path.join(skillPath, 'SKILL.md');
|
|
87
94
|
|
|
88
95
|
// Skip if SKILL.md doesn't exist
|
|
89
|
-
if (!fs.existsSync(skillFile))
|
|
96
|
+
if (!fs.existsSync(skillFile)) {
|
|
97
|
+
skipped.push({ dir: skillDir, reason: 'no SKILL.md file' });
|
|
98
|
+
debugLog(`Skipping ${skillDir}: no SKILL.md found`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
90
101
|
|
|
91
102
|
const frontmatter = extractFrontmatter(skillFile);
|
|
103
|
+
|
|
104
|
+
// Check if frontmatter was extracted successfully
|
|
105
|
+
if (Object.keys(frontmatter).length === 0) {
|
|
106
|
+
skipped.push({ dir: skillDir, reason: 'no frontmatter or parse error' });
|
|
107
|
+
debugLog(`Skipping ${skillDir}: no frontmatter in SKILL.md`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
const name = frontmatter.name || skillDir;
|
|
93
112
|
const description = frontmatter.description || '';
|
|
94
113
|
|
|
114
|
+
debugLog(`Loaded ${skillDir}: name="${name}"`);
|
|
115
|
+
|
|
95
116
|
skills.push({
|
|
96
117
|
name,
|
|
97
118
|
directory: skillDir,
|
|
98
119
|
file: 'SKILL.md',
|
|
99
120
|
path: skillFile,
|
|
100
121
|
description,
|
|
101
|
-
category: categorizeSkill(name, description)
|
|
122
|
+
category: categorizeSkill(name, description),
|
|
102
123
|
});
|
|
103
124
|
}
|
|
104
125
|
|
|
@@ -110,6 +131,12 @@ function scanSkills(skillsDir) {
|
|
|
110
131
|
return a.name.localeCompare(b.name);
|
|
111
132
|
});
|
|
112
133
|
|
|
134
|
+
if (skipped.length > 0) {
|
|
135
|
+
debugLog(`Skipped ${skipped.length} directories: ${skipped.map(s => s.dir).join(', ')}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
debugLog(`Found ${skills.length} skills`);
|
|
139
|
+
|
|
113
140
|
return skills;
|
|
114
141
|
}
|
|
115
142
|
|
|
@@ -136,7 +163,7 @@ function main() {
|
|
|
136
163
|
}
|
|
137
164
|
|
|
138
165
|
// Export for use in other scripts
|
|
139
|
-
module.exports = { scanSkills,
|
|
166
|
+
module.exports = { scanSkills, categorizeSkill };
|
|
140
167
|
|
|
141
168
|
// Run if called directly
|
|
142
169
|
if (require.main === module) {
|
package/scripts/get-env.js
CHANGED
|
@@ -35,9 +35,7 @@ function getProjectInfo() {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
try {
|
|
38
|
-
rootPackage = JSON.parse(
|
|
39
|
-
fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8')
|
|
40
|
-
);
|
|
38
|
+
rootPackage = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
|
|
41
39
|
} catch (err) {
|
|
42
40
|
// Ignore if not found
|
|
43
41
|
}
|
|
@@ -50,17 +48,17 @@ function getProjectInfo() {
|
|
|
50
48
|
try {
|
|
51
49
|
gitBranch = execSync('git branch --show-current', {
|
|
52
50
|
cwd: rootDir,
|
|
53
|
-
encoding: 'utf8'
|
|
51
|
+
encoding: 'utf8',
|
|
54
52
|
}).trim();
|
|
55
53
|
gitCommit = execSync('git rev-parse --short HEAD', {
|
|
56
54
|
cwd: rootDir,
|
|
57
|
-
encoding: 'utf8'
|
|
55
|
+
encoding: 'utf8',
|
|
58
56
|
}).trim();
|
|
59
57
|
|
|
60
58
|
// Get recent commits (last 5)
|
|
61
59
|
const commitLog = execSync('git log --oneline -5 2>/dev/null', {
|
|
62
60
|
cwd: rootDir,
|
|
63
|
-
encoding: 'utf8'
|
|
61
|
+
encoding: 'utf8',
|
|
64
62
|
}).trim();
|
|
65
63
|
recentCommits = commitLog.split('\n').filter(Boolean);
|
|
66
64
|
} catch (err) {
|
|
@@ -68,10 +66,10 @@ function getProjectInfo() {
|
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
// Get AgileFlow status info
|
|
71
|
-
|
|
69
|
+
const activeStories = [];
|
|
72
70
|
let wipCount = 0;
|
|
73
71
|
let blockedCount = 0;
|
|
74
|
-
|
|
72
|
+
const activeEpics = [];
|
|
75
73
|
|
|
76
74
|
try {
|
|
77
75
|
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
@@ -163,13 +161,16 @@ function formatOutput(info, asJson = false, compact = false) {
|
|
|
163
161
|
|
|
164
162
|
// Header line with project info (brand color name, dim version, colored branch)
|
|
165
163
|
const branchColor = info.git.branch === 'main' ? c.green : c.cyan;
|
|
166
|
-
lines.push(
|
|
164
|
+
lines.push(
|
|
165
|
+
`${c.brand}${c.bold}${info.project.name}${c.reset} ${c.dim}v${info.project.version}${c.reset} | ${branchColor}${info.git.branch}${c.reset} ${c.dim}(${info.git.commit})${c.reset}`
|
|
166
|
+
);
|
|
167
167
|
|
|
168
168
|
// Status line (yellow WIP, red blocked)
|
|
169
169
|
const wipColor = info.agileflow.wipCount > 0 ? c.yellow : c.dim;
|
|
170
|
-
let statusLine =
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
let statusLine =
|
|
171
|
+
info.agileflow.wipCount > 0
|
|
172
|
+
? `${wipColor}WIP: ${info.agileflow.wipCount}${c.reset}`
|
|
173
|
+
: `${c.dim}No active work${c.reset}`;
|
|
173
174
|
if (info.agileflow.blockedCount > 0) {
|
|
174
175
|
statusLine += ` | ${c.red}Blocked: ${info.agileflow.blockedCount}${c.reset}`;
|
|
175
176
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Frontmatter Parser - Shared YAML frontmatter extraction
|
|
5
|
+
*
|
|
6
|
+
* Consolidates frontmatter parsing logic used across:
|
|
7
|
+
* - command-registry.js
|
|
8
|
+
* - agent-registry.js
|
|
9
|
+
* - skill-registry.js
|
|
10
|
+
* - content-injector.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse YAML frontmatter from markdown content
|
|
18
|
+
* @param {string} content - Markdown content with frontmatter
|
|
19
|
+
* @returns {object} Parsed frontmatter object, or empty object if none found
|
|
20
|
+
*/
|
|
21
|
+
function parseFrontmatter(content) {
|
|
22
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
23
|
+
|
|
24
|
+
if (!match) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const parsed = yaml.load(match[1]);
|
|
30
|
+
// Return empty object if yaml.load returns null/undefined
|
|
31
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
32
|
+
} catch (err) {
|
|
33
|
+
// Return empty object on parse error (invalid YAML)
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract frontmatter from a markdown file
|
|
40
|
+
* @param {string} filePath - Path to markdown file
|
|
41
|
+
* @returns {object} Parsed frontmatter object, or empty object if none/error
|
|
42
|
+
*/
|
|
43
|
+
function extractFrontmatter(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
46
|
+
return parseFrontmatter(content);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
// Return empty object on file read error
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract markdown body (content after frontmatter)
|
|
55
|
+
* @param {string} content - Full markdown content
|
|
56
|
+
* @returns {string} Content after frontmatter, or original if no frontmatter
|
|
57
|
+
*/
|
|
58
|
+
function extractBody(content) {
|
|
59
|
+
const match = content.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)/);
|
|
60
|
+
return match ? match[1].trim() : content.trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Normalize tools field - handles array or comma-separated string
|
|
65
|
+
* @param {string|Array} tools - Tools field from frontmatter
|
|
66
|
+
* @returns {Array} Array of tool names
|
|
67
|
+
*/
|
|
68
|
+
function normalizeTools(tools) {
|
|
69
|
+
if (!tools) return [];
|
|
70
|
+
if (Array.isArray(tools)) return tools;
|
|
71
|
+
if (typeof tools === 'string') {
|
|
72
|
+
return tools.split(',').map(t => t.trim()).filter(Boolean);
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
parseFrontmatter,
|
|
79
|
+
extractFrontmatter,
|
|
80
|
+
extractBody,
|
|
81
|
+
normalizeTools,
|
|
82
|
+
};
|
|
@@ -28,7 +28,11 @@ if (commandName) {
|
|
|
28
28
|
if (fs.existsSync(sessionStatePath)) {
|
|
29
29
|
try {
|
|
30
30
|
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
31
|
-
state.active_command = {
|
|
31
|
+
state.active_command = {
|
|
32
|
+
name: commandName,
|
|
33
|
+
activated_at: new Date().toISOString(),
|
|
34
|
+
state: {},
|
|
35
|
+
};
|
|
32
36
|
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
33
37
|
} catch (e) {
|
|
34
38
|
// Silently continue if session state can't be updated
|
|
@@ -92,9 +96,16 @@ function safeExec(cmd) {
|
|
|
92
96
|
function generateSummary() {
|
|
93
97
|
// Box drawing characters
|
|
94
98
|
const box = {
|
|
95
|
-
tl: '╭',
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
tl: '╭',
|
|
100
|
+
tr: '╮',
|
|
101
|
+
bl: '╰',
|
|
102
|
+
br: '╯',
|
|
103
|
+
h: '─',
|
|
104
|
+
v: '│',
|
|
105
|
+
lT: '├',
|
|
106
|
+
rT: '┤',
|
|
107
|
+
tT: '┬',
|
|
108
|
+
bT: '┴',
|
|
98
109
|
cross: '┼',
|
|
99
110
|
};
|
|
100
111
|
|
|
@@ -144,7 +155,8 @@ function generateSummary() {
|
|
|
144
155
|
return `${C.dim}${box.v}${C.reset} ${pad(leftStr, L)} ${C.dim}${box.v}${C.reset} ${pad(rightStr, R)} ${C.dim}${box.v}${C.reset}\n`;
|
|
145
156
|
}
|
|
146
157
|
|
|
147
|
-
const divider = () =>
|
|
158
|
+
const divider = () =>
|
|
159
|
+
`${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.cross}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
|
|
148
160
|
const headerTopBorder = `${C.dim}${box.tl}${box.h.repeat(W + 2)}${box.tr}${C.reset}\n`;
|
|
149
161
|
const headerDivider = `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
|
|
150
162
|
const bottomBorder = `${C.dim}${box.bl}${box.h.repeat(L + 2)}${box.bT}${box.h.repeat(R + 2)}${box.br}${C.reset}\n`;
|
|
@@ -156,12 +168,15 @@ function generateSummary() {
|
|
|
156
168
|
const statusLines = (safeExec('git status --short') || '').split('\n').filter(Boolean);
|
|
157
169
|
const statusJson = safeReadJSON('docs/09-agents/status.json');
|
|
158
170
|
const sessionState = safeReadJSON('docs/09-agents/session-state.json');
|
|
159
|
-
const researchFiles = safeLs('docs/10-research')
|
|
171
|
+
const researchFiles = safeLs('docs/10-research')
|
|
172
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md')
|
|
173
|
+
.sort()
|
|
174
|
+
.reverse();
|
|
160
175
|
const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
161
176
|
|
|
162
177
|
// Count stories by status
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
const byStatus = {};
|
|
179
|
+
const readyStories = [];
|
|
165
180
|
if (statusJson && statusJson.stories) {
|
|
166
181
|
Object.entries(statusJson.stories).forEach(([id, story]) => {
|
|
167
182
|
const s = story.status || 'unknown';
|
|
@@ -187,23 +202,45 @@ function generateSummary() {
|
|
|
187
202
|
const title = commandName ? `Context [${commandName}]` : 'Context Summary';
|
|
188
203
|
const branchColor = branch === 'main' ? C.green : branch.startsWith('fix') ? C.red : C.cyan;
|
|
189
204
|
const maxBranchLen = 20;
|
|
190
|
-
const branchDisplay =
|
|
205
|
+
const branchDisplay =
|
|
206
|
+
branch.length > maxBranchLen ? branch.substring(0, maxBranchLen - 2) + '..' : branch;
|
|
191
207
|
const header = `${C.brand}${C.bold}${title}${C.reset} ${branchColor}${branchDisplay}${C.reset} ${C.dim}(${lastCommitShort})${C.reset}`;
|
|
192
208
|
summary += `${C.dim}${box.v}${C.reset} ${pad(header, W)} ${C.dim}${box.v}${C.reset}\n`;
|
|
193
209
|
|
|
194
210
|
summary += headerDivider;
|
|
195
211
|
|
|
196
212
|
// Story counts with colorful labels
|
|
197
|
-
summary += row(
|
|
198
|
-
|
|
199
|
-
|
|
213
|
+
summary += row(
|
|
214
|
+
'In Progress',
|
|
215
|
+
byStatus['in-progress'] ? `${byStatus['in-progress']}` : '0',
|
|
216
|
+
C.yellow,
|
|
217
|
+
byStatus['in-progress'] ? C.brightYellow : C.dim
|
|
218
|
+
);
|
|
219
|
+
summary += row(
|
|
220
|
+
'Blocked',
|
|
221
|
+
byStatus['blocked'] ? `${byStatus['blocked']}` : '0',
|
|
222
|
+
C.red,
|
|
223
|
+
byStatus['blocked'] ? C.red : C.dim
|
|
224
|
+
);
|
|
225
|
+
summary += row(
|
|
226
|
+
'Ready',
|
|
227
|
+
byStatus['ready'] ? `${byStatus['ready']}` : '0',
|
|
228
|
+
C.cyan,
|
|
229
|
+
byStatus['ready'] ? C.brightCyan : C.dim
|
|
230
|
+
);
|
|
200
231
|
const completedColor = `${C.bold}${C.green}`;
|
|
201
|
-
summary += row(
|
|
232
|
+
summary += row(
|
|
233
|
+
'Completed',
|
|
234
|
+
byStatus['done'] ? `${byStatus['done']}` : '0',
|
|
235
|
+
completedColor,
|
|
236
|
+
byStatus['done'] ? completedColor : C.dim
|
|
237
|
+
);
|
|
202
238
|
|
|
203
239
|
summary += divider();
|
|
204
240
|
|
|
205
241
|
// Git status
|
|
206
|
-
const uncommittedStatus =
|
|
242
|
+
const uncommittedStatus =
|
|
243
|
+
statusLines.length > 0 ? `${statusLines.length} uncommitted` : '✓ clean';
|
|
207
244
|
summary += row('Git', uncommittedStatus, C.blue, statusLines.length > 0 ? C.yellow : C.green);
|
|
208
245
|
|
|
209
246
|
// Session
|
|
@@ -228,10 +265,12 @@ function generateSummary() {
|
|
|
228
265
|
{ path: 'docs/04-architecture/README.md', label: 'arch' },
|
|
229
266
|
{ path: 'docs/02-practices/README.md', label: 'practices' },
|
|
230
267
|
];
|
|
231
|
-
const keyFileStatus = keyFileChecks
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
268
|
+
const keyFileStatus = keyFileChecks
|
|
269
|
+
.map(f => {
|
|
270
|
+
const exists = fs.existsSync(f.path);
|
|
271
|
+
return exists ? `${C.brightGreen}✓${C.reset}${f.label}` : `${C.dim}○${f.label}${C.reset}`;
|
|
272
|
+
})
|
|
273
|
+
.join(' ');
|
|
235
274
|
summary += row('Key files', keyFileStatus, C.magenta, '');
|
|
236
275
|
|
|
237
276
|
// Research
|
|
@@ -245,7 +284,12 @@ function generateSummary() {
|
|
|
245
284
|
summary += divider();
|
|
246
285
|
|
|
247
286
|
// Last commit
|
|
248
|
-
summary += row(
|
|
287
|
+
summary += row(
|
|
288
|
+
'Last commit',
|
|
289
|
+
`${C.yellow}${lastCommitShort}${C.reset} ${lastCommitMsg}`,
|
|
290
|
+
C.dim,
|
|
291
|
+
''
|
|
292
|
+
);
|
|
249
293
|
|
|
250
294
|
summary += bottomBorder;
|
|
251
295
|
|
|
@@ -276,8 +320,9 @@ function generateFullContent() {
|
|
|
276
320
|
content += `Last commit: ${C.dim}${lastCommit}${C.reset}\n`;
|
|
277
321
|
if (statusLines.length > 0) {
|
|
278
322
|
content += `Uncommitted: ${C.yellow}${statusLines.length} file(s)${C.reset}\n`;
|
|
279
|
-
statusLines.slice(0, 10).forEach(line => content += ` ${C.dim}${line}${C.reset}\n`);
|
|
280
|
-
if (statusLines.length > 10)
|
|
323
|
+
statusLines.slice(0, 10).forEach(line => (content += ` ${C.dim}${line}${C.reset}\n`));
|
|
324
|
+
if (statusLines.length > 10)
|
|
325
|
+
content += ` ${C.dim}... and ${statusLines.length - 10} more${C.reset}\n`;
|
|
281
326
|
} else {
|
|
282
327
|
content += `Uncommitted: ${C.green}clean${C.reset}\n`;
|
|
283
328
|
}
|
|
@@ -289,7 +334,11 @@ function generateFullContent() {
|
|
|
289
334
|
|
|
290
335
|
if (statusJson) {
|
|
291
336
|
content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
|
|
292
|
-
content +=
|
|
337
|
+
content +=
|
|
338
|
+
JSON.stringify(statusJson, null, 2)
|
|
339
|
+
.split('\n')
|
|
340
|
+
.map(l => ` ${l}`)
|
|
341
|
+
.join('\n') + '\n';
|
|
293
342
|
content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
|
|
294
343
|
} else {
|
|
295
344
|
content += `${C.dim}No status.json found${C.reset}\n`;
|
|
@@ -321,7 +370,11 @@ function generateFullContent() {
|
|
|
321
370
|
content += `\n${C.cyan}${C.bold}═══ Documentation ═══${C.reset}\n`;
|
|
322
371
|
const docsDir = 'docs';
|
|
323
372
|
const docFolders = safeLs(docsDir).filter(f => {
|
|
324
|
-
try {
|
|
373
|
+
try {
|
|
374
|
+
return fs.statSync(path.join(docsDir, f)).isDirectory();
|
|
375
|
+
} catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
325
378
|
});
|
|
326
379
|
|
|
327
380
|
if (docFolders.length > 0) {
|
|
@@ -330,7 +383,7 @@ function generateFullContent() {
|
|
|
330
383
|
const files = safeLs(folderPath);
|
|
331
384
|
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
332
385
|
const jsonFiles = files.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
|
|
333
|
-
|
|
386
|
+
const info = [];
|
|
334
387
|
if (mdFiles.length > 0) info.push(`${mdFiles.length} md`);
|
|
335
388
|
if (jsonFiles.length > 0) info.push(`${jsonFiles.length} json`);
|
|
336
389
|
content += ` ${C.dim}${folder}/${C.reset} ${info.length > 0 ? `(${info.join(', ')})` : ''}\n`;
|
|
@@ -344,7 +397,7 @@ function generateFullContent() {
|
|
|
344
397
|
if (researchFiles.length > 0) {
|
|
345
398
|
researchFiles.sort().reverse();
|
|
346
399
|
content += `${C.dim}───${C.reset} Available Research Notes\n`;
|
|
347
|
-
researchFiles.forEach(file => content += ` ${C.dim}${file}${C.reset}\n`);
|
|
400
|
+
researchFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
|
|
348
401
|
|
|
349
402
|
const mostRecentFile = researchFiles[0];
|
|
350
403
|
const mostRecentPath = path.join(researchDir, mostRecentFile);
|
|
@@ -414,7 +467,7 @@ function generateFullContent() {
|
|
|
414
467
|
content += `\n${C.cyan}${C.bold}═══ Epic Files ═══${C.reset}\n`;
|
|
415
468
|
const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
416
469
|
if (epicFiles.length > 0) {
|
|
417
|
-
epicFiles.forEach(file => content += ` ${C.dim}${file}${C.reset}\n`);
|
|
470
|
+
epicFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
|
|
418
471
|
} else {
|
|
419
472
|
content += `${C.dim}No epic files${C.reset}\n`;
|
|
420
473
|
}
|