@iservu-inc/adf-cli 0.10.0 → 0.12.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/.adf/feature-audit.md +208 -0
- package/.adf/final-summary.md +347 -0
- package/.adf/implementation-plan.md +244 -0
- package/.adf/implementation-progress.md +203 -0
- package/.adf/learning/answer-history.json +995 -0
- package/.adf/learning/config.json +25 -0
- package/.adf/learning/learned-rules.json +59 -0
- package/.adf/learning/patterns.json +277 -0
- package/.adf/learning/skip-history.json +1451 -0
- package/.adf/learning/stats.json +9 -0
- package/.claude/settings.local.json +10 -1
- package/.project/chats/current/SESSION-STATUS.md +102 -73
- package/.project/docs/ROADMAP.md +47 -32
- package/.project/docs/designs/LEARNING-ANALYTICS-DASHBOARD.md +1383 -0
- package/CHANGELOG.md +341 -0
- package/CLAUDE.md +479 -0
- package/README.md +7 -1
- package/lib/commands/deploy.js +42 -2
- package/lib/generators/agents-md-generator.js +431 -161
- package/lib/generators/antigravity-generator.js +140 -0
- package/lib/generators/index.js +22 -0
- package/lib/generators/zed-generator.js +252 -0
- package/lib/learning/analytics-exporter.js +241 -0
- package/lib/learning/analytics-view.js +508 -0
- package/lib/learning/analytics.js +681 -0
- package/lib/learning/learning-manager.js +19 -6
- package/lib/templates/shared/agents/architect.md +24 -24
- package/lib/templates/shared/agents/dev.md +25 -20
- package/lib/templates/shared/agents/pm.md +14 -4
- package/lib/templates/shared/agents/sm.md +18 -14
- package/lib/templates/shared/templates/openspec-delta.md +16 -0
- package/lib/templates/shared/templates/openspec-proposal.md +18 -0
- package/lib/templates/shared/templates/openspec-tasks.md +21 -0
- package/lib/utils/context-manager.js +484 -0
- package/package.json +6 -1
- package/scripts/generate-test-data.js +557 -0
- package/tests/agents-md-generator.test.js +47 -10
- package/tests/analytics-exporter.test.js +477 -0
- package/tests/analytics-view.test.js +466 -0
- package/tests/analytics.test.js +712 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ToolConfigGenerator = require('./tool-config-generator');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generator for Google Antigravity configurations
|
|
7
|
+
* Creates .antigravity/agents.yaml that mounts AGENTS.md
|
|
8
|
+
* Provides file system access to .context/ directory
|
|
9
|
+
*/
|
|
10
|
+
class AntigravityGenerator extends ToolConfigGenerator {
|
|
11
|
+
/**
|
|
12
|
+
* Generate Antigravity configurations
|
|
13
|
+
* @returns {Object} Generated file paths
|
|
14
|
+
*/
|
|
15
|
+
async generate() {
|
|
16
|
+
await this.initialize();
|
|
17
|
+
|
|
18
|
+
const antigravityDir = path.join(this.projectPath, '.antigravity');
|
|
19
|
+
await fs.ensureDir(antigravityDir);
|
|
20
|
+
|
|
21
|
+
const generated = {
|
|
22
|
+
agents: null
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Generate agents.yaml
|
|
26
|
+
generated.agents = await this.generateAgentsYaml(antigravityDir);
|
|
27
|
+
|
|
28
|
+
return generated;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generate .antigravity/agents.yaml
|
|
33
|
+
*/
|
|
34
|
+
async generateAgentsYaml(antigravityDir) {
|
|
35
|
+
const agentsPath = path.join(antigravityDir, 'agents.yaml');
|
|
36
|
+
|
|
37
|
+
const agentName = this.getAgentName();
|
|
38
|
+
const model = this.getModelForFramework();
|
|
39
|
+
|
|
40
|
+
// Generate YAML content
|
|
41
|
+
const yamlContent = `# Antigravity Agent Configuration
|
|
42
|
+
# Generated by ADF CLI v${this.getADFVersion()}
|
|
43
|
+
# Framework: ${this.getFrameworkName()}
|
|
44
|
+
|
|
45
|
+
agents:
|
|
46
|
+
- name: "${agentName}"
|
|
47
|
+
model: "${model}"
|
|
48
|
+
capabilities:
|
|
49
|
+
- file_system:
|
|
50
|
+
read_only:
|
|
51
|
+
- "AGENTS.md"
|
|
52
|
+
- ".context"
|
|
53
|
+
- ".adf/sessions"
|
|
54
|
+
exclude:
|
|
55
|
+
- ".adf/.env"
|
|
56
|
+
- "node_modules"
|
|
57
|
+
- ".git"
|
|
58
|
+
system_prompt: |
|
|
59
|
+
You are ${agentName}, an expert AI coding assistant.
|
|
60
|
+
|
|
61
|
+
CRITICAL INSTRUCTIONS:
|
|
62
|
+
1. Read "AGENTS.md" IMMEDIATELY upon startup to understand project rules and structure
|
|
63
|
+
2. Consult ".context/memory/architecture.md" for system design decisions
|
|
64
|
+
3. Follow all operational rules defined in AGENTS.md (they are NON-NEGOTIABLE)
|
|
65
|
+
4. Never output secrets from .env files or expose API keys
|
|
66
|
+
5. All code changes must pass the test suite defined in AGENTS.md
|
|
67
|
+
|
|
68
|
+
WORKFLOW:
|
|
69
|
+
- Before any code change, read AGENTS.md and .context/memory/architecture.md
|
|
70
|
+
- Follow the build & test commands specified in AGENTS.md
|
|
71
|
+
- Adhere to the project structure and workflow directives
|
|
72
|
+
- Ask clarifying questions if requirements are unclear
|
|
73
|
+
|
|
74
|
+
PROJECT CONTEXT:
|
|
75
|
+
- Framework: ${this.getFrameworkName()}
|
|
76
|
+
- Session: ${this.getSessionId()}
|
|
77
|
+
- Complete requirements: .adf/sessions/${this.getSessionId()}/outputs/
|
|
78
|
+
|
|
79
|
+
Remember: AGENTS.md is your single source of truth. Read it first, follow it always.
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
await fs.writeFile(agentsPath, yamlContent, 'utf-8');
|
|
83
|
+
return agentsPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get agent name based on framework
|
|
88
|
+
*/
|
|
89
|
+
getAgentName() {
|
|
90
|
+
const projectName = this.getProjectName();
|
|
91
|
+
|
|
92
|
+
const roleMap = {
|
|
93
|
+
'rapid': 'Developer',
|
|
94
|
+
'balanced': 'Project Architect',
|
|
95
|
+
'comprehensive': 'Solutions Architect'
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const role = roleMap[this.framework] || 'Developer';
|
|
99
|
+
return `${projectName} ${role}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get appropriate model for framework complexity
|
|
104
|
+
*/
|
|
105
|
+
getModelForFramework() {
|
|
106
|
+
const modelMap = {
|
|
107
|
+
'rapid': 'gemini-2.0-flash-exp',
|
|
108
|
+
'balanced': 'gemini-2.0-flash-thinking-exp',
|
|
109
|
+
'comprehensive': 'gemini-exp-1206'
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return modelMap[this.framework] || 'gemini-2.0-flash-exp';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get framework display name
|
|
117
|
+
*/
|
|
118
|
+
getFrameworkName() {
|
|
119
|
+
const names = {
|
|
120
|
+
'rapid': 'Rapid Development (PRP)',
|
|
121
|
+
'balanced': 'Balanced (Specification-Driven)',
|
|
122
|
+
'comprehensive': 'BMAD Comprehensive (Enterprise)'
|
|
123
|
+
};
|
|
124
|
+
return names[this.framework] || this.framework;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get ADF CLI version
|
|
129
|
+
*/
|
|
130
|
+
getADFVersion() {
|
|
131
|
+
try {
|
|
132
|
+
const packageJson = require('../../package.json');
|
|
133
|
+
return packageJson.version;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return '0.11.0';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = AntigravityGenerator;
|
package/lib/generators/index.js
CHANGED
|
@@ -7,6 +7,8 @@ const AgentsMdGenerator = require('./agents-md-generator');
|
|
|
7
7
|
const WindsurfGenerator = require('./windsurf-generator');
|
|
8
8
|
const CursorGenerator = require('./cursor-generator');
|
|
9
9
|
const VSCodeGenerator = require('./vscode-generator');
|
|
10
|
+
const ZedGenerator = require('./zed-generator');
|
|
11
|
+
const AntigravityGenerator = require('./antigravity-generator');
|
|
10
12
|
const ToolConfigGenerator = require('./tool-config-generator');
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -84,15 +86,35 @@ async function generateVSCode(sessionPath, projectPath, framework) {
|
|
|
84
86
|
return await generator.generate();
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Generate Zed Editor configurations
|
|
91
|
+
*/
|
|
92
|
+
async function generateZed(sessionPath, projectPath, framework) {
|
|
93
|
+
const generator = new ZedGenerator(sessionPath, projectPath, framework);
|
|
94
|
+
return await generator.generate();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate Google Antigravity configurations
|
|
99
|
+
*/
|
|
100
|
+
async function generateAntigravity(sessionPath, projectPath, framework) {
|
|
101
|
+
const generator = new AntigravityGenerator(sessionPath, projectPath, framework);
|
|
102
|
+
return await generator.generate();
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
module.exports = {
|
|
88
106
|
generateAll,
|
|
89
107
|
generateAgentsMd,
|
|
90
108
|
generateWindsurf,
|
|
91
109
|
generateCursor,
|
|
92
110
|
generateVSCode,
|
|
111
|
+
generateZed,
|
|
112
|
+
generateAntigravity,
|
|
93
113
|
AgentsMdGenerator,
|
|
94
114
|
WindsurfGenerator,
|
|
95
115
|
CursorGenerator,
|
|
96
116
|
VSCodeGenerator,
|
|
117
|
+
ZedGenerator,
|
|
118
|
+
AntigravityGenerator,
|
|
97
119
|
ToolConfigGenerator
|
|
98
120
|
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ToolConfigGenerator = require('./tool-config-generator');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generator for Zed Editor configurations
|
|
7
|
+
* Creates .zed/settings.json with MCP integration and agent configuration
|
|
8
|
+
* Creates .zed/keymap.json for agent switching shortcuts
|
|
9
|
+
* Creates symlink .zed/rules -> ../AGENTS.md (or copies on Windows)
|
|
10
|
+
*/
|
|
11
|
+
class ZedGenerator extends ToolConfigGenerator {
|
|
12
|
+
/**
|
|
13
|
+
* Generate Zed Editor configurations
|
|
14
|
+
* @returns {Object} Generated file paths
|
|
15
|
+
*/
|
|
16
|
+
async generate() {
|
|
17
|
+
await this.initialize();
|
|
18
|
+
|
|
19
|
+
const zedDir = path.join(this.projectPath, '.zed');
|
|
20
|
+
await fs.ensureDir(zedDir);
|
|
21
|
+
|
|
22
|
+
const generated = {
|
|
23
|
+
settings: null,
|
|
24
|
+
keymap: null,
|
|
25
|
+
rules: null
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Generate settings.json
|
|
29
|
+
generated.settings = await this.generateSettings(zedDir);
|
|
30
|
+
|
|
31
|
+
// Generate keymap.json
|
|
32
|
+
generated.keymap = await this.generateKeymap(zedDir);
|
|
33
|
+
|
|
34
|
+
// Create symlink or copy AGENTS.md
|
|
35
|
+
generated.rules = await this.linkAgentsMd(zedDir);
|
|
36
|
+
|
|
37
|
+
return generated;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate .zed/settings.json with MCP and agent configuration
|
|
42
|
+
*/
|
|
43
|
+
async generateSettings(zedDir) {
|
|
44
|
+
const settingsPath = path.join(zedDir, 'settings.json');
|
|
45
|
+
|
|
46
|
+
// Determine model configuration based on framework
|
|
47
|
+
const modelConfig = this.getModelConfiguration();
|
|
48
|
+
|
|
49
|
+
const settings = {
|
|
50
|
+
agent: {
|
|
51
|
+
default_model: modelConfig.default,
|
|
52
|
+
feature_specific_models: modelConfig.featureSpecific,
|
|
53
|
+
model_parameters: modelConfig.parameters,
|
|
54
|
+
always_allow_tool_actions: true,
|
|
55
|
+
single_file_review: true
|
|
56
|
+
},
|
|
57
|
+
context_servers: this.getMCPContextServers(),
|
|
58
|
+
terminal: {
|
|
59
|
+
env: this.getTerminalEnvironment()
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
await fs.writeJson(settingsPath, settings, { spaces: 2 });
|
|
64
|
+
return settingsPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get model configuration based on framework complexity
|
|
69
|
+
*/
|
|
70
|
+
getModelConfiguration() {
|
|
71
|
+
const configs = {
|
|
72
|
+
rapid: {
|
|
73
|
+
default: {
|
|
74
|
+
provider: 'anthropic',
|
|
75
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
76
|
+
},
|
|
77
|
+
featureSpecific: {
|
|
78
|
+
thread_summary_model: {
|
|
79
|
+
provider: 'google',
|
|
80
|
+
model: 'gemini-2.0-flash-exp'
|
|
81
|
+
},
|
|
82
|
+
commit_message_model: {
|
|
83
|
+
provider: 'google',
|
|
84
|
+
model: 'gemini-2.0-flash-exp'
|
|
85
|
+
},
|
|
86
|
+
inline_assistant_model: {
|
|
87
|
+
provider: 'anthropic',
|
|
88
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
parameters: [
|
|
92
|
+
{ provider: 'anthropic', temperature: 0.1 },
|
|
93
|
+
{ provider: 'google', temperature: 0.2 }
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
balanced: {
|
|
97
|
+
default: {
|
|
98
|
+
provider: 'anthropic',
|
|
99
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
100
|
+
},
|
|
101
|
+
featureSpecific: {
|
|
102
|
+
thread_summary_model: {
|
|
103
|
+
provider: 'google',
|
|
104
|
+
model: 'gemini-2.0-flash-exp'
|
|
105
|
+
},
|
|
106
|
+
commit_message_model: {
|
|
107
|
+
provider: 'google',
|
|
108
|
+
model: 'gemini-2.0-flash-exp'
|
|
109
|
+
},
|
|
110
|
+
inline_assistant_model: {
|
|
111
|
+
provider: 'anthropic',
|
|
112
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
parameters: [
|
|
116
|
+
{ provider: 'anthropic', temperature: 0.1 },
|
|
117
|
+
{ provider: 'google', temperature: 0.2 }
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
comprehensive: {
|
|
121
|
+
default: {
|
|
122
|
+
provider: 'anthropic',
|
|
123
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
124
|
+
},
|
|
125
|
+
featureSpecific: {
|
|
126
|
+
thread_summary_model: {
|
|
127
|
+
provider: 'anthropic',
|
|
128
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
129
|
+
},
|
|
130
|
+
commit_message_model: {
|
|
131
|
+
provider: 'anthropic',
|
|
132
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
133
|
+
},
|
|
134
|
+
inline_assistant_model: {
|
|
135
|
+
provider: 'anthropic',
|
|
136
|
+
model: 'claude-3-5-sonnet-20241022'
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
parameters: [
|
|
140
|
+
{ provider: 'anthropic', temperature: 0.05 }
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return configs[this.framework] || configs.balanced;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get MCP context servers configuration
|
|
150
|
+
*/
|
|
151
|
+
getMCPContextServers() {
|
|
152
|
+
const servers = {
|
|
153
|
+
project_context: {
|
|
154
|
+
command: 'npx',
|
|
155
|
+
args: ['@modelcontextprotocol/server-filesystem', '.context']
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Add Archon if configured
|
|
160
|
+
const archonUrl = process.env.ARCHON_MCP_URL || this.metadata?.archonUrl;
|
|
161
|
+
if (archonUrl) {
|
|
162
|
+
servers.archon = {
|
|
163
|
+
url: archonUrl
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return servers;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get terminal environment variables
|
|
172
|
+
*/
|
|
173
|
+
getTerminalEnvironment() {
|
|
174
|
+
const env = {};
|
|
175
|
+
|
|
176
|
+
// Add ARCHON_MCP_PORT if configured
|
|
177
|
+
if (process.env.ARCHON_MCP_URL) {
|
|
178
|
+
const url = new URL(process.env.ARCHON_MCP_URL);
|
|
179
|
+
if (url.port) {
|
|
180
|
+
env.ARCHON_MCP_PORT = url.port;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return env;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Generate .zed/keymap.json with agent switching shortcuts
|
|
189
|
+
*/
|
|
190
|
+
async generateKeymap(zedDir) {
|
|
191
|
+
const keymapPath = path.join(zedDir, 'keymap.json');
|
|
192
|
+
|
|
193
|
+
const keymap = [
|
|
194
|
+
{
|
|
195
|
+
context: 'Editor',
|
|
196
|
+
bindings: {
|
|
197
|
+
'ctrl-shift-a': 'assistant::Toggle',
|
|
198
|
+
'ctrl-shift-c': 'assistant::CycleMessageRole',
|
|
199
|
+
'ctrl-shift-r': 'assistant::Restart'
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
context: 'AssistantPanel',
|
|
204
|
+
bindings: {
|
|
205
|
+
'ctrl-enter': 'assistant::Assist',
|
|
206
|
+
'escape': 'assistant::Hide',
|
|
207
|
+
'ctrl-shift-n': 'assistant::NewConversation'
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
await fs.writeJson(keymapPath, keymap, { spaces: 2 });
|
|
213
|
+
return keymapPath;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create symlink .zed/rules -> ../AGENTS.md
|
|
218
|
+
* Falls back to file copy on Windows if symlink fails
|
|
219
|
+
*/
|
|
220
|
+
async linkAgentsMd(zedDir) {
|
|
221
|
+
const rulesPath = path.join(zedDir, 'rules');
|
|
222
|
+
const agentsMdPath = path.join(this.projectPath, 'AGENTS.md');
|
|
223
|
+
|
|
224
|
+
// Remove existing link/file if present
|
|
225
|
+
if (await fs.pathExists(rulesPath)) {
|
|
226
|
+
await fs.remove(rulesPath);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check if AGENTS.md exists
|
|
230
|
+
if (!await fs.pathExists(agentsMdPath)) {
|
|
231
|
+
console.warn('AGENTS.md not found, skipping Zed rules link');
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
// Try to create symlink (Unix/Mac/modern Windows)
|
|
237
|
+
await fs.symlink('../AGENTS.md', rulesPath, 'file');
|
|
238
|
+
return { type: 'symlink', path: rulesPath };
|
|
239
|
+
} catch (error) {
|
|
240
|
+
// Symlink failed (older Windows or permissions), copy file instead
|
|
241
|
+
try {
|
|
242
|
+
await fs.copy(agentsMdPath, rulesPath);
|
|
243
|
+
return { type: 'copy', path: rulesPath };
|
|
244
|
+
} catch (copyError) {
|
|
245
|
+
console.warn(`Failed to create Zed rules link: ${copyError.message}`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = ZedGenerator;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Learning Analytics Exporter
|
|
6
|
+
*
|
|
7
|
+
* Handles exporting analytics data in multiple formats:
|
|
8
|
+
* - JSON: Single comprehensive file with all analytics
|
|
9
|
+
* - CSV: Multiple files for different metrics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Export analytics data
|
|
14
|
+
* @param {Object} analyticsData - Complete analytics data
|
|
15
|
+
* @param {string} projectPath - Project root path
|
|
16
|
+
* @param {string} format - Export format ('json' or 'csv')
|
|
17
|
+
* @returns {Promise<Object>} Export result with file paths
|
|
18
|
+
*/
|
|
19
|
+
async function exportAnalytics(analyticsData, projectPath, format = 'json') {
|
|
20
|
+
const learningPath = path.join(projectPath, '.adf', 'learning');
|
|
21
|
+
const exportsPath = path.join(learningPath, 'exports');
|
|
22
|
+
|
|
23
|
+
// Ensure exports directory exists
|
|
24
|
+
await fs.ensureDir(exportsPath);
|
|
25
|
+
|
|
26
|
+
if (format === 'json') {
|
|
27
|
+
return await exportJSON(analyticsData, exportsPath);
|
|
28
|
+
} else if (format === 'csv') {
|
|
29
|
+
return await exportCSV(analyticsData, exportsPath);
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Export analytics as JSON
|
|
37
|
+
* @param {Object} analyticsData - Analytics data
|
|
38
|
+
* @param {string} exportsPath - Exports directory path
|
|
39
|
+
* @returns {Promise<Object>} Export result
|
|
40
|
+
*/
|
|
41
|
+
async function exportJSON(analyticsData, exportsPath) {
|
|
42
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
43
|
+
const filename = `analytics-${timestamp}.json`;
|
|
44
|
+
const filePath = path.join(exportsPath, filename);
|
|
45
|
+
|
|
46
|
+
// Write JSON file
|
|
47
|
+
await fs.writeJSON(filePath, analyticsData, { spaces: 2 });
|
|
48
|
+
|
|
49
|
+
// Get file size
|
|
50
|
+
const stats = await fs.stat(filePath);
|
|
51
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
file: filePath,
|
|
55
|
+
size: `${sizeKB} KB`,
|
|
56
|
+
format: 'json'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Export analytics as CSV (multiple files)
|
|
62
|
+
* @param {Object} analyticsData - Analytics data
|
|
63
|
+
* @param {string} exportsPath - Exports directory path
|
|
64
|
+
* @returns {Promise<Object>} Export result
|
|
65
|
+
*/
|
|
66
|
+
async function exportCSV(analyticsData, exportsPath) {
|
|
67
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
68
|
+
const files = [];
|
|
69
|
+
|
|
70
|
+
// Export overview
|
|
71
|
+
const overviewFile = path.join(exportsPath, `analytics-overview-${timestamp}.csv`);
|
|
72
|
+
await fs.writeFile(overviewFile, generateOverviewCSV(analyticsData.overview));
|
|
73
|
+
files.push(overviewFile);
|
|
74
|
+
|
|
75
|
+
// Export skip trends
|
|
76
|
+
const trendsFile = path.join(exportsPath, `analytics-skip-trends-${timestamp}.csv`);
|
|
77
|
+
await fs.writeFile(trendsFile, generateSkipTrendsCSV(analyticsData.skipTrends));
|
|
78
|
+
files.push(trendsFile);
|
|
79
|
+
|
|
80
|
+
// Export category preferences
|
|
81
|
+
const categoriesFile = path.join(exportsPath, `analytics-categories-${timestamp}.csv`);
|
|
82
|
+
await fs.writeFile(categoriesFile, generateCategoriesCSV(analyticsData.categoryPreferences));
|
|
83
|
+
files.push(categoriesFile);
|
|
84
|
+
|
|
85
|
+
// Export patterns
|
|
86
|
+
const patternsFile = path.join(exportsPath, `analytics-patterns-${timestamp}.csv`);
|
|
87
|
+
await fs.writeFile(patternsFile, generatePatternsCSV(analyticsData.patternDistribution, analyticsData.decayStatus));
|
|
88
|
+
files.push(patternsFile);
|
|
89
|
+
|
|
90
|
+
// Export effectiveness
|
|
91
|
+
const effectivenessFile = path.join(exportsPath, `analytics-effectiveness-${timestamp}.csv`);
|
|
92
|
+
await fs.writeFile(effectivenessFile, generateEffectivenessCSV(analyticsData.effectiveness));
|
|
93
|
+
files.push(effectivenessFile);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
files,
|
|
97
|
+
format: 'csv'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generate overview CSV
|
|
103
|
+
* @param {Object} overview - Overview data
|
|
104
|
+
* @returns {string} CSV content
|
|
105
|
+
*/
|
|
106
|
+
function generateOverviewCSV(overview) {
|
|
107
|
+
const rows = [
|
|
108
|
+
['Metric', 'Value', 'Unit'],
|
|
109
|
+
['Total Sessions', overview.totalSessions, 'sessions'],
|
|
110
|
+
['Total Skips', overview.totalSkips, 'questions'],
|
|
111
|
+
['Manual Skips', overview.manualSkips, 'questions'],
|
|
112
|
+
['Filtered Skips', overview.filteredSkips, 'questions'],
|
|
113
|
+
['Total Answers', overview.totalAnswers, 'questions'],
|
|
114
|
+
['Active Patterns', overview.activePatterns, 'patterns'],
|
|
115
|
+
['Active Rules', overview.activeRules, 'rules'],
|
|
116
|
+
['Learning Age', overview.learningAge, 'days']
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate skip trends CSV
|
|
124
|
+
* @param {Array} skipTrends - Skip trends data
|
|
125
|
+
* @returns {string} CSV content
|
|
126
|
+
*/
|
|
127
|
+
function generateSkipTrendsCSV(skipTrends) {
|
|
128
|
+
const rows = [
|
|
129
|
+
['Week', 'Week Number', 'Manual Skips', 'Filtered Skips', 'Total Skips', 'Skip Rate (%)']
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
for (const trend of skipTrends) {
|
|
133
|
+
rows.push([
|
|
134
|
+
trend.week,
|
|
135
|
+
trend.weekNumber,
|
|
136
|
+
trend.manualSkips,
|
|
137
|
+
trend.filteredSkips,
|
|
138
|
+
trend.totalSkips,
|
|
139
|
+
trend.skipRate
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate categories CSV
|
|
148
|
+
* @param {Array} categories - Category preferences data
|
|
149
|
+
* @returns {string} CSV content
|
|
150
|
+
*/
|
|
151
|
+
function generateCategoriesCSV(categories) {
|
|
152
|
+
const rows = [
|
|
153
|
+
['Category', 'Skips', 'Answers', 'Total', 'Skip Rate (%)', 'Level']
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
for (const cat of categories) {
|
|
157
|
+
rows.push([
|
|
158
|
+
escapeCSV(cat.category),
|
|
159
|
+
cat.skips,
|
|
160
|
+
cat.answers,
|
|
161
|
+
cat.total,
|
|
162
|
+
cat.skipRate,
|
|
163
|
+
cat.level
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate patterns CSV
|
|
172
|
+
* @param {Object} patternDistribution - Pattern distribution data
|
|
173
|
+
* @param {Object} decayStatus - Decay status data
|
|
174
|
+
* @returns {string} CSV content
|
|
175
|
+
*/
|
|
176
|
+
function generatePatternsCSV(patternDistribution, decayStatus) {
|
|
177
|
+
const dist = patternDistribution.distribution || { high: 0, medium: 0, low: 0, veryLow: 0 };
|
|
178
|
+
const decay = decayStatus || { healthy: 0, warning: 0, critical: 0, recentlyRenewed: 0, avgAge: 0 };
|
|
179
|
+
|
|
180
|
+
const rows = [
|
|
181
|
+
['Metric', 'Value', 'Description'],
|
|
182
|
+
['Total Patterns', patternDistribution.totalPatterns || 0, 'All patterns'],
|
|
183
|
+
['High Confidence (90%+)', dist.high, 'Very strong patterns'],
|
|
184
|
+
['Medium Confidence (75-89%)', dist.medium, 'Strong patterns'],
|
|
185
|
+
['Low Confidence (50-74%)', dist.low, 'Weak patterns'],
|
|
186
|
+
['Very Low Confidence (<50%)', dist.veryLow, 'Very weak patterns'],
|
|
187
|
+
['Average Confidence', (patternDistribution.avgConfidence || 0) + '%', 'Mean confidence score'],
|
|
188
|
+
['At Risk', patternDistribution.atRiskCount || 0, 'Patterns at risk of removal'],
|
|
189
|
+
['', '', ''],
|
|
190
|
+
['Decay Status', '', ''],
|
|
191
|
+
['Healthy Patterns', decay.healthy, 'Active and strong'],
|
|
192
|
+
['Warning Patterns', decay.warning, 'Needs attention'],
|
|
193
|
+
['Critical Patterns', decay.critical, 'May be removed soon'],
|
|
194
|
+
['Recently Renewed', decay.recentlyRenewed, 'Last 30 days'],
|
|
195
|
+
['Average Age', decay.avgAge + ' days', 'Mean pattern age']
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Generate effectiveness CSV
|
|
203
|
+
* @param {Object} effectiveness - Effectiveness data
|
|
204
|
+
* @returns {string} CSV content
|
|
205
|
+
*/
|
|
206
|
+
function generateEffectivenessCSV(effectiveness) {
|
|
207
|
+
const rows = [
|
|
208
|
+
['Metric', 'Value', 'Unit'],
|
|
209
|
+
['Time Saved', effectiveness.timeSavedMinutes, 'minutes'],
|
|
210
|
+
['Time Saved (Hours)', effectiveness.timeSavedHours, 'hours'],
|
|
211
|
+
['Questions Filtered', effectiveness.questionsFiltered, 'questions'],
|
|
212
|
+
['False Positives', effectiveness.falsePositives, 'questions'],
|
|
213
|
+
['False Positive Rate', effectiveness.falsePositiveRate, '%'],
|
|
214
|
+
['Pattern Accuracy', effectiveness.patternAccuracy, '%'],
|
|
215
|
+
['Total Rule Applications', effectiveness.totalRuleApplications, 'applications'],
|
|
216
|
+
['Avg Applications Per Rule', effectiveness.avgApplicationsPerRule, 'applications'],
|
|
217
|
+
['Overall Effectiveness', effectiveness.overallEffectiveness, '%']
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Escape CSV value
|
|
225
|
+
* @param {string} value - Value to escape
|
|
226
|
+
* @returns {string} Escaped value
|
|
227
|
+
*/
|
|
228
|
+
function escapeCSV(value) {
|
|
229
|
+
if (typeof value !== 'string') return value;
|
|
230
|
+
|
|
231
|
+
// Escape quotes and wrap in quotes if contains special chars
|
|
232
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
233
|
+
return '"' + value.replace(/"/g, '""') + '"';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
exportAnalytics
|
|
241
|
+
};
|