convoke-agents 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +37 -10
- package/_bmad/bme/_artifacts/config.yaml +15 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
- package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
- package/_bmad/bme/_team-factory/config.yaml +13 -0
- package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
- package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
- package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
- package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
- package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
- package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
- package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
- package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
- package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
- package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
- package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
- package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
- package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
- package/_bmad/bme/_team-factory/module-help.csv +3 -0
- package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
- package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
- package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
- package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
- package/_bmad/bme/_vortex/config.yaml +4 -4
- package/package.json +13 -7
- package/scripts/convoke-doctor.js +172 -1
- package/scripts/install-gyre-agents.js +0 -0
- package/scripts/lib/artifact-utils.js +521 -13
- package/scripts/lib/portfolio/portfolio-engine.js +301 -34
- package/scripts/lib/portfolio/rules/artifact-chain-rule.js +33 -3
- package/scripts/lib/portfolio/rules/conflict-resolver.js +22 -0
- package/scripts/migrate-artifacts.js +69 -10
- package/scripts/portability/catalog-generator.js +353 -0
- package/scripts/portability/classify-skills.js +646 -0
- package/scripts/portability/convoke-export.js +522 -0
- package/scripts/portability/export-engine.js +1156 -0
- package/scripts/portability/generate-adapters.js +79 -0
- package/scripts/portability/manifest-csv.js +147 -0
- package/scripts/portability/seed-catalog-repo.js +427 -0
- package/scripts/portability/templates/canonical-example.md +102 -0
- package/scripts/portability/templates/canonical-format.md +218 -0
- package/scripts/portability/templates/readme-template.md +72 -0
- package/scripts/portability/test-constants.js +42 -0
- package/scripts/portability/validate-classification.js +529 -0
- package/scripts/portability/validate-exports.js +348 -0
- package/scripts/update/lib/agent-registry.js +35 -0
- package/scripts/update/lib/config-merger.js +140 -10
- package/scripts/update/lib/refresh-installation.js +293 -8
- package/scripts/update/lib/utils.js +27 -1
- package/scripts/update/lib/validator.js +114 -4
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pattern-aware decision elimination for the Team Factory.
|
|
5
|
+
* A6'-coupled — adding a third composition pattern = adding a third column to DECISION_CATALOGUE.
|
|
6
|
+
*
|
|
7
|
+
* @module cascade-logic
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const KNOWN_PATTERNS = ['Independent', 'Sequential'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Master catalogue of factory decisions with per-pattern relevance.
|
|
14
|
+
* Each entry defines whether the decision is active for a given pattern
|
|
15
|
+
* and whether it's required or optional.
|
|
16
|
+
*
|
|
17
|
+
* @type {Array<{id: string, step: string, description: string, patterns: Object}>}
|
|
18
|
+
*/
|
|
19
|
+
const DECISION_CATALOGUE = [
|
|
20
|
+
{
|
|
21
|
+
id: 'agent-scope',
|
|
22
|
+
step: 'scope',
|
|
23
|
+
description: 'Define agent roles and capabilities',
|
|
24
|
+
patterns: {
|
|
25
|
+
Independent: { active: true, required: true },
|
|
26
|
+
Sequential: { active: true, required: true },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'pipeline-order',
|
|
31
|
+
step: 'scope',
|
|
32
|
+
description: 'Define agent execution sequence (pipeline positions)',
|
|
33
|
+
patterns: {
|
|
34
|
+
Independent: { active: false, required: false },
|
|
35
|
+
Sequential: { active: true, required: true },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'handoff-contracts',
|
|
40
|
+
step: 'connect',
|
|
41
|
+
description: 'Design inter-agent handoff contracts',
|
|
42
|
+
patterns: {
|
|
43
|
+
Independent: { active: false, required: false },
|
|
44
|
+
Sequential: { active: true, required: true },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'feedback-contracts',
|
|
49
|
+
step: 'connect',
|
|
50
|
+
description: 'Design feedback routing contracts (downstream → upstream)',
|
|
51
|
+
patterns: {
|
|
52
|
+
Independent: { active: false, required: false },
|
|
53
|
+
Sequential: { active: true, required: false },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'contract-prefix',
|
|
58
|
+
step: 'connect',
|
|
59
|
+
description: 'Define naming prefix for contracts (e.g., HC, GC)',
|
|
60
|
+
patterns: {
|
|
61
|
+
Independent: { active: false, required: false },
|
|
62
|
+
Sequential: { active: true, required: true },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'compass-routing',
|
|
67
|
+
step: 'connect',
|
|
68
|
+
description: 'Create compass routing reference for workflow navigation',
|
|
69
|
+
patterns: {
|
|
70
|
+
Independent: { active: true, required: false, defaultValue: 'per-agent' },
|
|
71
|
+
Sequential: { active: true, required: true, defaultValue: 'shared-reference' },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'orchestration-workflow',
|
|
76
|
+
step: 'connect',
|
|
77
|
+
description: 'Create pipeline orchestration workflow',
|
|
78
|
+
patterns: {
|
|
79
|
+
Independent: { active: false, required: false },
|
|
80
|
+
Sequential: { active: true, required: true },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'output-directory',
|
|
85
|
+
step: 'connect',
|
|
86
|
+
description: 'Define artifact output location',
|
|
87
|
+
patterns: {
|
|
88
|
+
Independent: { active: true, required: true, defaultValue: '_bmad-output/{team}-artifacts' },
|
|
89
|
+
Sequential: { active: true, required: true, defaultValue: '_bmad-output/{team}-artifacts' },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'naming-enforcement',
|
|
94
|
+
step: 'scope',
|
|
95
|
+
description: 'Validate all naming conventions (agent IDs, file names, module directory)',
|
|
96
|
+
patterns: {
|
|
97
|
+
Independent: { active: true, required: true },
|
|
98
|
+
Sequential: { active: true, required: true },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'overlap-detection',
|
|
103
|
+
step: 'scope',
|
|
104
|
+
description: 'Check proposed agents against existing agent manifest for collisions',
|
|
105
|
+
patterns: {
|
|
106
|
+
Independent: { active: true, required: true },
|
|
107
|
+
Sequential: { active: true, required: true },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Given a composition pattern, return which factory decisions are relevant
|
|
114
|
+
* and which are eliminated.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} pattern - "Independent" or "Sequential"
|
|
117
|
+
* @returns {{ decisions: CascadeDecision[], eliminated: CascadeDecision[], error?: string }}
|
|
118
|
+
*/
|
|
119
|
+
function getCascadeForPattern(pattern) {
|
|
120
|
+
const validation = validatePattern(pattern);
|
|
121
|
+
if (!validation.valid) {
|
|
122
|
+
return { decisions: [], eliminated: [], error: validation.error };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const decisions = [];
|
|
126
|
+
const eliminated = [];
|
|
127
|
+
|
|
128
|
+
for (const entry of DECISION_CATALOGUE) {
|
|
129
|
+
const patternConfig = entry.patterns[pattern];
|
|
130
|
+
const decision = {
|
|
131
|
+
id: entry.id,
|
|
132
|
+
step: entry.step,
|
|
133
|
+
description: entry.description,
|
|
134
|
+
required: patternConfig.required,
|
|
135
|
+
defaultValue: patternConfig.defaultValue || undefined,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (patternConfig.active) {
|
|
139
|
+
decisions.push(decision);
|
|
140
|
+
} else {
|
|
141
|
+
eliminated.push(decision);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { decisions, eliminated };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate that a pattern string is recognized.
|
|
150
|
+
* @param {string} pattern
|
|
151
|
+
* @returns {{ valid: boolean, error?: string }}
|
|
152
|
+
*/
|
|
153
|
+
function validatePattern(pattern) {
|
|
154
|
+
if (!pattern || typeof pattern !== 'string') {
|
|
155
|
+
return { valid: false, error: 'Pattern must be a non-empty string' };
|
|
156
|
+
}
|
|
157
|
+
if (!KNOWN_PATTERNS.includes(pattern)) {
|
|
158
|
+
return { valid: false, error: `Unknown pattern "${pattern}". Expected one of: ${KNOWN_PATTERNS.join(', ')}` };
|
|
159
|
+
}
|
|
160
|
+
return { valid: true };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get the list of known composition patterns.
|
|
165
|
+
* @returns {string[]}
|
|
166
|
+
*/
|
|
167
|
+
function getKnownPatterns() {
|
|
168
|
+
return [...KNOWN_PATTERNS];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @typedef {Object} CascadeDecision
|
|
173
|
+
* @property {string} id - Decision identifier
|
|
174
|
+
* @property {string} step - Factory step this belongs to (scope, connect, generate, validate)
|
|
175
|
+
* @property {string} description - Human-readable description
|
|
176
|
+
* @property {boolean} required - Whether mandatory for the pattern
|
|
177
|
+
* @property {string} [defaultValue] - Default if applicable
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
getCascadeForPattern,
|
|
182
|
+
validatePattern,
|
|
183
|
+
getKnownPatterns,
|
|
184
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect collisions between a new team spec and existing framework state.
|
|
7
|
+
* Handles Level 1 (exact ID match) and Level 2 (name similarity).
|
|
8
|
+
* Level 3 (capability overlap) is LLM reasoning in the workflow step.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} specData - Parsed team spec
|
|
11
|
+
* @param {string} manifestPath - Absolute path to agent-manifest.csv
|
|
12
|
+
* @param {string} [bmeDir] - Path to _bmad/bme/ directory for submodule name checks
|
|
13
|
+
* @returns {Promise<CollisionResult>}
|
|
14
|
+
*/
|
|
15
|
+
async function detectCollisions(specData, manifestPath, bmeDir) {
|
|
16
|
+
const blocks = [];
|
|
17
|
+
const warnings = [];
|
|
18
|
+
|
|
19
|
+
const dataWarnings = [];
|
|
20
|
+
|
|
21
|
+
// Parse existing agent manifest
|
|
22
|
+
const { agents: existingAgents, warning: manifestWarning } = await parseManifest(manifestPath);
|
|
23
|
+
if (manifestWarning) dataWarnings.push(manifestWarning);
|
|
24
|
+
|
|
25
|
+
// Parse existing submodule directories
|
|
26
|
+
const { modules: existingModules, warning: modulesWarning } = bmeDir
|
|
27
|
+
? await listSubmodules(bmeDir)
|
|
28
|
+
: { modules: [], warning: null };
|
|
29
|
+
if (modulesWarning) dataWarnings.push(modulesWarning);
|
|
30
|
+
|
|
31
|
+
const proposedTeam = specData.team_name_kebab || '';
|
|
32
|
+
const proposedAgents = (specData.agents || []).map(a => a.id).filter(Boolean);
|
|
33
|
+
|
|
34
|
+
// --- Level 1: Exact submodule name collision ---
|
|
35
|
+
if (proposedTeam && existingModules.includes(`_${proposedTeam}`)) {
|
|
36
|
+
blocks.push({
|
|
37
|
+
level: 'exact',
|
|
38
|
+
field: 'submodule_name',
|
|
39
|
+
newValue: `_${proposedTeam}`,
|
|
40
|
+
existingValue: `_${proposedTeam}`,
|
|
41
|
+
existingModule: proposedTeam,
|
|
42
|
+
suggestion: `Module directory _bmad/bme/_${proposedTeam}/ already exists. Choose a different team name.`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Level 1: Exact agent ID collision ---
|
|
47
|
+
for (const agentId of proposedAgents) {
|
|
48
|
+
const match = existingAgents.find(a => a.id === agentId);
|
|
49
|
+
if (match) {
|
|
50
|
+
blocks.push({
|
|
51
|
+
level: 'exact',
|
|
52
|
+
field: 'agent_id',
|
|
53
|
+
newValue: agentId,
|
|
54
|
+
existingValue: match.id,
|
|
55
|
+
existingModule: match.module,
|
|
56
|
+
suggestion: `Agent ID "${agentId}" already exists in module "${match.module}". Rename your agent.`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --- Level 2: Similar agent name detection ---
|
|
62
|
+
for (const agentId of proposedAgents) {
|
|
63
|
+
for (const existing of existingAgents) {
|
|
64
|
+
if (existing.id === agentId) continue; // Already caught by Level 1
|
|
65
|
+
|
|
66
|
+
const dist = levenshtein(agentId, existing.id);
|
|
67
|
+
const sharePrefix = sharedPrefix(agentId, existing.id);
|
|
68
|
+
|
|
69
|
+
if (dist <= 2 || (sharePrefix >= 4 && dist <= 3)) {
|
|
70
|
+
warnings.push({
|
|
71
|
+
level: 'similar',
|
|
72
|
+
field: 'agent_id',
|
|
73
|
+
newValue: agentId,
|
|
74
|
+
existingValue: existing.id,
|
|
75
|
+
existingModule: existing.module,
|
|
76
|
+
suggestion: `"${agentId}" is similar to existing "${existing.id}" (${existing.module}). Intentional?`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
blocks,
|
|
84
|
+
warnings,
|
|
85
|
+
dataWarnings,
|
|
86
|
+
hasBlocking: blocks.length > 0,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse agent-manifest.csv to extract agent IDs and their modules.
|
|
92
|
+
* @param {string} manifestPath
|
|
93
|
+
* @returns {Promise<{agents: Array<{id: string, module: string}>, warning: string|null}>}
|
|
94
|
+
*/
|
|
95
|
+
async function parseManifest(manifestPath) {
|
|
96
|
+
try {
|
|
97
|
+
const content = await fs.readFile(manifestPath, 'utf8');
|
|
98
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
99
|
+
if (lines.length < 2) return { agents: [], warning: null };
|
|
100
|
+
|
|
101
|
+
const results = [];
|
|
102
|
+
// Skip header (line 0), parse data rows
|
|
103
|
+
for (let i = 1; i < lines.length; i++) {
|
|
104
|
+
const fields = parseCSVLine(lines[i]);
|
|
105
|
+
if (fields.length >= 10) {
|
|
106
|
+
const id = fields[0].replace(/^"|"$/g, '').trim();
|
|
107
|
+
const module = fields[9].replace(/^"|"$/g, '').trim();
|
|
108
|
+
if (id) results.push({ id, module });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { agents: results, warning: null };
|
|
112
|
+
} catch (err) {
|
|
113
|
+
return { agents: [], warning: `Could not read agent manifest at ${manifestPath}: ${err.message}. Collision detection may be incomplete.` };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse a single CSV line handling quoted fields.
|
|
119
|
+
* @param {string} line
|
|
120
|
+
* @returns {string[]}
|
|
121
|
+
*/
|
|
122
|
+
function parseCSVLine(line) {
|
|
123
|
+
const fields = [];
|
|
124
|
+
let current = '';
|
|
125
|
+
let inQuotes = false;
|
|
126
|
+
|
|
127
|
+
for (let i = 0; i < line.length; i++) {
|
|
128
|
+
const ch = line[i];
|
|
129
|
+
if (inQuotes) {
|
|
130
|
+
if (ch === '"' && line[i + 1] === '"') {
|
|
131
|
+
current += '"';
|
|
132
|
+
i++; // skip escaped quote
|
|
133
|
+
} else if (ch === '"') {
|
|
134
|
+
inQuotes = false;
|
|
135
|
+
} else {
|
|
136
|
+
current += ch;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
if (ch === '"') {
|
|
140
|
+
inQuotes = true;
|
|
141
|
+
} else if (ch === ',') {
|
|
142
|
+
fields.push(current);
|
|
143
|
+
current = '';
|
|
144
|
+
} else {
|
|
145
|
+
current += ch;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
fields.push(current);
|
|
150
|
+
return fields;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* List existing submodule directory names under _bmad/bme/.
|
|
155
|
+
* @param {string} bmeDir
|
|
156
|
+
* @returns {Promise<{modules: string[], warning: string|null}>}
|
|
157
|
+
*/
|
|
158
|
+
async function listSubmodules(bmeDir) {
|
|
159
|
+
try {
|
|
160
|
+
const entries = await fs.readdir(bmeDir, { withFileTypes: true });
|
|
161
|
+
const modules = entries
|
|
162
|
+
.filter(e => e.isDirectory() && e.name.startsWith('_'))
|
|
163
|
+
.map(e => e.name);
|
|
164
|
+
return { modules, warning: null };
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return { modules: [], warning: `Could not read bme directory at ${bmeDir}: ${err.message}. Submodule collision detection skipped.` };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Compute Levenshtein edit distance between two strings.
|
|
172
|
+
* @param {string} a
|
|
173
|
+
* @param {string} b
|
|
174
|
+
* @returns {number}
|
|
175
|
+
*/
|
|
176
|
+
function levenshtein(a, b) {
|
|
177
|
+
if (a.length === 0) return b.length;
|
|
178
|
+
if (b.length === 0) return a.length;
|
|
179
|
+
|
|
180
|
+
const matrix = Array.from({ length: b.length + 1 }, (_, i) => [i]);
|
|
181
|
+
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
|
182
|
+
|
|
183
|
+
for (let i = 1; i <= b.length; i++) {
|
|
184
|
+
for (let j = 1; j <= a.length; j++) {
|
|
185
|
+
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
|
|
186
|
+
matrix[i][j] = Math.min(
|
|
187
|
+
matrix[i - 1][j] + 1,
|
|
188
|
+
matrix[i][j - 1] + 1,
|
|
189
|
+
matrix[i - 1][j - 1] + cost,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return matrix[b.length][a.length];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Count shared prefix length between two strings.
|
|
199
|
+
* @param {string} a
|
|
200
|
+
* @param {string} b
|
|
201
|
+
* @returns {number}
|
|
202
|
+
*/
|
|
203
|
+
function sharedPrefix(a, b) {
|
|
204
|
+
let i = 0;
|
|
205
|
+
while (i < a.length && i < b.length && a[i] === b[i]) i++;
|
|
206
|
+
return i;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @typedef {Object} CollisionResult
|
|
211
|
+
* @property {CollisionEntry[]} blocks - Level 1: exact matches, must be resolved
|
|
212
|
+
* @property {CollisionEntry[]} warnings - Level 2: similar names, review recommended
|
|
213
|
+
* @property {boolean} hasBlocking - true if any blocks exist
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @typedef {Object} CollisionEntry
|
|
218
|
+
* @property {string} level - "exact" or "similar"
|
|
219
|
+
* @property {string} field - "agent_id", "submodule_name", or "workflow_name"
|
|
220
|
+
* @property {string} newValue
|
|
221
|
+
* @property {string} existingValue
|
|
222
|
+
* @property {string} existingModule
|
|
223
|
+
* @property {string} suggestion
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
detectCollisions,
|
|
228
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/** @typedef {import('./types/factory-types').ManifestEntry} ManifestEntry */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build a file manifest from spec data and generation context.
|
|
9
|
+
* The manifest is built from tracked generation variables — NOT filesystem scanning.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} specData - Parsed team spec (needs team_name_kebab, spec_file_path)
|
|
12
|
+
* @param {Object} generationContext - Context from Step 4
|
|
13
|
+
* @param {string[]} generationContext.agent_files - Agent .md file paths
|
|
14
|
+
* @param {string[]} generationContext.workflow_dirs - Workflow directory paths
|
|
15
|
+
* @param {string[]} [generationContext.contract_files] - Contract .md file paths
|
|
16
|
+
* @param {string} generationContext.config_yaml_path - Path to config.yaml
|
|
17
|
+
* @param {string} generationContext.module_help_csv_path - Path to module-help.csv
|
|
18
|
+
* @param {string} generationContext.module_root - Module root directory
|
|
19
|
+
* @returns {ManifestEntry[]}
|
|
20
|
+
*/
|
|
21
|
+
function buildManifest(specData, generationContext) {
|
|
22
|
+
const entries = [];
|
|
23
|
+
const moduleName = specData.team_name_kebab || 'unknown';
|
|
24
|
+
|
|
25
|
+
// Agent files — created
|
|
26
|
+
for (const agentFile of (generationContext.agent_files || [])) {
|
|
27
|
+
entries.push({ path: agentFile, operation: 'created', module: moduleName });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Workflow directories — each gets a workflow.md and SKILL.md
|
|
31
|
+
for (const wfDir of (generationContext.workflow_dirs || [])) {
|
|
32
|
+
entries.push({ path: path.join(wfDir, 'workflow.md'), operation: 'created', module: moduleName });
|
|
33
|
+
entries.push({ path: path.join(wfDir, 'SKILL.md'), operation: 'created', module: moduleName });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Contract files — created
|
|
37
|
+
for (const contractFile of (generationContext.contract_files || [])) {
|
|
38
|
+
entries.push({ path: contractFile, operation: 'created', module: moduleName });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Compass routing reference — created (if exists in generated_files)
|
|
42
|
+
const compassFile = path.join(generationContext.module_root || '', 'compass-routing-reference.md');
|
|
43
|
+
const generatedFiles = generationContext.generated_files || [];
|
|
44
|
+
if (generatedFiles.includes(compassFile)) {
|
|
45
|
+
entries.push({ path: compassFile, operation: 'created', module: moduleName });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Config.yaml — created
|
|
49
|
+
if (generationContext.config_yaml_path) {
|
|
50
|
+
entries.push({ path: generationContext.config_yaml_path, operation: 'created', module: moduleName });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Module-help.csv — created
|
|
54
|
+
if (generationContext.module_help_csv_path) {
|
|
55
|
+
entries.push({ path: generationContext.module_help_csv_path, operation: 'created', module: moduleName });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// agent-registry.js — modified
|
|
59
|
+
entries.push({ path: 'scripts/update/lib/agent-registry.js', operation: 'modified', module: moduleName });
|
|
60
|
+
|
|
61
|
+
// Spec file — modified (progress updated)
|
|
62
|
+
if (specData.spec_file_path) {
|
|
63
|
+
entries.push({ path: specData.spec_file_path, operation: 'modified', module: moduleName });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return entries;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format a manifest as a human-readable markdown table.
|
|
71
|
+
* @param {ManifestEntry[]} entries
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function formatManifest(entries) {
|
|
75
|
+
const lines = [];
|
|
76
|
+
lines.push('| # | Path | Operation | Module |');
|
|
77
|
+
lines.push('|---|------|-----------|--------|');
|
|
78
|
+
entries.forEach((entry, i) => {
|
|
79
|
+
lines.push(`| ${i + 1} | \`${entry.path}\` | ${entry.operation} | ${entry.module} |`);
|
|
80
|
+
});
|
|
81
|
+
return lines.join('\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Format abort/removal instructions from a manifest.
|
|
86
|
+
* Created files get `rm`, modified files get `git checkout --`.
|
|
87
|
+
* @param {ManifestEntry[]} entries
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function formatAbortInstructions(entries) {
|
|
91
|
+
const lines = [];
|
|
92
|
+
lines.push('# Removal instructions');
|
|
93
|
+
lines.push('');
|
|
94
|
+
|
|
95
|
+
const created = entries.filter(e => e.operation === 'created');
|
|
96
|
+
const modified = entries.filter(e => e.operation === 'modified');
|
|
97
|
+
|
|
98
|
+
if (created.length > 0) {
|
|
99
|
+
lines.push('# Created files — remove:');
|
|
100
|
+
for (const entry of created) {
|
|
101
|
+
lines.push(`rm "${entry.path}"`);
|
|
102
|
+
}
|
|
103
|
+
lines.push('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (modified.length > 0) {
|
|
107
|
+
lines.push('# Modified files — revert:');
|
|
108
|
+
for (const entry of modified) {
|
|
109
|
+
lines.push(`git checkout -- "${entry.path}"`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return lines.join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build a file manifest for an extension operation (add agent to existing team).
|
|
118
|
+
* New agent/workflow/contract files are "created"; config, CSV, and registry are "modified".
|
|
119
|
+
*
|
|
120
|
+
* @param {Object} extensionContext
|
|
121
|
+
* @param {string} extensionContext.new_agent_id - New agent ID (for module label)
|
|
122
|
+
* @param {string[]} extensionContext.new_agent_files - New agent .md file paths
|
|
123
|
+
* @param {string[]} extensionContext.new_workflow_dirs - New workflow directory paths
|
|
124
|
+
* @param {string[]} [extensionContext.new_contract_files] - New contract file paths
|
|
125
|
+
* @param {string} extensionContext.config_yaml_path - Path to config.yaml (modified)
|
|
126
|
+
* @param {string} extensionContext.module_help_csv_path - Path to module-help.csv (modified)
|
|
127
|
+
* @returns {ManifestEntry[]}
|
|
128
|
+
*/
|
|
129
|
+
function buildExtensionManifest(extensionContext) {
|
|
130
|
+
const entries = [];
|
|
131
|
+
const moduleName = extensionContext.new_agent_id || 'unknown-agent';
|
|
132
|
+
|
|
133
|
+
// New agent files — created
|
|
134
|
+
for (const agentFile of (extensionContext.new_agent_files || [])) {
|
|
135
|
+
entries.push({ path: agentFile, operation: 'created', module: moduleName });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// New workflow directories — each gets workflow.md and SKILL.md
|
|
139
|
+
for (const wfDir of (extensionContext.new_workflow_dirs || [])) {
|
|
140
|
+
entries.push({ path: path.join(wfDir, 'workflow.md'), operation: 'created', module: moduleName });
|
|
141
|
+
entries.push({ path: path.join(wfDir, 'SKILL.md'), operation: 'created', module: moduleName });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// New contract files — created
|
|
145
|
+
for (const contractFile of (extensionContext.new_contract_files || [])) {
|
|
146
|
+
entries.push({ path: contractFile, operation: 'created', module: moduleName });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Config.yaml — modified (agent appended)
|
|
150
|
+
if (extensionContext.config_yaml_path) {
|
|
151
|
+
entries.push({ path: extensionContext.config_yaml_path, operation: 'modified', module: moduleName });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Module-help.csv — modified (row appended)
|
|
155
|
+
if (extensionContext.module_help_csv_path) {
|
|
156
|
+
entries.push({ path: extensionContext.module_help_csv_path, operation: 'modified', module: moduleName });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// agent-registry.js — modified (agent appended to existing block)
|
|
160
|
+
entries.push({ path: 'scripts/update/lib/agent-registry.js', operation: 'modified', module: moduleName });
|
|
161
|
+
|
|
162
|
+
return entries;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build a file manifest for a skill/workflow extension (add workflow to existing agent).
|
|
167
|
+
* New workflow files are "created"; agent .md, config, CSV, and registry are "modified".
|
|
168
|
+
*
|
|
169
|
+
* @param {Object} skillContext
|
|
170
|
+
* @param {string} skillContext.new_workflow_name - New workflow name (for module label)
|
|
171
|
+
* @param {string} skillContext.agent_id - Target agent ID
|
|
172
|
+
* @param {string[]} skillContext.new_workflow_files - New workflow file paths (workflow.md, template)
|
|
173
|
+
* @param {string} [skillContext.agent_file_path] - Agent .md file path (modified for menu)
|
|
174
|
+
* @param {string} skillContext.config_yaml_path - Path to config.yaml (modified)
|
|
175
|
+
* @param {string} skillContext.module_help_csv_path - Path to module-help.csv (modified)
|
|
176
|
+
* @returns {ManifestEntry[]}
|
|
177
|
+
*/
|
|
178
|
+
function buildSkillExtensionManifest(skillContext) {
|
|
179
|
+
const entries = [];
|
|
180
|
+
const moduleName = skillContext.new_workflow_name || 'unknown-workflow';
|
|
181
|
+
|
|
182
|
+
// New workflow files — created
|
|
183
|
+
for (const wfFile of (skillContext.new_workflow_files || [])) {
|
|
184
|
+
entries.push({ path: wfFile, operation: 'created', module: moduleName });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Agent .md file — modified (menu item added)
|
|
188
|
+
if (skillContext.agent_file_path) {
|
|
189
|
+
entries.push({ path: skillContext.agent_file_path, operation: 'modified', module: moduleName });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Config.yaml — modified (workflow appended)
|
|
193
|
+
if (skillContext.config_yaml_path) {
|
|
194
|
+
entries.push({ path: skillContext.config_yaml_path, operation: 'modified', module: moduleName });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Module-help.csv — modified (row appended)
|
|
198
|
+
if (skillContext.module_help_csv_path) {
|
|
199
|
+
entries.push({ path: skillContext.module_help_csv_path, operation: 'modified', module: moduleName });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// agent-registry.js — modified (workflow appended to existing block)
|
|
203
|
+
entries.push({ path: 'scripts/update/lib/agent-registry.js', operation: 'modified', module: moduleName });
|
|
204
|
+
|
|
205
|
+
return entries;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
buildManifest,
|
|
210
|
+
buildExtensionManifest,
|
|
211
|
+
buildSkillExtensionManifest,
|
|
212
|
+
formatManifest,
|
|
213
|
+
formatAbortInstructions,
|
|
214
|
+
};
|