create-byan-agent 2.0.1 → 2.1.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/API-BYAN-V2.md +741 -0
- package/BMAD-QUICK-REFERENCE.md +370 -0
- package/CHANGELOG-v2.1.0.md +371 -0
- package/LICENSE +1 -1
- package/MIGRATION-v2.0-to-v2.1.md +430 -0
- package/README-BYAN-V2.md +446 -0
- package/README.md +264 -201
- package/install/.eslintrc.js +20 -0
- package/install/.prettierrc +7 -0
- package/install/BUGFIX-CHALK.md +173 -0
- package/install/BUGFIX-DOCUMENTATION-INDEX.md +299 -0
- package/install/BUGFIX-PATH-RESOLUTION.md +293 -0
- package/install/BUGFIX-QUICKSTART.md +184 -0
- package/install/BUGFIX-SUMMARY.txt +91 -0
- package/install/BUGFIX-VISUAL-SUMMARY.md +253 -0
- package/install/DEPLOYMENT-GUIDE-V2.md +431 -0
- package/install/DOCS-INDEX.md +261 -0
- package/install/GUIDE-INSTALLATION-BYAN-SIMPLE.md +1083 -0
- package/install/INSTALLER-V2-CHANGES.md +472 -0
- package/install/LICENSE +21 -0
- package/install/PUBLICATION-CHECKLIST.md +265 -0
- package/install/PUBLISH-GUIDE.md +190 -0
- package/install/QUICKSTART.md +311 -0
- package/install/README-NPM-PUBLISH.md +298 -0
- package/install/README-NPM-SHORT.md +298 -0
- package/install/README-NPM.md +433 -0
- package/install/README-RACHID.md +302 -0
- package/install/README-V2-INDEX.md +306 -0
- package/install/README.md +298 -0
- package/install/RESUME-EXECUTIF-YAN.md +408 -0
- package/install/UPDATE-SUMMARY.md +205 -0
- package/install/__tests__/integration/detection-flow.test.js +154 -0
- package/install/__tests__/platforms/claude-code.test.js +175 -0
- package/install/__tests__/platforms/codex.test.js +80 -0
- package/install/__tests__/platforms/copilot-cli.test.js +118 -0
- package/install/__tests__/platforms/vscode.test.js +67 -0
- package/install/__tests__/utils/file-utils.test.js +87 -0
- package/install/__tests__/utils/git-detector.test.js +80 -0
- package/install/__tests__/utils/logger.test.js +83 -0
- package/install/__tests__/utils/node-detector.test.js +71 -0
- package/install/__tests__/utils/os-detector.test.js +63 -0
- package/install/__tests__/utils/yaml-utils.test.js +85 -0
- package/install/__tests__/yanstaller/detector.test.js +210 -0
- package/install/coverage/clover.xml +219 -0
- package/install/coverage/coverage-final.json +13 -0
- package/install/coverage/lcov-report/base.css +224 -0
- package/install/coverage/lcov-report/block-navigation.js +87 -0
- package/install/coverage/lcov-report/favicon.png +0 -0
- package/install/coverage/lcov-report/index.html +146 -0
- package/install/coverage/lcov-report/lib/errors.js.html +268 -0
- package/install/coverage/lcov-report/lib/exit-codes.js.html +247 -0
- package/install/coverage/lcov-report/lib/index.html +131 -0
- package/install/coverage/lcov-report/lib/platforms/claude-code.js.html +343 -0
- package/install/coverage/lcov-report/lib/platforms/codex.js.html +361 -0
- package/install/coverage/lcov-report/lib/platforms/copilot-cli.js.html +454 -0
- package/install/coverage/lcov-report/lib/platforms/index.html +176 -0
- package/install/coverage/lcov-report/lib/platforms/index.js.html +127 -0
- package/install/coverage/lcov-report/lib/platforms/vscode.js.html +238 -0
- package/install/coverage/lcov-report/lib/utils/config-loader.js.html +322 -0
- package/install/coverage/lcov-report/lib/utils/file-utils.js.html +397 -0
- package/install/coverage/lcov-report/lib/utils/git-detector.js.html +190 -0
- package/install/coverage/lcov-report/lib/utils/index.html +206 -0
- package/install/coverage/lcov-report/lib/utils/logger.js.html +277 -0
- package/install/coverage/lcov-report/lib/utils/node-detector.js.html +259 -0
- package/install/coverage/lcov-report/lib/utils/os-detector.js.html +307 -0
- package/install/coverage/lcov-report/lib/utils/yaml-utils.js.html +346 -0
- package/install/coverage/lcov-report/lib/yanstaller/backuper.js.html +409 -0
- package/install/coverage/lcov-report/lib/yanstaller/detector.js.html +508 -0
- package/install/coverage/lcov-report/lib/yanstaller/index.html +236 -0
- package/install/coverage/lcov-report/lib/yanstaller/index.js.html +364 -0
- package/install/coverage/lcov-report/lib/yanstaller/installer.js.html +505 -0
- package/install/coverage/lcov-report/lib/yanstaller/interviewer.js.html +349 -0
- package/install/coverage/lcov-report/lib/yanstaller/recommender.js.html +379 -0
- package/install/coverage/lcov-report/lib/yanstaller/troubleshooter.js.html +352 -0
- package/install/coverage/lcov-report/lib/yanstaller/validator.js.html +679 -0
- package/install/coverage/lcov-report/lib/yanstaller/wizard.js.html +412 -0
- package/install/coverage/lcov-report/platforms/claude-code.js.html +343 -0
- package/install/coverage/lcov-report/platforms/codex.js.html +361 -0
- package/install/coverage/lcov-report/platforms/copilot-cli.js.html +454 -0
- package/install/coverage/lcov-report/platforms/index.html +176 -0
- package/install/coverage/lcov-report/platforms/index.js.html +127 -0
- package/install/coverage/lcov-report/platforms/vscode.js.html +238 -0
- package/install/coverage/lcov-report/prettify.css +1 -0
- package/install/coverage/lcov-report/prettify.js +2 -0
- package/install/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/install/coverage/lcov-report/sorter.js +210 -0
- package/install/coverage/lcov-report/utils/file-utils.js.html +397 -0
- package/install/coverage/lcov-report/utils/git-detector.js.html +190 -0
- package/install/coverage/lcov-report/utils/index.html +191 -0
- package/install/coverage/lcov-report/utils/logger.js.html +277 -0
- package/install/coverage/lcov-report/utils/node-detector.js.html +259 -0
- package/install/coverage/lcov-report/utils/os-detector.js.html +307 -0
- package/install/coverage/lcov-report/utils/yaml-utils.js.html +346 -0
- package/install/coverage/lcov-report/yanstaller/detector.js.html +508 -0
- package/install/coverage/lcov-report/yanstaller/index.html +116 -0
- package/install/coverage/lcov.info +414 -0
- package/install/install.sh +239 -0
- package/install/jest.config.js +33 -0
- package/install/lib/errors.js +61 -0
- package/install/lib/exit-codes.js +54 -0
- package/install/lib/platforms/claude-code.js +86 -0
- package/install/lib/platforms/codex.js +92 -0
- package/install/lib/platforms/copilot-cli.js +123 -0
- package/install/lib/platforms/index.js +14 -0
- package/install/lib/platforms/vscode.js +51 -0
- package/install/lib/utils/config-loader.js +79 -0
- package/install/lib/utils/file-utils.js +104 -0
- package/install/lib/utils/git-detector.js +35 -0
- package/install/lib/utils/logger.js +64 -0
- package/install/lib/utils/node-detector.js +58 -0
- package/install/lib/utils/os-detector.js +74 -0
- package/install/lib/utils/yaml-utils.js +87 -0
- package/install/lib/yanstaller/backuper.js +108 -0
- package/install/lib/yanstaller/detector.js +141 -0
- package/install/lib/yanstaller/index.js +93 -0
- package/install/lib/yanstaller/installer.js +140 -0
- package/install/lib/yanstaller/interviewer.js +88 -0
- package/install/lib/yanstaller/recommender.js +98 -0
- package/install/lib/yanstaller/troubleshooter.js +89 -0
- package/install/lib/yanstaller/validator.js +198 -0
- package/install/lib/yanstaller/wizard.js +109 -0
- package/install/package-npm.json +55 -0
- package/install/package.json +63 -0
- package/install/src/byan-v2/context/copilot-context.js +79 -0
- package/install/src/byan-v2/context/session-state.js +98 -0
- package/install/src/byan-v2/dispatcher/complexity-scorer.js +232 -0
- package/install/src/byan-v2/dispatcher/local-executor.js +221 -0
- package/install/src/byan-v2/dispatcher/task-router.js +122 -0
- package/install/src/byan-v2/dispatcher/task-tool-interface-mock.js +134 -0
- package/install/src/byan-v2/dispatcher/task-tool-interface.js +123 -0
- package/install/src/byan-v2/generation/agent-profile-validator.js +113 -0
- package/install/src/byan-v2/generation/profile-template.js +113 -0
- package/install/src/byan-v2/generation/templates/default-agent.md +49 -0
- package/install/src/byan-v2/generation/templates/test-template.md +1 -0
- package/install/src/byan-v2/index.js +199 -0
- package/install/src/byan-v2/observability/error-tracker.js +105 -0
- package/install/src/byan-v2/observability/logger.js +154 -0
- package/install/src/byan-v2/observability/metrics-collector.js +194 -0
- package/install/src/byan-v2/orchestrator/analysis-state.js +268 -0
- package/install/src/byan-v2/orchestrator/generation-state.js +340 -0
- package/install/src/byan-v2/orchestrator/interview-state.js +271 -0
- package/install/src/byan-v2/orchestrator/state-machine.js +204 -0
- package/install/src/core/cache/cache.js +126 -0
- package/install/src/core/context/context.js +86 -0
- package/install/src/core/dispatcher/dispatcher.js +135 -0
- package/install/src/core/worker-pool/worker-pool.js +194 -0
- package/install/src/core/workflow/workflow-executor.js +220 -0
- package/install/src/index.js +139 -0
- package/install/src/observability/dashboard/dashboard.js +191 -0
- package/install/src/observability/logger/structured-logger.js +254 -0
- package/install/src/observability/metrics/metrics-collector.js +325 -0
- package/install/switch-to-v2.sh +126 -0
- package/install/test-chalk-fix.sh +210 -0
- package/install/test-installer-v2.sh +204 -0
- package/install/test-path-resolution.sh +200 -0
- package/package.json +53 -33
- package/src/byan-v2/context/copilot-context.js +79 -0
- package/src/byan-v2/context/session-state.js +98 -0
- package/src/byan-v2/data/mantras.json +852 -0
- package/src/byan-v2/dispatcher/complexity-scorer.js +232 -0
- package/src/byan-v2/dispatcher/five-whys-analyzer.js +310 -0
- package/src/byan-v2/dispatcher/local-executor.js +221 -0
- package/src/byan-v2/dispatcher/task-router.js +122 -0
- package/src/byan-v2/dispatcher/task-tool-interface-mock.js +134 -0
- package/src/byan-v2/dispatcher/task-tool-interface.js +123 -0
- package/src/byan-v2/generation/agent-profile-validator.js +113 -0
- package/src/byan-v2/generation/mantra-validator.js +416 -0
- package/src/byan-v2/generation/profile-template.js +113 -0
- package/src/byan-v2/generation/templates/default-agent.md +49 -0
- package/src/byan-v2/generation/templates/test-template.md +1 -0
- package/src/byan-v2/index.js +652 -0
- package/src/byan-v2/integration/voice-integration.js +295 -0
- package/src/byan-v2/observability/error-tracker.js +105 -0
- package/src/byan-v2/observability/logger.js +154 -0
- package/src/byan-v2/observability/metrics-collector.js +194 -0
- package/src/byan-v2/orchestrator/active-listener.js +541 -0
- package/src/byan-v2/orchestrator/analysis-state.js +268 -0
- package/src/byan-v2/orchestrator/generation-state.js +340 -0
- package/src/byan-v2/orchestrator/glossary-builder.js +431 -0
- package/src/byan-v2/orchestrator/interview-state.js +353 -0
- package/src/byan-v2/orchestrator/state-machine.js +253 -0
- package/src/core/cache/cache.js +126 -0
- package/src/core/context/context.js +86 -0
- package/src/core/dispatcher/dispatcher.js +135 -0
- package/src/core/worker-pool/worker-pool.js +194 -0
- package/src/core/workflow/workflow-executor.js +220 -0
- package/src/index.js +139 -0
- package/src/observability/dashboard/dashboard.js +191 -0
- package/src/observability/logger/structured-logger.js +254 -0
- package/src/observability/metrics/metrics-collector.js +325 -0
- package/templates/.github/agents/bmad-agent-test-dynamic.md +0 -21
- package/templates/.github/agents/franck.md +0 -379
- /package/{CHANGELOG.md → install/CHANGELOG.md} +0 -0
- /package/{bin → install/bin}/create-byan-agent-backup.js +0 -0
- /package/{bin → install/bin}/create-byan-agent-fixed.js +0 -0
- /package/{bin → install/bin}/create-byan-agent-v2.js +0 -0
- /package/{bin → install/bin}/create-byan-agent.js +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmad-master.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmb-agent-builder.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmb-module-builder.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmb-workflow-builder.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-analyst.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-architect.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-dev.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-pm.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-quick-flow-solo-dev.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-quinn.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-sm.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-tech-writer.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-bmm-ux-designer.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-byan-test.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-byan.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-carmack.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-cis-brainstorming-coach.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-cis-creative-problem-solver.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-cis-design-thinking-coach.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-cis-innovation-strategist.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-cis-presentation-master.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-cis-storyteller.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-marc.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-patnote.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-rachid.md +0 -0
- /package/{templates → install/templates}/.github/agents/bmad-agent-tea-tea.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/agent-builder.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/byan-test.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/byan.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/marc.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/module-builder.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/patnote.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/rachid.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/agents/workflow-builder.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/data/mantras.yaml +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/data/templates.yaml +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/delete-agent-workflow.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/edit-agent-workflow.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/interview-workflow.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/quick-create-workflow.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/templates/base-agent-template.md +0 -0
- /package/{templates → install/templates}/_bmad/bmb/workflows/byan/validate-agent-workflow.md +0 -0
- /package/{templates → install/templates}/_bmad/core/agents/carmack.md +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenerationState - Story 4.4
|
|
3
|
+
* Generates agent profile in BMAD/Copilot format
|
|
4
|
+
*
|
|
5
|
+
* Format:
|
|
6
|
+
* - YAML frontmatter (name, description)
|
|
7
|
+
* - XML structure (<agent>, <persona>, <menu>, <capabilities>)
|
|
8
|
+
* - Compliant with .github/copilot/agents/ standard
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const Logger = require('../observability/logger');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
class GenerationState {
|
|
16
|
+
constructor(sessionState) {
|
|
17
|
+
if (!sessionState) {
|
|
18
|
+
throw new Error('SessionState is required');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.sessionState = sessionState;
|
|
22
|
+
this.logger = new Logger();
|
|
23
|
+
this.profileGenerated = false;
|
|
24
|
+
this.generatedProfile = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* AC2: Generate agent profile from analysis results
|
|
29
|
+
* Creates: name, description, persona, menu, capabilities
|
|
30
|
+
*
|
|
31
|
+
* @returns {Promise<string>} Agent profile content
|
|
32
|
+
*/
|
|
33
|
+
async generateProfile() {
|
|
34
|
+
this.logger.info('Starting agent profile generation');
|
|
35
|
+
|
|
36
|
+
// AC6: Try to retrieve analysis results, fallback to user responses
|
|
37
|
+
let requirements;
|
|
38
|
+
|
|
39
|
+
if (this.sessionState.analysisResults && this.sessionState.analysisResults.requirements) {
|
|
40
|
+
requirements = this.sessionState.analysisResults.requirements;
|
|
41
|
+
} else {
|
|
42
|
+
// Fallback: Generate minimal requirements from user responses
|
|
43
|
+
this.logger.warn('No analysis results, generating from user responses');
|
|
44
|
+
requirements = this._extractRequirementsFromResponses();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// AC2: Extract components from requirements
|
|
48
|
+
const agentName = this._deriveAgentName(requirements.purpose || 'custom-agent');
|
|
49
|
+
const description = this._deriveDescription(requirements.purpose || 'Custom agent');
|
|
50
|
+
const persona = this._generatePersona(requirements);
|
|
51
|
+
const menu = this._generateMenu(requirements.capabilities || []);
|
|
52
|
+
const capabilities = this._generateCapabilities(requirements);
|
|
53
|
+
|
|
54
|
+
// AC1 & AC3: Build profile with YAML frontmatter + XML
|
|
55
|
+
this.generatedProfile = this._buildProfile({
|
|
56
|
+
name: agentName,
|
|
57
|
+
description,
|
|
58
|
+
persona,
|
|
59
|
+
menu,
|
|
60
|
+
capabilities,
|
|
61
|
+
requirements
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// AC6: Store in SessionState
|
|
65
|
+
this.sessionState.agentProfileDraft = {
|
|
66
|
+
content: this.generatedProfile,
|
|
67
|
+
name: agentName,
|
|
68
|
+
timestamp: Date.now()
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.profileGenerated = true;
|
|
72
|
+
|
|
73
|
+
this.logger.info('Agent profile generated', {
|
|
74
|
+
name: agentName,
|
|
75
|
+
length: this.generatedProfile.length
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return this.generatedProfile;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* AC4: Validate profile format and compliance
|
|
83
|
+
* Checks: YAML frontmatter, XML well-formed, required fields, no emojis in code
|
|
84
|
+
*
|
|
85
|
+
* @param {string} profile - Profile content to validate
|
|
86
|
+
* @returns {boolean} True if valid
|
|
87
|
+
*/
|
|
88
|
+
validateProfile(profile) {
|
|
89
|
+
if (!profile || typeof profile !== 'string') {
|
|
90
|
+
this.logger.warn('Validation failed: invalid profile');
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// AC4: Validate YAML frontmatter
|
|
96
|
+
const frontmatterMatch = profile.match(/^---([\s\S]*?)---/);
|
|
97
|
+
if (!frontmatterMatch) {
|
|
98
|
+
this.logger.warn('Validation failed: missing YAML frontmatter');
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const frontmatter = frontmatterMatch[1];
|
|
103
|
+
|
|
104
|
+
// Check required YAML fields
|
|
105
|
+
if (!frontmatter.includes('name:')) {
|
|
106
|
+
this.logger.warn('Validation failed: missing name in frontmatter');
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (!frontmatter.includes('description:')) {
|
|
110
|
+
this.logger.warn('Validation failed: missing description in frontmatter');
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// AC4: Validate XML block
|
|
115
|
+
const xmlMatch = profile.match(/```xml\s*([\s\S]*?)\s*```/);
|
|
116
|
+
if (!xmlMatch) {
|
|
117
|
+
this.logger.warn('Validation failed: missing XML block');
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const xml = xmlMatch[1];
|
|
122
|
+
|
|
123
|
+
// AC4: Check XML well-formedness (basic)
|
|
124
|
+
if (!xml.includes('<agent') || !xml.includes('</agent>')) {
|
|
125
|
+
this.logger.warn('Validation failed: malformed XML');
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// AC4: Validate no emojis in XML code sections
|
|
130
|
+
const emojiRegex = /[\u{1F300}-\u{1F9FF}]/u;
|
|
131
|
+
if (emojiRegex.test(xml)) {
|
|
132
|
+
this.logger.warn('Validation failed: emojis found in XML');
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.logger.info('Profile validation passed');
|
|
137
|
+
return true;
|
|
138
|
+
|
|
139
|
+
} catch (error) {
|
|
140
|
+
this.logger.error('Validation error', { error: error.message });
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* AC5: Save profile to disk
|
|
147
|
+
* @param {string} filePath - Path to save profile
|
|
148
|
+
*/
|
|
149
|
+
saveProfile(filePath) {
|
|
150
|
+
if (!this.generatedProfile) {
|
|
151
|
+
throw new Error('No profile to save - generate profile first');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
// Ensure directory exists
|
|
156
|
+
const dir = path.dirname(filePath);
|
|
157
|
+
if (!fs.existsSync(dir)) {
|
|
158
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Write profile
|
|
162
|
+
fs.writeFileSync(filePath, this.generatedProfile, 'utf-8');
|
|
163
|
+
|
|
164
|
+
this.logger.info('Profile saved', {
|
|
165
|
+
path: filePath,
|
|
166
|
+
size: this.generatedProfile.length
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
this.logger.error('Save failed', { error: error.message, path: filePath });
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get default save path
|
|
177
|
+
* @returns {string} Default path
|
|
178
|
+
*/
|
|
179
|
+
getDefaultSavePath() {
|
|
180
|
+
const name = this.sessionState.agentProfileDraft?.name || 'agent';
|
|
181
|
+
return `.github/copilot/agents/${name}.md`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Extract requirements from user responses (fallback)
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
188
|
+
_extractRequirementsFromResponses() {
|
|
189
|
+
const responseData = this.sessionState.userResponses || [];
|
|
190
|
+
|
|
191
|
+
// Extract response text from response objects
|
|
192
|
+
const responses = responseData.map(r => typeof r === 'string' ? r : (r.response || ''));
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
purpose: responses[0] || 'Custom agent',
|
|
196
|
+
domain: responses[3] || 'General',
|
|
197
|
+
capabilities: responses[7] ? responses[7].split(',').map(c => c.trim()) : ['General capability'],
|
|
198
|
+
knowledgeAreas: responses[2] ? responses[2].split(',').map(k => k.trim()) : ['General'],
|
|
199
|
+
users: responses[4] ? responses[4].split(',').map(u => u.trim()) : ['Users'],
|
|
200
|
+
constraints: responses.length > 10 ? [responses[10]] : []
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Derive agent name from purpose
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
_deriveAgentName(purpose) {
|
|
209
|
+
// Extract key words, sanitize, hyphenate
|
|
210
|
+
const name = purpose
|
|
211
|
+
.toLowerCase()
|
|
212
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
213
|
+
.trim()
|
|
214
|
+
.split(/\s+/)
|
|
215
|
+
.slice(0, 3) // Max 3 words
|
|
216
|
+
.join('-');
|
|
217
|
+
|
|
218
|
+
return name || 'custom-agent';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Derive description from purpose
|
|
223
|
+
* @private
|
|
224
|
+
*/
|
|
225
|
+
_deriveDescription(purpose) {
|
|
226
|
+
// Use first sentence or truncate
|
|
227
|
+
const firstSentence = purpose.split(/[.!?]/)[0].trim();
|
|
228
|
+
return firstSentence.length > 100
|
|
229
|
+
? firstSentence.substring(0, 97) + '...'
|
|
230
|
+
: firstSentence;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate persona from requirements
|
|
235
|
+
* @private
|
|
236
|
+
*/
|
|
237
|
+
_generatePersona(requirements) {
|
|
238
|
+
const { purpose, capabilities, knowledgeAreas } = requirements;
|
|
239
|
+
|
|
240
|
+
return `<persona>
|
|
241
|
+
<role>Specialized Agent</role>
|
|
242
|
+
<identity>${purpose}</identity>
|
|
243
|
+
<expertise>Expert in ${knowledgeAreas.slice(0, 3).join(', ')}</expertise>
|
|
244
|
+
<communication_style>Professional, clear, and focused on delivering results</communication_style>
|
|
245
|
+
<capabilities>
|
|
246
|
+
${capabilities.slice(0, 5).map(cap => ` <capability>${this._escapeXml(cap)}</capability>`).join('\n')}
|
|
247
|
+
</capabilities>
|
|
248
|
+
</persona>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Generate menu from capabilities
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
_generateMenu(capabilities) {
|
|
256
|
+
const menuItems = capabilities.slice(0, 5).map((cap, index) => {
|
|
257
|
+
const cmdKey = `C${index + 1}`;
|
|
258
|
+
return ` <item cmd="${cmdKey}">[${cmdKey}] ${this._escapeXml(cap)}</item>`;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return `<menu>
|
|
262
|
+
${menuItems.join('\n')}
|
|
263
|
+
<item cmd="MH">[MH] Menu Help</item>
|
|
264
|
+
</menu>`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generate capabilities section
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
_generateCapabilities(requirements) {
|
|
272
|
+
const { capabilities, knowledgeAreas, constraints } = requirements;
|
|
273
|
+
|
|
274
|
+
return `<capabilities>
|
|
275
|
+
<primary>
|
|
276
|
+
${capabilities.map(cap => ` <capability>${this._escapeXml(cap)}</capability>`).join('\n')}
|
|
277
|
+
</primary>
|
|
278
|
+
<knowledge>
|
|
279
|
+
${knowledgeAreas.map(area => ` <domain>${this._escapeXml(area)}</domain>`).join('\n')}
|
|
280
|
+
</knowledge>
|
|
281
|
+
<constraints>
|
|
282
|
+
${(constraints || []).map(c => ` <constraint>${this._escapeXml(c)}</constraint>`).join('\n')}
|
|
283
|
+
</constraints>
|
|
284
|
+
</capabilities>`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Build complete profile
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
_buildProfile({ name, description, persona, menu, capabilities, requirements }) {
|
|
292
|
+
const agentId = `${name}.agent.yaml`;
|
|
293
|
+
|
|
294
|
+
return `---
|
|
295
|
+
name: "${name}"
|
|
296
|
+
description: "${description}"
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
\`\`\`xml
|
|
300
|
+
<agent id="${agentId}" name="${name}" title="${description}">
|
|
301
|
+
<activation>
|
|
302
|
+
<step n="1">Load agent context and requirements</step>
|
|
303
|
+
<step n="2">Initialize with user's project context</step>
|
|
304
|
+
<step n="3">Display greeting and menu</step>
|
|
305
|
+
<step n="4">Await user input</step>
|
|
306
|
+
</activation>
|
|
307
|
+
|
|
308
|
+
${persona}
|
|
309
|
+
|
|
310
|
+
${menu}
|
|
311
|
+
|
|
312
|
+
${capabilities}
|
|
313
|
+
|
|
314
|
+
<guidelines>
|
|
315
|
+
<guideline>Follow user requirements precisely</guideline>
|
|
316
|
+
<guideline>Maintain professional communication</guideline>
|
|
317
|
+
<guideline>Provide actionable, clear responses</guideline>
|
|
318
|
+
<guideline>Leverage knowledge domains effectively</guideline>
|
|
319
|
+
</guidelines>
|
|
320
|
+
</agent>
|
|
321
|
+
\`\`\`
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Escape XML special characters
|
|
327
|
+
* @private
|
|
328
|
+
*/
|
|
329
|
+
_escapeXml(str) {
|
|
330
|
+
if (!str) return '';
|
|
331
|
+
return String(str)
|
|
332
|
+
.replace(/&/g, '&')
|
|
333
|
+
.replace(/</g, '<')
|
|
334
|
+
.replace(/>/g, '>')
|
|
335
|
+
.replace(/"/g, '"')
|
|
336
|
+
.replace(/'/g, ''');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = GenerationState;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InterviewState - Story 4.2
|
|
3
|
+
* Manages structured interview flow through 4 phases
|
|
4
|
+
*
|
|
5
|
+
* Phases:
|
|
6
|
+
* 1. CONTEXT: Project context, domain, users
|
|
7
|
+
* 2. BUSINESS: Goals, problems, success criteria
|
|
8
|
+
* 3. AGENT_NEEDS: Agent capabilities, behavior, constraints
|
|
9
|
+
* 4. VALIDATION: Confirm understanding, clarifications
|
|
10
|
+
*
|
|
11
|
+
* Integrates with SessionState to persist interview data
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const Logger = require('../observability/logger');
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
|
|
17
|
+
class InterviewState {
|
|
18
|
+
constructor(sessionState) {
|
|
19
|
+
this.sessionState = sessionState;
|
|
20
|
+
this.logger = new Logger();
|
|
21
|
+
|
|
22
|
+
// AC1: Define 4 phases
|
|
23
|
+
this.PHASES = {
|
|
24
|
+
CONTEXT: 'CONTEXT',
|
|
25
|
+
BUSINESS: 'BUSINESS',
|
|
26
|
+
AGENT_NEEDS: 'AGENT_NEEDS',
|
|
27
|
+
VALIDATION: 'VALIDATION'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Current interview state
|
|
31
|
+
this.currentPhase = this.PHASES.CONTEXT;
|
|
32
|
+
this.currentQuestionIndex = 0;
|
|
33
|
+
this.awaitingResponse = false;
|
|
34
|
+
this.lastAskedQuestion = null;
|
|
35
|
+
|
|
36
|
+
// AC4: Track responses per phase
|
|
37
|
+
this.phaseResponses = {
|
|
38
|
+
CONTEXT: [],
|
|
39
|
+
BUSINESS: [],
|
|
40
|
+
AGENT_NEEDS: [],
|
|
41
|
+
VALIDATION: []
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// AC1: Question banks for each phase (min 3 per phase)
|
|
45
|
+
this.questionBanks = {
|
|
46
|
+
CONTEXT: [
|
|
47
|
+
'What is the main purpose or domain of your project?',
|
|
48
|
+
'Who are the primary users or stakeholders?',
|
|
49
|
+
'What is the current workflow or process this agent will support?',
|
|
50
|
+
'What technologies or platforms are you using?',
|
|
51
|
+
'What is the scale or scope of your project?'
|
|
52
|
+
],
|
|
53
|
+
BUSINESS: [
|
|
54
|
+
'What specific problem or challenge does this agent need to solve?',
|
|
55
|
+
'What are the key goals or objectives for this agent?',
|
|
56
|
+
'How will you measure the success of this agent?',
|
|
57
|
+
'What are the most time-consuming or error-prone tasks currently?',
|
|
58
|
+
'What business value or ROI do you expect?'
|
|
59
|
+
],
|
|
60
|
+
AGENT_NEEDS: [
|
|
61
|
+
'What specific capabilities should this agent have?',
|
|
62
|
+
'What knowledge or expertise should the agent possess?',
|
|
63
|
+
'How should the agent interact with users (tone, style, format)?',
|
|
64
|
+
'What are the critical constraints or limitations for the agent?',
|
|
65
|
+
'What level of autonomy should the agent have in decision-making?'
|
|
66
|
+
],
|
|
67
|
+
VALIDATION: [
|
|
68
|
+
'Let me confirm: The agent will help with [SUMMARY]. Is this correct?',
|
|
69
|
+
'Are there any critical requirements or edge cases we haven\'t covered?',
|
|
70
|
+
'What would make this agent a complete failure in your eyes?',
|
|
71
|
+
'Is there anything else important I should know about this agent?'
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.phaseOrder = ['CONTEXT', 'BUSINESS', 'AGENT_NEEDS', 'VALIDATION'];
|
|
76
|
+
this.minResponsesPerPhase = 3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* AC2: Ask next question based on phase and responses
|
|
81
|
+
* @returns {Object|null} Question object or null if complete
|
|
82
|
+
*/
|
|
83
|
+
askNextQuestion() {
|
|
84
|
+
// Check if waiting for response to last question
|
|
85
|
+
if (this.awaitingResponse && this.lastAskedQuestion) {
|
|
86
|
+
this.logger.warn('Awaiting response to previous question');
|
|
87
|
+
return this.lastAskedQuestion;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if interview complete
|
|
91
|
+
if (this.isInterviewComplete()) {
|
|
92
|
+
this.logger.info('Interview complete - no more questions');
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if current phase needs transition
|
|
97
|
+
if (this.isPhaseComplete(this.currentPhase)) {
|
|
98
|
+
this._transitionToNextPhase();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Get question from current phase
|
|
102
|
+
const phaseQuestions = this.questionBanks[this.currentPhase];
|
|
103
|
+
const phaseResponseCount = this.phaseResponses[this.currentPhase].length;
|
|
104
|
+
|
|
105
|
+
// Use response count as index (allows revisiting questions)
|
|
106
|
+
const questionIndex = Math.min(phaseResponseCount, phaseQuestions.length - 1);
|
|
107
|
+
const questionText = phaseQuestions[questionIndex];
|
|
108
|
+
|
|
109
|
+
// Build question object
|
|
110
|
+
const question = {
|
|
111
|
+
questionId: crypto.randomUUID(),
|
|
112
|
+
text: questionText,
|
|
113
|
+
phase: this.currentPhase,
|
|
114
|
+
questionNumber: this.currentQuestionIndex + 1,
|
|
115
|
+
totalInPhase: phaseQuestions.length,
|
|
116
|
+
phaseProgress: `${phaseResponseCount + 1}/${this.minResponsesPerPhase} (min)`
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Store in SessionState
|
|
120
|
+
this.sessionState.addQuestion(questionText);
|
|
121
|
+
|
|
122
|
+
// Mark as awaiting response
|
|
123
|
+
this.awaitingResponse = true;
|
|
124
|
+
this.lastAskedQuestion = question;
|
|
125
|
+
|
|
126
|
+
this.logger.info('Question asked', {
|
|
127
|
+
phase: this.currentPhase,
|
|
128
|
+
questionNumber: question.questionNumber,
|
|
129
|
+
phaseProgress: question.phaseProgress
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return question;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* AC3: Process user response and store
|
|
137
|
+
* @param {string} response - User's response
|
|
138
|
+
*/
|
|
139
|
+
processResponse(response) {
|
|
140
|
+
if (!this.awaitingResponse) {
|
|
141
|
+
this.logger.warn('No question pending - ignoring response');
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Store response in current phase
|
|
146
|
+
const responseData = {
|
|
147
|
+
questionId: this.lastAskedQuestion.questionId,
|
|
148
|
+
response: response || '',
|
|
149
|
+
timestamp: Date.now(),
|
|
150
|
+
phase: this.currentPhase
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
this.phaseResponses[this.currentPhase].push(responseData);
|
|
154
|
+
|
|
155
|
+
// Store in SessionState
|
|
156
|
+
this.sessionState.addResponse(this.lastAskedQuestion.questionId, response);
|
|
157
|
+
|
|
158
|
+
// Update state
|
|
159
|
+
this.currentQuestionIndex++;
|
|
160
|
+
this.awaitingResponse = false;
|
|
161
|
+
this.lastAskedQuestion = null;
|
|
162
|
+
|
|
163
|
+
this.logger.info('Response processed', {
|
|
164
|
+
phase: this.currentPhase,
|
|
165
|
+
responseCount: this.phaseResponses[this.currentPhase].length,
|
|
166
|
+
phaseComplete: this.isPhaseComplete(this.currentPhase)
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Transition to next phase if current phase is complete
|
|
170
|
+
if (this.isPhaseComplete(this.currentPhase)) {
|
|
171
|
+
const currentPhaseIndex = this.phaseOrder.indexOf(this.currentPhase);
|
|
172
|
+
if (currentPhaseIndex < this.phaseOrder.length - 1) {
|
|
173
|
+
this._transitionToNextPhase();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return this.isInterviewComplete();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* AC4: Check if phase has minimum responses
|
|
182
|
+
* @param {string} phase - Phase name
|
|
183
|
+
* @returns {boolean} True if phase complete
|
|
184
|
+
*/
|
|
185
|
+
isPhaseComplete(phase) {
|
|
186
|
+
return this.phaseResponses[phase].length >= this.minResponsesPerPhase;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* AC5: Check if all phases complete and can transition to ANALYSIS
|
|
191
|
+
* @returns {boolean} True if interview complete
|
|
192
|
+
*/
|
|
193
|
+
canTransitionToAnalysis() {
|
|
194
|
+
return this.phaseOrder.every(phase => this.isPhaseComplete(phase));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if interview is complete
|
|
199
|
+
* @returns {boolean} True if all phases done
|
|
200
|
+
*/
|
|
201
|
+
isInterviewComplete() {
|
|
202
|
+
return this.canTransitionToAnalysis();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get all collected responses
|
|
207
|
+
* @returns {Object} All phase responses
|
|
208
|
+
*/
|
|
209
|
+
getAllResponses() {
|
|
210
|
+
return { ...this.phaseResponses };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get responses for specific phase
|
|
215
|
+
* @param {string} phase - Phase name
|
|
216
|
+
* @returns {Array} Phase responses
|
|
217
|
+
*/
|
|
218
|
+
getPhaseResponses(phase) {
|
|
219
|
+
return [...this.phaseResponses[phase]];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get interview progress summary
|
|
224
|
+
* @returns {Object} Progress data
|
|
225
|
+
*/
|
|
226
|
+
getProgress() {
|
|
227
|
+
const phaseProgress = {};
|
|
228
|
+
|
|
229
|
+
this.phaseOrder.forEach(phase => {
|
|
230
|
+
const count = this.phaseResponses[phase].length;
|
|
231
|
+
phaseProgress[phase] = {
|
|
232
|
+
responses: count,
|
|
233
|
+
complete: this.isPhaseComplete(phase),
|
|
234
|
+
progress: `${count}/${this.minResponsesPerPhase}`
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
currentPhase: this.currentPhase,
|
|
240
|
+
totalQuestions: this.currentQuestionIndex,
|
|
241
|
+
phases: phaseProgress,
|
|
242
|
+
canTransitionToAnalysis: this.canTransitionToAnalysis()
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Transition to next phase
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
_transitionToNextPhase() {
|
|
251
|
+
const currentIndex = this.phaseOrder.indexOf(this.currentPhase);
|
|
252
|
+
|
|
253
|
+
if (currentIndex === -1 || currentIndex >= this.phaseOrder.length - 1) {
|
|
254
|
+
// Already at last phase or invalid
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const nextPhase = this.phaseOrder[currentIndex + 1];
|
|
259
|
+
const previousPhase = this.currentPhase;
|
|
260
|
+
|
|
261
|
+
this.currentPhase = nextPhase;
|
|
262
|
+
|
|
263
|
+
this.logger.info('Interview phase transition', {
|
|
264
|
+
from: previousPhase,
|
|
265
|
+
to: nextPhase,
|
|
266
|
+
previousPhaseResponses: this.phaseResponses[previousPhase].length
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = InterviewState;
|