@schilling.mark.a/software-methodology 1.0.0 → 1.0.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/.github/copilot-instructions.md +159 -0
- package/README.md +172 -6
- package/docs/story-map/backbone.md +141 -0
- package/docs/story-map/releases/r1-walking-skeleton.md +152 -0
- package/docs/story-map/user-tasks/ACT-001-task-001.md +45 -0
- package/docs/story-map/user-tasks/ACT-001-task-002.md +48 -0
- package/docs/story-map/user-tasks/ACT-002-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-002-task-002.md +47 -0
- package/docs/story-map/user-tasks/ACT-002-task-003.md +46 -0
- package/docs/story-map/user-tasks/ACT-003-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-003-task-002.md +46 -0
- package/docs/story-map/user-tasks/ACT-003-task-003.md +49 -0
- package/docs/story-map/user-tasks/ACT-003-task-004.md +47 -0
- package/docs/story-map/user-tasks/ACT-004-task-001.md +48 -0
- package/docs/story-map/user-tasks/ACT-004-task-002.md +49 -0
- package/docs/story-map/user-tasks/ACT-004-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-005-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-005-task-002.md +48 -0
- package/docs/story-map/user-tasks/ACT-005-task-003.md +48 -0
- package/docs/story-map/user-tasks/ACT-005-task-004.md +48 -0
- package/docs/story-map/user-tasks/ACT-006-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-006-task-002.md +46 -0
- package/docs/story-map/user-tasks/ACT-006-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-006-task-004.md +46 -0
- package/docs/story-map/user-tasks/ACT-007-task-001.md +48 -0
- package/docs/story-map/user-tasks/ACT-007-task-002.md +47 -0
- package/docs/story-map/user-tasks/ACT-007-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-007-task-004.md +48 -0
- package/docs/story-map/user-tasks/ACT-008-task-001.md +48 -0
- package/docs/story-map/user-tasks/ACT-008-task-002.md +48 -0
- package/docs/story-map/user-tasks/ACT-008-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-008-task-004.md +48 -0
- package/docs/story-map/walking-skeleton.md +95 -0
- package/docs/value-proposition-canvas.md +171 -0
- package/features/mcp-server/query-vpc.feature +48 -0
- package/features/mcp-server/read-reference.feature +41 -0
- package/features/mcp-server/read-skill.feature +33 -0
- package/features/mcp-server/search-guidance.feature +42 -0
- package/features/mcp-server/suggest-next-step.feature +61 -0
- package/features/mcp-server/validate-gherkin.feature +54 -0
- package/mcp-server/QUICKSTART.md +172 -0
- package/mcp-server/README.md +171 -0
- package/mcp-server/dist/index.d.ts +12 -0
- package/mcp-server/dist/index.js +296 -0
- package/mcp-server/dist/repository.d.ts +59 -0
- package/mcp-server/dist/repository.js +211 -0
- package/mcp-server/dist/tools/gherkin-validator.d.ts +16 -0
- package/mcp-server/dist/tools/gherkin-validator.js +152 -0
- package/mcp-server/dist/tools/guidance-searcher.d.ts +11 -0
- package/mcp-server/dist/tools/guidance-searcher.js +34 -0
- package/mcp-server/dist/tools/next-step-suggester.d.ts +16 -0
- package/mcp-server/dist/tools/next-step-suggester.js +210 -0
- package/mcp-server/dist/tools/reference-reader.d.ts +17 -0
- package/mcp-server/dist/tools/reference-reader.js +57 -0
- package/mcp-server/dist/tools/skill-reader.d.ts +17 -0
- package/mcp-server/dist/tools/skill-reader.js +38 -0
- package/mcp-server/dist/tools/vpc-querier.d.ts +37 -0
- package/mcp-server/dist/tools/vpc-querier.js +158 -0
- package/mcp-server/package.json +42 -0
- package/mcp-server/src/index.ts +331 -0
- package/mcp-server/src/repository.ts +254 -0
- package/mcp-server/src/tools/gherkin-validator.ts +206 -0
- package/mcp-server/src/tools/guidance-searcher.ts +42 -0
- package/mcp-server/src/tools/next-step-suggester.ts +243 -0
- package/mcp-server/src/tools/reference-reader.ts +71 -0
- package/mcp-server/src/tools/skill-reader.ts +47 -0
- package/mcp-server/src/tools/vpc-querier.ts +201 -0
- package/mcp-server/tsconfig.json +17 -0
- package/package.json +8 -2
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
/**
|
|
7
|
+
* Repository for accessing methodology files
|
|
8
|
+
*/
|
|
9
|
+
export class MethodologyRepository {
|
|
10
|
+
methodologyRoot;
|
|
11
|
+
constructor() {
|
|
12
|
+
// Point to parent directory from mcp-server
|
|
13
|
+
this.methodologyRoot = join(__dirname, "..", "..");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* List all available skills
|
|
17
|
+
*/
|
|
18
|
+
async listSkills() {
|
|
19
|
+
const skills = [
|
|
20
|
+
"product-strategy",
|
|
21
|
+
"ux-research",
|
|
22
|
+
"story-mapping",
|
|
23
|
+
"bdd-specification",
|
|
24
|
+
"ux-design",
|
|
25
|
+
"ui-design-workflow",
|
|
26
|
+
"ui-design-system",
|
|
27
|
+
"atdd-workflow",
|
|
28
|
+
"clean-code",
|
|
29
|
+
"cicd-pipeline",
|
|
30
|
+
"continuous-improvement",
|
|
31
|
+
];
|
|
32
|
+
const skillList = [];
|
|
33
|
+
for (const skillName of skills) {
|
|
34
|
+
const skillPath = join(this.methodologyRoot, skillName, "SKILL.md");
|
|
35
|
+
try {
|
|
36
|
+
const content = await fs.readFile(skillPath, "utf-8");
|
|
37
|
+
const description = this.extractDescription(content);
|
|
38
|
+
skillList.push({
|
|
39
|
+
name: skillName,
|
|
40
|
+
displayName: this.toDisplayName(skillName),
|
|
41
|
+
description,
|
|
42
|
+
path: skillPath,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Skip skills that don't have SKILL.md yet
|
|
47
|
+
console.error(`Warning: Could not read ${skillPath}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return skillList;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read a skill's main documentation
|
|
54
|
+
*/
|
|
55
|
+
async readSkill(skillName) {
|
|
56
|
+
const skillPath = join(this.methodologyRoot, skillName, "SKILL.md");
|
|
57
|
+
return await fs.readFile(skillPath, "utf-8");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* List references for a skill
|
|
61
|
+
*/
|
|
62
|
+
async listReferences(skillName) {
|
|
63
|
+
const referencesDir = join(this.methodologyRoot, skillName, "references");
|
|
64
|
+
try {
|
|
65
|
+
const files = await fs.readdir(referencesDir);
|
|
66
|
+
return files
|
|
67
|
+
.filter((f) => f.endsWith(".md"))
|
|
68
|
+
.map((f) => ({
|
|
69
|
+
name: f,
|
|
70
|
+
path: join(referencesDir, f),
|
|
71
|
+
skill: skillName,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return []; // No references directory
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read a reference document
|
|
80
|
+
*/
|
|
81
|
+
async readReference(skillName, referenceName) {
|
|
82
|
+
const refPath = join(this.methodologyRoot, skillName, "references", referenceName);
|
|
83
|
+
return await fs.readFile(refPath, "utf-8");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read VPC content
|
|
87
|
+
*/
|
|
88
|
+
async readVPC() {
|
|
89
|
+
const vpcPath = join(this.methodologyRoot, "docs", "value-proposition-canvas.md");
|
|
90
|
+
return await fs.readFile(vpcPath, "utf-8");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Read story map backbone
|
|
94
|
+
*/
|
|
95
|
+
async readStoryMap() {
|
|
96
|
+
const storyMapPath = join(this.methodologyRoot, "docs", "story-map", "backbone.md");
|
|
97
|
+
try {
|
|
98
|
+
return await fs.readFile(storyMapPath, "utf-8");
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* List feature files
|
|
106
|
+
*/
|
|
107
|
+
async listFeatureFiles(projectPath) {
|
|
108
|
+
const featuresDir = projectPath
|
|
109
|
+
? join(projectPath, "features")
|
|
110
|
+
: join(this.methodologyRoot, "features");
|
|
111
|
+
try {
|
|
112
|
+
const files = await this.walkDirectory(featuresDir);
|
|
113
|
+
return files.filter((f) => f.endsWith(".feature"));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Search all content
|
|
121
|
+
*/
|
|
122
|
+
async searchContent(query) {
|
|
123
|
+
const results = [];
|
|
124
|
+
const skills = await this.listSkills();
|
|
125
|
+
const queryLower = query.toLowerCase();
|
|
126
|
+
for (const skill of skills) {
|
|
127
|
+
const content = await this.readSkill(skill.name);
|
|
128
|
+
const relevance = this.calculateRelevance(content, queryLower);
|
|
129
|
+
if (relevance > 0) {
|
|
130
|
+
results.push({
|
|
131
|
+
file: `${skill.name}/SKILL.md`,
|
|
132
|
+
content: this.extractRelevantSection(content, queryLower),
|
|
133
|
+
relevance,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const references = await this.listReferences(skill.name);
|
|
137
|
+
for (const ref of references) {
|
|
138
|
+
const refContent = await this.readReference(skill.name, ref.name);
|
|
139
|
+
const refRelevance = this.calculateRelevance(refContent, queryLower);
|
|
140
|
+
if (refRelevance > 0) {
|
|
141
|
+
results.push({
|
|
142
|
+
file: `${skill.name}/references/${ref.name}`,
|
|
143
|
+
content: this.extractRelevantSection(refContent, queryLower),
|
|
144
|
+
relevance: refRelevance,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return results.sort((a, b) => b.relevance - a.relevance);
|
|
150
|
+
}
|
|
151
|
+
async walkDirectory(dir) {
|
|
152
|
+
const files = [];
|
|
153
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const fullPath = join(dir, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
files.push(...(await this.walkDirectory(fullPath)));
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
files.push(fullPath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return files;
|
|
164
|
+
}
|
|
165
|
+
extractDescription(content) {
|
|
166
|
+
// Extract description from frontmatter if exists
|
|
167
|
+
const frontmatterMatch = content.match(/---\n.*?description:\s*(.+?)\n/s);
|
|
168
|
+
if (frontmatterMatch) {
|
|
169
|
+
return frontmatterMatch[1].trim();
|
|
170
|
+
}
|
|
171
|
+
// Otherwise get first paragraph
|
|
172
|
+
const lines = content.split("\n");
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
if (line.trim() && !line.startsWith("#") && !line.startsWith("---")) {
|
|
175
|
+
return line.trim();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
180
|
+
toDisplayName(skillName) {
|
|
181
|
+
return skillName
|
|
182
|
+
.split("-")
|
|
183
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
184
|
+
.join(" ");
|
|
185
|
+
}
|
|
186
|
+
calculateRelevance(content, query) {
|
|
187
|
+
const contentLower = content.toLowerCase();
|
|
188
|
+
const words = query.split(/\s+/);
|
|
189
|
+
let score = 0;
|
|
190
|
+
for (const word of words) {
|
|
191
|
+
const count = (contentLower.match(new RegExp(word, "g")) || []).length;
|
|
192
|
+
score += count;
|
|
193
|
+
}
|
|
194
|
+
return score;
|
|
195
|
+
}
|
|
196
|
+
extractRelevantSection(content, query) {
|
|
197
|
+
const lines = content.split("\n");
|
|
198
|
+
const queryWords = query.split(/\s+/);
|
|
199
|
+
for (let i = 0; i < lines.length; i++) {
|
|
200
|
+
const lineLower = lines[i].toLowerCase();
|
|
201
|
+
if (queryWords.some((word) => lineLower.includes(word))) {
|
|
202
|
+
// Extract context around match (5 lines before and after)
|
|
203
|
+
const start = Math.max(0, i - 5);
|
|
204
|
+
const end = Math.min(lines.length, i + 6);
|
|
205
|
+
return lines.slice(start, end).join("\n");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Fallback: return first 300 chars
|
|
209
|
+
return content.slice(0, 300) + "...";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MethodologyRepository } from "../repository.js";
|
|
2
|
+
export declare class GherkinValidator {
|
|
3
|
+
private repository;
|
|
4
|
+
constructor(repository: MethodologyRepository);
|
|
5
|
+
validate(content: string): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
}>;
|
|
11
|
+
private extractScenarios;
|
|
12
|
+
private validateScenarioStructure;
|
|
13
|
+
private getScenarioStructure;
|
|
14
|
+
private hasSimilarStructures;
|
|
15
|
+
private formatValidationResults;
|
|
16
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export class GherkinValidator {
|
|
2
|
+
repository;
|
|
3
|
+
constructor(repository) {
|
|
4
|
+
this.repository = repository;
|
|
5
|
+
}
|
|
6
|
+
async validate(content) {
|
|
7
|
+
const result = {
|
|
8
|
+
isValid: true,
|
|
9
|
+
errors: [],
|
|
10
|
+
warnings: [],
|
|
11
|
+
suggestions: [],
|
|
12
|
+
};
|
|
13
|
+
// Check for feature header
|
|
14
|
+
if (!content.includes("Feature:")) {
|
|
15
|
+
result.isValid = false;
|
|
16
|
+
result.errors.push("Missing 'Feature:' declaration");
|
|
17
|
+
}
|
|
18
|
+
// Check for user story (As a... I want... So that...)
|
|
19
|
+
const hasAsA = /As an?/i.test(content);
|
|
20
|
+
const hasIWant = /I want/i.test(content);
|
|
21
|
+
const hasSoThat = /So that/i.test(content);
|
|
22
|
+
if (!hasAsA || !hasIWant || !hasSoThat) {
|
|
23
|
+
result.isValid = false;
|
|
24
|
+
result.errors.push("Missing user story format (As a... I want... So that...)");
|
|
25
|
+
}
|
|
26
|
+
// Check for VPC traceability
|
|
27
|
+
const vpcKeywords = [
|
|
28
|
+
"business value",
|
|
29
|
+
"pain",
|
|
30
|
+
"gain",
|
|
31
|
+
"user need",
|
|
32
|
+
"customer job",
|
|
33
|
+
];
|
|
34
|
+
const hasSomeTrace = vpcKeywords.some((keyword) => content.toLowerCase().includes(keyword.toLowerCase()));
|
|
35
|
+
if (!hasSomeTrace) {
|
|
36
|
+
result.warnings.push("No clear VPC traceability found. Consider referencing pains/gains in 'So that' clause");
|
|
37
|
+
}
|
|
38
|
+
// Check Given-When-Then structure
|
|
39
|
+
const scenarios = this.extractScenarios(content);
|
|
40
|
+
for (const scenario of scenarios) {
|
|
41
|
+
this.validateScenarioStructure(scenario, result);
|
|
42
|
+
}
|
|
43
|
+
// Check for repeated scenarios (suggest Scenario Outline)
|
|
44
|
+
if (scenarios.length >= 3) {
|
|
45
|
+
const structures = scenarios.map((s) => this.getScenarioStructure(s));
|
|
46
|
+
const hasSimilar = this.hasSimilarStructures(structures);
|
|
47
|
+
if (hasSimilar) {
|
|
48
|
+
result.suggestions.push("Multiple scenarios have similar structure. Consider using Scenario Outline with Examples table. Reference: bdd-specification/gherkin-patterns.md");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Format results
|
|
52
|
+
const text = this.formatValidationResults(result);
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: "text",
|
|
57
|
+
text,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
extractScenarios(content) {
|
|
63
|
+
const scenarios = [];
|
|
64
|
+
const scenarioRegex = /Scenario(?: Outline)?:(.+?)(?=\n\s*Scenario|$)/gs;
|
|
65
|
+
let match;
|
|
66
|
+
while ((match = scenarioRegex.exec(content)) !== null) {
|
|
67
|
+
scenarios.push(match[0]);
|
|
68
|
+
}
|
|
69
|
+
return scenarios;
|
|
70
|
+
}
|
|
71
|
+
validateScenarioStructure(scenario, result) {
|
|
72
|
+
const lines = scenario.split("\n").map((l) => l.trim());
|
|
73
|
+
// Check for proper tense in Given
|
|
74
|
+
const givenLines = lines.filter((l) => l.startsWith("Given"));
|
|
75
|
+
for (const given of givenLines) {
|
|
76
|
+
if (/\bwill\b|\bshall\b/i.test(given)) {
|
|
77
|
+
result.warnings.push(`Given step should be past tense, not future: "${given}". Reference: Given establishes existing state.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Check for single When action
|
|
81
|
+
const whenLines = lines.filter((l) => l.startsWith("When"));
|
|
82
|
+
if (whenLines.length > 1) {
|
|
83
|
+
const hasAnd = lines.some((l) => l.startsWith("And") &&
|
|
84
|
+
lines.indexOf(l) > lines.findIndex((l) => l.startsWith("When")));
|
|
85
|
+
if (!hasAnd) {
|
|
86
|
+
result.warnings.push("Multiple When steps should use 'And' for readability");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Check for observable outcomes in Then
|
|
90
|
+
const thenLines = lines.filter((l) => l.startsWith("Then"));
|
|
91
|
+
for (const then of thenLines) {
|
|
92
|
+
const nonObservable = [
|
|
93
|
+
"database",
|
|
94
|
+
"queue",
|
|
95
|
+
"log",
|
|
96
|
+
"internal state",
|
|
97
|
+
"cache",
|
|
98
|
+
];
|
|
99
|
+
for (const keyword of nonObservable) {
|
|
100
|
+
if (then.toLowerCase().includes(keyword)) {
|
|
101
|
+
result.warnings.push(`Then step references internal state: "${then}". Then steps should verify user-observable outcomes only.`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
getScenarioStructure(scenario) {
|
|
107
|
+
const lines = scenario.split("\n").map((l) => l.trim());
|
|
108
|
+
return lines
|
|
109
|
+
.filter((l) => l.startsWith("Given") || l.startsWith("When") || l.startsWith("Then"))
|
|
110
|
+
.map((l) => l.split(":")[0])
|
|
111
|
+
.join(" -> ");
|
|
112
|
+
}
|
|
113
|
+
hasSimilarStructures(structures) {
|
|
114
|
+
if (structures.length < 3)
|
|
115
|
+
return false;
|
|
116
|
+
const counts = structures.reduce((acc, s) => {
|
|
117
|
+
acc[s] = (acc[s] || 0) + 1;
|
|
118
|
+
return acc;
|
|
119
|
+
}, {});
|
|
120
|
+
return Object.values(counts).some((count) => count >= 3);
|
|
121
|
+
}
|
|
122
|
+
formatValidationResults(result) {
|
|
123
|
+
let text = "# Gherkin Validation Results\n\n";
|
|
124
|
+
if (result.isValid && result.warnings.length === 0 && result.suggestions.length === 0) {
|
|
125
|
+
text += "✅ **Validation PASSED**\n\nYour feature file follows best practices!";
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
if (!result.isValid) {
|
|
129
|
+
text += "❌ **Validation FAILED**\n\n";
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
text += "⚠️ **Validation PASSED with warnings**\n\n";
|
|
133
|
+
}
|
|
134
|
+
if (result.errors.length > 0) {
|
|
135
|
+
text += "## Errors\n\n";
|
|
136
|
+
text += result.errors.map((e) => `- ❌ ${e}`).join("\n");
|
|
137
|
+
text += "\n\n";
|
|
138
|
+
}
|
|
139
|
+
if (result.warnings.length > 0) {
|
|
140
|
+
text += "## Warnings\n\n";
|
|
141
|
+
text += result.warnings.map((w) => `- ⚠️ ${w}`).join("\n");
|
|
142
|
+
text += "\n\n";
|
|
143
|
+
}
|
|
144
|
+
if (result.suggestions.length > 0) {
|
|
145
|
+
text += "## Suggestions\n\n";
|
|
146
|
+
text += result.suggestions.map((s) => `- 💡 ${s}`).join("\n");
|
|
147
|
+
text += "\n\n";
|
|
148
|
+
}
|
|
149
|
+
text += "\n**Reference:** bdd-specification/references/gherkin-patterns.md";
|
|
150
|
+
return text;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MethodologyRepository } from "../repository.js";
|
|
2
|
+
export declare class GuidanceSearcher {
|
|
3
|
+
private repository;
|
|
4
|
+
constructor(repository: MethodologyRepository);
|
|
5
|
+
search(query: string): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class GuidanceSearcher {
|
|
2
|
+
repository;
|
|
3
|
+
constructor(repository) {
|
|
4
|
+
this.repository = repository;
|
|
5
|
+
}
|
|
6
|
+
async search(query) {
|
|
7
|
+
const results = await this.repository.searchContent(query);
|
|
8
|
+
if (results.length === 0) {
|
|
9
|
+
const skills = await this.repository.listSkills();
|
|
10
|
+
const skillList = skills.map((s) => s.name).join(", ");
|
|
11
|
+
return {
|
|
12
|
+
content: [
|
|
13
|
+
{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: `No results found for '${query}'.\n\nAvailable skills: ${skillList}`,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Take top 5 results
|
|
21
|
+
const topResults = results.slice(0, 5);
|
|
22
|
+
const text = topResults
|
|
23
|
+
.map((r, i) => `### ${i + 1}. ${r.file} (relevance: ${r.relevance})\n\n${r.content}\n`)
|
|
24
|
+
.join("\n---\n\n");
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: `# Search Results for '${query}'\n\n${text}`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MethodologyRepository } from "../repository.js";
|
|
2
|
+
export declare class NextStepSuggester {
|
|
3
|
+
private repository;
|
|
4
|
+
constructor(repository: MethodologyRepository);
|
|
5
|
+
suggest(projectPath?: string): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
}>;
|
|
11
|
+
private analyzeProjectContext;
|
|
12
|
+
private determineSuggestion;
|
|
13
|
+
private fileExists;
|
|
14
|
+
private hasTestFiles;
|
|
15
|
+
private hasPipelineConfig;
|
|
16
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
export class NextStepSuggester {
|
|
4
|
+
repository;
|
|
5
|
+
constructor(repository) {
|
|
6
|
+
this.repository = repository;
|
|
7
|
+
}
|
|
8
|
+
async suggest(projectPath) {
|
|
9
|
+
const context = await this.analyzeProjectContext(projectPath);
|
|
10
|
+
const suggestion = this.determineSuggestion(context);
|
|
11
|
+
return {
|
|
12
|
+
content: [
|
|
13
|
+
{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: suggestion,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async analyzeProjectContext(projectPath) {
|
|
21
|
+
const docsPath = projectPath ? join(projectPath, "docs") : join(process.cwd(), "docs");
|
|
22
|
+
try {
|
|
23
|
+
const vpcContent = await this.repository.readVPC();
|
|
24
|
+
const storyMapContent = await this.repository.readStoryMap();
|
|
25
|
+
const featureFiles = await this.repository.listFeatureFiles(projectPath);
|
|
26
|
+
return {
|
|
27
|
+
hasVPC: !!vpcContent,
|
|
28
|
+
hasBMC: await this.fileExists(join(docsPath, "business-model-canvas.md")),
|
|
29
|
+
hasStoryMap: !!storyMapContent,
|
|
30
|
+
hasFeatureFiles: featureFiles.length > 0,
|
|
31
|
+
hasTests: await this.hasTestFiles(projectPath),
|
|
32
|
+
testsPassing: false, // TODO: Implement test execution check
|
|
33
|
+
hasPipeline: await this.hasPipelineConfig(projectPath),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {
|
|
38
|
+
hasVPC: false,
|
|
39
|
+
hasBMC: false,
|
|
40
|
+
hasStoryMap: false,
|
|
41
|
+
hasFeatureFiles: false,
|
|
42
|
+
hasTests: false,
|
|
43
|
+
testsPassing: false,
|
|
44
|
+
hasPipeline: false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
determineSuggestion(context) {
|
|
49
|
+
if (!context.hasVPC) {
|
|
50
|
+
return `# Next Step: Create Value Proposition Canvas
|
|
51
|
+
|
|
52
|
+
You should start by defining business value using the Value Proposition Canvas.
|
|
53
|
+
|
|
54
|
+
**Why:** The VPC establishes the foundation for all downstream work. It defines customer segments, jobs, pains, and gains that inform every feature decision.
|
|
55
|
+
|
|
56
|
+
**Reference:** product-strategy/SKILL.md
|
|
57
|
+
|
|
58
|
+
**What to create:** /docs/value-proposition-canvas.md
|
|
59
|
+
|
|
60
|
+
The VPC should include:
|
|
61
|
+
- Customer segments (who are your users?)
|
|
62
|
+
- Customer jobs (what are they trying to accomplish?)
|
|
63
|
+
- Pains (what frustrates them?)
|
|
64
|
+
- Gains (what success looks like)
|
|
65
|
+
- Pain relievers (how your product helps)
|
|
66
|
+
- Gain creators (how your product delivers value)`;
|
|
67
|
+
}
|
|
68
|
+
if (!context.hasStoryMap) {
|
|
69
|
+
return `# Next Step: Create Story Map Backbone
|
|
70
|
+
|
|
71
|
+
You have a Value Proposition Canvas. Now organize user activities into a story map.
|
|
72
|
+
|
|
73
|
+
**Why:** The story map backbone organizes work by user activities (derived from VPC customer jobs) rather than technical components.
|
|
74
|
+
|
|
75
|
+
**Reference:** story-mapping/SKILL.md
|
|
76
|
+
|
|
77
|
+
**What to create:** /docs/story-map/backbone.md
|
|
78
|
+
|
|
79
|
+
Each customer job from the VPC becomes one user activity in the backbone.`;
|
|
80
|
+
}
|
|
81
|
+
if (!context.hasFeatureFiles) {
|
|
82
|
+
return `# Next Step: Write Gherkin Scenarios
|
|
83
|
+
|
|
84
|
+
You have a story map. Now specify behavior using concrete examples.
|
|
85
|
+
|
|
86
|
+
**Why:** Gherkin scenarios define "done" before implementation starts. They become executable acceptance tests.
|
|
87
|
+
|
|
88
|
+
**Reference:** bdd-specification/SKILL.md
|
|
89
|
+
|
|
90
|
+
**What to create:** /features/*.feature
|
|
91
|
+
|
|
92
|
+
Each scenario should:
|
|
93
|
+
- Use Given-When-Then format
|
|
94
|
+
- Trace "So that" clause to a VPC gain
|
|
95
|
+
- Specify observable user outcomes`;
|
|
96
|
+
}
|
|
97
|
+
if (!context.hasTests) {
|
|
98
|
+
return `# Next Step: Write Failing Acceptance Test (RED Phase)
|
|
99
|
+
|
|
100
|
+
You have Gherkin scenarios. Now write automated tests that verify them.
|
|
101
|
+
|
|
102
|
+
**Why:** Test-first development defines success criteria before implementation. Tests fail initially because the feature doesn't exist yet.
|
|
103
|
+
|
|
104
|
+
**Reference:** atdd-workflow/red-phase.md
|
|
105
|
+
|
|
106
|
+
**What to do:**
|
|
107
|
+
1. Pick one Gherkin scenario
|
|
108
|
+
2. Write automated test that verifies it
|
|
109
|
+
3. Run test and confirm it fails with clear error message
|
|
110
|
+
4. Proceed to GREEN phase once RED phase passes`;
|
|
111
|
+
}
|
|
112
|
+
if (context.hasTests && !context.testsPassing) {
|
|
113
|
+
return `# Next Step: Make Tests Pass (GREEN Phase)
|
|
114
|
+
|
|
115
|
+
You have failing tests. Now implement minimum code to make them pass.
|
|
116
|
+
|
|
117
|
+
**Why:** GREEN phase focuses on making it work, not making it perfect. Simplicity first, optimization later.
|
|
118
|
+
|
|
119
|
+
**Reference:** atdd-workflow/green-phase.md
|
|
120
|
+
|
|
121
|
+
**What to do:**
|
|
122
|
+
1. Write simplest implementation that passes the test
|
|
123
|
+
2. No premature optimization
|
|
124
|
+
3. No "future-proofing"
|
|
125
|
+
4. Run tests until they pass
|
|
126
|
+
5. Proceed to REFACTOR phase once tests are green`;
|
|
127
|
+
}
|
|
128
|
+
if (context.testsPassing) {
|
|
129
|
+
return `# Next Step: Refactor for Maintainability
|
|
130
|
+
|
|
131
|
+
You have passing tests. Now improve code structure.
|
|
132
|
+
|
|
133
|
+
**Why:** REFACTOR phase is where clean code principles apply. With tests protecting you, safely improve structure.
|
|
134
|
+
|
|
135
|
+
**References:**
|
|
136
|
+
- atdd-workflow/refactor-phase.md
|
|
137
|
+
- clean-code/solid.md
|
|
138
|
+
|
|
139
|
+
**What to do:**
|
|
140
|
+
1. Keep tests green throughout refactoring
|
|
141
|
+
2. Apply SOLID principles
|
|
142
|
+
3. Extract duplications
|
|
143
|
+
4. Use ubiquitous language
|
|
144
|
+
5. Reference clean-code patterns as needed`;
|
|
145
|
+
}
|
|
146
|
+
if (!context.hasPipeline) {
|
|
147
|
+
return `# Next Step: Set Up CI/CD Pipeline
|
|
148
|
+
|
|
149
|
+
You have tested code. Now automate deployment.
|
|
150
|
+
|
|
151
|
+
**Why:** Automated pipeline builds, tests, and deploys code without manual steps. Makes frequent delivery safe and easy.
|
|
152
|
+
|
|
153
|
+
**Reference:** cicd-pipeline/SKILL.md
|
|
154
|
+
|
|
155
|
+
**What to create:** Pipeline configuration (e.g., .github/workflows/ci.yml)
|
|
156
|
+
|
|
157
|
+
Pipeline should include:
|
|
158
|
+
- Build stage
|
|
159
|
+
- Test stage (runs all tests)
|
|
160
|
+
- Deploy stage (on successful tests)`;
|
|
161
|
+
}
|
|
162
|
+
return `# Next Step: Measure and Improve
|
|
163
|
+
|
|
164
|
+
You have a complete delivery workflow. Now collect data and iterate.
|
|
165
|
+
|
|
166
|
+
**Why:** Continuous improvement validates that delivered features create expected value and identifies process improvements.
|
|
167
|
+
|
|
168
|
+
**Reference:** continuous-improvement/SKILL.md
|
|
169
|
+
|
|
170
|
+
**What to do:**
|
|
171
|
+
1. Define success metrics (connects to VPC gains)
|
|
172
|
+
2. Collect measurement data
|
|
173
|
+
3. Perform root cause analysis when issues arise
|
|
174
|
+
4. Update upstream artifacts based on learnings`;
|
|
175
|
+
}
|
|
176
|
+
async fileExists(path) {
|
|
177
|
+
try {
|
|
178
|
+
await fs.access(path);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async hasTestFiles(projectPath) {
|
|
186
|
+
const testDirs = ["test", "tests", "spec", "__tests__"];
|
|
187
|
+
const basePath = projectPath || process.cwd();
|
|
188
|
+
for (const dir of testDirs) {
|
|
189
|
+
if (await this.fileExists(join(basePath, dir))) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
async hasPipelineConfig(projectPath) {
|
|
196
|
+
const pipelineFiles = [
|
|
197
|
+
".github/workflows",
|
|
198
|
+
".gitlab-ci.yml",
|
|
199
|
+
"Jenkinsfile",
|
|
200
|
+
".circleci/config.yml",
|
|
201
|
+
];
|
|
202
|
+
const basePath = projectPath || process.cwd();
|
|
203
|
+
for (const file of pipelineFiles) {
|
|
204
|
+
if (await this.fileExists(join(basePath, file))) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MethodologyRepository } from "../repository.js";
|
|
2
|
+
export declare class ReferenceReader {
|
|
3
|
+
private repository;
|
|
4
|
+
constructor(repository: MethodologyRepository);
|
|
5
|
+
readReference(referencePath: string): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
}>;
|
|
11
|
+
listReferences(skillName: string): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
17
|
+
}
|