@iservu-inc/adf-cli 0.16.0 → 0.17.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/.context/memory/architecture.md +1 -1
- package/.context/memory/glossary.md +1 -1
- package/CLAUDE.md +4 -4
- package/README.md +1 -1
- package/lib/analysis/heuristic-gap-analyzer.js +4 -4
- package/lib/analysis/synthesis-engine.js +5 -5
- package/lib/commands/deploy.js +14 -0
- package/lib/commands/guide.js +32 -0
- package/lib/commands/init.js +544 -458
- package/lib/frameworks/output-generators.js +54 -147
- package/lib/frameworks/progress-tracker.js +16 -0
- package/lib/frameworks/questions.js +156 -464
- package/lib/frameworks/session-manager.js +56 -0
- package/lib/generators/a2a-generator.js +289 -0
- package/lib/generators/index.js +11 -0
- package/lib/learning/analytics-view.js +5 -5
- package/lib/learning/analytics.js +22 -6
- package/lib/templates/scripts/analyze-docs.js +11 -11
- package/lib/templates/scripts/build.js +1 -1
- package/lib/templates/scripts/check-framework-updates.js +1 -1
- package/lib/templates/scripts/init.js +1 -1
- package/lib/templates/shared/agents/analyst.md +1 -1
- package/lib/templates/shared/agents/architect.md +1 -1
- package/lib/templates/shared/agents/dev.md +1 -1
- package/lib/templates/shared/agents/pm.md +2 -2
- package/lib/templates/shared/agents/qa.md +1 -1
- package/lib/templates/shared/agents/sm.md +3 -3
- package/lib/templates/shared/memory/constitution.md +2 -2
- package/lib/templates/shared/templates/README.md +13 -13
- package/lib/templates/shared/templates/prd-template.md +1 -1
- package/lib/utils/context-extractor.js +3 -3
- package/lib/utils/framework-detector.js +1 -1
- package/lib/utils/tool-feature-registry.js +6 -0
- package/package.json +1 -1
- package/tests/a2a-generator.test.js +288 -0
- package/tests/analytics-view.test.js +12 -10
- package/tests/context-extractor.test.js +2 -2
- package/tests/decay-manager.test.js +22 -19
- package/tests/deploy.test.js +7 -3
- package/tests/dynamic-question-generator.test.js +2 -2
- package/tests/framework-detector.test.js +3 -3
- package/tests/heuristic-gap-analyzer.test.js +5 -5
- package/tests/pattern-decay.test.js +34 -52
- package/tests/session-manager.test.js +125 -0
|
@@ -2,6 +2,7 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
+
const { getQuestionsForFramework } = require('./questions');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Session Manager
|
|
@@ -45,6 +46,61 @@ class SessionManager {
|
|
|
45
46
|
return sessions.filter(s => s.progress.status === 'in-progress' && s.progress.canResume);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
async getSessionsWithDetails() {
|
|
50
|
+
const sessions = await this.listSessions();
|
|
51
|
+
const detailed = [];
|
|
52
|
+
|
|
53
|
+
for (const session of sessions) {
|
|
54
|
+
const { progress } = session;
|
|
55
|
+
const framework = progress.framework || 'rapid';
|
|
56
|
+
const questions = getQuestionsForFramework(framework);
|
|
57
|
+
const totalQuestions = questions.length;
|
|
58
|
+
|
|
59
|
+
// Count answers that match actual framework questions
|
|
60
|
+
const questionIds = new Set(questions.map(q => q.id));
|
|
61
|
+
const answeredCount = Object.keys(progress.answers || {})
|
|
62
|
+
.filter(id => questionIds.has(id)).length;
|
|
63
|
+
const unansweredCount = totalQuestions - answeredCount;
|
|
64
|
+
|
|
65
|
+
detailed.push({
|
|
66
|
+
...session,
|
|
67
|
+
totalQuestions,
|
|
68
|
+
answeredCount,
|
|
69
|
+
unansweredCount,
|
|
70
|
+
hasGaps: unansweredCount > 0
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return detailed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async reopenSession(sessionId) {
|
|
78
|
+
const sessionPath = path.join(this.sessionsDir, sessionId);
|
|
79
|
+
const progressFile = path.join(sessionPath, '_progress.json');
|
|
80
|
+
|
|
81
|
+
if (!await fs.pathExists(progressFile)) {
|
|
82
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const progress = await fs.readJson(progressFile);
|
|
86
|
+
|
|
87
|
+
// Reset status to in-progress while preserving all answer data
|
|
88
|
+
progress.status = 'in-progress';
|
|
89
|
+
progress.canResume = true;
|
|
90
|
+
progress.completedAt = null;
|
|
91
|
+
|
|
92
|
+
// Reset block-tracking counters to avoid duplicates when interviewer
|
|
93
|
+
// replays blocks (it pushes to completedBlocks and increments cumulatively)
|
|
94
|
+
progress.completedBlocks = [];
|
|
95
|
+
progress.skippedBlocks = [];
|
|
96
|
+
progress.totalQuestionsAnswered = 0;
|
|
97
|
+
progress.currentBlock = 0;
|
|
98
|
+
|
|
99
|
+
await fs.writeJson(progressFile, progress, { spaces: 2 });
|
|
100
|
+
|
|
101
|
+
return { sessionId, sessionPath, progress };
|
|
102
|
+
}
|
|
103
|
+
|
|
48
104
|
async promptToResume() {
|
|
49
105
|
const resumableSessions = await this.getResumableSessions();
|
|
50
106
|
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ToolConfigGenerator = require('./tool-config-generator');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generator for A2A (Agent-to-Agent) protocol agent cards
|
|
7
|
+
* Creates compliant agent discovery cards for interoperability
|
|
8
|
+
* with any A2A-compatible tool.
|
|
9
|
+
*
|
|
10
|
+
* Output structure:
|
|
11
|
+
* .a2a/
|
|
12
|
+
* ├── agent-card.json # Combined discovery card (all agents)
|
|
13
|
+
* └── agents/
|
|
14
|
+
* ├── dev.json # Individual agent cards
|
|
15
|
+
* ├── qa.json
|
|
16
|
+
* └── ...
|
|
17
|
+
*/
|
|
18
|
+
class A2AGenerator extends ToolConfigGenerator {
|
|
19
|
+
/**
|
|
20
|
+
* Generate A2A agent cards for all agents in the current framework
|
|
21
|
+
*/
|
|
22
|
+
async generate() {
|
|
23
|
+
await this.initialize();
|
|
24
|
+
|
|
25
|
+
const a2aDir = path.join(this.projectPath, '.a2a');
|
|
26
|
+
const agentsDir = path.join(a2aDir, 'agents');
|
|
27
|
+
await fs.ensureDir(agentsDir);
|
|
28
|
+
|
|
29
|
+
const agentsList = this.getAgentsList();
|
|
30
|
+
const individualCards = [];
|
|
31
|
+
|
|
32
|
+
for (const agentName of agentsList) {
|
|
33
|
+
const card = await this.generateAgentCard(agentName);
|
|
34
|
+
if (card) {
|
|
35
|
+
await fs.writeJson(
|
|
36
|
+
path.join(agentsDir, `${agentName}.json`),
|
|
37
|
+
card,
|
|
38
|
+
{ spaces: 2 }
|
|
39
|
+
);
|
|
40
|
+
individualCards.push(card);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const combinedCard = this.generateCombinedCard(individualCards);
|
|
45
|
+
await fs.writeJson(
|
|
46
|
+
path.join(a2aDir, 'agent-card.json'),
|
|
47
|
+
combinedCard,
|
|
48
|
+
{ spaces: 2 }
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
agentCard: path.join(a2aDir, 'agent-card.json'),
|
|
53
|
+
agents: agentsList.map(a => path.join(agentsDir, `${a}.json`))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate an individual A2A agent card from agent markdown frontmatter
|
|
59
|
+
*/
|
|
60
|
+
async generateAgentCard(agentName) {
|
|
61
|
+
const agentPath = path.join(__dirname, '../templates/shared/agents', `${agentName}.md`);
|
|
62
|
+
|
|
63
|
+
if (!await fs.pathExists(agentPath)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const content = await fs.readFile(agentPath, 'utf-8');
|
|
68
|
+
const frontmatter = this.parseAgentFrontmatter(content);
|
|
69
|
+
const skills = this.mapAgentToSkills(agentName, frontmatter);
|
|
70
|
+
|
|
71
|
+
const agentId = (frontmatter.agent && frontmatter.agent.id) || agentName;
|
|
72
|
+
const agentDisplayName = (frontmatter.agent && frontmatter.agent.name) || agentName;
|
|
73
|
+
const role = (frontmatter.agent && frontmatter.agent.role) || agentName;
|
|
74
|
+
const focus = (frontmatter.agent && frontmatter.agent.focus) || '';
|
|
75
|
+
|
|
76
|
+
const description = focus ? `${role} - ${focus}` : role;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
name: `ADF ${agentDisplayName} Agent`,
|
|
80
|
+
description,
|
|
81
|
+
version: this.getADFVersion(),
|
|
82
|
+
url: `local://adf/agents/${agentId}`,
|
|
83
|
+
supportedInterfaces: [{
|
|
84
|
+
url: `local://adf/agents/${agentId}`,
|
|
85
|
+
protocolBinding: 'JSONRPC',
|
|
86
|
+
protocolVersion: '0.3'
|
|
87
|
+
}],
|
|
88
|
+
defaultInputModes: ['text/plain', 'application/json'],
|
|
89
|
+
defaultOutputModes: ['text/plain', 'application/json'],
|
|
90
|
+
capabilities: {
|
|
91
|
+
streaming: false,
|
|
92
|
+
pushNotifications: false
|
|
93
|
+
},
|
|
94
|
+
provider: {
|
|
95
|
+
organization: 'ADF CLI',
|
|
96
|
+
url: 'https://github.com/iservu-inc/adf-cli'
|
|
97
|
+
},
|
|
98
|
+
skills
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generate combined agent-card.json with all agents' skills merged
|
|
104
|
+
*/
|
|
105
|
+
generateCombinedCard(individualCards) {
|
|
106
|
+
const projectName = this.getProjectName();
|
|
107
|
+
const allSkills = [];
|
|
108
|
+
|
|
109
|
+
for (const card of individualCards) {
|
|
110
|
+
if (card.skills) {
|
|
111
|
+
allSkills.push(...card.skills);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
name: `ADF ${projectName} Agents`,
|
|
117
|
+
description: `Agent-to-Agent discovery card for ${projectName} (${this.framework} workflow)`,
|
|
118
|
+
version: this.getADFVersion(),
|
|
119
|
+
url: 'local://adf/agents',
|
|
120
|
+
supportedInterfaces: [{
|
|
121
|
+
url: 'local://adf/agents',
|
|
122
|
+
protocolBinding: 'JSONRPC',
|
|
123
|
+
protocolVersion: '0.3'
|
|
124
|
+
}],
|
|
125
|
+
defaultInputModes: ['text/plain', 'application/json'],
|
|
126
|
+
defaultOutputModes: ['text/plain', 'application/json'],
|
|
127
|
+
capabilities: {
|
|
128
|
+
streaming: false,
|
|
129
|
+
pushNotifications: false
|
|
130
|
+
},
|
|
131
|
+
provider: {
|
|
132
|
+
organization: 'ADF CLI',
|
|
133
|
+
url: 'https://github.com/iservu-inc/adf-cli'
|
|
134
|
+
},
|
|
135
|
+
skills: allSkills,
|
|
136
|
+
agents: individualCards.map(card => ({
|
|
137
|
+
name: card.name,
|
|
138
|
+
url: card.url,
|
|
139
|
+
description: card.description
|
|
140
|
+
}))
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Parse YAML frontmatter from agent markdown file
|
|
146
|
+
*/
|
|
147
|
+
parseAgentFrontmatter(content) {
|
|
148
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
149
|
+
if (!match) return {};
|
|
150
|
+
|
|
151
|
+
const yaml = match[1];
|
|
152
|
+
return this.simpleYamlParse(yaml);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Simple YAML parser for agent frontmatter
|
|
157
|
+
* Handles the specific nested structure used in agent files
|
|
158
|
+
*/
|
|
159
|
+
simpleYamlParse(yaml) {
|
|
160
|
+
const result = {};
|
|
161
|
+
const lines = yaml.split('\n');
|
|
162
|
+
let currentTopKey = null;
|
|
163
|
+
let currentSubKey = null;
|
|
164
|
+
let currentArray = null;
|
|
165
|
+
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
// Skip empty lines and comments
|
|
168
|
+
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
169
|
+
|
|
170
|
+
// Check for top-level key (no indentation)
|
|
171
|
+
const topMatch = line.match(/^(\w[\w_]*):(.*)$/);
|
|
172
|
+
if (topMatch) {
|
|
173
|
+
currentTopKey = topMatch[1];
|
|
174
|
+
const value = topMatch[2].trim();
|
|
175
|
+
if (value && !value.startsWith('|')) {
|
|
176
|
+
result[currentTopKey] = value;
|
|
177
|
+
} else if (!value) {
|
|
178
|
+
result[currentTopKey] = {};
|
|
179
|
+
}
|
|
180
|
+
currentSubKey = null;
|
|
181
|
+
currentArray = null;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check for sub-key (2-space indentation)
|
|
186
|
+
const subMatch = line.match(/^ (\w[\w_]*):(.*)$/);
|
|
187
|
+
if (subMatch && currentTopKey) {
|
|
188
|
+
currentSubKey = subMatch[1];
|
|
189
|
+
const value = subMatch[2].trim();
|
|
190
|
+
if (typeof result[currentTopKey] !== 'object') {
|
|
191
|
+
result[currentTopKey] = {};
|
|
192
|
+
}
|
|
193
|
+
if (value) {
|
|
194
|
+
result[currentTopKey][currentSubKey] = value;
|
|
195
|
+
} else {
|
|
196
|
+
result[currentTopKey][currentSubKey] = [];
|
|
197
|
+
currentArray = result[currentTopKey][currentSubKey];
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check for array item (4-space indentation with dash)
|
|
203
|
+
const arrayMatch = line.match(/^ - (.+)$/);
|
|
204
|
+
if (arrayMatch && currentArray) {
|
|
205
|
+
// Strip inline comments
|
|
206
|
+
const val = arrayMatch[1].replace(/#.*$/, '').trim();
|
|
207
|
+
currentArray.push(val);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check for top-level array item (2-space indentation with dash)
|
|
212
|
+
const topArrayMatch = line.match(/^ - (.+)$/);
|
|
213
|
+
if (topArrayMatch && currentTopKey && !currentSubKey) {
|
|
214
|
+
if (!Array.isArray(result[currentTopKey])) {
|
|
215
|
+
result[currentTopKey] = [];
|
|
216
|
+
}
|
|
217
|
+
const val = topArrayMatch[1].replace(/#.*$/, '').trim();
|
|
218
|
+
result[currentTopKey].push(val);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Map an ADF agent's frontmatter to A2A skills
|
|
227
|
+
*/
|
|
228
|
+
mapAgentToSkills(agentName, frontmatter) {
|
|
229
|
+
const AGENT_SKILLS = {
|
|
230
|
+
dev: [
|
|
231
|
+
{ id: 'tdd-implementation', name: 'TDD Implementation', description: 'Test-driven development with context engineering', tags: ['development', 'tdd', 'testing'] },
|
|
232
|
+
{ id: 'code-review', name: 'Code Review', description: 'Self-review against coding standards', tags: ['development', 'review'] },
|
|
233
|
+
{ id: 'refactoring', name: 'Refactoring', description: 'Code refactoring while maintaining tests', tags: ['development', 'refactoring'] }
|
|
234
|
+
],
|
|
235
|
+
qa: [
|
|
236
|
+
{ id: 'risk-assessment', name: 'Risk Assessment', description: 'Identify and score project risks', tags: ['quality', 'risk'] },
|
|
237
|
+
{ id: 'test-design', name: 'Test Design', description: 'Comprehensive test strategy creation', tags: ['quality', 'testing'] },
|
|
238
|
+
{ id: 'quality-gates', name: 'Quality Gates', description: 'Quality gate management and decisions', tags: ['quality', 'gates'] }
|
|
239
|
+
],
|
|
240
|
+
pm: [
|
|
241
|
+
{ id: 'requirements-gathering', name: 'Requirements Gathering', description: 'Product requirements analysis and documentation', tags: ['product', 'requirements'] },
|
|
242
|
+
{ id: 'story-creation', name: 'Story Creation', description: 'User story and specification writing', tags: ['product', 'stories'] },
|
|
243
|
+
{ id: 'project-planning', name: 'Project Planning', description: 'Sprint planning and backlog management', tags: ['product', 'planning'] }
|
|
244
|
+
],
|
|
245
|
+
analyst: [
|
|
246
|
+
{ id: 'market-analysis', name: 'Market Analysis', description: 'Market research and competitive analysis', tags: ['analysis', 'market'] },
|
|
247
|
+
{ id: 'business-requirements', name: 'Business Requirements', description: 'Business requirements documentation', tags: ['analysis', 'requirements'] }
|
|
248
|
+
],
|
|
249
|
+
architect: [
|
|
250
|
+
{ id: 'system-design', name: 'System Design', description: 'System architecture and technical design', tags: ['architecture', 'design'] },
|
|
251
|
+
{ id: 'tech-stack-selection', name: 'Tech Stack Selection', description: 'Technology evaluation and selection', tags: ['architecture', 'technology'] }
|
|
252
|
+
],
|
|
253
|
+
sm: [
|
|
254
|
+
{ id: 'sprint-management', name: 'Sprint Management', description: 'Agile sprint planning and execution', tags: ['agile', 'sprint'] },
|
|
255
|
+
{ id: 'ceremony-facilitation', name: 'Ceremony Facilitation', description: 'Scrum ceremony facilitation', tags: ['agile', 'ceremonies'] }
|
|
256
|
+
]
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const skills = AGENT_SKILLS[agentName] || [];
|
|
260
|
+
|
|
261
|
+
// Enrich with MCP tools from frontmatter if present
|
|
262
|
+
if (frontmatter.mcp_tools && Array.isArray(frontmatter.mcp_tools)) {
|
|
263
|
+
for (const tool of frontmatter.mcp_tools) {
|
|
264
|
+
skills.push({
|
|
265
|
+
id: `mcp-${tool}`,
|
|
266
|
+
name: `MCP: ${tool}`,
|
|
267
|
+
description: `Integration with ${tool} MCP server`,
|
|
268
|
+
tags: ['mcp', tool]
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return skills;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get ADF CLI version from package.json
|
|
278
|
+
*/
|
|
279
|
+
getADFVersion() {
|
|
280
|
+
try {
|
|
281
|
+
const packageJson = require('../../package.json');
|
|
282
|
+
return packageJson.version;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return '0.16.0';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = A2AGenerator;
|
package/lib/generators/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const DeepAgentGenerator = require('./deepagent-generator');
|
|
|
15
15
|
const KiroGenerator = require('./kiro-generator');
|
|
16
16
|
const TraeGenerator = require('./trae-generator');
|
|
17
17
|
const CodexCLIGenerator = require('./codex-cli-generator');
|
|
18
|
+
const A2AGenerator = require('./a2a-generator');
|
|
18
19
|
const ToolConfigGenerator = require('./tool-config-generator');
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -171,6 +172,14 @@ async function generateCodexCLI(sessionPath, projectPath, framework) {
|
|
|
171
172
|
return await generator.generate();
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Generate A2A (Agent-to-Agent) protocol agent cards
|
|
177
|
+
*/
|
|
178
|
+
async function generateA2A(sessionPath, projectPath, framework) {
|
|
179
|
+
const generator = new A2AGenerator(sessionPath, projectPath, framework);
|
|
180
|
+
return await generator.generate();
|
|
181
|
+
}
|
|
182
|
+
|
|
174
183
|
module.exports = {
|
|
175
184
|
generateAll,
|
|
176
185
|
generateAgentsMd,
|
|
@@ -185,6 +194,7 @@ module.exports = {
|
|
|
185
194
|
generateKiro,
|
|
186
195
|
generateTrae,
|
|
187
196
|
generateCodexCLI,
|
|
197
|
+
generateA2A,
|
|
188
198
|
AgentsMdGenerator,
|
|
189
199
|
WindsurfGenerator,
|
|
190
200
|
CursorGenerator,
|
|
@@ -197,5 +207,6 @@ module.exports = {
|
|
|
197
207
|
KiroGenerator,
|
|
198
208
|
TraeGenerator,
|
|
199
209
|
CodexCLIGenerator,
|
|
210
|
+
A2AGenerator,
|
|
200
211
|
ToolConfigGenerator
|
|
201
212
|
};
|
|
@@ -261,14 +261,14 @@ async function showTimelineView(analyticsData) {
|
|
|
261
261
|
|
|
262
262
|
console.log(chalk.bold('📅 SESSION HISTORY\n'));
|
|
263
263
|
console.log(`Total Sessions: ${chalk.cyan(sessionTimeline.totalSessions)}`);
|
|
264
|
-
console.log(`Sessions per Week: ${chalk.yellow(sessionTimeline.
|
|
264
|
+
console.log(`Sessions per Week: ${chalk.yellow(sessionTimeline.avgSessionsPerWeek)}`);
|
|
265
265
|
console.log(`First Session: ${chalk.white(sessionTimeline.firstSession || 'N/A')}`);
|
|
266
266
|
console.log(`Last Session: ${chalk.white(sessionTimeline.lastSession || 'N/A')}\n`);
|
|
267
267
|
|
|
268
|
-
if (sessionTimeline.
|
|
268
|
+
if (sessionTimeline.sessions.length > 0) {
|
|
269
269
|
console.log(chalk.bold('Recent Sessions (Last 10):\n'));
|
|
270
270
|
|
|
271
|
-
const recentSessions = sessionTimeline.
|
|
271
|
+
const recentSessions = sessionTimeline.sessions.slice(0, 10);
|
|
272
272
|
|
|
273
273
|
for (const session of recentSessions) {
|
|
274
274
|
const date = new Date(session.timestamp).toLocaleDateString();
|
|
@@ -336,8 +336,8 @@ async function showImpactfulPatternsView(analyticsData) {
|
|
|
336
336
|
|
|
337
337
|
// Top by time saved
|
|
338
338
|
console.log(chalk.bold('⏱️ TOP PATTERNS BY TIME SAVED\n'));
|
|
339
|
-
if (impactfulPatterns.
|
|
340
|
-
impactfulPatterns.
|
|
339
|
+
if (impactfulPatterns.byTimeSaved.length > 0) {
|
|
340
|
+
impactfulPatterns.byTimeSaved.forEach((item, i) => {
|
|
341
341
|
console.log(`${chalk.green((i + 1) + '.')} ${item.pattern.questionText || item.pattern.questionId}`);
|
|
342
342
|
console.log(` ${chalk.gray(`${item.timeSaved} minutes saved | ${item.appliedCount} applications | ${item.pattern.confidence}% confidence`)}\n`);
|
|
343
343
|
});
|
|
@@ -363,8 +363,8 @@ function calculateSessionTimeline(skipHistory, answerHistory) {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
return {
|
|
366
|
-
timeline,
|
|
367
|
-
sessionsPerWeek,
|
|
366
|
+
sessions: timeline,
|
|
367
|
+
avgSessionsPerWeek: sessionsPerWeek,
|
|
368
368
|
totalSessions: sessions.length,
|
|
369
369
|
firstSession,
|
|
370
370
|
lastSession
|
|
@@ -401,10 +401,10 @@ function calculateImpactfulPatterns(patterns, rules, skipHistory) {
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
return {
|
|
404
|
-
|
|
404
|
+
byTimeSaved: impactData
|
|
405
405
|
.sort((a, b) => b.timeSaved - a.timeSaved)
|
|
406
406
|
.slice(0, 5),
|
|
407
|
-
|
|
407
|
+
byApplications: impactData
|
|
408
408
|
.sort((a, b) => b.appliedCount - a.appliedCount)
|
|
409
409
|
.slice(0, 5),
|
|
410
410
|
perfectConfidence: patterns.filter(p => p.confidence === 100),
|
|
@@ -477,7 +477,8 @@ function calculateQuestionStats(skipHistory, answerHistory) {
|
|
|
477
477
|
lowestSkipRate: stats
|
|
478
478
|
.filter(q => q.skips + q.answers > 0)
|
|
479
479
|
.sort((a, b) => a.skipRate - b.skipRate)
|
|
480
|
-
.slice(0, 10)
|
|
480
|
+
.slice(0, 10),
|
|
481
|
+
totalUniqueQuestions: Object.keys(questionData).length
|
|
481
482
|
};
|
|
482
483
|
}
|
|
483
484
|
|
|
@@ -506,11 +507,15 @@ function getLast12Weeks() {
|
|
|
506
507
|
for (let i = 11; i >= 0; i--) {
|
|
507
508
|
const weekStart = new Date(now);
|
|
508
509
|
weekStart.setDate(now.getDate() - (i * 7));
|
|
510
|
+
weekStart.setHours(0, 0, 0, 0);
|
|
509
511
|
const weekEnd = new Date(weekStart);
|
|
510
512
|
weekEnd.setDate(weekStart.getDate() + 6);
|
|
513
|
+
weekEnd.setHours(23, 59, 59, 999);
|
|
514
|
+
|
|
515
|
+
const isoLabel = getISOWeekLabel(weekStart);
|
|
511
516
|
|
|
512
517
|
weeks.push({
|
|
513
|
-
label:
|
|
518
|
+
label: isoLabel,
|
|
514
519
|
weekNumber: getWeekNumber(weekStart),
|
|
515
520
|
start: weekStart,
|
|
516
521
|
end: weekEnd
|
|
@@ -531,6 +536,17 @@ function getWeekNumber(date) {
|
|
|
531
536
|
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
|
|
532
537
|
}
|
|
533
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Get ISO week label for a date (e.g., "2026-W07")
|
|
541
|
+
* @param {Date} date - Date
|
|
542
|
+
* @returns {string} ISO week label
|
|
543
|
+
*/
|
|
544
|
+
function getISOWeekLabel(date) {
|
|
545
|
+
const year = date.getFullYear();
|
|
546
|
+
const weekNum = getWeekNumber(date);
|
|
547
|
+
return `${year}-W${String(weekNum).padStart(2, '0')}`;
|
|
548
|
+
}
|
|
549
|
+
|
|
534
550
|
/**
|
|
535
551
|
* Filter sessions by week
|
|
536
552
|
* @param {Array} sessions - Array of sessions
|
|
@@ -194,7 +194,7 @@ class DocumentAnalyzer {
|
|
|
194
194
|
|
|
195
195
|
// Framework indicators
|
|
196
196
|
if (content.includes('BMAD')) keywords.add('bmad-method');
|
|
197
|
-
if (content.includes('
|
|
197
|
+
if (content.includes('OpenSpec')) keywords.add('openspec');
|
|
198
198
|
if (content.includes('Context Engineering')) keywords.add('context-engineering');
|
|
199
199
|
if (content.includes('AgentDevFramework')) keywords.add('adf-framework');
|
|
200
200
|
|
|
@@ -229,7 +229,7 @@ class DocumentAnalyzer {
|
|
|
229
229
|
detectFramework() {
|
|
230
230
|
const frameworkIndicators = {
|
|
231
231
|
bmad: 0,
|
|
232
|
-
|
|
232
|
+
openSpec: 0,
|
|
233
233
|
contextEngineering: 0,
|
|
234
234
|
adf: 0
|
|
235
235
|
};
|
|
@@ -246,12 +246,12 @@ class DocumentAnalyzer {
|
|
|
246
246
|
if (doc.type === 'prd') frameworkIndicators.bmad += 1;
|
|
247
247
|
if (doc.type === 'story' && doc.size > 5000) frameworkIndicators.bmad += 2; // Large stories with context
|
|
248
248
|
|
|
249
|
-
// Specification-Driven (
|
|
250
|
-
if (doc.keywords.includes('
|
|
251
|
-
if (doc.keywords.includes('specification-driven')) frameworkIndicators.
|
|
252
|
-
if (doc.type === 'constitution') frameworkIndicators.
|
|
253
|
-
if (doc.type === 'spec') frameworkIndicators.
|
|
254
|
-
if (doc.metadata.status && doc.metadata.status.match(/draft|review|approved/i)) frameworkIndicators.
|
|
249
|
+
// Specification-Driven (OpenSpec) indicators
|
|
250
|
+
if (doc.keywords.includes('openspec')) frameworkIndicators.openSpec += 5;
|
|
251
|
+
if (doc.keywords.includes('specification-driven')) frameworkIndicators.openSpec += 5; if (doc.keywords.includes('constitutional')) frameworkIndicators.openSpec += 3;
|
|
252
|
+
if (doc.type === 'constitution') frameworkIndicators.openSpec += 4;
|
|
253
|
+
if (doc.type === 'spec') frameworkIndicators.openSpec += 2;
|
|
254
|
+
if (doc.metadata.status && doc.metadata.status.match(/draft|review|approved/i)) frameworkIndicators.openSpec += 1;
|
|
255
255
|
|
|
256
256
|
// Context Engineering indicators
|
|
257
257
|
if (doc.keywords.includes('context-engineering')) frameworkIndicators.contextEngineering += 5;
|
|
@@ -328,7 +328,7 @@ class DocumentAnalyzer {
|
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
//
|
|
331
|
+
// OpenSpec workflow: Has specs with structured workflow
|
|
332
332
|
if (docCounts.spec > 0 && docCounts.plan > 0) {
|
|
333
333
|
this.analysis.detectedWorkflow = 'spec-driven';
|
|
334
334
|
return;
|
|
@@ -397,11 +397,11 @@ class DocumentAnalyzer {
|
|
|
397
397
|
priority: 'medium',
|
|
398
398
|
message: 'BMAD framework detected. Continue using agent-based workflow. Consider adding missing agents if needed.'
|
|
399
399
|
});
|
|
400
|
-
} else if (existingFramework === '
|
|
400
|
+
} else if (existingFramework === 'openSpec') {
|
|
401
401
|
this.analysis.recommendations.push({
|
|
402
402
|
type: 'framework',
|
|
403
403
|
priority: 'medium',
|
|
404
|
-
message: '
|
|
404
|
+
message: 'OpenSpec framework detected. Continue using specification-driven workflow. Ensure constitutional compliance.'
|
|
405
405
|
});
|
|
406
406
|
} else if (existingFramework === 'contextEngineering') {
|
|
407
407
|
this.analysis.recommendations.push({
|
|
@@ -343,7 +343,7 @@ class FrameworkBuilder {
|
|
|
343
343
|
'QUICK-START.md',
|
|
344
344
|
'AGENTDEVFRAMEWORK-CLAUDE.md',
|
|
345
345
|
'BMAD-METHOD-CLAUDE.md',
|
|
346
|
-
'
|
|
346
|
+
'OPENSPEC-CLAUDE.md',
|
|
347
347
|
'CONTEXT-ENGINEERING-INTRO-CLAUDE.md',
|
|
348
348
|
'FRAMEWORK-ANALYSIS.md',
|
|
349
349
|
'DEPLOYMENT-STATUS.md',
|
|
@@ -12,7 +12,7 @@ const chalk = require('chalk');
|
|
|
12
12
|
|
|
13
13
|
const SUBMODULES = [
|
|
14
14
|
{ name: 'BMAD-METHOD', path: 'references/bmad-method', branch: 'main' },
|
|
15
|
-
{ name: '
|
|
15
|
+
{ name: 'OpenSpec', path: 'references/spec-kit', branch: 'main' },
|
|
16
16
|
{ name: 'Context Engineering', path: 'references/context-engineering', branch: 'main' },
|
|
17
17
|
{ name: 'PRPs Agentic', path: 'references/prps-agentic', branch: 'main' }
|
|
18
18
|
];
|
|
@@ -42,7 +42,7 @@ const FRAMEWORK_ELEMENTS = {
|
|
|
42
42
|
agents: ['Analyst', 'PM', 'Architect', 'Dev', 'QA'],
|
|
43
43
|
whenToUse: 'Complex projects, teams, enterprise, unclear requirements'
|
|
44
44
|
},
|
|
45
|
-
|
|
45
|
+
openSpec: {
|
|
46
46
|
strengths: ['Constitutional governance', 'Quality gates', 'Validation checkpoints'],
|
|
47
47
|
workflow: ['Constitution', 'Specify', 'Clarify', 'Plan', 'Tasks', 'Implement'],
|
|
48
48
|
whenToUse: 'Clear requirements, validation needed, compliance-critical'
|
|
@@ -118,7 +118,7 @@ You are a Senior Full-Stack Developer focused on implementing features from deta
|
|
|
118
118
|
### Core Commands
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
|
-
/implement <story-id> # Implement a
|
|
121
|
+
/implement <story-id> # Implement a story from PRD
|
|
122
122
|
/test # Run tests and analyze results
|
|
123
123
|
/review # Perform self-code-review
|
|
124
124
|
/commit # Create well-formatted commit
|
|
@@ -50,7 +50,7 @@ You are a Product Manager focused on creating comprehensive Product Requirements
|
|
|
50
50
|
|
|
51
51
|
## Primary Responsibilities
|
|
52
52
|
|
|
53
|
-
### 1. PRD Creation (Path A:
|
|
53
|
+
### 1. PRD Creation (Path A: Greenfield)
|
|
54
54
|
|
|
55
55
|
**Create comprehensive Product Requirements Document**:
|
|
56
56
|
- Executive Summary
|
|
@@ -672,4 +672,4 @@ PRD complete. Ready for architect review.
|
|
|
672
672
|
|
|
673
673
|
**Agent Version**: 1.0.0
|
|
674
674
|
**Framework**: AgentDevFramework
|
|
675
|
-
**Methodology**:
|
|
675
|
+
**Methodology**: BMAD-METHOD + Agile Best Practices
|
|
@@ -537,4 +537,4 @@ Recommendation: Implement rate limiting and CAPTCHA before beginning development
|
|
|
537
537
|
|
|
538
538
|
**Agent Version**: 1.0.0
|
|
539
539
|
**Framework**: AgentDevFramework
|
|
540
|
-
**Methodology**:
|
|
540
|
+
**Methodology**: BMAD-METHOD QA + Risk-Based Testing Best Practices
|
|
@@ -67,7 +67,7 @@ You are Samira, a Scrum Master focused on creating implementation-ready stories
|
|
|
67
67
|
|
|
68
68
|
## Story Creation Workflow
|
|
69
69
|
|
|
70
|
-
### 1. Context Gathering (Path A:
|
|
70
|
+
### 1. Context Gathering (Path A: Greenfield)
|
|
71
71
|
|
|
72
72
|
```
|
|
73
73
|
1. Read Planning Phase Outputs
|
|
@@ -77,7 +77,7 @@ You are Samira, a Scrum Master focused on creating implementation-ready stories
|
|
|
77
77
|
- Related stories
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
### 2. Task Breakdown (
|
|
80
|
+
### 2. Task Breakdown (Path B: OpenSpec)
|
|
81
81
|
|
|
82
82
|
```
|
|
83
83
|
1. Read Change Context
|
|
@@ -785,4 +785,4 @@ Would you like me to:
|
|
|
785
785
|
|
|
786
786
|
**Agent Version**: 1.0.0
|
|
787
787
|
**Framework**: AgentDevFramework
|
|
788
|
-
**Methodology**:
|
|
788
|
+
**Methodology**: BMAD-METHOD + Context Engineering + Agile Best Practices
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Development Constitution
|
|
2
2
|
|
|
3
|
-
This document defines the immutable principles governing all development in this project. These principles synthesize best practices from
|
|
3
|
+
This document defines the immutable principles governing all development in this project. These principles synthesize best practices from BMAD-METHOD, OpenSpec, and Context Engineering methodologies.
|
|
4
4
|
|
|
5
5
|
## Article 1: Specification-First Development
|
|
6
6
|
|
|
@@ -287,6 +287,6 @@ This constitution is enforced through:
|
|
|
287
287
|
|
|
288
288
|
**Version**: 1.0.0
|
|
289
289
|
**Last Updated**: 2025-01-XX
|
|
290
|
-
**Source**: Synthesized from
|
|
290
|
+
**Source**: Synthesized from BMAD-METHOD, OpenSpec, Context Engineering
|
|
291
291
|
|
|
292
292
|
**Living Document**: This constitution evolves with project needs while maintaining core principles.
|