@ankh-studio/ai-enablement 1.0.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +2511 -30
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -16799,9 +16799,2010 @@ class PersonaFactory {
|
|
|
16799
16799
|
}
|
|
16800
16800
|
}
|
|
16801
16801
|
|
|
16802
|
+
// src/recommendations/challenger.ts
|
|
16803
|
+
class Challenger {
|
|
16804
|
+
context;
|
|
16805
|
+
constructor(context) {
|
|
16806
|
+
this.context = context;
|
|
16807
|
+
}
|
|
16808
|
+
challengeRecommendations(recommendations, findings, hypotheses, validationResults) {
|
|
16809
|
+
const assessments = [];
|
|
16810
|
+
for (const recommendation of recommendations) {
|
|
16811
|
+
const assessment = this.challengeRecommendation(recommendation, findings, hypotheses, validationResults);
|
|
16812
|
+
assessments.push(assessment);
|
|
16813
|
+
}
|
|
16814
|
+
return assessments;
|
|
16815
|
+
}
|
|
16816
|
+
challengeRecommendation(recommendation, findings, hypotheses, validationResults) {
|
|
16817
|
+
const criticisms = [];
|
|
16818
|
+
const strengths = [];
|
|
16819
|
+
const alternativeActions = [];
|
|
16820
|
+
const downgradeReasons = [];
|
|
16821
|
+
const genericnessCheck = this.challengeGenericness(recommendation);
|
|
16822
|
+
criticisms.push(...genericnessCheck.criticisms);
|
|
16823
|
+
strengths.push(...genericnessCheck.strengths);
|
|
16824
|
+
downgradeReasons.push(...genericnessCheck.downgradeReasons);
|
|
16825
|
+
const redundancyCheck = this.challengeRedundancy(recommendation, findings, hypotheses);
|
|
16826
|
+
criticisms.push(...redundancyCheck.criticisms);
|
|
16827
|
+
strengths.push(...redundancyCheck.strengths);
|
|
16828
|
+
downgradeReasons.push(...redundancyCheck.downgradeReasons);
|
|
16829
|
+
const evidenceCheck = this.challengeEvidenceSupport(recommendation, findings);
|
|
16830
|
+
criticisms.push(...evidenceCheck.criticisms);
|
|
16831
|
+
strengths.push(...evidenceCheck.strengths);
|
|
16832
|
+
downgradeReasons.push(...evidenceCheck.downgradeReasons);
|
|
16833
|
+
const impactCheck = this.challengeImpactClaims(recommendation);
|
|
16834
|
+
criticisms.push(...impactCheck.criticisms);
|
|
16835
|
+
strengths.push(...impactCheck.strengths);
|
|
16836
|
+
downgradeReasons.push(...impactCheck.downgradeReasons);
|
|
16837
|
+
const priorityCheck = this.challengePriority(recommendation);
|
|
16838
|
+
criticisms.push(...priorityCheck.criticisms);
|
|
16839
|
+
strengths.push(...priorityCheck.strengths);
|
|
16840
|
+
downgradeReasons.push(...priorityCheck.downgradeReasons);
|
|
16841
|
+
const specificityCheck = this.challengeSpecificity(recommendation);
|
|
16842
|
+
criticisms.push(...specificityCheck.criticisms);
|
|
16843
|
+
strengths.push(...specificityCheck.strengths);
|
|
16844
|
+
downgradeReasons.push(...specificityCheck.downgradeReasons);
|
|
16845
|
+
alternativeActions.push(...this.generateAlternatives(recommendation));
|
|
16846
|
+
const { verdict, confidenceAdjustment } = this.determineVerdict(criticisms, strengths, downgradeReasons, recommendation);
|
|
16847
|
+
return {
|
|
16848
|
+
recommendationId: recommendation.id,
|
|
16849
|
+
criticisms,
|
|
16850
|
+
strengths,
|
|
16851
|
+
alternativeActions,
|
|
16852
|
+
downgradeReasons,
|
|
16853
|
+
finalVerdict: verdict,
|
|
16854
|
+
confidenceAdjustment
|
|
16855
|
+
};
|
|
16856
|
+
}
|
|
16857
|
+
challengeGenericness(recommendation) {
|
|
16858
|
+
const result = {
|
|
16859
|
+
criticisms: [],
|
|
16860
|
+
strengths: [],
|
|
16861
|
+
downgradeReasons: []
|
|
16862
|
+
};
|
|
16863
|
+
const title = recommendation.title.toLowerCase();
|
|
16864
|
+
const summary = recommendation.summary.toLowerCase();
|
|
16865
|
+
const whyMatters = recommendation.whyThisMatters.toLowerCase();
|
|
16866
|
+
const genericPatterns = [
|
|
16867
|
+
"improve",
|
|
16868
|
+
"enhance",
|
|
16869
|
+
"optimize",
|
|
16870
|
+
"better",
|
|
16871
|
+
"best practices",
|
|
16872
|
+
"standardize",
|
|
16873
|
+
"add",
|
|
16874
|
+
"implement",
|
|
16875
|
+
"establish"
|
|
16876
|
+
];
|
|
16877
|
+
const hasGenericLanguage = genericPatterns.some((pattern) => title.includes(pattern) || summary.includes(pattern));
|
|
16878
|
+
const specificPatterns = [
|
|
16879
|
+
"codeowners",
|
|
16880
|
+
"ci/cd",
|
|
16881
|
+
"typescript",
|
|
16882
|
+
"readme",
|
|
16883
|
+
"license",
|
|
16884
|
+
"gitignore",
|
|
16885
|
+
"copilot",
|
|
16886
|
+
"tests",
|
|
16887
|
+
"contributing"
|
|
16888
|
+
];
|
|
16889
|
+
const hasSpecificLanguage = specificPatterns.some((pattern) => title.includes(pattern) || summary.includes(pattern) || whyMatters.includes(pattern));
|
|
16890
|
+
if (hasGenericLanguage && !hasSpecificLanguage) {
|
|
16891
|
+
result.criticisms.push("Recommendation uses generic language without specific references");
|
|
16892
|
+
result.downgradeReasons.push("Generic advice lacks specificity");
|
|
16893
|
+
}
|
|
16894
|
+
if (hasSpecificLanguage) {
|
|
16895
|
+
result.strengths.push("Recommendation addresses specific tools or files");
|
|
16896
|
+
}
|
|
16897
|
+
const vagueImpactPatterns = [
|
|
16898
|
+
"will improve",
|
|
16899
|
+
"will enhance",
|
|
16900
|
+
"will help",
|
|
16901
|
+
"will make better",
|
|
16902
|
+
"will increase quality"
|
|
16903
|
+
];
|
|
16904
|
+
const hasVagueImpact = vagueImpactPatterns.some((pattern) => whyMatters.includes(pattern));
|
|
16905
|
+
if (hasVagueImpact) {
|
|
16906
|
+
result.criticisms.push("Impact statement is vague and non-specific");
|
|
16907
|
+
result.downgradeReasons.push("Unclear value proposition");
|
|
16908
|
+
}
|
|
16909
|
+
return result;
|
|
16910
|
+
}
|
|
16911
|
+
challengeRedundancy(recommendation, findings, hypotheses) {
|
|
16912
|
+
const result = {
|
|
16913
|
+
criticisms: [],
|
|
16914
|
+
strengths: [],
|
|
16915
|
+
downgradeReasons: []
|
|
16916
|
+
};
|
|
16917
|
+
const supportingFindings = findings.filter((f) => recommendation.supportingFindings.includes(f.id));
|
|
16918
|
+
for (const finding of supportingFindings) {
|
|
16919
|
+
if (this.calculateSimilarity(recommendation.title, finding.summary) > 0.8) {
|
|
16920
|
+
result.criticisms.push(`Recommendation appears to restate finding: "${finding.summary}"`);
|
|
16921
|
+
result.downgradeReasons.push("Redundant with existing findings");
|
|
16922
|
+
break;
|
|
16923
|
+
}
|
|
16924
|
+
}
|
|
16925
|
+
const supportingHypotheses = hypotheses.filter((h) => recommendation.supportingHypotheses.includes(h.id));
|
|
16926
|
+
if (supportingHypotheses.length === 0 && supportingFindings.length > 0) {
|
|
16927
|
+
result.criticisms.push("Recommendation lacks hypothesis-driven insight");
|
|
16928
|
+
result.downgradeReasons.push("No analytical depth beyond findings");
|
|
16929
|
+
}
|
|
16930
|
+
if (supportingHypotheses.length > 0) {
|
|
16931
|
+
result.strengths.push("Recommendation builds on hypothesis-driven analysis");
|
|
16932
|
+
}
|
|
16933
|
+
return result;
|
|
16934
|
+
}
|
|
16935
|
+
challengeEvidenceSupport(recommendation, findings) {
|
|
16936
|
+
const result = {
|
|
16937
|
+
criticisms: [],
|
|
16938
|
+
strengths: [],
|
|
16939
|
+
downgradeReasons: []
|
|
16940
|
+
};
|
|
16941
|
+
const strongAnchors = recommendation.evidenceAnchors.filter((a) => a.confidence >= 80);
|
|
16942
|
+
const weakAnchors = recommendation.evidenceAnchors.filter((a) => a.confidence < 60);
|
|
16943
|
+
if (weakAnchors.length > strongAnchors.length) {
|
|
16944
|
+
result.criticisms.push("Evidence anchors are predominantly weak");
|
|
16945
|
+
result.downgradeReasons.push("Insufficient evidence support");
|
|
16946
|
+
}
|
|
16947
|
+
if (strongAnchors.length > 0) {
|
|
16948
|
+
result.strengths.push("Strong evidence anchors support recommendation");
|
|
16949
|
+
}
|
|
16950
|
+
for (const anchor of recommendation.evidenceAnchors) {
|
|
16951
|
+
if (anchor.type === "missing" && recommendation.title.toLowerCase().includes("improve")) {
|
|
16952
|
+
result.criticisms.push('Recommendation to "improve" something that doesn\'t exist');
|
|
16953
|
+
result.downgradeReasons.push("Logical inconsistency in evidence");
|
|
16954
|
+
break;
|
|
16955
|
+
}
|
|
16956
|
+
}
|
|
16957
|
+
const anchorTypes = new Set(recommendation.evidenceAnchors.map((a) => a.type));
|
|
16958
|
+
if (anchorTypes.size === 1) {
|
|
16959
|
+
result.criticisms.push("Evidence comes from only one type of source");
|
|
16960
|
+
result.downgradeReasons.push("Limited evidence diversity");
|
|
16961
|
+
}
|
|
16962
|
+
return result;
|
|
16963
|
+
}
|
|
16964
|
+
challengeImpactClaims(recommendation) {
|
|
16965
|
+
const result = {
|
|
16966
|
+
criticisms: [],
|
|
16967
|
+
strengths: [],
|
|
16968
|
+
downgradeReasons: []
|
|
16969
|
+
};
|
|
16970
|
+
const impact = recommendation.expectedImpact;
|
|
16971
|
+
const effort = recommendation.estimatedEffort;
|
|
16972
|
+
const effortValue = { small: 1, medium: 2, large: 3 }[effort.size];
|
|
16973
|
+
const impactValue = { low: 1, medium: 2, high: 3, critical: 4 }[recommendation.priority];
|
|
16974
|
+
if (effortValue > impactValue) {
|
|
16975
|
+
result.criticisms.push("Estimated effort outweighs expected impact");
|
|
16976
|
+
result.downgradeReasons.push("Poor effort-to-impact ratio");
|
|
16977
|
+
}
|
|
16978
|
+
if (effortValue < impactValue) {
|
|
16979
|
+
result.strengths.push("Good effort-to-impact ratio");
|
|
16980
|
+
}
|
|
16981
|
+
if (impact.metrics.length === 0) {
|
|
16982
|
+
result.criticisms.push("No specific impact metrics identified");
|
|
16983
|
+
result.downgradeReasons.push("Impact cannot be measured");
|
|
16984
|
+
} else {
|
|
16985
|
+
result.strengths.push("Specific impact metrics identified");
|
|
16986
|
+
}
|
|
16987
|
+
if (impact.description.length < 50) {
|
|
16988
|
+
result.criticisms.push("Impact description is too brief");
|
|
16989
|
+
result.downgradeReasons.push("Impact not clearly explained");
|
|
16990
|
+
}
|
|
16991
|
+
return result;
|
|
16992
|
+
}
|
|
16993
|
+
challengePriority(recommendation) {
|
|
16994
|
+
const result = {
|
|
16995
|
+
criticisms: [],
|
|
16996
|
+
strengths: [],
|
|
16997
|
+
downgradeReasons: []
|
|
16998
|
+
};
|
|
16999
|
+
const avgEvidenceConfidence = recommendation.evidenceAnchors.reduce((sum, a) => sum + a.confidence, 0) / recommendation.evidenceAnchors.length;
|
|
17000
|
+
const priorityValue = { critical: 4, high: 3, medium: 2, low: 1 }[recommendation.priority];
|
|
17001
|
+
if (priorityValue === 4 && avgEvidenceConfidence < 70) {
|
|
17002
|
+
result.criticisms.push("Critical priority with weak evidence support");
|
|
17003
|
+
result.downgradeReasons.push("Priority overestimated relative to evidence");
|
|
17004
|
+
}
|
|
17005
|
+
if (priorityValue === 1 && avgEvidenceConfidence > 85) {
|
|
17006
|
+
result.criticisms.push("Low priority with strong evidence support");
|
|
17007
|
+
result.downgradeReasons.push("Priority underestimated relative to evidence");
|
|
17008
|
+
}
|
|
17009
|
+
const categoryPriorityMap = {
|
|
17010
|
+
security: "should be high or critical",
|
|
17011
|
+
foundation: "should be medium or high",
|
|
17012
|
+
workflow: "should be medium or high",
|
|
17013
|
+
ai: "should be low or medium",
|
|
17014
|
+
governance: "should be low or medium"
|
|
17015
|
+
};
|
|
17016
|
+
const expectedPriority = categoryPriorityMap[recommendation.category];
|
|
17017
|
+
const actualPriority = recommendation.priority;
|
|
17018
|
+
if (expectedPriority.includes("critical") && actualPriority !== "critical" || expectedPriority.includes("high") && !["critical", "high"].includes(actualPriority) || expectedPriority.includes("low") && !["low", "medium"].includes(actualPriority)) {
|
|
17019
|
+
result.criticisms.push(`Priority seems inappropriate for ${recommendation.category} category`);
|
|
17020
|
+
result.downgradeReasons.push("Category-priority mismatch");
|
|
17021
|
+
}
|
|
17022
|
+
return result;
|
|
17023
|
+
}
|
|
17024
|
+
challengeSpecificity(recommendation) {
|
|
17025
|
+
const result = {
|
|
17026
|
+
criticisms: [],
|
|
17027
|
+
strengths: [],
|
|
17028
|
+
downgradeReasons: []
|
|
17029
|
+
};
|
|
17030
|
+
const fileReferences = recommendation.evidenceAnchors.filter((a) => a.type === "file" || a.type === "missing").map((a) => a.path).filter(Boolean);
|
|
17031
|
+
if (fileReferences.length === 0) {
|
|
17032
|
+
result.criticisms.push("No specific file references in evidence");
|
|
17033
|
+
result.downgradeReasons.push("Lacks concrete file-level guidance");
|
|
17034
|
+
} else {
|
|
17035
|
+
result.strengths.push("References specific files for implementation");
|
|
17036
|
+
}
|
|
17037
|
+
if (recommendation.suggestedNextStep.length < 20) {
|
|
17038
|
+
result.criticisms.push("Suggested next step is too brief");
|
|
17039
|
+
result.downgradeReasons.push("Next step not clearly actionable");
|
|
17040
|
+
}
|
|
17041
|
+
if (!recommendation.implementationHints) {
|
|
17042
|
+
result.criticisms.push("No implementation hints provided");
|
|
17043
|
+
result.downgradeReasons.push("Implementation guidance missing");
|
|
17044
|
+
} else if ((recommendation.implementationHints.commands?.length || 0) > 0 || (recommendation.implementationHints.fileTemplates?.length || 0) > 0) {
|
|
17045
|
+
result.strengths.push("Provides concrete implementation guidance");
|
|
17046
|
+
}
|
|
17047
|
+
return result;
|
|
17048
|
+
}
|
|
17049
|
+
generateAlternatives(recommendation) {
|
|
17050
|
+
const alternatives = [];
|
|
17051
|
+
switch (recommendation.category) {
|
|
17052
|
+
case "security":
|
|
17053
|
+
if (recommendation.title.includes("CODEOWNERS")) {
|
|
17054
|
+
alternatives.push("Consider using branch protection rules instead");
|
|
17055
|
+
alternatives.push("Implement required reviews for sensitive files");
|
|
17056
|
+
}
|
|
17057
|
+
break;
|
|
17058
|
+
case "foundation":
|
|
17059
|
+
if (recommendation.title.includes("README")) {
|
|
17060
|
+
alternatives.push("Add inline code documentation instead");
|
|
17061
|
+
alternatives.push("Create video walkthrough for onboarding");
|
|
17062
|
+
}
|
|
17063
|
+
break;
|
|
17064
|
+
case "workflow":
|
|
17065
|
+
if (recommendation.title.includes("CI/CD")) {
|
|
17066
|
+
alternatives.push("Start with simple pre-commit hooks");
|
|
17067
|
+
alternatives.push("Use external CI service before building internal");
|
|
17068
|
+
}
|
|
17069
|
+
break;
|
|
17070
|
+
case "ai":
|
|
17071
|
+
if (recommendation.title.includes("Copilot")) {
|
|
17072
|
+
alternatives.push("Focus on code comments and documentation first");
|
|
17073
|
+
alternatives.push("Establish coding standards without AI tools");
|
|
17074
|
+
}
|
|
17075
|
+
break;
|
|
17076
|
+
}
|
|
17077
|
+
if (recommendation.estimatedEffort.size === "large") {
|
|
17078
|
+
alternatives.push("Break down into smaller, incremental changes");
|
|
17079
|
+
}
|
|
17080
|
+
if (recommendation.priority === "critical") {
|
|
17081
|
+
alternatives.push("Address underlying issues first before this recommendation");
|
|
17082
|
+
}
|
|
17083
|
+
return alternatives;
|
|
17084
|
+
}
|
|
17085
|
+
determineVerdict(criticisms, strengths, downgradeReasons, recommendation) {
|
|
17086
|
+
const criticismWeight = -10;
|
|
17087
|
+
const strengthWeight = 5;
|
|
17088
|
+
const downgradeWeight = -15;
|
|
17089
|
+
let score = 0;
|
|
17090
|
+
score += criticisms.length * criticismWeight;
|
|
17091
|
+
score += strengths.length * strengthWeight;
|
|
17092
|
+
score += downgradeReasons.length * downgradeWeight;
|
|
17093
|
+
score += (recommendation.confidence.overall - 50) * 0.5;
|
|
17094
|
+
let verdict;
|
|
17095
|
+
let confidenceAdjustment;
|
|
17096
|
+
if (score <= -40) {
|
|
17097
|
+
verdict = "reject";
|
|
17098
|
+
confidenceAdjustment = -50;
|
|
17099
|
+
} else if (score <= -20) {
|
|
17100
|
+
verdict = "downgrade";
|
|
17101
|
+
confidenceAdjustment = -25;
|
|
17102
|
+
} else if (score <= 0) {
|
|
17103
|
+
verdict = "require_human_review";
|
|
17104
|
+
confidenceAdjustment = -10;
|
|
17105
|
+
} else if (downgradeReasons.length > 0) {
|
|
17106
|
+
verdict = "require_human_review";
|
|
17107
|
+
confidenceAdjustment = -5;
|
|
17108
|
+
} else {
|
|
17109
|
+
verdict = "approve";
|
|
17110
|
+
confidenceAdjustment = Math.min(10, score * 0.2);
|
|
17111
|
+
}
|
|
17112
|
+
return { verdict, confidenceAdjustment };
|
|
17113
|
+
}
|
|
17114
|
+
calculateSimilarity(text1, text2) {
|
|
17115
|
+
const words1 = text1.toLowerCase().split(" ");
|
|
17116
|
+
const words2 = text2.toLowerCase().split(" ");
|
|
17117
|
+
const commonWords = words1.filter((word) => words2.includes(word));
|
|
17118
|
+
const totalWords = new Set([...words1, ...words2]).size;
|
|
17119
|
+
return commonWords.length / totalWords;
|
|
17120
|
+
}
|
|
17121
|
+
}
|
|
17122
|
+
|
|
17123
|
+
// src/recommendations/feedback.ts
|
|
17124
|
+
import {readFile as readFile3, writeFile} from "node:fs/promises";
|
|
17125
|
+
import {join as join4} from "node:path";
|
|
17126
|
+
|
|
17127
|
+
class FeedbackCollector {
|
|
17128
|
+
feedbackPath;
|
|
17129
|
+
feedback = [];
|
|
17130
|
+
constructor(repoPath) {
|
|
17131
|
+
this.feedbackPath = join4(repoPath, ".ai-enablement", "recommendation-feedback.json");
|
|
17132
|
+
}
|
|
17133
|
+
async loadFeedback() {
|
|
17134
|
+
try {
|
|
17135
|
+
const data = await readFile3(this.feedbackPath, "utf-8");
|
|
17136
|
+
this.feedback = JSON.parse(data);
|
|
17137
|
+
} catch (error) {
|
|
17138
|
+
this.feedback = [];
|
|
17139
|
+
}
|
|
17140
|
+
}
|
|
17141
|
+
async saveFeedback() {
|
|
17142
|
+
try {
|
|
17143
|
+
await writeFile(this.feedbackPath, JSON.stringify(this.feedback, null, 2), "utf-8");
|
|
17144
|
+
} catch (error) {
|
|
17145
|
+
console.warn("Could not save feedback file:", error);
|
|
17146
|
+
}
|
|
17147
|
+
}
|
|
17148
|
+
addFeedback(feedback) {
|
|
17149
|
+
const fullFeedback = {
|
|
17150
|
+
...feedback,
|
|
17151
|
+
timestamp: new Date().toISOString()
|
|
17152
|
+
};
|
|
17153
|
+
this.feedback.push(fullFeedback);
|
|
17154
|
+
}
|
|
17155
|
+
getFeedbackForRecommendation(recommendationId) {
|
|
17156
|
+
return this.feedback.filter((f) => f.recommendationId === recommendationId);
|
|
17157
|
+
}
|
|
17158
|
+
getAllFeedback() {
|
|
17159
|
+
return this.feedback;
|
|
17160
|
+
}
|
|
17161
|
+
getFeedbackStats() {
|
|
17162
|
+
if (this.feedback.length === 0) {
|
|
17163
|
+
return {
|
|
17164
|
+
total: 0,
|
|
17165
|
+
averageScores: {
|
|
17166
|
+
grounded: 0,
|
|
17167
|
+
correct: 0,
|
|
17168
|
+
specific: 0,
|
|
17169
|
+
actionable: 0,
|
|
17170
|
+
valuable: 0
|
|
17171
|
+
},
|
|
17172
|
+
implementationRate: 0
|
|
17173
|
+
};
|
|
17174
|
+
}
|
|
17175
|
+
const scores = this.feedback.reduce((acc, f) => ({
|
|
17176
|
+
grounded: acc.grounded + f.scores.grounded,
|
|
17177
|
+
correct: acc.correct + f.scores.correct,
|
|
17178
|
+
specific: acc.specific + f.scores.specific,
|
|
17179
|
+
actionable: acc.actionable + f.scores.actionable,
|
|
17180
|
+
valuable: acc.valuable + f.scores.valuable
|
|
17181
|
+
}), { grounded: 0, correct: 0, specific: 0, actionable: 0, valuable: 0 });
|
|
17182
|
+
const implemented = this.feedback.filter((f) => f.implemented).length;
|
|
17183
|
+
return {
|
|
17184
|
+
total: this.feedback.length,
|
|
17185
|
+
averageScores: {
|
|
17186
|
+
grounded: scores.grounded / this.feedback.length,
|
|
17187
|
+
correct: scores.correct / this.feedback.length,
|
|
17188
|
+
specific: scores.specific / this.feedback.length,
|
|
17189
|
+
actionable: scores.actionable / this.feedback.length,
|
|
17190
|
+
valuable: scores.valuable / this.feedback.length
|
|
17191
|
+
},
|
|
17192
|
+
implementationRate: implemented / this.feedback.length
|
|
17193
|
+
};
|
|
17194
|
+
}
|
|
17195
|
+
generateFeedbackTemplate(recommendations) {
|
|
17196
|
+
const template = {
|
|
17197
|
+
instructions: "Rate each recommendation on a scale of 0-2 for each criterion",
|
|
17198
|
+
criteria: {
|
|
17199
|
+
grounded: "How well is the recommendation grounded in concrete evidence?",
|
|
17200
|
+
correct: "How accurate is the recommendation for this repository?",
|
|
17201
|
+
specific: "How specific and actionable is the recommendation?",
|
|
17202
|
+
actionable: "How easy is it to implement this recommendation?",
|
|
17203
|
+
valuable: "How valuable would this recommendation be if implemented?"
|
|
17204
|
+
},
|
|
17205
|
+
scale: {
|
|
17206
|
+
0: "Poor - fails completely",
|
|
17207
|
+
1: "Fair - meets expectations",
|
|
17208
|
+
2: "Excellent - exceeds expectations"
|
|
17209
|
+
},
|
|
17210
|
+
recommendations: recommendations.map((rec) => ({
|
|
17211
|
+
recommendationId: rec.id,
|
|
17212
|
+
title: rec.title,
|
|
17213
|
+
category: rec.category,
|
|
17214
|
+
priority: rec.priority,
|
|
17215
|
+
summary: rec.summary,
|
|
17216
|
+
scores: {
|
|
17217
|
+
grounded: 0,
|
|
17218
|
+
correct: 0,
|
|
17219
|
+
specific: 0,
|
|
17220
|
+
actionable: 0,
|
|
17221
|
+
valuable: 0
|
|
17222
|
+
},
|
|
17223
|
+
notes: "",
|
|
17224
|
+
implemented: false,
|
|
17225
|
+
implementationNotes: ""
|
|
17226
|
+
}))
|
|
17227
|
+
};
|
|
17228
|
+
return JSON.stringify(template, null, 2);
|
|
17229
|
+
}
|
|
17230
|
+
async processFeedbackTemplate(templateJson) {
|
|
17231
|
+
try {
|
|
17232
|
+
const template = JSON.parse(templateJson);
|
|
17233
|
+
if (!template.recommendations || !Array.isArray(template.recommendations)) {
|
|
17234
|
+
throw new Error("Invalid template format");
|
|
17235
|
+
}
|
|
17236
|
+
for (const recFeedback of template.recommendations) {
|
|
17237
|
+
this.addFeedback({
|
|
17238
|
+
recommendationId: recFeedback.recommendationId,
|
|
17239
|
+
reviewer: "human",
|
|
17240
|
+
scores: recFeedback.scores,
|
|
17241
|
+
notes: recFeedback.notes,
|
|
17242
|
+
implemented: recFeedback.implemented,
|
|
17243
|
+
implementationNotes: recFeedback.implementationNotes
|
|
17244
|
+
});
|
|
17245
|
+
}
|
|
17246
|
+
await this.saveFeedback();
|
|
17247
|
+
} catch (error) {
|
|
17248
|
+
console.error("Error processing feedback template:", error);
|
|
17249
|
+
throw error;
|
|
17250
|
+
}
|
|
17251
|
+
}
|
|
17252
|
+
getRecommendationQualityTrends() {
|
|
17253
|
+
const trends = {
|
|
17254
|
+
byCategory: {},
|
|
17255
|
+
byPriority: {}
|
|
17256
|
+
};
|
|
17257
|
+
for (const feedback of this.feedback) {
|
|
17258
|
+
const totalScore = Object.values(feedback.scores).reduce((sum, score) => sum + score, 0);
|
|
17259
|
+
const maxScore = Object.values(feedback.scores).length * 2;
|
|
17260
|
+
const normalizedScore = totalScore / maxScore;
|
|
17261
|
+
}
|
|
17262
|
+
return trends;
|
|
17263
|
+
}
|
|
17264
|
+
identifyLowQualityRecommendations(threshold = 0.5) {
|
|
17265
|
+
const lowQualityIds = [];
|
|
17266
|
+
for (const feedback of this.feedback) {
|
|
17267
|
+
const totalScore = Object.values(feedback.scores).reduce((sum, score) => sum + score, 0);
|
|
17268
|
+
const maxScore = Object.values(feedback.scores).length * 2;
|
|
17269
|
+
const normalizedScore = totalScore / maxScore;
|
|
17270
|
+
if (normalizedScore < threshold) {
|
|
17271
|
+
lowQualityIds.push(feedback.recommendationId);
|
|
17272
|
+
}
|
|
17273
|
+
}
|
|
17274
|
+
return [...new Set(lowQualityIds)];
|
|
17275
|
+
}
|
|
17276
|
+
identifyHighValueRecommendations(threshold = 0.8) {
|
|
17277
|
+
const highValueIds = [];
|
|
17278
|
+
for (const feedback of this.feedback) {
|
|
17279
|
+
if (feedback.scores.valuable >= 1.5 && feedback.implemented) {
|
|
17280
|
+
highValueIds.push(feedback.recommendationId);
|
|
17281
|
+
}
|
|
17282
|
+
}
|
|
17283
|
+
return [...new Set(highValueIds)];
|
|
17284
|
+
}
|
|
17285
|
+
generateQualityReport() {
|
|
17286
|
+
const stats = this.getFeedbackStats();
|
|
17287
|
+
const lowQuality = this.identifyLowQualityRecommendations();
|
|
17288
|
+
const highValue = this.identifyHighValueRecommendations();
|
|
17289
|
+
return `
|
|
17290
|
+
# Recommendation Quality Report
|
|
17291
|
+
|
|
17292
|
+
## Overall Statistics
|
|
17293
|
+
- Total recommendations reviewed: ${stats.total}
|
|
17294
|
+
- Average scores (0-2 scale):
|
|
17295
|
+
- Grounded: ${stats.averageScores.grounded.toFixed(2)}
|
|
17296
|
+
- Correct: ${stats.averageScores.correct.toFixed(2)}
|
|
17297
|
+
- Specific: ${stats.averageScores.specific.toFixed(2)}
|
|
17298
|
+
- Actionable: ${stats.averageScores.actionable.toFixed(2)}
|
|
17299
|
+
- Valuable: ${stats.averageScores.valuable.toFixed(2)}
|
|
17300
|
+
- Implementation rate: ${(stats.implementationRate * 100).toFixed(1)}%
|
|
17301
|
+
|
|
17302
|
+
## Quality Insights
|
|
17303
|
+
- Low quality recommendations (<50% score): ${lowQuality.length}
|
|
17304
|
+
- High value implemented recommendations (>80% valuable): ${highValue.length}
|
|
17305
|
+
|
|
17306
|
+
## Recommendations
|
|
17307
|
+
- Focus on improving: ${lowQuality.length > 0 ? lowQuality.join(", ") : "None identified"}
|
|
17308
|
+
- Replicate success patterns: ${highValue.length > 0 ? highValue.join(", ") : "Insufficient data"}
|
|
17309
|
+
|
|
17310
|
+
## Next Steps
|
|
17311
|
+
1. Address low-quality recommendation patterns
|
|
17312
|
+
2. Analyze high-value recommendations for common characteristics
|
|
17313
|
+
3. Use feedback to tune confidence thresholds and validation criteria
|
|
17314
|
+
`.trim();
|
|
17315
|
+
}
|
|
17316
|
+
}
|
|
17317
|
+
|
|
17318
|
+
// src/recommendations/finding-builder.ts
|
|
17319
|
+
class FindingBuilder {
|
|
17320
|
+
context;
|
|
17321
|
+
constructor(context) {
|
|
17322
|
+
this.context = context;
|
|
17323
|
+
}
|
|
17324
|
+
buildFindings() {
|
|
17325
|
+
const findings = [];
|
|
17326
|
+
findings.push(...this.buildFoundationFindings());
|
|
17327
|
+
findings.push(...this.buildSecurityFindings());
|
|
17328
|
+
findings.push(...this.buildWorkflowFindings());
|
|
17329
|
+
findings.push(...this.buildAIFindings());
|
|
17330
|
+
findings.push(...this.buildGovernanceFindings());
|
|
17331
|
+
return findings;
|
|
17332
|
+
}
|
|
17333
|
+
buildFoundationFindings() {
|
|
17334
|
+
const findings = [];
|
|
17335
|
+
const { evidence } = this.context;
|
|
17336
|
+
if (!evidence.structure.hasReadme) {
|
|
17337
|
+
findings.push(this.createFinding("found-missing-readme", "foundation", "medium", "Repository lacks README documentation", [
|
|
17338
|
+
{
|
|
17339
|
+
type: "missing",
|
|
17340
|
+
path: "README.md",
|
|
17341
|
+
description: "No README.md found in repository root",
|
|
17342
|
+
confidence: 100
|
|
17343
|
+
}
|
|
17344
|
+
], ["README.md"]));
|
|
17345
|
+
}
|
|
17346
|
+
if (!evidence.structure.hasLicense) {
|
|
17347
|
+
findings.push(this.createFinding("found-missing-license", "foundation", "high", "Repository lacks license file", [
|
|
17348
|
+
{
|
|
17349
|
+
type: "missing",
|
|
17350
|
+
path: "LICENSE",
|
|
17351
|
+
description: "No LICENSE file found",
|
|
17352
|
+
confidence: 100
|
|
17353
|
+
}
|
|
17354
|
+
], ["LICENSE"]));
|
|
17355
|
+
}
|
|
17356
|
+
if (evidence.structure.directoryDepth > 8) {
|
|
17357
|
+
findings.push(this.createFinding("found-deep-structure", "foundation", "medium", "Repository has excessively deep directory structure", [
|
|
17358
|
+
{
|
|
17359
|
+
type: "metric",
|
|
17360
|
+
metric: {
|
|
17361
|
+
name: "directory_depth",
|
|
17362
|
+
value: evidence.structure.directoryDepth,
|
|
17363
|
+
threshold: 8
|
|
17364
|
+
},
|
|
17365
|
+
description: `Directory depth is ${evidence.structure.directoryDepth} levels (recommended: \u22648)`,
|
|
17366
|
+
confidence: 90
|
|
17367
|
+
}
|
|
17368
|
+
], []));
|
|
17369
|
+
}
|
|
17370
|
+
if (!evidence.configuration.hasGitignore) {
|
|
17371
|
+
findings.push(this.createFinding("found-missing-gitignore", "foundation", "high", "Repository lacks .gitignore file", [
|
|
17372
|
+
{
|
|
17373
|
+
type: "missing",
|
|
17374
|
+
path: ".gitignore",
|
|
17375
|
+
description: "No .gitignore file found",
|
|
17376
|
+
confidence: 100
|
|
17377
|
+
}
|
|
17378
|
+
], [".gitignore"]));
|
|
17379
|
+
}
|
|
17380
|
+
if (!evidence.configuration.hasTypeScript && this.isTypeScriptProject()) {
|
|
17381
|
+
findings.push(this.createFinding("found-typescript-unconfigured", "foundation", "medium", "TypeScript project lacks configuration", [
|
|
17382
|
+
{
|
|
17383
|
+
type: "missing",
|
|
17384
|
+
path: "tsconfig.json",
|
|
17385
|
+
description: "TypeScript files detected but no tsconfig.json found",
|
|
17386
|
+
confidence: 95
|
|
17387
|
+
}
|
|
17388
|
+
], ["tsconfig.json"]));
|
|
17389
|
+
}
|
|
17390
|
+
return findings;
|
|
17391
|
+
}
|
|
17392
|
+
buildSecurityFindings() {
|
|
17393
|
+
const findings = [];
|
|
17394
|
+
const { copilotFeatures } = this.context;
|
|
17395
|
+
if (!copilotFeatures.githubFeatures.codeowners.found) {
|
|
17396
|
+
findings.push(this.createFinding("found-missing-codeowners", "security", "high", "Repository lacks CODEOWNERS file", [
|
|
17397
|
+
{
|
|
17398
|
+
type: "missing",
|
|
17399
|
+
path: ".github/CODEOWNERS",
|
|
17400
|
+
description: "No CODEOWNERS file found for code ownership rules",
|
|
17401
|
+
confidence: 100
|
|
17402
|
+
}
|
|
17403
|
+
], [".github/CODEOWNERS"]));
|
|
17404
|
+
}
|
|
17405
|
+
if (this.context.evidence.metrics.dependencyHealth === "poor") {
|
|
17406
|
+
findings.push(this.createFinding("found-poor-dependencies", "security", "high", "Repository has poor dependency health", [
|
|
17407
|
+
{
|
|
17408
|
+
type: "metric",
|
|
17409
|
+
metric: {
|
|
17410
|
+
name: "dependency_health",
|
|
17411
|
+
value: this.context.evidence.metrics.dependencyHealth
|
|
17412
|
+
},
|
|
17413
|
+
description: "Dependency health assessment indicates security or maintenance issues",
|
|
17414
|
+
confidence: 70
|
|
17415
|
+
}
|
|
17416
|
+
], ["package.json", "yarn.lock", "package-lock.json"]));
|
|
17417
|
+
}
|
|
17418
|
+
return findings;
|
|
17419
|
+
}
|
|
17420
|
+
buildWorkflowFindings() {
|
|
17421
|
+
const findings = [];
|
|
17422
|
+
const { evidence, copilotFeatures } = this.context;
|
|
17423
|
+
if (!evidence.configuration.hasCi) {
|
|
17424
|
+
findings.push(this.createFinding("found-no-ci", "workflow", "high", "Repository lacks CI/CD configuration", [
|
|
17425
|
+
{
|
|
17426
|
+
type: "missing",
|
|
17427
|
+
path: ".github/workflows/",
|
|
17428
|
+
description: "No CI/CD workflows found in .github/workflows/",
|
|
17429
|
+
confidence: 100
|
|
17430
|
+
}
|
|
17431
|
+
], [".github/workflows/"]));
|
|
17432
|
+
}
|
|
17433
|
+
if (!evidence.configuration.hasTests) {
|
|
17434
|
+
findings.push(this.createFinding("found-no-tests", "workflow", "high", "Repository lacks test configuration", [
|
|
17435
|
+
{
|
|
17436
|
+
type: "missing",
|
|
17437
|
+
description: "No test framework configuration detected",
|
|
17438
|
+
confidence: 90
|
|
17439
|
+
}
|
|
17440
|
+
], []));
|
|
17441
|
+
}
|
|
17442
|
+
if (!copilotFeatures.githubFeatures.prTemplates.found) {
|
|
17443
|
+
findings.push(this.createFinding("found-no-pr-templates", "workflow", "medium", "Repository lacks PR templates", [
|
|
17444
|
+
{
|
|
17445
|
+
type: "missing",
|
|
17446
|
+
path: ".github/pull_request_template.md",
|
|
17447
|
+
description: "No PR template found for standardizing contributions",
|
|
17448
|
+
confidence: 100
|
|
17449
|
+
}
|
|
17450
|
+
], [".github/pull_request_template.md"]));
|
|
17451
|
+
}
|
|
17452
|
+
return findings;
|
|
17453
|
+
}
|
|
17454
|
+
buildAIFindings() {
|
|
17455
|
+
const findings = [];
|
|
17456
|
+
const { copilotFeatures } = this.context;
|
|
17457
|
+
if (!copilotFeatures.githubFeatures.copilotInstructions.found) {
|
|
17458
|
+
findings.push(this.createFinding("found-no-copilot-instructions", "ai", "medium", "Repository lacks Copilot instructions", [
|
|
17459
|
+
{
|
|
17460
|
+
type: "missing",
|
|
17461
|
+
path: ".github/copilot-instructions.md",
|
|
17462
|
+
description: "No Copilot instructions found for AI assistance guidance",
|
|
17463
|
+
confidence: 100
|
|
17464
|
+
}
|
|
17465
|
+
], [".github/copilot-instructions.md"]));
|
|
17466
|
+
}
|
|
17467
|
+
if (copilotFeatures.codePatterns.aiFriendlyComments < 5) {
|
|
17468
|
+
findings.push(this.createFinding("found-low-ai-comments", "ai", "low", "Low AI-friendly comment density", [
|
|
17469
|
+
{
|
|
17470
|
+
type: "metric",
|
|
17471
|
+
metric: {
|
|
17472
|
+
name: "ai_friendly_comments",
|
|
17473
|
+
value: copilotFeatures.codePatterns.aiFriendlyComments,
|
|
17474
|
+
threshold: 5
|
|
17475
|
+
},
|
|
17476
|
+
description: `Only ${copilotFeatures.codePatterns.aiFriendlyComments} AI-friendly comment patterns found`,
|
|
17477
|
+
confidence: 80
|
|
17478
|
+
}
|
|
17479
|
+
], []));
|
|
17480
|
+
}
|
|
17481
|
+
return findings;
|
|
17482
|
+
}
|
|
17483
|
+
buildGovernanceFindings() {
|
|
17484
|
+
const findings = [];
|
|
17485
|
+
const { evidence } = this.context;
|
|
17486
|
+
if (!evidence.structure.hasContributing) {
|
|
17487
|
+
findings.push(this.createFinding("found-no-contributing", "governance", "medium", "Repository lacks contributing guidelines", [
|
|
17488
|
+
{
|
|
17489
|
+
type: "missing",
|
|
17490
|
+
path: "CONTRIBUTING.md",
|
|
17491
|
+
description: "No CONTRIBUTING.md found for contribution process",
|
|
17492
|
+
confidence: 100
|
|
17493
|
+
}
|
|
17494
|
+
], ["CONTRIBUTING.md"]));
|
|
17495
|
+
}
|
|
17496
|
+
if (!evidence.structure.hasChangelog) {
|
|
17497
|
+
findings.push(this.createFinding("found-no-changelog", "governance", "low", "Repository lacks changelog", [
|
|
17498
|
+
{
|
|
17499
|
+
type: "missing",
|
|
17500
|
+
path: "CHANGELOG.md",
|
|
17501
|
+
description: "No CHANGELOG.md found for tracking changes",
|
|
17502
|
+
confidence: 100
|
|
17503
|
+
}
|
|
17504
|
+
], ["CHANGELOG.md"]));
|
|
17505
|
+
}
|
|
17506
|
+
if (evidence.patterns.documentationCoverage < 10) {
|
|
17507
|
+
findings.push(this.createFinding("found-low-docs", "governance", "medium", "Low documentation coverage", [
|
|
17508
|
+
{
|
|
17509
|
+
type: "metric",
|
|
17510
|
+
metric: {
|
|
17511
|
+
name: "documentation_coverage",
|
|
17512
|
+
value: evidence.patterns.documentationCoverage,
|
|
17513
|
+
threshold: 10
|
|
17514
|
+
},
|
|
17515
|
+
description: `Documentation coverage is only ${evidence.patterns.documentationCoverage}%`,
|
|
17516
|
+
confidence: 85
|
|
17517
|
+
}
|
|
17518
|
+
], []));
|
|
17519
|
+
}
|
|
17520
|
+
return findings;
|
|
17521
|
+
}
|
|
17522
|
+
createFinding(id, category, severity, summary, evidenceAnchors, affectedFiles) {
|
|
17523
|
+
return {
|
|
17524
|
+
id,
|
|
17525
|
+
category,
|
|
17526
|
+
severity,
|
|
17527
|
+
summary,
|
|
17528
|
+
evidenceAnchors,
|
|
17529
|
+
affectedFiles,
|
|
17530
|
+
relatedScores: this.getRelatedScores(category),
|
|
17531
|
+
metadata: {
|
|
17532
|
+
detectionMethod: "deterministic_pattern_match",
|
|
17533
|
+
reproducible: true,
|
|
17534
|
+
timestamp: new Date().toISOString()
|
|
17535
|
+
}
|
|
17536
|
+
};
|
|
17537
|
+
}
|
|
17538
|
+
getRelatedScores(category) {
|
|
17539
|
+
const { scores } = this.context;
|
|
17540
|
+
const scoreMap = {
|
|
17541
|
+
foundation: ["foundation"],
|
|
17542
|
+
security: ["security"],
|
|
17543
|
+
workflow: ["workflow"],
|
|
17544
|
+
ai: ["ai"],
|
|
17545
|
+
governance: ["governance"]
|
|
17546
|
+
};
|
|
17547
|
+
return scoreMap[category].map((scoreCategory) => ({
|
|
17548
|
+
category: scoreCategory,
|
|
17549
|
+
score: scores.breakdown[scoreCategory],
|
|
17550
|
+
impact: "negative"
|
|
17551
|
+
}));
|
|
17552
|
+
}
|
|
17553
|
+
isTypeScriptProject() {
|
|
17554
|
+
const { techStack } = this.context;
|
|
17555
|
+
return techStack.aiReadiness.typescriptUsage || this.context.evidence.configuration.hasTypeScript;
|
|
17556
|
+
}
|
|
17557
|
+
}
|
|
17558
|
+
|
|
17559
|
+
// src/recommendations/hypothesis-engine.ts
|
|
17560
|
+
class HypothesisEngine {
|
|
17561
|
+
context;
|
|
17562
|
+
constructor(context) {
|
|
17563
|
+
this.context = context;
|
|
17564
|
+
}
|
|
17565
|
+
generateHypotheses(findings) {
|
|
17566
|
+
const hypotheses = [];
|
|
17567
|
+
const findingsByCategory = this.groupFindingsByCategory(findings);
|
|
17568
|
+
for (const [category, categoryFindings] of Object.entries(findingsByCategory)) {
|
|
17569
|
+
hypotheses.push(...this.generateCategoryHypotheses(category, categoryFindings));
|
|
17570
|
+
}
|
|
17571
|
+
hypotheses.push(...this.generateCrossCategoryHypotheses(findings));
|
|
17572
|
+
return hypotheses;
|
|
17573
|
+
}
|
|
17574
|
+
groupFindingsByCategory(findings) {
|
|
17575
|
+
const grouped = {};
|
|
17576
|
+
for (const finding of findings) {
|
|
17577
|
+
if (!grouped[finding.category]) {
|
|
17578
|
+
grouped[finding.category] = [];
|
|
17579
|
+
}
|
|
17580
|
+
grouped[finding.category].push(finding);
|
|
17581
|
+
}
|
|
17582
|
+
return grouped;
|
|
17583
|
+
}
|
|
17584
|
+
generateCategoryHypotheses(category, findings) {
|
|
17585
|
+
const hypotheses = [];
|
|
17586
|
+
switch (category) {
|
|
17587
|
+
case "security":
|
|
17588
|
+
hypotheses.push(...this.generateSecurityHypotheses(findings));
|
|
17589
|
+
break;
|
|
17590
|
+
case "foundation":
|
|
17591
|
+
hypotheses.push(...this.generateFoundationHypotheses(findings));
|
|
17592
|
+
break;
|
|
17593
|
+
case "workflow":
|
|
17594
|
+
hypotheses.push(...this.generateWorkflowHypotheses(findings));
|
|
17595
|
+
break;
|
|
17596
|
+
case "ai":
|
|
17597
|
+
hypotheses.push(...this.generateAIHypotheses(findings));
|
|
17598
|
+
break;
|
|
17599
|
+
case "governance":
|
|
17600
|
+
hypotheses.push(...this.generateGovernanceHypotheses(findings));
|
|
17601
|
+
break;
|
|
17602
|
+
}
|
|
17603
|
+
return hypotheses;
|
|
17604
|
+
}
|
|
17605
|
+
generateSecurityHypotheses(findings) {
|
|
17606
|
+
const hypotheses = [];
|
|
17607
|
+
const codeownersFinding = findings.find((f) => f.id === "found-missing-codeowners");
|
|
17608
|
+
if (codeownersFinding) {
|
|
17609
|
+
hypotheses.push({
|
|
17610
|
+
id: "hyp-codeowners-velocity",
|
|
17611
|
+
title: "Lack of CODEOWNERS may reduce safe AI-assisted change velocity",
|
|
17612
|
+
description: "Without clear code ownership, AI-assisted changes may lack proper review and approval pathways, potentially reducing the safety and speed of AI-driven development.",
|
|
17613
|
+
category: "security",
|
|
17614
|
+
supportingFindings: [codeownersFinding.id],
|
|
17615
|
+
reasoning: "CODEOWNERS files establish clear ownership and review requirements. In AI-assisted development, this becomes critical for ensuring AI-generated changes receive appropriate human oversight.",
|
|
17616
|
+
confidence: 75,
|
|
17617
|
+
alternativeInterpretations: [
|
|
17618
|
+
"Team may use alternative approval mechanisms",
|
|
17619
|
+
"Repository may not require formal review processes",
|
|
17620
|
+
"Code ownership might be documented elsewhere"
|
|
17621
|
+
],
|
|
17622
|
+
validationChecks: [
|
|
17623
|
+
{
|
|
17624
|
+
id: "vc-codeowners-alternatives",
|
|
17625
|
+
type: "repo_specificity",
|
|
17626
|
+
description: "Check if alternative approval mechanisms exist",
|
|
17627
|
+
expected: "No alternative approval mechanisms found"
|
|
17628
|
+
},
|
|
17629
|
+
{
|
|
17630
|
+
id: "vc-codeowners-necessity",
|
|
17631
|
+
type: "actionability",
|
|
17632
|
+
description: "Assess if CODEOWNERS is necessary for this repo type",
|
|
17633
|
+
expected: "Repository would benefit from clear ownership"
|
|
17634
|
+
}
|
|
17635
|
+
],
|
|
17636
|
+
estimatedImpact: "high",
|
|
17637
|
+
estimatedEffort: "small"
|
|
17638
|
+
});
|
|
17639
|
+
}
|
|
17640
|
+
const depFinding = findings.find((f) => f.id === "found-poor-dependencies");
|
|
17641
|
+
if (depFinding) {
|
|
17642
|
+
hypotheses.push({
|
|
17643
|
+
id: "hyp-deps-security-risk",
|
|
17644
|
+
title: "Poor dependency health indicates potential security vulnerabilities",
|
|
17645
|
+
description: "The repository's dependencies show signs of poor maintenance, which could introduce security vulnerabilities and compatibility issues.",
|
|
17646
|
+
category: "security",
|
|
17647
|
+
supportingFindings: [depFinding.id],
|
|
17648
|
+
reasoning: "Poor dependency health often indicates outdated packages with known vulnerabilities or lack of security updates.",
|
|
17649
|
+
confidence: 70,
|
|
17650
|
+
alternativeInterpretations: [
|
|
17651
|
+
"Dependencies may be stable but infrequently updated",
|
|
17652
|
+
"Security scanning might be handled separately",
|
|
17653
|
+
"Repository might use dependency pinning for stability"
|
|
17654
|
+
],
|
|
17655
|
+
validationChecks: [
|
|
17656
|
+
{
|
|
17657
|
+
id: "vc-deps-actual-vulnerabilities",
|
|
17658
|
+
type: "evidence_strength",
|
|
17659
|
+
description: "Check for actual security vulnerabilities",
|
|
17660
|
+
expected: "Security audit would reveal vulnerabilities"
|
|
17661
|
+
},
|
|
17662
|
+
{
|
|
17663
|
+
id: "vc-deps-update-frequency",
|
|
17664
|
+
type: "repo_specificity",
|
|
17665
|
+
description: "Assess dependency update patterns",
|
|
17666
|
+
expected: "Dependencies are significantly outdated"
|
|
17667
|
+
}
|
|
17668
|
+
],
|
|
17669
|
+
estimatedImpact: "high",
|
|
17670
|
+
estimatedEffort: "medium"
|
|
17671
|
+
});
|
|
17672
|
+
}
|
|
17673
|
+
return hypotheses;
|
|
17674
|
+
}
|
|
17675
|
+
generateFoundationHypotheses(findings) {
|
|
17676
|
+
const hypotheses = [];
|
|
17677
|
+
const criticalFoundationFindings = findings.filter((f) => f.category === "foundation" && (f.severity === "high" || f.severity === "critical"));
|
|
17678
|
+
if (criticalFoundationFindings.length >= 2) {
|
|
17679
|
+
hypotheses.push({
|
|
17680
|
+
id: "hyp-foundation-immaturity",
|
|
17681
|
+
title: "Multiple foundation issues indicate repository immaturity",
|
|
17682
|
+
description: "The repository shows several foundational gaps that suggest it's in early stages of development or lacks proper project hygiene.",
|
|
17683
|
+
category: "foundation",
|
|
17684
|
+
supportingFindings: criticalFoundationFindings.map((f) => f.id),
|
|
17685
|
+
reasoning: "Multiple foundational issues (README, license, .gitignore, etc.) indicate lack of project maturity and potentially poor developer experience.",
|
|
17686
|
+
confidence: 85,
|
|
17687
|
+
alternativeInterpretations: [
|
|
17688
|
+
"Repository might be intentionally minimal",
|
|
17689
|
+
"Project might be internal or temporary",
|
|
17690
|
+
"Documentation might exist in external systems"
|
|
17691
|
+
],
|
|
17692
|
+
validationChecks: [
|
|
17693
|
+
{
|
|
17694
|
+
id: "vc-foundation-repo-age",
|
|
17695
|
+
type: "repo_specificity",
|
|
17696
|
+
description: "Check repository age and activity",
|
|
17697
|
+
expected: "Repository is relatively new or inactive"
|
|
17698
|
+
},
|
|
17699
|
+
{
|
|
17700
|
+
id: "vc-foundation-project-type",
|
|
17701
|
+
type: "actionability",
|
|
17702
|
+
description: "Assess if foundation items are needed for this project type",
|
|
17703
|
+
expected: "Project would benefit from standard foundation items"
|
|
17704
|
+
}
|
|
17705
|
+
],
|
|
17706
|
+
estimatedImpact: "medium",
|
|
17707
|
+
estimatedEffort: "medium"
|
|
17708
|
+
});
|
|
17709
|
+
}
|
|
17710
|
+
const tsFinding = findings.find((f) => f.id === "found-typescript-unconfigured");
|
|
17711
|
+
if (tsFinding) {
|
|
17712
|
+
hypotheses.push({
|
|
17713
|
+
id: "hyp-typescript-consistency",
|
|
17714
|
+
title: "Unconfigured TypeScript may lead to inconsistent code quality",
|
|
17715
|
+
description: "TypeScript files exist without proper configuration, which could lead to inconsistent type checking and reduced developer experience.",
|
|
17716
|
+
category: "foundation",
|
|
17717
|
+
supportingFindings: [tsFinding.id],
|
|
17718
|
+
reasoning: "TypeScript configuration ensures consistent type checking across the project. Without it, developers may have different experiences and code quality may vary.",
|
|
17719
|
+
confidence: 80,
|
|
17720
|
+
alternativeInterpretations: [
|
|
17721
|
+
"TypeScript might be used with default settings",
|
|
17722
|
+
"Configuration might be inherited from parent package",
|
|
17723
|
+
"Project might be in migration phase"
|
|
17724
|
+
],
|
|
17725
|
+
validationChecks: [
|
|
17726
|
+
{
|
|
17727
|
+
id: "vc-typescript-usage",
|
|
17728
|
+
type: "evidence_strength",
|
|
17729
|
+
description: "Verify actual TypeScript usage extent",
|
|
17730
|
+
expected: "Significant TypeScript files exist"
|
|
17731
|
+
},
|
|
17732
|
+
{
|
|
17733
|
+
id: "vc-typescript-config-impact",
|
|
17734
|
+
type: "actionability",
|
|
17735
|
+
description: "Assess impact of missing configuration",
|
|
17736
|
+
expected: "Configuration would significantly improve development"
|
|
17737
|
+
}
|
|
17738
|
+
],
|
|
17739
|
+
estimatedImpact: "medium",
|
|
17740
|
+
estimatedEffort: "small"
|
|
17741
|
+
});
|
|
17742
|
+
}
|
|
17743
|
+
return hypotheses;
|
|
17744
|
+
}
|
|
17745
|
+
generateWorkflowHypotheses(findings) {
|
|
17746
|
+
const hypotheses = [];
|
|
17747
|
+
const ciFinding = findings.find((f) => f.id === "found-no-ci");
|
|
17748
|
+
if (ciFinding) {
|
|
17749
|
+
hypotheses.push({
|
|
17750
|
+
id: "hyp-ci-automation-gap",
|
|
17751
|
+
title: "Lack of CI/CD indicates manual deployment and higher risk",
|
|
17752
|
+
description: "Without automated CI/CD, the repository relies on manual processes which increase deployment risk and reduce development velocity.",
|
|
17753
|
+
category: "workflow",
|
|
17754
|
+
supportingFindings: [ciFinding.id],
|
|
17755
|
+
reasoning: "CI/CD automation is standard practice for reliable deployments and quality control. Manual processes are error-prone and slow.",
|
|
17756
|
+
confidence: 90,
|
|
17757
|
+
alternativeInterpretations: [
|
|
17758
|
+
"Repository might be deployed through external systems",
|
|
17759
|
+
"Project might not require automated deployments",
|
|
17760
|
+
"CI/CD might be handled at organizational level"
|
|
17761
|
+
],
|
|
17762
|
+
validationChecks: [
|
|
17763
|
+
{
|
|
17764
|
+
id: "vc-ci-deployment-frequency",
|
|
17765
|
+
type: "repo_specificity",
|
|
17766
|
+
description: "Check deployment patterns and frequency",
|
|
17767
|
+
expected: "Manual deployments would benefit from automation"
|
|
17768
|
+
},
|
|
17769
|
+
{
|
|
17770
|
+
id: "vc-ci-complexity",
|
|
17771
|
+
type: "actionability",
|
|
17772
|
+
description: "Assess if CI/CD setup is feasible",
|
|
17773
|
+
expected: "Project complexity warrants CI/CD implementation"
|
|
17774
|
+
}
|
|
17775
|
+
],
|
|
17776
|
+
estimatedImpact: "high",
|
|
17777
|
+
estimatedEffort: "medium"
|
|
17778
|
+
});
|
|
17779
|
+
}
|
|
17780
|
+
const testFinding = findings.find((f) => f.id === "found-no-tests");
|
|
17781
|
+
if (testFinding) {
|
|
17782
|
+
hypotheses.push({
|
|
17783
|
+
id: "hyp-testing-quality-risk",
|
|
17784
|
+
title: "No test configuration indicates quality and maintenance risks",
|
|
17785
|
+
description: "The absence of test configuration suggests minimal automated testing, which increases the risk of bugs and makes changes more dangerous.",
|
|
17786
|
+
category: "workflow",
|
|
17787
|
+
supportingFindings: [testFinding.id],
|
|
17788
|
+
reasoning: "Automated testing is essential for maintaining code quality and enabling safe refactoring. Without tests, changes are risky and maintenance is costly.",
|
|
17789
|
+
confidence: 85,
|
|
17790
|
+
alternativeInterpretations: [
|
|
17791
|
+
"Testing might be done manually or externally",
|
|
17792
|
+
"Project might be simple enough to not need tests",
|
|
17793
|
+
"Tests might exist but not be configured for the test runner"
|
|
17794
|
+
],
|
|
17795
|
+
validationChecks: [
|
|
17796
|
+
{
|
|
17797
|
+
id: "vc-testing-complexity",
|
|
17798
|
+
type: "repo_specificity",
|
|
17799
|
+
description: "Assess project complexity and testing needs",
|
|
17800
|
+
expected: "Project complexity warrants automated testing"
|
|
17801
|
+
},
|
|
17802
|
+
{
|
|
17803
|
+
id: "vc-testing-implementation",
|
|
17804
|
+
type: "actionability",
|
|
17805
|
+
description: "Evaluate feasibility of implementing tests",
|
|
17806
|
+
expected: "Testing implementation would provide significant value"
|
|
17807
|
+
}
|
|
17808
|
+
],
|
|
17809
|
+
estimatedImpact: "high",
|
|
17810
|
+
estimatedEffort: "large"
|
|
17811
|
+
});
|
|
17812
|
+
}
|
|
17813
|
+
return hypotheses;
|
|
17814
|
+
}
|
|
17815
|
+
generateAIHypotheses(findings) {
|
|
17816
|
+
const hypotheses = [];
|
|
17817
|
+
const copilotFinding = findings.find((f) => f.id === "found-no-copilot-instructions");
|
|
17818
|
+
if (copilotFinding) {
|
|
17819
|
+
hypotheses.push({
|
|
17820
|
+
id: "hyp-copilot-effectiveness",
|
|
17821
|
+
title: "Lack of Copilot instructions reduces AI assistance effectiveness",
|
|
17822
|
+
description: "Without specific Copilot instructions, AI assistance may not align with project standards and coding practices, reducing its effectiveness.",
|
|
17823
|
+
category: "ai",
|
|
17824
|
+
supportingFindings: [copilotFinding.id],
|
|
17825
|
+
reasoning: "Copilot instructions guide AI tools to generate code that matches project patterns, standards, and preferences. Without them, AI assistance is generic and less valuable.",
|
|
17826
|
+
confidence: 75,
|
|
17827
|
+
alternativeInterpretations: [
|
|
17828
|
+
"Code patterns might be self-documenting",
|
|
17829
|
+
"Team might not use AI assistance extensively",
|
|
17830
|
+
"Instructions might exist in project documentation"
|
|
17831
|
+
],
|
|
17832
|
+
validationChecks: [
|
|
17833
|
+
{
|
|
17834
|
+
id: "vc-copilot-usage",
|
|
17835
|
+
type: "repo_specificity",
|
|
17836
|
+
description: "Assess AI tool usage in the project",
|
|
17837
|
+
expected: "AI assistance would benefit from specific guidance"
|
|
17838
|
+
},
|
|
17839
|
+
{
|
|
17840
|
+
id: "vc-copilot-patterns",
|
|
17841
|
+
type: "actionability",
|
|
17842
|
+
description: "Evaluate if project has specific patterns worth documenting",
|
|
17843
|
+
expected: "Project has unique patterns that AI should follow"
|
|
17844
|
+
}
|
|
17845
|
+
],
|
|
17846
|
+
estimatedImpact: "medium",
|
|
17847
|
+
estimatedEffort: "small"
|
|
17848
|
+
});
|
|
17849
|
+
}
|
|
17850
|
+
return hypotheses;
|
|
17851
|
+
}
|
|
17852
|
+
generateGovernanceHypotheses(findings) {
|
|
17853
|
+
const hypotheses = [];
|
|
17854
|
+
const contributingFinding = findings.find((f) => f.id === "found-no-contributing");
|
|
17855
|
+
if (contributingFinding) {
|
|
17856
|
+
hypotheses.push({
|
|
17857
|
+
id: "hyp-contribution-barrier",
|
|
17858
|
+
title: "Missing contributing guidelines creates contribution barriers",
|
|
17859
|
+
description: "Without clear contribution guidelines, potential contributors face uncertainty and may abandon participation, limiting community growth.",
|
|
17860
|
+
category: "governance",
|
|
17861
|
+
supportingFindings: [contributingFinding.id],
|
|
17862
|
+
reasoning: "Contributing guidelines lower the barrier to entry by clearly communicating expectations, processes, and standards for participation.",
|
|
17863
|
+
confidence: 70,
|
|
17864
|
+
alternativeInterpretations: [
|
|
17865
|
+
"Project might not accept external contributions",
|
|
17866
|
+
"Guidelines might be documented in README",
|
|
17867
|
+
"Project might be intentionally closed to contributions"
|
|
17868
|
+
],
|
|
17869
|
+
validationChecks: [
|
|
17870
|
+
{
|
|
17871
|
+
id: "vc-contribution-activity",
|
|
17872
|
+
type: "repo_specificity",
|
|
17873
|
+
description: "Check if repository accepts external contributions",
|
|
17874
|
+
expected: "Repository would benefit from external contributions"
|
|
17875
|
+
},
|
|
17876
|
+
{
|
|
17877
|
+
id: "vc-contribution-clarity",
|
|
17878
|
+
type: "actionability",
|
|
17879
|
+
description: "Assess if contribution process is unclear",
|
|
17880
|
+
expected: "Contribution process would be clearer with guidelines"
|
|
17881
|
+
}
|
|
17882
|
+
],
|
|
17883
|
+
estimatedImpact: "medium",
|
|
17884
|
+
estimatedEffort: "small"
|
|
17885
|
+
});
|
|
17886
|
+
}
|
|
17887
|
+
return hypotheses;
|
|
17888
|
+
}
|
|
17889
|
+
generateCrossCategoryHypotheses(findings) {
|
|
17890
|
+
const hypotheses = [];
|
|
17891
|
+
const securityFindings = findings.filter((f) => f.category === "security" && f.severity === "high");
|
|
17892
|
+
const workflowFindings = findings.filter((f) => f.category === "workflow" && f.severity === "high");
|
|
17893
|
+
if (securityFindings.length > 0 && workflowFindings.length > 0) {
|
|
17894
|
+
hypotheses.push({
|
|
17895
|
+
id: "hyp-security-workflow-gap",
|
|
17896
|
+
title: "Security and workflow gaps indicate process immaturity",
|
|
17897
|
+
description: "The combination of security and workflow issues suggests the repository lacks mature development processes that could pose risks for AI-assisted development.",
|
|
17898
|
+
category: "governance",
|
|
17899
|
+
supportingFindings: [
|
|
17900
|
+
...securityFindings.map((f) => f.id),
|
|
17901
|
+
...workflowFindings.map((f) => f.id)
|
|
17902
|
+
],
|
|
17903
|
+
reasoning: "Mature development processes integrate security practices into automated workflows. Gaps in both areas suggest process immaturity.",
|
|
17904
|
+
confidence: 80,
|
|
17905
|
+
alternativeInterpretations: [
|
|
17906
|
+
"Security might be handled through external processes",
|
|
17907
|
+
"Workflow might be intentionally simple",
|
|
17908
|
+
"Project might be in early development phase"
|
|
17909
|
+
],
|
|
17910
|
+
validationChecks: [
|
|
17911
|
+
{
|
|
17912
|
+
id: "vc-process-maturity",
|
|
17913
|
+
type: "repo_specificity",
|
|
17914
|
+
description: "Assess overall development process maturity",
|
|
17915
|
+
expected: "Process maturity is low and would benefit from improvement"
|
|
17916
|
+
},
|
|
17917
|
+
{
|
|
17918
|
+
id: "vc-ai-readiness",
|
|
17919
|
+
type: "actionability",
|
|
17920
|
+
description: "Evaluate impact on AI-assisted development",
|
|
17921
|
+
expected: "Process gaps would significantly impact AI development safety"
|
|
17922
|
+
}
|
|
17923
|
+
],
|
|
17924
|
+
estimatedImpact: "high",
|
|
17925
|
+
estimatedEffort: "large"
|
|
17926
|
+
});
|
|
17927
|
+
}
|
|
17928
|
+
return hypotheses;
|
|
17929
|
+
}
|
|
17930
|
+
}
|
|
17931
|
+
|
|
17932
|
+
// src/recommendations/ranker.ts
|
|
17933
|
+
class Ranker {
|
|
17934
|
+
context;
|
|
17935
|
+
constructor(context) {
|
|
17936
|
+
this.context = context;
|
|
17937
|
+
}
|
|
17938
|
+
rankRecommendations(validationResults, challengerAssessments, findings, hypotheses) {
|
|
17939
|
+
const promotedValidations = validationResults.filter((v) => v.recommendation === "promote" || v.recommendation === "downgrade");
|
|
17940
|
+
const approvedRecommendations = challengerAssessments.filter((a) => a.finalVerdict === "approve" || a.finalVerdict === "require_human_review");
|
|
17941
|
+
const finalRecommendations = [];
|
|
17942
|
+
for (const validation of promotedValidations) {
|
|
17943
|
+
const hypothesis = hypotheses.find((h) => h.id === validation.hypothesisId);
|
|
17944
|
+
if (!hypothesis)
|
|
17945
|
+
continue;
|
|
17946
|
+
const challengerAssessment = approvedRecommendations.find((a) => a.recommendationId === hypothesis.id);
|
|
17947
|
+
if (challengerAssessment?.finalVerdict === "reject") {
|
|
17948
|
+
continue;
|
|
17949
|
+
}
|
|
17950
|
+
const recommendation = this.createRecommendation(hypothesis, validation, challengerAssessment, findings);
|
|
17951
|
+
finalRecommendations.push(recommendation);
|
|
17952
|
+
}
|
|
17953
|
+
return finalRecommendations.sort((a, b) => {
|
|
17954
|
+
const scoreA = this.calculatePriorityScore(a);
|
|
17955
|
+
const scoreB = this.calculatePriorityScore(b);
|
|
17956
|
+
return scoreB - scoreA;
|
|
17957
|
+
});
|
|
17958
|
+
}
|
|
17959
|
+
createRecommendation(hypothesis, validation, challengerAssessment, findings) {
|
|
17960
|
+
const supportingFindings = findings.filter((f) => hypothesis.supportingFindings.includes(f.id));
|
|
17961
|
+
const evidenceAnchors = supportingFindings.flatMap((f) => f.evidenceAnchors);
|
|
17962
|
+
const affectedFiles = [
|
|
17963
|
+
...new Set(supportingFindings.flatMap((f) => f.affectedFiles))
|
|
17964
|
+
];
|
|
17965
|
+
const baseConfidence = validation.confidence;
|
|
17966
|
+
const challengerAdjustment = challengerAssessment?.confidenceAdjustment || 0;
|
|
17967
|
+
const finalConfidence = Math.max(0, Math.min(100, baseConfidence + challengerAdjustment));
|
|
17968
|
+
const needsHumanReview = validation.recommendation === "human_review" || challengerAssessment?.finalVerdict === "require_human_review" || finalConfidence < this.context.config.humanReviewThreshold;
|
|
17969
|
+
const recommendation = {
|
|
17970
|
+
id: `rec-${hypothesis.id}`,
|
|
17971
|
+
title: hypothesis.title,
|
|
17972
|
+
category: hypothesis.category,
|
|
17973
|
+
priority: this.determinePriority(hypothesis, validation, finalConfidence),
|
|
17974
|
+
summary: hypothesis.description,
|
|
17975
|
+
whyThisMatters: this.generateWhyMatters(hypothesis, supportingFindings),
|
|
17976
|
+
evidenceAnchors,
|
|
17977
|
+
affectedFiles,
|
|
17978
|
+
supportingFindings: hypothesis.supportingFindings,
|
|
17979
|
+
supportingHypotheses: [hypothesis.id],
|
|
17980
|
+
validationSummary: {
|
|
17981
|
+
passed: validation.passed,
|
|
17982
|
+
confidence: validation.confidence,
|
|
17983
|
+
blockers: validation.blockers,
|
|
17984
|
+
warnings: validation.warnings
|
|
17985
|
+
},
|
|
17986
|
+
expectedImpact: {
|
|
17987
|
+
description: this.generateImpactDescription(hypothesis),
|
|
17988
|
+
areas: this.determineImpactAreas(hypothesis),
|
|
17989
|
+
metrics: this.generateImpactMetrics(hypothesis)
|
|
17990
|
+
},
|
|
17991
|
+
estimatedEffort: {
|
|
17992
|
+
size: hypothesis.estimatedEffort,
|
|
17993
|
+
timeframes: this.generateTimeframes(hypothesis.estimatedEffort),
|
|
17994
|
+
resources: this.generateResourceList(hypothesis)
|
|
17995
|
+
},
|
|
17996
|
+
confidence: {
|
|
17997
|
+
overall: finalConfidence,
|
|
17998
|
+
evidenceQuality: this.calculateEvidenceQuality(evidenceAnchors),
|
|
17999
|
+
repoSpecificity: this.calculateRepoSpecificity(hypothesis),
|
|
18000
|
+
actionability: this.calculateActionability(hypothesis)
|
|
18001
|
+
},
|
|
18002
|
+
caveats: this.generateCaveats(validation, challengerAssessment),
|
|
18003
|
+
suggestedNextStep: this.generateNextStep(hypothesis, supportingFindings),
|
|
18004
|
+
humanReviewNeeded: needsHumanReview,
|
|
18005
|
+
implementationHints: this.generateImplementationHints(hypothesis, supportingFindings),
|
|
18006
|
+
tags: this.generateTags(hypothesis, supportingFindings),
|
|
18007
|
+
metadata: {
|
|
18008
|
+
generated: new Date().toISOString(),
|
|
18009
|
+
version: "2.0.0",
|
|
18010
|
+
pipeline: [
|
|
18011
|
+
"findings",
|
|
18012
|
+
"hypotheses",
|
|
18013
|
+
"validation",
|
|
18014
|
+
"challenger",
|
|
18015
|
+
"ranking"
|
|
18016
|
+
]
|
|
18017
|
+
}
|
|
18018
|
+
};
|
|
18019
|
+
return recommendation;
|
|
18020
|
+
}
|
|
18021
|
+
calculatePriorityScore(recommendation) {
|
|
18022
|
+
let score = 0;
|
|
18023
|
+
const priorityScores = { critical: 100, high: 75, medium: 50, low: 25 };
|
|
18024
|
+
score += priorityScores[recommendation.priority];
|
|
18025
|
+
score += recommendation.confidence.overall / 100 * 20;
|
|
18026
|
+
const impactBonus = recommendation.expectedImpact.areas.length * 5;
|
|
18027
|
+
score += impactBonus;
|
|
18028
|
+
const effortPenalties = { small: 0, medium: -5, large: -10 };
|
|
18029
|
+
score += effortPenalties[recommendation.estimatedEffort.size];
|
|
18030
|
+
score += recommendation.confidence.evidenceQuality / 100 * 10;
|
|
18031
|
+
score += recommendation.confidence.repoSpecificity / 100 * 10;
|
|
18032
|
+
const categoryWeights = {
|
|
18033
|
+
security: 1.2,
|
|
18034
|
+
foundation: 1.1,
|
|
18035
|
+
workflow: 1,
|
|
18036
|
+
ai: 0.9,
|
|
18037
|
+
governance: 0.8
|
|
18038
|
+
};
|
|
18039
|
+
score *= categoryWeights[recommendation.category];
|
|
18040
|
+
return Math.round(score);
|
|
18041
|
+
}
|
|
18042
|
+
determinePriority(hypothesis, validation, confidence) {
|
|
18043
|
+
let priority = hypothesis.estimatedImpact;
|
|
18044
|
+
if (confidence < 30) {
|
|
18045
|
+
priority = "low";
|
|
18046
|
+
} else if (confidence < 60 && priority === "critical") {
|
|
18047
|
+
priority = "high";
|
|
18048
|
+
} else if (confidence < 80 && priority === "high") {
|
|
18049
|
+
priority = "medium";
|
|
18050
|
+
}
|
|
18051
|
+
if (validation.warnings.length > 2 && priority !== "low") {
|
|
18052
|
+
const priorities = [
|
|
18053
|
+
"critical",
|
|
18054
|
+
"high",
|
|
18055
|
+
"medium",
|
|
18056
|
+
"low"
|
|
18057
|
+
];
|
|
18058
|
+
const currentIndex = priorities.indexOf(priority);
|
|
18059
|
+
if (currentIndex < priorities.length - 1) {
|
|
18060
|
+
priority = priorities[currentIndex + 1];
|
|
18061
|
+
}
|
|
18062
|
+
}
|
|
18063
|
+
return priority;
|
|
18064
|
+
}
|
|
18065
|
+
generateWhyMatters(hypothesis, findings) {
|
|
18066
|
+
const categoryReasons = {
|
|
18067
|
+
security: "Security issues pose risks to code integrity, team safety, and organizational compliance. Addressing security gaps is foundational to safe AI-assisted development.",
|
|
18068
|
+
foundation: "Foundation issues affect developer experience, onboarding, and project maintainability. Strong foundations enable scalable, sustainable development.",
|
|
18069
|
+
workflow: "Workflow improvements directly impact development velocity, code quality, and team collaboration. Better workflows reduce friction and increase productivity.",
|
|
18070
|
+
ai: "AI readiness improvements enhance the effectiveness of AI assistance, leading to better code suggestions and more efficient development.",
|
|
18071
|
+
governance: "Governance improvements ensure consistent decision-making, clear contribution processes, and long-term project health."
|
|
18072
|
+
};
|
|
18073
|
+
let baseReason = categoryReasons[hypothesis.category] || "";
|
|
18074
|
+
if (findings.length > 0) {
|
|
18075
|
+
const highSeverityFindings = findings.filter((f) => f.severity === "high" || f.severity === "critical");
|
|
18076
|
+
if (highSeverityFindings.length > 0) {
|
|
18077
|
+
baseReason += ` This is particularly important given the ${highSeverityFindings.length} high-severity issues detected.`;
|
|
18078
|
+
}
|
|
18079
|
+
}
|
|
18080
|
+
return baseReason;
|
|
18081
|
+
}
|
|
18082
|
+
generateImpactDescription(hypothesis) {
|
|
18083
|
+
const impactTemplates = {
|
|
18084
|
+
critical: "Critical impact on {category} posture and overall development safety",
|
|
18085
|
+
high: "Significant improvement in {category} practices and team productivity",
|
|
18086
|
+
medium: "Moderate enhancement to {category} processes and developer experience",
|
|
18087
|
+
low: "Minor improvement to {category} hygiene and best practices"
|
|
18088
|
+
};
|
|
18089
|
+
return impactTemplates[hypothesis.estimatedImpact].replace("{category}", hypothesis.category);
|
|
18090
|
+
}
|
|
18091
|
+
determineImpactAreas(hypothesis) {
|
|
18092
|
+
const areaMap = {
|
|
18093
|
+
security: ["security", "compliance", "risk_management"],
|
|
18094
|
+
foundation: ["developer_experience", "onboarding", "maintainability"],
|
|
18095
|
+
workflow: ["productivity", "code_quality", "collaboration"],
|
|
18096
|
+
ai: ["ai_effectiveness", "code_generation", "development_speed"],
|
|
18097
|
+
governance: [
|
|
18098
|
+
"process_consistency",
|
|
18099
|
+
"contribution_quality",
|
|
18100
|
+
"project_health"
|
|
18101
|
+
]
|
|
18102
|
+
};
|
|
18103
|
+
return areaMap[hypothesis.category] || [];
|
|
18104
|
+
}
|
|
18105
|
+
generateImpactMetrics(hypothesis) {
|
|
18106
|
+
const metricsMap = {
|
|
18107
|
+
security: ["security_score", "vulnerability_count", "compliance_status"],
|
|
18108
|
+
foundation: [
|
|
18109
|
+
"setup_time",
|
|
18110
|
+
"onboarding_success_rate",
|
|
18111
|
+
"documentation_coverage"
|
|
18112
|
+
],
|
|
18113
|
+
workflow: ["deployment_frequency", "lead_time", "change_failure_rate"],
|
|
18114
|
+
ai: ["ai_adoption_rate", "code_acceptance_rate", "development_velocity"],
|
|
18115
|
+
governance: [
|
|
18116
|
+
"contribution_rate",
|
|
18117
|
+
"process_adherence",
|
|
18118
|
+
"decision_quality"
|
|
18119
|
+
]
|
|
18120
|
+
};
|
|
18121
|
+
return metricsMap[hypothesis.category] || [];
|
|
18122
|
+
}
|
|
18123
|
+
generateTimeframes(effort) {
|
|
18124
|
+
const timeframes = {
|
|
18125
|
+
small: {
|
|
18126
|
+
best: "1-2 days",
|
|
18127
|
+
expected: "3-5 days",
|
|
18128
|
+
worst: "1 week"
|
|
18129
|
+
},
|
|
18130
|
+
medium: {
|
|
18131
|
+
best: "1 week",
|
|
18132
|
+
expected: "2-3 weeks",
|
|
18133
|
+
worst: "1 month"
|
|
18134
|
+
},
|
|
18135
|
+
large: {
|
|
18136
|
+
best: "2-3 weeks",
|
|
18137
|
+
expected: "1-2 months",
|
|
18138
|
+
worst: "3 months"
|
|
18139
|
+
}
|
|
18140
|
+
};
|
|
18141
|
+
return timeframes[effort];
|
|
18142
|
+
}
|
|
18143
|
+
generateResourceList(hypothesis) {
|
|
18144
|
+
const baseResources = ["developer_time", "code_review"];
|
|
18145
|
+
const categoryResources = {
|
|
18146
|
+
security: ["security_review", "compliance_check"],
|
|
18147
|
+
foundation: ["documentation_effort", "setup_configuration"],
|
|
18148
|
+
workflow: ["devops_time", "process_design"],
|
|
18149
|
+
ai: ["ai_tool_configuration", "team_training"],
|
|
18150
|
+
governance: ["stakeholder_input", "process_documentation"]
|
|
18151
|
+
};
|
|
18152
|
+
return [
|
|
18153
|
+
...baseResources,
|
|
18154
|
+
...categoryResources[hypothesis.category] || []
|
|
18155
|
+
];
|
|
18156
|
+
}
|
|
18157
|
+
calculateEvidenceQuality(evidenceAnchors) {
|
|
18158
|
+
if (evidenceAnchors.length === 0)
|
|
18159
|
+
return 0;
|
|
18160
|
+
const totalConfidence = evidenceAnchors.reduce((sum, anchor) => sum + anchor.confidence, 0);
|
|
18161
|
+
return Math.round(totalConfidence / evidenceAnchors.length);
|
|
18162
|
+
}
|
|
18163
|
+
calculateRepoSpecificity(hypothesis) {
|
|
18164
|
+
let specificity = 50;
|
|
18165
|
+
if (hypothesis.validationChecks.some((check) => check.description.includes("specific") || check.description.includes("file"))) {
|
|
18166
|
+
specificity += 20;
|
|
18167
|
+
}
|
|
18168
|
+
if (hypothesis.reasoning.includes("this repository") || hypothesis.reasoning.includes("the repository")) {
|
|
18169
|
+
specificity += 15;
|
|
18170
|
+
}
|
|
18171
|
+
if (hypothesis.description.includes("all repositories") || hypothesis.description.includes("every project")) {
|
|
18172
|
+
specificity -= 20;
|
|
18173
|
+
}
|
|
18174
|
+
return Math.max(0, Math.min(100, specificity));
|
|
18175
|
+
}
|
|
18176
|
+
calculateActionability(hypothesis) {
|
|
18177
|
+
let actionability = 50;
|
|
18178
|
+
if (["small", "medium", "large"].includes(hypothesis.estimatedEffort)) {
|
|
18179
|
+
actionability += 15;
|
|
18180
|
+
}
|
|
18181
|
+
if (hypothesis.validationChecks.some((check) => check.type === "actionability")) {
|
|
18182
|
+
actionability += 20;
|
|
18183
|
+
}
|
|
18184
|
+
const actionWords = [
|
|
18185
|
+
"add",
|
|
18186
|
+
"create",
|
|
18187
|
+
"implement",
|
|
18188
|
+
"configure",
|
|
18189
|
+
"establish"
|
|
18190
|
+
];
|
|
18191
|
+
if (actionWords.some((word) => hypothesis.description.includes(word))) {
|
|
18192
|
+
actionability += 15;
|
|
18193
|
+
}
|
|
18194
|
+
return Math.max(0, Math.min(100, actionability));
|
|
18195
|
+
}
|
|
18196
|
+
generateCaveats(validation, challengerAssessment) {
|
|
18197
|
+
const caveats = [];
|
|
18198
|
+
caveats.push(...validation.warnings);
|
|
18199
|
+
if (challengerAssessment) {
|
|
18200
|
+
caveats.push(...challengerAssessment.criticisms);
|
|
18201
|
+
}
|
|
18202
|
+
if (validation.confidence < 70) {
|
|
18203
|
+
caveats.push("Limited evidence support - verify before implementation");
|
|
18204
|
+
}
|
|
18205
|
+
if (validation.confidence < 50) {
|
|
18206
|
+
caveats.push("Low confidence - requires human validation");
|
|
18207
|
+
}
|
|
18208
|
+
return caveats;
|
|
18209
|
+
}
|
|
18210
|
+
generateNextStep(hypothesis, findings) {
|
|
18211
|
+
const categoryNextSteps = {
|
|
18212
|
+
security: "Conduct security review and implement ownership controls",
|
|
18213
|
+
foundation: "Create foundational files and establish project standards",
|
|
18214
|
+
workflow: "Set up automated workflows and testing infrastructure",
|
|
18215
|
+
ai: "Configure AI tools and establish coding guidelines",
|
|
18216
|
+
governance: "Document processes and establish contribution guidelines"
|
|
18217
|
+
};
|
|
18218
|
+
let nextStep = categoryNextSteps[hypothesis.category] || "Analyze requirements and create implementation plan";
|
|
18219
|
+
const missingFileFindings = findings.filter((f) => f.evidenceAnchors.some((a) => a.type === "missing" && a.path));
|
|
18220
|
+
if (missingFileFindings.length > 0) {
|
|
18221
|
+
const missingFiles = missingFileFindings.flatMap((f) => f.evidenceAnchors.filter((a) => a.type === "missing" && a.path).map((a) => a.path)).slice(0, 2);
|
|
18222
|
+
if (missingFiles.length > 0) {
|
|
18223
|
+
nextStep = `Create ${missingFiles.join(" and ")} to address immediate gaps`;
|
|
18224
|
+
}
|
|
18225
|
+
}
|
|
18226
|
+
return nextStep;
|
|
18227
|
+
}
|
|
18228
|
+
generateImplementationHints(hypothesis, findings) {
|
|
18229
|
+
const hints = {
|
|
18230
|
+
commands: [],
|
|
18231
|
+
fileTemplates: [],
|
|
18232
|
+
references: []
|
|
18233
|
+
};
|
|
18234
|
+
const categoryCommands = {
|
|
18235
|
+
security: ["touch .github/CODEOWNERS", "git add .github/CODEOWNERS"],
|
|
18236
|
+
foundation: ["touch README.md", "touch LICENSE", "touch .gitignore"],
|
|
18237
|
+
workflow: [
|
|
18238
|
+
"mkdir -p .github/workflows",
|
|
18239
|
+
"touch .github/workflows/ci.yml"
|
|
18240
|
+
],
|
|
18241
|
+
ai: ["touch .github/copilot-instructions.md"],
|
|
18242
|
+
governance: ["touch CONTRIBUTING.md", "touch CHANGELOG.md"]
|
|
18243
|
+
};
|
|
18244
|
+
hints.commands = categoryCommands[hypothesis.category] || [];
|
|
18245
|
+
hints.references = [
|
|
18246
|
+
"https://docs.github.com/en/get-started/quickstart",
|
|
18247
|
+
"https://www.conventionalcommits.org/",
|
|
18248
|
+
"https://semver.org/"
|
|
18249
|
+
];
|
|
18250
|
+
return hints;
|
|
18251
|
+
}
|
|
18252
|
+
generateTags(hypothesis, findings) {
|
|
18253
|
+
const tags = [hypothesis.category];
|
|
18254
|
+
if (hypothesis.estimatedImpact === "critical") {
|
|
18255
|
+
tags.push("urgent", "security");
|
|
18256
|
+
}
|
|
18257
|
+
if (hypothesis.estimatedEffort === "small") {
|
|
18258
|
+
tags.push("quick-win");
|
|
18259
|
+
} else if (hypothesis.estimatedEffort === "large") {
|
|
18260
|
+
tags.push("significant-effort");
|
|
18261
|
+
}
|
|
18262
|
+
if (hypothesis.supportingFindings.length > 1) {
|
|
18263
|
+
tags.push("well-supported");
|
|
18264
|
+
}
|
|
18265
|
+
return tags;
|
|
18266
|
+
}
|
|
18267
|
+
}
|
|
18268
|
+
|
|
18269
|
+
// src/recommendations/validator.ts
|
|
18270
|
+
class Validator {
|
|
18271
|
+
context;
|
|
18272
|
+
constructor(context) {
|
|
18273
|
+
this.context = context;
|
|
18274
|
+
}
|
|
18275
|
+
validateHypotheses(hypotheses, findings) {
|
|
18276
|
+
const results = [];
|
|
18277
|
+
for (const hypothesis of hypotheses) {
|
|
18278
|
+
const result = this.validateHypothesis(hypothesis, findings);
|
|
18279
|
+
results.push(result);
|
|
18280
|
+
}
|
|
18281
|
+
return results;
|
|
18282
|
+
}
|
|
18283
|
+
validateHypothesis(hypothesis, findings) {
|
|
18284
|
+
const validationChecks = [];
|
|
18285
|
+
const blockers = [];
|
|
18286
|
+
const warnings = [];
|
|
18287
|
+
for (const check of hypothesis.validationChecks) {
|
|
18288
|
+
const result = this.executeValidationCheck(check, hypothesis, findings);
|
|
18289
|
+
validationChecks.push(result);
|
|
18290
|
+
if (!result.passed) {
|
|
18291
|
+
if (this.isBlocker(check.type, result.reason)) {
|
|
18292
|
+
blockers.push(result.reason || "Validation failed");
|
|
18293
|
+
} else {
|
|
18294
|
+
warnings.push(result.reason || "Validation warning");
|
|
18295
|
+
}
|
|
18296
|
+
}
|
|
18297
|
+
}
|
|
18298
|
+
const adjustedConfidence = this.calculateAdjustedConfidence(hypothesis.confidence, validationChecks);
|
|
18299
|
+
const recommendation = this.determineRecommendation(blockers, warnings, adjustedConfidence);
|
|
18300
|
+
return {
|
|
18301
|
+
hypothesisId: hypothesis.id,
|
|
18302
|
+
passed: recommendation === "promote",
|
|
18303
|
+
confidence: adjustedConfidence,
|
|
18304
|
+
validationChecks,
|
|
18305
|
+
blockers,
|
|
18306
|
+
warnings,
|
|
18307
|
+
recommendation
|
|
18308
|
+
};
|
|
18309
|
+
}
|
|
18310
|
+
executeValidationCheck(check, hypothesis, findings) {
|
|
18311
|
+
const resultCheck = { ...check };
|
|
18312
|
+
switch (check.type) {
|
|
18313
|
+
case "evidence_strength":
|
|
18314
|
+
resultCheck.passed = this.validateEvidenceStrength(hypothesis, findings);
|
|
18315
|
+
resultCheck.actual = resultCheck.passed ? "Strong evidence" : "Weak evidence";
|
|
18316
|
+
resultCheck.reason = resultCheck.passed ? "Supporting findings provide strong evidence" : "Supporting findings are weak or insufficient";
|
|
18317
|
+
break;
|
|
18318
|
+
case "repo_specificity":
|
|
18319
|
+
resultCheck.passed = this.validateRepoSpecificity(hypothesis);
|
|
18320
|
+
resultCheck.actual = resultCheck.passed ? "Repo-specific" : "Generic";
|
|
18321
|
+
resultCheck.reason = resultCheck.passed ? "Hypothesis addresses repo-specific characteristics" : "Hypothesis appears generic and could apply to any repo";
|
|
18322
|
+
break;
|
|
18323
|
+
case "actionability":
|
|
18324
|
+
resultCheck.passed = this.validateActionability(hypothesis);
|
|
18325
|
+
resultCheck.actual = resultCheck.passed ? "Actionable" : "Vague";
|
|
18326
|
+
resultCheck.reason = resultCheck.passed ? "Clear, actionable steps can be taken" : "Hypothesis lacks clear actionable steps";
|
|
18327
|
+
break;
|
|
18328
|
+
case "redundancy":
|
|
18329
|
+
resultCheck.passed = this.validateRedundancy(hypothesis, findings);
|
|
18330
|
+
resultCheck.actual = resultCheck.passed ? "Unique" : "Redundant";
|
|
18331
|
+
resultCheck.reason = resultCheck.passed ? "Hypothesis provides unique insight" : "Hypothesis overlaps with other findings/hypotheses";
|
|
18332
|
+
break;
|
|
18333
|
+
case "contradiction":
|
|
18334
|
+
resultCheck.passed = this.validateContradiction(hypothesis, findings);
|
|
18335
|
+
resultCheck.actual = resultCheck.passed ? "Consistent" : "Contradictory";
|
|
18336
|
+
resultCheck.reason = resultCheck.passed ? "Hypothesis is consistent with evidence" : "Hypothesis contradicts available evidence";
|
|
18337
|
+
break;
|
|
18338
|
+
}
|
|
18339
|
+
return resultCheck;
|
|
18340
|
+
}
|
|
18341
|
+
validateEvidenceStrength(hypothesis, findings) {
|
|
18342
|
+
const supportingFindings = findings.filter((f) => hypothesis.supportingFindings.includes(f.id));
|
|
18343
|
+
if (supportingFindings.length === 0) {
|
|
18344
|
+
return false;
|
|
18345
|
+
}
|
|
18346
|
+
let totalEvidenceQuality = 0;
|
|
18347
|
+
let evidenceCount = 0;
|
|
18348
|
+
for (const finding of supportingFindings) {
|
|
18349
|
+
for (const anchor of finding.evidenceAnchors) {
|
|
18350
|
+
totalEvidenceQuality += anchor.confidence;
|
|
18351
|
+
evidenceCount++;
|
|
18352
|
+
}
|
|
18353
|
+
}
|
|
18354
|
+
if (evidenceCount === 0) {
|
|
18355
|
+
return false;
|
|
18356
|
+
}
|
|
18357
|
+
const averageConfidence = totalEvidenceQuality / evidenceCount;
|
|
18358
|
+
return averageConfidence >= 70;
|
|
18359
|
+
}
|
|
18360
|
+
validateRepoSpecificity(hypothesis) {
|
|
18361
|
+
const repoSpecificIndicators = [
|
|
18362
|
+
this.context.repoPath,
|
|
18363
|
+
this.context.evidence.structure.fileCount,
|
|
18364
|
+
this.context.evidence.structure.directoryDepth,
|
|
18365
|
+
this.context.techStack.primaryLanguage,
|
|
18366
|
+
this.context.techStack.frameworks?.length || 0
|
|
18367
|
+
];
|
|
18368
|
+
const description = hypothesis.description.toLowerCase();
|
|
18369
|
+
const reasoning = hypothesis.reasoning.toLowerCase();
|
|
18370
|
+
const specificIndicators = [
|
|
18371
|
+
"this repository",
|
|
18372
|
+
"the repository",
|
|
18373
|
+
"project shows",
|
|
18374
|
+
"codebase indicates",
|
|
18375
|
+
"analysis reveals",
|
|
18376
|
+
"detected",
|
|
18377
|
+
"found",
|
|
18378
|
+
"shows signs of"
|
|
18379
|
+
];
|
|
18380
|
+
const hasSpecificLanguage = specificIndicators.some((indicator) => description.includes(indicator) || reasoning.includes(indicator));
|
|
18381
|
+
const genericIndicators = [
|
|
18382
|
+
"all repositories",
|
|
18383
|
+
"every project",
|
|
18384
|
+
"always",
|
|
18385
|
+
"never",
|
|
18386
|
+
"should always"
|
|
18387
|
+
];
|
|
18388
|
+
const hasGenericLanguage = genericIndicators.some((indicator) => description.includes(indicator) || reasoning.includes(indicator));
|
|
18389
|
+
return hasSpecificLanguage && !hasGenericLanguage;
|
|
18390
|
+
}
|
|
18391
|
+
validateActionability(hypothesis) {
|
|
18392
|
+
const actionableIndicators = [
|
|
18393
|
+
"add",
|
|
18394
|
+
"create",
|
|
18395
|
+
"implement",
|
|
18396
|
+
"configure",
|
|
18397
|
+
"establish",
|
|
18398
|
+
"set up",
|
|
18399
|
+
"enable",
|
|
18400
|
+
"introduce",
|
|
18401
|
+
"define",
|
|
18402
|
+
"document"
|
|
18403
|
+
];
|
|
18404
|
+
const description = hypothesis.description.toLowerCase();
|
|
18405
|
+
const reasoning = hypothesis.reasoning.toLowerCase();
|
|
18406
|
+
const hasActionableLanguage = actionableIndicators.some((indicator) => description.includes(indicator) || reasoning.includes(indicator));
|
|
18407
|
+
const hasRealisticEffort = ["small", "medium", "large"].includes(hypothesis.estimatedEffort);
|
|
18408
|
+
const hasMeaningfulImpact = ["critical", "high", "medium", "low"].includes(hypothesis.estimatedImpact);
|
|
18409
|
+
return hasActionableLanguage && hasRealisticEffort && hasMeaningfulImpact;
|
|
18410
|
+
}
|
|
18411
|
+
validateRedundancy(hypothesis, findings) {
|
|
18412
|
+
const supportingFindings = findings.filter((f) => hypothesis.supportingFindings.includes(f.id));
|
|
18413
|
+
if (supportingFindings.length === 0) {
|
|
18414
|
+
return true;
|
|
18415
|
+
}
|
|
18416
|
+
const findingSummaries = supportingFindings.map((f) => f.summary.toLowerCase()).join(" ");
|
|
18417
|
+
const hypothesisContent = (hypothesis.description + " " + hypothesis.reasoning).toLowerCase();
|
|
18418
|
+
const commonWords = this.getCommonWords(findingSummaries, hypothesisContent);
|
|
18419
|
+
const similarityRatio = commonWords.length / Math.max(findingSummaries.split(" ").length, 1);
|
|
18420
|
+
return similarityRatio < 0.8;
|
|
18421
|
+
}
|
|
18422
|
+
validateContradiction(hypothesis, findings) {
|
|
18423
|
+
const supportingFindings = findings.filter((f) => hypothesis.supportingFindings.includes(f.id));
|
|
18424
|
+
for (const finding of supportingFindings) {
|
|
18425
|
+
if (finding.severity === "low" && hypothesis.estimatedImpact === "critical") {
|
|
18426
|
+
if (!hypothesis.reasoning.toLowerCase().includes("despite") && !hypothesis.reasoning.toLowerCase().includes("even though") && !hypothesis.reasoning.toLowerCase().includes("potential")) {
|
|
18427
|
+
return false;
|
|
18428
|
+
}
|
|
18429
|
+
}
|
|
18430
|
+
for (const anchor of finding.evidenceAnchors) {
|
|
18431
|
+
if (anchor.type === "missing" && !hypothesis.description.toLowerCase().includes("lack") && !hypothesis.description.toLowerCase().includes("missing") && !hypothesis.description.toLowerCase().includes("without")) {
|
|
18432
|
+
return false;
|
|
18433
|
+
}
|
|
18434
|
+
}
|
|
18435
|
+
}
|
|
18436
|
+
return true;
|
|
18437
|
+
}
|
|
18438
|
+
calculateAdjustedConfidence(originalConfidence, validationChecks) {
|
|
18439
|
+
let adjustedConfidence = originalConfidence;
|
|
18440
|
+
for (const check of validationChecks) {
|
|
18441
|
+
if (!check.passed) {
|
|
18442
|
+
switch (check.type) {
|
|
18443
|
+
case "evidence_strength":
|
|
18444
|
+
adjustedConfidence -= 30;
|
|
18445
|
+
break;
|
|
18446
|
+
case "repo_specificity":
|
|
18447
|
+
adjustedConfidence -= 25;
|
|
18448
|
+
break;
|
|
18449
|
+
case "actionability":
|
|
18450
|
+
adjustedConfidence -= 20;
|
|
18451
|
+
break;
|
|
18452
|
+
case "redundancy":
|
|
18453
|
+
adjustedConfidence -= 15;
|
|
18454
|
+
break;
|
|
18455
|
+
case "contradiction":
|
|
18456
|
+
adjustedConfidence -= 35;
|
|
18457
|
+
break;
|
|
18458
|
+
}
|
|
18459
|
+
}
|
|
18460
|
+
}
|
|
18461
|
+
return Math.max(0, Math.min(100, adjustedConfidence));
|
|
18462
|
+
}
|
|
18463
|
+
determineRecommendation(blockers, warnings, confidence) {
|
|
18464
|
+
if (blockers.length > 0) {
|
|
18465
|
+
return "reject";
|
|
18466
|
+
}
|
|
18467
|
+
if (confidence < 50 && warnings.length > 0) {
|
|
18468
|
+
return "human_review";
|
|
18469
|
+
}
|
|
18470
|
+
if (confidence < 70 && warnings.length > 0) {
|
|
18471
|
+
return "downgrade";
|
|
18472
|
+
}
|
|
18473
|
+
if (confidence >= 70 && warnings.length <= 1) {
|
|
18474
|
+
return "promote";
|
|
18475
|
+
}
|
|
18476
|
+
return "human_review";
|
|
18477
|
+
}
|
|
18478
|
+
isBlocker(checkType, reason) {
|
|
18479
|
+
const blockerTypes = [
|
|
18480
|
+
"contradiction",
|
|
18481
|
+
"evidence_strength"
|
|
18482
|
+
];
|
|
18483
|
+
if (blockerTypes.includes(checkType)) {
|
|
18484
|
+
return true;
|
|
18485
|
+
}
|
|
18486
|
+
const blockerReasons = [
|
|
18487
|
+
"Supporting findings are weak or insufficient",
|
|
18488
|
+
"Hypothesis contradicts available evidence"
|
|
18489
|
+
];
|
|
18490
|
+
return blockerReasons.includes(reason || "");
|
|
18491
|
+
}
|
|
18492
|
+
getCommonWords(text1, text2) {
|
|
18493
|
+
const words1 = text1.split(" ").filter((w) => w.length > 3);
|
|
18494
|
+
const words2 = text2.split(" ").filter((w) => w.length > 3);
|
|
18495
|
+
return words1.filter((word) => words2.includes(word));
|
|
18496
|
+
}
|
|
18497
|
+
}
|
|
18498
|
+
|
|
18499
|
+
// src/recommendations/index.ts
|
|
18500
|
+
class RecommendationEngine {
|
|
18501
|
+
config;
|
|
18502
|
+
constructor(config = {}) {
|
|
18503
|
+
this.config = {
|
|
18504
|
+
enableChallenger: true,
|
|
18505
|
+
confidenceThreshold: 50,
|
|
18506
|
+
humanReviewThreshold: 70,
|
|
18507
|
+
enableFeedback: true,
|
|
18508
|
+
...config
|
|
18509
|
+
};
|
|
18510
|
+
}
|
|
18511
|
+
async generateRecommendations(repoPath, evidence, scores, copilotFeatures, techStack) {
|
|
18512
|
+
const startTime = Date.now();
|
|
18513
|
+
const context = {
|
|
18514
|
+
repoPath,
|
|
18515
|
+
evidence,
|
|
18516
|
+
scores,
|
|
18517
|
+
copilotFeatures,
|
|
18518
|
+
techStack,
|
|
18519
|
+
config: this.config
|
|
18520
|
+
};
|
|
18521
|
+
const findingsResult = await this.runFindingsStage(context);
|
|
18522
|
+
const hypothesesResult = await this.runHypothesesStage(context, findingsResult.data);
|
|
18523
|
+
const validationResult = await this.runValidationStage(context, hypothesesResult.data, findingsResult.data);
|
|
18524
|
+
const challengerResult = await this.runChallengerStage(context, validationResult.data, findingsResult.data, hypothesesResult.data);
|
|
18525
|
+
const rankingResult = await this.runRankingStage(validationResult.data, challengerResult.data, findingsResult.data, hypothesesResult.data);
|
|
18526
|
+
const totalDuration = Date.now() - startTime;
|
|
18527
|
+
const result = {
|
|
18528
|
+
findings: findingsResult.data,
|
|
18529
|
+
hypotheses: hypothesesResult.data,
|
|
18530
|
+
validationResults: validationResult.data,
|
|
18531
|
+
challengerAssessments: challengerResult.data,
|
|
18532
|
+
recommendations: rankingResult.data,
|
|
18533
|
+
pipeline: {
|
|
18534
|
+
findings: findingsResult,
|
|
18535
|
+
hypotheses: hypothesesResult,
|
|
18536
|
+
validation: validationResult,
|
|
18537
|
+
challenger: challengerResult,
|
|
18538
|
+
ranking: rankingResult
|
|
18539
|
+
},
|
|
18540
|
+
metadata: {
|
|
18541
|
+
totalDuration,
|
|
18542
|
+
evidenceCount: this.countEvidenceItems(evidence),
|
|
18543
|
+
confidenceDistribution: this.calculateConfidenceDistribution(rankingResult.data),
|
|
18544
|
+
categoryDistribution: this.calculateCategoryDistribution(rankingResult.data)
|
|
18545
|
+
}
|
|
18546
|
+
};
|
|
18547
|
+
if (this.config.enableFeedback) {
|
|
18548
|
+
await this.saveFeedbackTemplate(repoPath, rankingResult.data);
|
|
18549
|
+
}
|
|
18550
|
+
return result;
|
|
18551
|
+
}
|
|
18552
|
+
async runFindingsStage(context) {
|
|
18553
|
+
const startTime = Date.now();
|
|
18554
|
+
const findingBuilder = new FindingBuilder(context);
|
|
18555
|
+
try {
|
|
18556
|
+
const findings = findingBuilder.buildFindings();
|
|
18557
|
+
return {
|
|
18558
|
+
stage: "findings",
|
|
18559
|
+
success: true,
|
|
18560
|
+
data: findings,
|
|
18561
|
+
errors: [],
|
|
18562
|
+
warnings: [],
|
|
18563
|
+
metadata: {
|
|
18564
|
+
duration: Date.now() - startTime,
|
|
18565
|
+
inputCount: 1,
|
|
18566
|
+
outputCount: findings.length
|
|
18567
|
+
}
|
|
18568
|
+
};
|
|
18569
|
+
} catch (error) {
|
|
18570
|
+
return {
|
|
18571
|
+
stage: "findings",
|
|
18572
|
+
success: false,
|
|
18573
|
+
data: [],
|
|
18574
|
+
errors: [
|
|
18575
|
+
error instanceof Error ? error.message : "Unknown error in findings stage"
|
|
18576
|
+
],
|
|
18577
|
+
warnings: [],
|
|
18578
|
+
metadata: {
|
|
18579
|
+
duration: Date.now() - startTime,
|
|
18580
|
+
inputCount: 1,
|
|
18581
|
+
outputCount: 0
|
|
18582
|
+
}
|
|
18583
|
+
};
|
|
18584
|
+
}
|
|
18585
|
+
}
|
|
18586
|
+
async runHypothesesStage(context, findings) {
|
|
18587
|
+
const startTime = Date.now();
|
|
18588
|
+
const hypothesisEngine = new HypothesisEngine(context);
|
|
18589
|
+
try {
|
|
18590
|
+
const hypotheses = hypothesisEngine.generateHypotheses(findings);
|
|
18591
|
+
return {
|
|
18592
|
+
stage: "hypotheses",
|
|
18593
|
+
success: true,
|
|
18594
|
+
data: hypotheses,
|
|
18595
|
+
errors: [],
|
|
18596
|
+
warnings: [],
|
|
18597
|
+
metadata: {
|
|
18598
|
+
duration: Date.now() - startTime,
|
|
18599
|
+
inputCount: findings.length,
|
|
18600
|
+
outputCount: hypotheses.length
|
|
18601
|
+
}
|
|
18602
|
+
};
|
|
18603
|
+
} catch (error) {
|
|
18604
|
+
return {
|
|
18605
|
+
stage: "hypotheses",
|
|
18606
|
+
success: false,
|
|
18607
|
+
data: [],
|
|
18608
|
+
errors: [
|
|
18609
|
+
error instanceof Error ? error.message : "Unknown error in hypotheses stage"
|
|
18610
|
+
],
|
|
18611
|
+
warnings: [],
|
|
18612
|
+
metadata: {
|
|
18613
|
+
duration: Date.now() - startTime,
|
|
18614
|
+
inputCount: findings.length,
|
|
18615
|
+
outputCount: 0
|
|
18616
|
+
}
|
|
18617
|
+
};
|
|
18618
|
+
}
|
|
18619
|
+
}
|
|
18620
|
+
async runValidationStage(context, hypotheses, findings) {
|
|
18621
|
+
const startTime = Date.now();
|
|
18622
|
+
const validator2 = new Validator(context);
|
|
18623
|
+
try {
|
|
18624
|
+
const validationResults = validator2.validateHypotheses(hypotheses, findings);
|
|
18625
|
+
return {
|
|
18626
|
+
stage: "validation",
|
|
18627
|
+
success: true,
|
|
18628
|
+
data: validationResults,
|
|
18629
|
+
errors: [],
|
|
18630
|
+
warnings: [],
|
|
18631
|
+
metadata: {
|
|
18632
|
+
duration: Date.now() - startTime,
|
|
18633
|
+
inputCount: hypotheses.length,
|
|
18634
|
+
outputCount: validationResults.length
|
|
18635
|
+
}
|
|
18636
|
+
};
|
|
18637
|
+
} catch (error) {
|
|
18638
|
+
return {
|
|
18639
|
+
stage: "validation",
|
|
18640
|
+
success: false,
|
|
18641
|
+
data: [],
|
|
18642
|
+
errors: [
|
|
18643
|
+
error instanceof Error ? error.message : "Unknown error in validation stage"
|
|
18644
|
+
],
|
|
18645
|
+
warnings: [],
|
|
18646
|
+
metadata: {
|
|
18647
|
+
duration: Date.now() - startTime,
|
|
18648
|
+
inputCount: hypotheses.length,
|
|
18649
|
+
outputCount: 0
|
|
18650
|
+
}
|
|
18651
|
+
};
|
|
18652
|
+
}
|
|
18653
|
+
}
|
|
18654
|
+
async runChallengerStage(context, validationResults, findings, hypotheses) {
|
|
18655
|
+
const startTime = Date.now();
|
|
18656
|
+
if (!this.config.enableChallenger) {
|
|
18657
|
+
return {
|
|
18658
|
+
stage: "challenger",
|
|
18659
|
+
success: true,
|
|
18660
|
+
data: [],
|
|
18661
|
+
errors: [],
|
|
18662
|
+
warnings: ["Challenger stage disabled by configuration"],
|
|
18663
|
+
metadata: {
|
|
18664
|
+
duration: Date.now() - startTime,
|
|
18665
|
+
inputCount: validationResults.length,
|
|
18666
|
+
outputCount: 0
|
|
18667
|
+
}
|
|
18668
|
+
};
|
|
18669
|
+
}
|
|
18670
|
+
try {
|
|
18671
|
+
const ranker2 = new Ranker(context);
|
|
18672
|
+
const preliminaryRecommendations = ranker2.rankRecommendations(validationResults, [], findings, hypotheses);
|
|
18673
|
+
const challenger2 = new Challenger(context);
|
|
18674
|
+
const challengerAssessments = challenger2.challengeRecommendations(preliminaryRecommendations, findings, hypotheses, validationResults);
|
|
18675
|
+
return {
|
|
18676
|
+
stage: "challenger",
|
|
18677
|
+
success: true,
|
|
18678
|
+
data: challengerAssessments,
|
|
18679
|
+
errors: [],
|
|
18680
|
+
warnings: [],
|
|
18681
|
+
metadata: {
|
|
18682
|
+
duration: Date.now() - startTime,
|
|
18683
|
+
inputCount: validationResults.length,
|
|
18684
|
+
outputCount: challengerAssessments.length
|
|
18685
|
+
}
|
|
18686
|
+
};
|
|
18687
|
+
} catch (error) {
|
|
18688
|
+
return {
|
|
18689
|
+
stage: "challenger",
|
|
18690
|
+
success: false,
|
|
18691
|
+
data: [],
|
|
18692
|
+
errors: [
|
|
18693
|
+
error instanceof Error ? error.message : "Unknown error in challenger stage"
|
|
18694
|
+
],
|
|
18695
|
+
warnings: [],
|
|
18696
|
+
metadata: {
|
|
18697
|
+
duration: Date.now() - startTime,
|
|
18698
|
+
inputCount: validationResults.length,
|
|
18699
|
+
outputCount: 0
|
|
18700
|
+
}
|
|
18701
|
+
};
|
|
18702
|
+
}
|
|
18703
|
+
}
|
|
18704
|
+
async runRankingStage(validationResults, challengerAssessments, findings, hypotheses) {
|
|
18705
|
+
const startTime = Date.now();
|
|
18706
|
+
const ranker2 = new Ranker(this.createRankingContext());
|
|
18707
|
+
try {
|
|
18708
|
+
const recommendations = ranker2.rankRecommendations(validationResults, challengerAssessments, findings, hypotheses);
|
|
18709
|
+
return {
|
|
18710
|
+
stage: "ranking",
|
|
18711
|
+
success: true,
|
|
18712
|
+
data: recommendations,
|
|
18713
|
+
errors: [],
|
|
18714
|
+
warnings: [],
|
|
18715
|
+
metadata: {
|
|
18716
|
+
duration: Date.now() - startTime,
|
|
18717
|
+
inputCount: validationResults.length,
|
|
18718
|
+
outputCount: recommendations.length
|
|
18719
|
+
}
|
|
18720
|
+
};
|
|
18721
|
+
} catch (error) {
|
|
18722
|
+
return {
|
|
18723
|
+
stage: "ranking",
|
|
18724
|
+
success: false,
|
|
18725
|
+
data: [],
|
|
18726
|
+
errors: [
|
|
18727
|
+
error instanceof Error ? error.message : "Unknown error in ranking stage"
|
|
18728
|
+
],
|
|
18729
|
+
warnings: [],
|
|
18730
|
+
metadata: {
|
|
18731
|
+
duration: Date.now() - startTime,
|
|
18732
|
+
inputCount: validationResults.length,
|
|
18733
|
+
outputCount: 0
|
|
18734
|
+
}
|
|
18735
|
+
};
|
|
18736
|
+
}
|
|
18737
|
+
}
|
|
18738
|
+
createRankingContext() {
|
|
18739
|
+
return {
|
|
18740
|
+
repoPath: "",
|
|
18741
|
+
evidence: {},
|
|
18742
|
+
scores: {},
|
|
18743
|
+
copilotFeatures: {},
|
|
18744
|
+
techStack: {},
|
|
18745
|
+
config: this.config
|
|
18746
|
+
};
|
|
18747
|
+
}
|
|
18748
|
+
countEvidenceItems(evidence) {
|
|
18749
|
+
let count = 0;
|
|
18750
|
+
count += Object.values(evidence.structure).filter(Boolean).length;
|
|
18751
|
+
count += Object.values(evidence.configuration).filter(Boolean).length;
|
|
18752
|
+
count += Object.values(evidence.patterns).filter((p) => typeof p === "number" ? p > 0 : Boolean(p)).length;
|
|
18753
|
+
count += Object.values(evidence.metrics).filter((m) => typeof m === "number" ? m > 0 : m !== "fair").length;
|
|
18754
|
+
return count;
|
|
18755
|
+
}
|
|
18756
|
+
calculateConfidenceDistribution(recommendations) {
|
|
18757
|
+
const distribution = { high: 0, medium: 0, low: 0 };
|
|
18758
|
+
for (const rec of recommendations) {
|
|
18759
|
+
if (rec.confidence.overall >= 70)
|
|
18760
|
+
distribution.high++;
|
|
18761
|
+
else if (rec.confidence.overall >= 40)
|
|
18762
|
+
distribution.medium++;
|
|
18763
|
+
else
|
|
18764
|
+
distribution.low++;
|
|
18765
|
+
}
|
|
18766
|
+
return distribution;
|
|
18767
|
+
}
|
|
18768
|
+
calculateCategoryDistribution(recommendations) {
|
|
18769
|
+
const distribution = {};
|
|
18770
|
+
for (const rec of recommendations) {
|
|
18771
|
+
distribution[rec.category] = (distribution[rec.category] || 0) + 1;
|
|
18772
|
+
}
|
|
18773
|
+
return distribution;
|
|
18774
|
+
}
|
|
18775
|
+
async saveFeedbackTemplate(repoPath, recommendations) {
|
|
18776
|
+
try {
|
|
18777
|
+
const feedbackCollector = new FeedbackCollector(repoPath);
|
|
18778
|
+
const template = feedbackCollector.generateFeedbackTemplate(recommendations);
|
|
18779
|
+
const { writeFile: writeFile2 } = await import("node:fs/promises");
|
|
18780
|
+
const { join: join5 } = await import("node:path");
|
|
18781
|
+
const templatePath = join5(repoPath, ".ai-enablement", "feedback-template.json");
|
|
18782
|
+
await writeFile2(templatePath, template, "utf-8");
|
|
18783
|
+
console.log(`\uD83D\uDCDD Feedback template saved to: ${templatePath}`);
|
|
18784
|
+
} catch (error) {
|
|
18785
|
+
console.warn("Could not save feedback template:", error);
|
|
18786
|
+
}
|
|
18787
|
+
}
|
|
18788
|
+
toLegacyRecommendations(v2Recommendations) {
|
|
18789
|
+
return v2Recommendations.map((rec) => ({
|
|
18790
|
+
id: rec.id,
|
|
18791
|
+
title: rec.title,
|
|
18792
|
+
description: rec.summary,
|
|
18793
|
+
priority: rec.priority,
|
|
18794
|
+
category: rec.category,
|
|
18795
|
+
effort: rec.estimatedEffort.size,
|
|
18796
|
+
timeframe: rec.estimatedEffort.timeframes.expected,
|
|
18797
|
+
dependencies: [],
|
|
18798
|
+
evidence: rec.evidenceAnchors.map((a) => a.description)
|
|
18799
|
+
}));
|
|
18800
|
+
}
|
|
18801
|
+
}
|
|
18802
|
+
|
|
16802
18803
|
// src/scanners/copilot-feature-scanner.ts
|
|
16803
|
-
import {constants as constants3, access as access3, readFile as
|
|
16804
|
-
import {join as
|
|
18804
|
+
import {constants as constants3, access as access3, readFile as readFile4} from "node:fs/promises";
|
|
18805
|
+
import {join as join5} from "node:path";
|
|
16805
18806
|
|
|
16806
18807
|
// node_modules/simple-git/dist/esm/index.js
|
|
16807
18808
|
var file_exists = __toESM(require_dist(), 1);
|
|
@@ -20867,10 +22868,10 @@ class CopilotFeatureScanner {
|
|
|
20867
22868
|
".github/COPYLOIT_INSTRUCTIONS.md"
|
|
20868
22869
|
];
|
|
20869
22870
|
for (const path of instructionPaths) {
|
|
20870
|
-
const fullPath =
|
|
22871
|
+
const fullPath = join5(repoPath, path);
|
|
20871
22872
|
try {
|
|
20872
22873
|
await access3(fullPath, constants3.R_OK);
|
|
20873
|
-
const content = await
|
|
22874
|
+
const content = await readFile4(fullPath, "utf-8");
|
|
20874
22875
|
analysis.githubFeatures.copilotInstructions = {
|
|
20875
22876
|
found: true,
|
|
20876
22877
|
path,
|
|
@@ -20889,10 +22890,10 @@ class CopilotFeatureScanner {
|
|
|
20889
22890
|
"docs/CODEOWNERS"
|
|
20890
22891
|
];
|
|
20891
22892
|
for (const path of codeownersPaths) {
|
|
20892
|
-
const fullPath =
|
|
22893
|
+
const fullPath = join5(repoPath, path);
|
|
20893
22894
|
try {
|
|
20894
22895
|
await access3(fullPath, constants3.R_OK);
|
|
20895
|
-
const content = await
|
|
22896
|
+
const content = await readFile4(fullPath, "utf-8");
|
|
20896
22897
|
analysis.githubFeatures.codeowners = {
|
|
20897
22898
|
found: true,
|
|
20898
22899
|
path,
|
|
@@ -20917,10 +22918,10 @@ class CopilotFeatureScanner {
|
|
|
20917
22918
|
".github/PULL_REQUEST_TEMPLATE.md"
|
|
20918
22919
|
];
|
|
20919
22920
|
for (const path of prTemplatePaths) {
|
|
20920
|
-
const fullPath =
|
|
22921
|
+
const fullPath = join5(repoPath, path);
|
|
20921
22922
|
try {
|
|
20922
22923
|
await access3(fullPath, constants3.R_OK);
|
|
20923
|
-
const content = await
|
|
22924
|
+
const content = await readFile4(fullPath, "utf-8");
|
|
20924
22925
|
analysis.githubFeatures.prTemplates = {
|
|
20925
22926
|
found: true,
|
|
20926
22927
|
aiFriendly: this.isAiFriendlyTemplate(content)
|
|
@@ -20939,7 +22940,7 @@ class CopilotFeatureScanner {
|
|
|
20939
22940
|
}
|
|
20940
22941
|
async scanCodePatterns(repoPath, analysis) {
|
|
20941
22942
|
try {
|
|
20942
|
-
const tsconfigPath =
|
|
22943
|
+
const tsconfigPath = join5(repoPath, "tsconfig.json");
|
|
20943
22944
|
try {
|
|
20944
22945
|
await access3(tsconfigPath, constants3.R_OK);
|
|
20945
22946
|
analysis.codePatterns.typeScriptUsage = true;
|
|
@@ -20955,7 +22956,7 @@ class CopilotFeatureScanner {
|
|
|
20955
22956
|
"__tests__/"
|
|
20956
22957
|
];
|
|
20957
22958
|
for (const indicator of testIndicators) {
|
|
20958
|
-
const indicatorPath =
|
|
22959
|
+
const indicatorPath = join5(repoPath, indicator);
|
|
20959
22960
|
try {
|
|
20960
22961
|
await access3(indicatorPath, indicator.endsWith("/") ? constants3.F_OK : constants3.R_OK);
|
|
20961
22962
|
analysis.codePatterns.testCoverage = true;
|
|
@@ -21280,6 +23281,7 @@ class AssessmentEngine {
|
|
|
21280
23281
|
scorer;
|
|
21281
23282
|
adrGenerator;
|
|
21282
23283
|
evidenceCollector;
|
|
23284
|
+
recommendationEngine;
|
|
21283
23285
|
config;
|
|
21284
23286
|
constructor(config) {
|
|
21285
23287
|
this.config = {
|
|
@@ -21297,6 +23299,7 @@ class AssessmentEngine {
|
|
|
21297
23299
|
this.scorer = new ReadinessScorer;
|
|
21298
23300
|
this.adrGenerator = new ADRGenerator;
|
|
21299
23301
|
this.evidenceCollector = new EvidenceCollector;
|
|
23302
|
+
this.recommendationEngine = new RecommendationEngine(this.config.recommendationConfig);
|
|
21300
23303
|
}
|
|
21301
23304
|
async execute() {
|
|
21302
23305
|
const startTime = Date.now();
|
|
@@ -21314,7 +23317,71 @@ class AssessmentEngine {
|
|
|
21314
23317
|
evidence
|
|
21315
23318
|
});
|
|
21316
23319
|
console.log("\uD83C\uDFAF Scoring complete, generating recommendations...");
|
|
21317
|
-
|
|
23320
|
+
let recommendations2 = [];
|
|
23321
|
+
let recommendationEngine;
|
|
23322
|
+
if (this.config.includeRecommendations) {
|
|
23323
|
+
console.log("\uD83D\uDE80 Using Recommendation Engine V2...");
|
|
23324
|
+
recommendationEngine = await this.recommendationEngine.generateRecommendations(this.config.repoPath, evidence, scores, copilotFeatures, techStack);
|
|
23325
|
+
recommendations2 = this.recommendationEngine.toLegacyRecommendations(recommendationEngine.recommendations);
|
|
23326
|
+
console.log(`\u2705 V2 Engine generated ${recommendationEngine.recommendations.length} recommendations`);
|
|
23327
|
+
} else {
|
|
23328
|
+
recommendationEngine = {
|
|
23329
|
+
findings: [],
|
|
23330
|
+
hypotheses: [],
|
|
23331
|
+
validationResults: [],
|
|
23332
|
+
challengerAssessments: [],
|
|
23333
|
+
recommendations: [],
|
|
23334
|
+
pipeline: {
|
|
23335
|
+
findings: {
|
|
23336
|
+
stage: "findings",
|
|
23337
|
+
success: true,
|
|
23338
|
+
data: [],
|
|
23339
|
+
errors: [],
|
|
23340
|
+
warnings: [],
|
|
23341
|
+
metadata: { duration: 0, inputCount: 0, outputCount: 0 }
|
|
23342
|
+
},
|
|
23343
|
+
hypotheses: {
|
|
23344
|
+
stage: "hypotheses",
|
|
23345
|
+
success: true,
|
|
23346
|
+
data: [],
|
|
23347
|
+
errors: [],
|
|
23348
|
+
warnings: [],
|
|
23349
|
+
metadata: { duration: 0, inputCount: 0, outputCount: 0 }
|
|
23350
|
+
},
|
|
23351
|
+
validation: {
|
|
23352
|
+
stage: "validation",
|
|
23353
|
+
success: true,
|
|
23354
|
+
data: [],
|
|
23355
|
+
errors: [],
|
|
23356
|
+
warnings: [],
|
|
23357
|
+
metadata: { duration: 0, inputCount: 0, outputCount: 0 }
|
|
23358
|
+
},
|
|
23359
|
+
challenger: {
|
|
23360
|
+
stage: "challenger",
|
|
23361
|
+
success: true,
|
|
23362
|
+
data: [],
|
|
23363
|
+
errors: [],
|
|
23364
|
+
warnings: [],
|
|
23365
|
+
metadata: { duration: 0, inputCount: 0, outputCount: 0 }
|
|
23366
|
+
},
|
|
23367
|
+
ranking: {
|
|
23368
|
+
stage: "ranking",
|
|
23369
|
+
success: true,
|
|
23370
|
+
data: [],
|
|
23371
|
+
errors: [],
|
|
23372
|
+
warnings: [],
|
|
23373
|
+
metadata: { duration: 0, inputCount: 0, outputCount: 0 }
|
|
23374
|
+
}
|
|
23375
|
+
},
|
|
23376
|
+
metadata: {
|
|
23377
|
+
totalDuration: 0,
|
|
23378
|
+
evidenceCount: 0,
|
|
23379
|
+
confidenceDistribution: { high: 0, medium: 0, low: 0 },
|
|
23380
|
+
categoryDistribution: {}
|
|
23381
|
+
}
|
|
23382
|
+
};
|
|
23383
|
+
recommendations2 = [];
|
|
23384
|
+
}
|
|
21318
23385
|
let personaInsights;
|
|
21319
23386
|
if (this.config.persona) {
|
|
21320
23387
|
console.log("\uD83E\uDD16 Generating persona insights...");
|
|
@@ -21328,7 +23395,7 @@ class AssessmentEngine {
|
|
|
21328
23395
|
copilotFeatures,
|
|
21329
23396
|
techStack,
|
|
21330
23397
|
evidence,
|
|
21331
|
-
recommendations,
|
|
23398
|
+
recommendations: recommendations2,
|
|
21332
23399
|
structuredInsights: personaInsights?.structuredInsights
|
|
21333
23400
|
});
|
|
21334
23401
|
}
|
|
@@ -21337,7 +23404,7 @@ class AssessmentEngine {
|
|
|
21337
23404
|
metadata: {
|
|
21338
23405
|
timestamp: new Date().toISOString(),
|
|
21339
23406
|
repository: this.config.repoPath,
|
|
21340
|
-
version: "
|
|
23407
|
+
version: "2.0.0",
|
|
21341
23408
|
duration
|
|
21342
23409
|
},
|
|
21343
23410
|
analysis: {
|
|
@@ -21346,7 +23413,8 @@ class AssessmentEngine {
|
|
|
21346
23413
|
evidence
|
|
21347
23414
|
},
|
|
21348
23415
|
scores,
|
|
21349
|
-
recommendations
|
|
23416
|
+
recommendations: recommendations2,
|
|
23417
|
+
recommendationEngine
|
|
21350
23418
|
};
|
|
21351
23419
|
if (personaInsights) {
|
|
21352
23420
|
result.personaInsights = personaInsights;
|
|
@@ -21362,9 +23430,9 @@ class AssessmentEngine {
|
|
|
21362
23430
|
}
|
|
21363
23431
|
}
|
|
21364
23432
|
async generateRecommendations(scores, features, evidence) {
|
|
21365
|
-
const
|
|
23433
|
+
const recommendations2 = [];
|
|
21366
23434
|
if (scores.repoReadiness < 70) {
|
|
21367
|
-
|
|
23435
|
+
recommendations2.push({
|
|
21368
23436
|
id: "found-001",
|
|
21369
23437
|
title: "Improve Repository Structure",
|
|
21370
23438
|
description: "Standardize repository structure and add essential configuration files",
|
|
@@ -21377,7 +23445,7 @@ class AssessmentEngine {
|
|
|
21377
23445
|
});
|
|
21378
23446
|
}
|
|
21379
23447
|
if (!features.githubFeatures.codeowners.found) {
|
|
21380
|
-
|
|
23448
|
+
recommendations2.push({
|
|
21381
23449
|
id: "sec-001",
|
|
21382
23450
|
title: "Add CODEOWNERS File",
|
|
21383
23451
|
description: "Define code ownership for better security and review processes",
|
|
@@ -21390,7 +23458,7 @@ class AssessmentEngine {
|
|
|
21390
23458
|
});
|
|
21391
23459
|
}
|
|
21392
23460
|
if (!features.githubFeatures.copilotInstructions.found) {
|
|
21393
|
-
|
|
23461
|
+
recommendations2.push({
|
|
21394
23462
|
id: "ai-001",
|
|
21395
23463
|
title: "Add Copilot Instructions",
|
|
21396
23464
|
description: "Create Copilot instructions to guide AI assistance in your repository",
|
|
@@ -21403,7 +23471,7 @@ class AssessmentEngine {
|
|
|
21403
23471
|
});
|
|
21404
23472
|
}
|
|
21405
23473
|
if (scores.orgReadiness < 60) {
|
|
21406
|
-
|
|
23474
|
+
recommendations2.push({
|
|
21407
23475
|
id: "gov-001",
|
|
21408
23476
|
title: "Establish Governance Processes",
|
|
21409
23477
|
description: "Implement clear processes for code review, testing, and deployment",
|
|
@@ -21415,7 +23483,7 @@ class AssessmentEngine {
|
|
|
21415
23483
|
evidence: this.extractEvidence(evidence, ["governance", "process"])
|
|
21416
23484
|
});
|
|
21417
23485
|
}
|
|
21418
|
-
return
|
|
23486
|
+
return recommendations2.sort((a, b) => {
|
|
21419
23487
|
const priorityOrder = { high: 3, medium: 2, low: 1 };
|
|
21420
23488
|
return priorityOrder[b.priority] - priorityOrder[a.priority];
|
|
21421
23489
|
});
|
|
@@ -21440,7 +23508,7 @@ class AssessmentEngine {
|
|
|
21440
23508
|
metadata: {
|
|
21441
23509
|
timestamp: new Date().toISOString(),
|
|
21442
23510
|
repository: this.config.repoPath,
|
|
21443
|
-
version: "
|
|
23511
|
+
version: "2.0.0",
|
|
21444
23512
|
duration: 0
|
|
21445
23513
|
},
|
|
21446
23514
|
analysis: {
|
|
@@ -21538,9 +23606,11 @@ function displayResults(result, persona) {
|
|
|
21538
23606
|
console.log(import_chalk.default.gray(`Repository: ${result.metadata.repository}`));
|
|
21539
23607
|
console.log(import_chalk.default.gray(`Assessed: ${result.metadata.timestamp}`));
|
|
21540
23608
|
console.log(import_chalk.default.gray(`Duration: ${result.metadata.duration}ms`));
|
|
23609
|
+
console.log(import_chalk.default.gray(`Version: ${result.metadata.version}`));
|
|
21541
23610
|
console.log(import_chalk.default.gray(`Persona: ${persona}\n`));
|
|
21542
23611
|
displayScores(result.scores);
|
|
21543
23612
|
displayRecommendations(result.recommendations);
|
|
23613
|
+
displayRecommendationEngine(result.recommendationEngine);
|
|
21544
23614
|
if (result.personaInsights) {
|
|
21545
23615
|
displayPersonaInsights(result.personaInsights);
|
|
21546
23616
|
}
|
|
@@ -21581,13 +23651,52 @@ function displayScores(scores) {
|
|
|
21581
23651
|
});
|
|
21582
23652
|
console.log(import_chalk.default.gray(`Confidence: ${scores.confidence}\n`));
|
|
21583
23653
|
}
|
|
21584
|
-
function
|
|
21585
|
-
|
|
23654
|
+
function displayRecommendationEngine(engineResult) {
|
|
23655
|
+
console.log(import_chalk.default.bold.blue(`
|
|
23656
|
+
\uD83D\uDE80 Recommendation Engine Results`));
|
|
23657
|
+
console.log(import_chalk.default.gray(`Pipeline Duration: ${engineResult.metadata.totalDuration}ms`));
|
|
23658
|
+
console.log(import_chalk.default.gray(`Evidence Items: ${engineResult.metadata.evidenceCount}`));
|
|
23659
|
+
console.log(import_chalk.default.gray(`Findings: ${engineResult.findings.length}`));
|
|
23660
|
+
console.log(import_chalk.default.gray(`Hypotheses: ${engineResult.hypotheses.length}`));
|
|
23661
|
+
console.log(import_chalk.default.gray(`Final Recommendations: ${engineResult.recommendations.length}\n`));
|
|
23662
|
+
if (engineResult.recommendations.length === 0) {
|
|
23663
|
+
console.log(import_chalk.default.green("\u2705 No recommendations - repository is well prepared!"));
|
|
23664
|
+
return;
|
|
23665
|
+
}
|
|
23666
|
+
const groupedRecs = engineResult.recommendations.reduce((groups, rec) => {
|
|
23667
|
+
if (!groups[rec.priority])
|
|
23668
|
+
groups[rec.priority] = [];
|
|
23669
|
+
groups[rec.priority].push(rec);
|
|
23670
|
+
return groups;
|
|
23671
|
+
}, {});
|
|
23672
|
+
["critical", "high", "medium", "low"].forEach((priority) => {
|
|
23673
|
+
const recs = groupedRecs[priority];
|
|
23674
|
+
if (recs && recs.length > 0) {
|
|
23675
|
+
const priorityColor = priority === "critical" ? import_chalk.default.red : priority === "high" ? import_chalk.default.red : priority === "medium" ? import_chalk.default.yellow : import_chalk.default.gray;
|
|
23676
|
+
console.log(import_chalk.default.bold(`\n${priorityColor(priority.toUpperCase())} PRIORITY:`));
|
|
23677
|
+
recs.forEach((rec) => {
|
|
23678
|
+
console.log(` ${import_chalk.default.bold(rec.title)} (${rec.category})`);
|
|
23679
|
+
console.log(` ${import_chalk.default.gray(rec.summary)}`);
|
|
23680
|
+
console.log(` ${import_chalk.default.blue(`Confidence: ${rec.confidence.overall}% | Evidence: ${rec.evidenceAnchors.length} anchors`)}`);
|
|
23681
|
+
if (rec.humanReviewNeeded) {
|
|
23682
|
+
console.log(` ${import_chalk.default.yellow("\u26A0\uFE0F Requires human review")}`);
|
|
23683
|
+
}
|
|
23684
|
+
console.log(` ${import_chalk.default.cyan(`Next: ${rec.suggestedNextStep}`)}`);
|
|
23685
|
+
if (rec.caveats.length > 0) {
|
|
23686
|
+
console.log(` ${import_chalk.default.hex("#FFA500")(`Caveats: ${rec.caveats.slice(0, 2).join("; ")}`)}`);
|
|
23687
|
+
}
|
|
23688
|
+
console.log("");
|
|
23689
|
+
});
|
|
23690
|
+
}
|
|
23691
|
+
});
|
|
23692
|
+
}
|
|
23693
|
+
function displayRecommendations(recommendations2) {
|
|
23694
|
+
if (recommendations2.length === 0) {
|
|
21586
23695
|
console.log(import_chalk.default.green("\u2705 No recommendations needed - repository is well prepared!"));
|
|
21587
23696
|
return;
|
|
21588
23697
|
}
|
|
21589
23698
|
console.log(import_chalk.default.bold.blue("\uD83C\uDFAF Recommendations"));
|
|
21590
|
-
const groupedRecs =
|
|
23699
|
+
const groupedRecs = recommendations2.reduce((groups, rec) => {
|
|
21591
23700
|
if (!groups[rec.priority])
|
|
21592
23701
|
groups[rec.priority] = [];
|
|
21593
23702
|
groups[rec.priority].push(rec);
|
|
@@ -21671,9 +23780,352 @@ function getMaturityStatus(level) {
|
|
|
21671
23780
|
return import_chalk.default.hex("#FFA500")("\uD83D\uDD36 Basic");
|
|
21672
23781
|
return import_chalk.default.red("\u274C Initial");
|
|
21673
23782
|
}
|
|
23783
|
+
async function generateRoadmap(result, options) {
|
|
23784
|
+
const timeframe = parseInt(options.timeframe) || 12;
|
|
23785
|
+
const pace = options.pace || "moderate";
|
|
23786
|
+
const paceMultipliers = {
|
|
23787
|
+
aggressive: 0.75,
|
|
23788
|
+
moderate: 1,
|
|
23789
|
+
conservative: 1.5
|
|
23790
|
+
};
|
|
23791
|
+
const adjustedTimeframe = timeframe * paceMultipliers[pace];
|
|
23792
|
+
const groupedRecs = result.recommendations.reduce((groups, rec) => {
|
|
23793
|
+
const key = `${rec.category}-${rec.priority}`;
|
|
23794
|
+
if (!groups[key])
|
|
23795
|
+
groups[key] = [];
|
|
23796
|
+
groups[key].push(rec);
|
|
23797
|
+
return groups;
|
|
23798
|
+
}, {});
|
|
23799
|
+
const phases = createPhases(result.scores.overallMaturity, groupedRecs, adjustedTimeframe);
|
|
23800
|
+
const roadmap = {
|
|
23801
|
+
metadata: {
|
|
23802
|
+
repository: result.metadata.repository,
|
|
23803
|
+
generated: new Date().toISOString(),
|
|
23804
|
+
timeframe,
|
|
23805
|
+
pace,
|
|
23806
|
+
persona: options.persona || "consultant"
|
|
23807
|
+
},
|
|
23808
|
+
summary: {
|
|
23809
|
+
currentMaturity: result.scores.overallMaturity,
|
|
23810
|
+
targetMaturity: Math.min(8, result.scores.overallMaturity + 3),
|
|
23811
|
+
totalPhases: phases.length,
|
|
23812
|
+
estimatedDuration: `${Math.ceil(adjustedTimeframe)} months`,
|
|
23813
|
+
criticalPath: getCriticalPath(phases)
|
|
23814
|
+
},
|
|
23815
|
+
phases,
|
|
23816
|
+
successMetrics: options.includeMetrics ? generateSuccessMetrics(phases) : { kpis: [], milestones: [] }
|
|
23817
|
+
};
|
|
23818
|
+
return roadmap;
|
|
23819
|
+
}
|
|
23820
|
+
function createPhases(currentMaturity, groupedRecs, timeframe) {
|
|
23821
|
+
const phases = [];
|
|
23822
|
+
if (currentMaturity < 6) {
|
|
23823
|
+
const foundationRecs = Object.entries(groupedRecs).filter(([key]) => key.includes("foundation") || key.includes("high")).flatMap(([, recs]) => recs);
|
|
23824
|
+
phases.push({
|
|
23825
|
+
id: "foundation",
|
|
23826
|
+
name: "Foundation & Infrastructure",
|
|
23827
|
+
description: "Establish the technical and organizational foundation for AI enablement",
|
|
23828
|
+
duration: `${Math.ceil(timeframe * 0.25)} months`,
|
|
23829
|
+
priority: "critical",
|
|
23830
|
+
objectives: [
|
|
23831
|
+
"Establish robust development infrastructure",
|
|
23832
|
+
"Implement security and governance frameworks",
|
|
23833
|
+
"Build foundational AI tooling and workflows"
|
|
23834
|
+
],
|
|
23835
|
+
actions: foundationRecs.map((rec) => ({
|
|
23836
|
+
title: rec.title,
|
|
23837
|
+
description: rec.description,
|
|
23838
|
+
effort: rec.effort,
|
|
23839
|
+
timeframe: rec.timeframe,
|
|
23840
|
+
dependencies: rec.dependencies,
|
|
23841
|
+
expectedOutcomes: [
|
|
23842
|
+
`Improved ${rec.category} maturity`,
|
|
23843
|
+
"Reduced technical debt",
|
|
23844
|
+
"Enhanced team productivity"
|
|
23845
|
+
]
|
|
23846
|
+
})),
|
|
23847
|
+
risks: [
|
|
23848
|
+
{
|
|
23849
|
+
description: "Resource constraints delaying foundation work",
|
|
23850
|
+
mitigation: "Phase critical items, secure executive sponsorship",
|
|
23851
|
+
probability: "medium",
|
|
23852
|
+
impact: "high"
|
|
23853
|
+
}
|
|
23854
|
+
]
|
|
23855
|
+
});
|
|
23856
|
+
}
|
|
23857
|
+
if (currentMaturity < 7) {
|
|
23858
|
+
const aiRecs = Object.entries(groupedRecs).filter(([key]) => key.includes("ai") || key.includes("workflow")).flatMap(([, recs]) => recs);
|
|
23859
|
+
phases.push({
|
|
23860
|
+
id: "integration",
|
|
23861
|
+
name: "AI Integration & Adoption",
|
|
23862
|
+
description: "Integrate AI tools into development workflows and build team capabilities",
|
|
23863
|
+
duration: `${Math.ceil(timeframe * 0.35)} months`,
|
|
23864
|
+
priority: "high",
|
|
23865
|
+
objectives: [
|
|
23866
|
+
"Deploy AI-powered development tools",
|
|
23867
|
+
"Train teams on AI-assisted workflows",
|
|
23868
|
+
"Establish AI governance and best practices"
|
|
23869
|
+
],
|
|
23870
|
+
actions: aiRecs.map((rec) => ({
|
|
23871
|
+
title: rec.title,
|
|
23872
|
+
description: rec.description,
|
|
23873
|
+
effort: rec.effort,
|
|
23874
|
+
timeframe: rec.timeframe,
|
|
23875
|
+
dependencies: rec.dependencies,
|
|
23876
|
+
expectedOutcomes: [
|
|
23877
|
+
"Increased developer productivity",
|
|
23878
|
+
"Improved code quality",
|
|
23879
|
+
"Enhanced innovation capacity"
|
|
23880
|
+
]
|
|
23881
|
+
})),
|
|
23882
|
+
risks: [
|
|
23883
|
+
{
|
|
23884
|
+
description: "Team resistance to AI adoption",
|
|
23885
|
+
mitigation: "Comprehensive training, demonstrate quick wins",
|
|
23886
|
+
probability: "medium",
|
|
23887
|
+
impact: "medium"
|
|
23888
|
+
}
|
|
23889
|
+
]
|
|
23890
|
+
});
|
|
23891
|
+
}
|
|
23892
|
+
phases.push({
|
|
23893
|
+
id: "optimization",
|
|
23894
|
+
name: "Optimization & Scale",
|
|
23895
|
+
description: "Optimize AI workflows and scale successful practices across the organization",
|
|
23896
|
+
duration: `${Math.ceil(timeframe * 0.4)} months`,
|
|
23897
|
+
priority: "medium",
|
|
23898
|
+
objectives: [
|
|
23899
|
+
"Optimize AI tooling and workflows",
|
|
23900
|
+
"Scale successful practices organization-wide",
|
|
23901
|
+
"Establish continuous improvement processes"
|
|
23902
|
+
],
|
|
23903
|
+
actions: [
|
|
23904
|
+
{
|
|
23905
|
+
title: "Implement Advanced AI Analytics",
|
|
23906
|
+
description: "Deploy sophisticated AI-powered analytics for development insights",
|
|
23907
|
+
effort: "High",
|
|
23908
|
+
timeframe: "2-3 months",
|
|
23909
|
+
dependencies: [
|
|
23910
|
+
"Foundation & Infrastructure",
|
|
23911
|
+
"AI Integration & Adoption"
|
|
23912
|
+
],
|
|
23913
|
+
expectedOutcomes: [
|
|
23914
|
+
"Data-driven decision making",
|
|
23915
|
+
"Predictive insights for development",
|
|
23916
|
+
"Continuous performance monitoring"
|
|
23917
|
+
],
|
|
23918
|
+
successMetrics: [
|
|
23919
|
+
"50% reduction in bug detection time",
|
|
23920
|
+
"30% improvement in deployment frequency",
|
|
23921
|
+
"25% reduction in technical debt accumulation"
|
|
23922
|
+
]
|
|
23923
|
+
},
|
|
23924
|
+
{
|
|
23925
|
+
title: "Establish AI Center of Excellence",
|
|
23926
|
+
description: "Create a centralized team to drive AI innovation and best practices",
|
|
23927
|
+
effort: "Medium",
|
|
23928
|
+
timeframe: "1-2 months",
|
|
23929
|
+
dependencies: ["AI Integration & Adoption"],
|
|
23930
|
+
expectedOutcomes: [
|
|
23931
|
+
"Centralized AI expertise",
|
|
23932
|
+
"Standardized best practices",
|
|
23933
|
+
"Innovation incubation"
|
|
23934
|
+
]
|
|
23935
|
+
}
|
|
23936
|
+
],
|
|
23937
|
+
risks: [
|
|
23938
|
+
{
|
|
23939
|
+
description: "Scaling challenges as organization grows",
|
|
23940
|
+
mitigation: "Build scalable processes, document best practices",
|
|
23941
|
+
probability: "low",
|
|
23942
|
+
impact: "medium"
|
|
23943
|
+
}
|
|
23944
|
+
]
|
|
23945
|
+
});
|
|
23946
|
+
return phases;
|
|
23947
|
+
}
|
|
23948
|
+
function getCriticalPath(phases) {
|
|
23949
|
+
return phases.filter((phase) => phase.priority === "critical" || phase.priority === "high").map((phase) => phase.name);
|
|
23950
|
+
}
|
|
23951
|
+
function generateSuccessMetrics(phases) {
|
|
23952
|
+
return {
|
|
23953
|
+
kpis: [
|
|
23954
|
+
{
|
|
23955
|
+
name: "Developer Productivity",
|
|
23956
|
+
description: "Measure of developer output and efficiency",
|
|
23957
|
+
target: "+30% in 6 months",
|
|
23958
|
+
measurement: "Lines of code per developer, deployment frequency"
|
|
23959
|
+
},
|
|
23960
|
+
{
|
|
23961
|
+
name: "Code Quality",
|
|
23962
|
+
description: "Overall codebase health and maintainability",
|
|
23963
|
+
target: "Reduce bugs by 40%",
|
|
23964
|
+
measurement: "Bug density, test coverage, code review metrics"
|
|
23965
|
+
},
|
|
23966
|
+
{
|
|
23967
|
+
name: "AI Adoption Rate",
|
|
23968
|
+
description: "Percentage of team actively using AI tools",
|
|
23969
|
+
target: "80% adoption in 3 months",
|
|
23970
|
+
measurement: "Tool usage analytics, survey responses"
|
|
23971
|
+
}
|
|
23972
|
+
],
|
|
23973
|
+
milestones: [
|
|
23974
|
+
{
|
|
23975
|
+
name: "Foundation Complete",
|
|
23976
|
+
date: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
|
|
23977
|
+
criteria: [
|
|
23978
|
+
"All infrastructure recommendations implemented",
|
|
23979
|
+
"Security frameworks in place",
|
|
23980
|
+
"Team training completed"
|
|
23981
|
+
]
|
|
23982
|
+
},
|
|
23983
|
+
{
|
|
23984
|
+
name: "AI Integration Milestone",
|
|
23985
|
+
date: new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
|
|
23986
|
+
criteria: [
|
|
23987
|
+
"AI tools deployed to all teams",
|
|
23988
|
+
"70% adoption rate achieved",
|
|
23989
|
+
"Productivity gains measurable"
|
|
23990
|
+
]
|
|
23991
|
+
}
|
|
23992
|
+
]
|
|
23993
|
+
};
|
|
23994
|
+
}
|
|
23995
|
+
function displayRoadmapSummary(roadmap) {
|
|
23996
|
+
console.log(import_chalk.default.bold.blue(`
|
|
23997
|
+
\uD83D\uDEE3\uFE0F AI Enablement Roadmap`));
|
|
23998
|
+
console.log(import_chalk.default.gray(`Repository: ${roadmap.metadata.repository}`));
|
|
23999
|
+
console.log(import_chalk.default.gray(`Generated: ${new Date(roadmap.metadata.generated).toLocaleDateString()}`));
|
|
24000
|
+
console.log(import_chalk.default.gray(`Timeframe: ${roadmap.summary.estimatedDuration}`));
|
|
24001
|
+
console.log(import_chalk.default.gray(`Pace: ${roadmap.metadata.pace}\n`));
|
|
24002
|
+
console.log(import_chalk.default.bold("\uD83D\uDCCA Maturity Progression"));
|
|
24003
|
+
console.log(`Current: ${import_chalk.default.yellow(roadmap.summary.currentMaturity)}/8 \u2192 Target: ${import_chalk.default.green(roadmap.summary.targetMaturity)}/8`);
|
|
24004
|
+
console.log(`Total Phases: ${roadmap.summary.totalPhases}`);
|
|
24005
|
+
console.log(`Critical Path: ${roadmap.summary.criticalPath.join(" \u2192 ")}\n`);
|
|
24006
|
+
console.log(import_chalk.default.bold("\uD83C\uDFAF Phases Overview"));
|
|
24007
|
+
roadmap.phases.forEach((phase, index) => {
|
|
24008
|
+
const priorityColor = phase.priority === "critical" ? import_chalk.default.red : phase.priority === "high" ? import_chalk.default.yellow : phase.priority === "medium" ? import_chalk.default.blue : import_chalk.default.gray;
|
|
24009
|
+
console.log(`\n${index + 1}. ${import_chalk.default.bold(phase.name)} ${priorityColor(`(${phase.priority.toUpperCase()})`)}`);
|
|
24010
|
+
console.log(` ${import_chalk.default.gray(phase.description)}`);
|
|
24011
|
+
console.log(` ${import_chalk.default.blue(`Duration: ${phase.duration} | Actions: ${phase.actions.length}`)}`);
|
|
24012
|
+
phase.objectives.slice(0, 2).forEach((obj) => {
|
|
24013
|
+
console.log(` \u2022 ${import_chalk.default.white(obj)}`);
|
|
24014
|
+
});
|
|
24015
|
+
});
|
|
24016
|
+
if (roadmap.successMetrics.kpis.length > 0) {
|
|
24017
|
+
console.log(import_chalk.default.bold(`
|
|
24018
|
+
\uD83D\uDCC8 Key Success Metrics`));
|
|
24019
|
+
roadmap.successMetrics.kpis.slice(0, 3).forEach((kpi) => {
|
|
24020
|
+
console.log(`\u2022 ${import_chalk.default.bold(kpi.name)}: ${kpi.target}`);
|
|
24021
|
+
});
|
|
24022
|
+
}
|
|
24023
|
+
}
|
|
24024
|
+
async function saveRoadmap(roadmap, outputPath, format) {
|
|
24025
|
+
const fs = await import("node:fs/promises");
|
|
24026
|
+
const path = await import("node:path");
|
|
24027
|
+
await fs.mkdir(outputPath, { recursive: true });
|
|
24028
|
+
if (format === "markdown" || format === "both") {
|
|
24029
|
+
const markdown = generateMarkdownRoadmap(roadmap);
|
|
24030
|
+
await fs.writeFile(path.join(outputPath, "roadmap.md"), markdown, "utf-8");
|
|
24031
|
+
console.log(import_chalk.default.green(`\uD83D\uDCC4 Roadmap saved to: ${path.join(outputPath, "roadmap.md")}`));
|
|
24032
|
+
}
|
|
24033
|
+
if (format === "json" || format === "both") {
|
|
24034
|
+
await fs.writeFile(path.join(outputPath, "roadmap.json"), JSON.stringify(roadmap, null, 2), "utf-8");
|
|
24035
|
+
console.log(import_chalk.default.green(`\uD83D\uDCC4 Roadmap saved to: ${path.join(outputPath, "roadmap.json")}`));
|
|
24036
|
+
}
|
|
24037
|
+
}
|
|
24038
|
+
function generateMarkdownRoadmap(roadmap) {
|
|
24039
|
+
let markdown = `# AI Enablement Roadmap
|
|
24040
|
+
|
|
24041
|
+
**Repository:** ${roadmap.metadata.repository}
|
|
24042
|
+
**Generated:** ${new Date(roadmap.metadata.generated).toLocaleDateString()}
|
|
24043
|
+
**Timeframe:** ${roadmap.summary.estimatedDuration} (${roadmap.metadata.pace} pace)
|
|
24044
|
+
**Persona:** ${roadmap.metadata.persona}
|
|
24045
|
+
|
|
24046
|
+
## Executive Summary
|
|
24047
|
+
|
|
24048
|
+
- **Current Maturity:** ${roadmap.summary.currentMaturity}/8
|
|
24049
|
+
- **Target Maturity:** ${roadmap.summary.targetMaturity}/8
|
|
24050
|
+
- **Total Phases:** ${roadmap.summary.totalPhases}
|
|
24051
|
+
- **Critical Path:** ${roadmap.summary.criticalPath.join(" \u2192 ")}
|
|
24052
|
+
|
|
24053
|
+
`;
|
|
24054
|
+
roadmap.phases.forEach((phase, index) => {
|
|
24055
|
+
markdown += `## Phase ${index + 1}: ${phase.name}
|
|
24056
|
+
|
|
24057
|
+
**Priority:** ${phase.priority.toUpperCase()}
|
|
24058
|
+
**Duration:** ${phase.duration}
|
|
24059
|
+
|
|
24060
|
+
${phase.description}
|
|
24061
|
+
|
|
24062
|
+
### Objectives
|
|
24063
|
+
`;
|
|
24064
|
+
phase.objectives.forEach((obj) => {
|
|
24065
|
+
markdown += `- ${obj}\n`;
|
|
24066
|
+
});
|
|
24067
|
+
markdown += `\n### Actions\n`;
|
|
24068
|
+
phase.actions.forEach((action) => {
|
|
24069
|
+
markdown += `#### ${action.title}
|
|
24070
|
+
|
|
24071
|
+
**Effort:** ${action.effort} | **Timeframe:** ${action.timeframe}
|
|
24072
|
+
|
|
24073
|
+
${action.description}
|
|
24074
|
+
|
|
24075
|
+
**Expected Outcomes:**
|
|
24076
|
+
`;
|
|
24077
|
+
action.expectedOutcomes.forEach((outcome) => {
|
|
24078
|
+
markdown += `- ${outcome}\n`;
|
|
24079
|
+
});
|
|
24080
|
+
if (action.dependencies.length > 0) {
|
|
24081
|
+
markdown += `\n**Dependencies:** ${action.dependencies.join(", ")}\n`;
|
|
24082
|
+
}
|
|
24083
|
+
if (action.successMetrics && action.successMetrics.length > 0) {
|
|
24084
|
+
markdown += `\n**Success Metrics:**\n`;
|
|
24085
|
+
action.successMetrics.forEach((metric) => {
|
|
24086
|
+
markdown += `- ${metric}\n`;
|
|
24087
|
+
});
|
|
24088
|
+
}
|
|
24089
|
+
markdown += "\n";
|
|
24090
|
+
});
|
|
24091
|
+
if (phase.risks.length > 0) {
|
|
24092
|
+
markdown += `### Risks & Mitigations\n`;
|
|
24093
|
+
phase.risks.forEach((risk) => {
|
|
24094
|
+
markdown += `- **${risk.description}** (${risk.probability} probability, ${risk.impact} impact)\n`;
|
|
24095
|
+
markdown += ` - *Mitigation:* ${risk.mitigation}\n`;
|
|
24096
|
+
});
|
|
24097
|
+
markdown += "\n";
|
|
24098
|
+
}
|
|
24099
|
+
});
|
|
24100
|
+
if (roadmap.successMetrics.kpis.length > 0) {
|
|
24101
|
+
markdown += `## Success Metrics\n\n### Key Performance Indicators\n`;
|
|
24102
|
+
roadmap.successMetrics.kpis.forEach((kpi) => {
|
|
24103
|
+
markdown += `#### ${kpi.name}
|
|
24104
|
+
|
|
24105
|
+
**Target:** ${kpi.target}
|
|
24106
|
+
|
|
24107
|
+
${kpi.description}
|
|
24108
|
+
|
|
24109
|
+
**Measurement:** ${kpi.measurement}
|
|
24110
|
+
|
|
24111
|
+
`;
|
|
24112
|
+
});
|
|
24113
|
+
markdown += `### Milestones\n`;
|
|
24114
|
+
roadmap.successMetrics.milestones.forEach((milestone) => {
|
|
24115
|
+
markdown += `#### ${milestone.name} (${milestone.date})
|
|
24116
|
+
|
|
24117
|
+
`;
|
|
24118
|
+
milestone.criteria.forEach((criteria) => {
|
|
24119
|
+
markdown += `- ${criteria}\n`;
|
|
24120
|
+
});
|
|
24121
|
+
markdown += "\n";
|
|
24122
|
+
});
|
|
24123
|
+
}
|
|
24124
|
+
return markdown;
|
|
24125
|
+
}
|
|
21674
24126
|
var program2 = new Command;
|
|
21675
24127
|
program2.name("ai-enablement").description("AI Enablement Platform - Analyze repository readiness for AI adoption").version("1.0.0");
|
|
21676
|
-
program2.command("analyze").description("Analyze repository for AI enablement readiness").argument("<repo-path>", "Path to the repository to analyze").option("-o, --output <path>", "Output directory for results").option("-f, --format <format>", "Output format (json, adr, markdown)", "json").option("--no-recommendations", "Skip recommendations generation").option("--no-adr", "Skip ADR generation").option("--persona <type>", "Analysis persona (consultant, evangelist, team-lead)", "consultant").option("--llm-coalescing", "Enable LLM coalescing with adversarial validation").option("--adversarial-validation", "Enable adversarial validation (default: enabled with LLM)").action(async (repoPath, options) => {
|
|
24128
|
+
program2.command("analyze").description("Analyze repository for AI enablement readiness").argument("<repo-path>", "Path to the repository to analyze").option("-o, --output <path>", "Output directory for results").option("-f, --format <format>", "Output format (json, adr, markdown)", "json").option("--no-recommendations", "Skip recommendations generation").option("--no-adr", "Skip ADR generation").option("--persona <type>", "Analysis persona (consultant, evangelist, team-lead)", "consultant").option("--llm-coalescing", "Enable LLM coalescing with adversarial validation").option("--adversarial-validation", "Enable adversarial validation (default: enabled with LLM)").option("--challenger", "Enable challenger pass for recommendation validation").option("--confidence-threshold <number>", "Minimum confidence threshold for recommendations", "50").option("--human-review-threshold <number>", "Confidence threshold requiring human review", "70").action(async (repoPath, options) => {
|
|
21677
24129
|
try {
|
|
21678
24130
|
const spinner = import_ora.default("Initializing AI Enablement Assessment...").start();
|
|
21679
24131
|
const config = {
|
|
@@ -21684,7 +24136,13 @@ program2.command("analyze").description("Analyze repository for AI enablement re
|
|
|
21684
24136
|
outputFormat: options.format,
|
|
21685
24137
|
persona: options.persona,
|
|
21686
24138
|
enableLLMCoalescing: options.llmCoalescing || false,
|
|
21687
|
-
enableAdversarialValidation: options.adversarialValidation || options.llmCoalescing
|
|
24139
|
+
enableAdversarialValidation: options.adversarialValidation || options.llmCoalescing,
|
|
24140
|
+
recommendationConfig: {
|
|
24141
|
+
enableChallenger: options.challenger || true,
|
|
24142
|
+
confidenceThreshold: parseInt(options.confidenceThreshold) || 50,
|
|
24143
|
+
humanReviewThreshold: parseInt(options.humanReviewThreshold) || 70,
|
|
24144
|
+
enableFeedback: true
|
|
24145
|
+
}
|
|
21688
24146
|
};
|
|
21689
24147
|
const engine = new AssessmentEngine(config);
|
|
21690
24148
|
spinner.text = "Analyzing repository...";
|
|
@@ -21729,14 +24187,14 @@ program2.command("recommend").description("Generate recommendations for a reposi
|
|
|
21729
24187
|
const spinner = import_ora.default("Generating recommendations...").start();
|
|
21730
24188
|
const result = await engine.execute();
|
|
21731
24189
|
spinner.succeed("Recommendations generated!");
|
|
21732
|
-
let
|
|
24190
|
+
let recommendations2 = result.recommendations;
|
|
21733
24191
|
if (options.priority) {
|
|
21734
|
-
|
|
24192
|
+
recommendations2 = recommendations2.filter((r) => r.priority === options.priority);
|
|
21735
24193
|
}
|
|
21736
24194
|
if (options.category) {
|
|
21737
|
-
|
|
24195
|
+
recommendations2 = recommendations2.filter((r) => r.category === options.category);
|
|
21738
24196
|
}
|
|
21739
|
-
displayRecommendations(
|
|
24197
|
+
displayRecommendations(recommendations2);
|
|
21740
24198
|
} catch (error) {
|
|
21741
24199
|
console.error(import_chalk.default.red("\u274C Recommendation generation failed:"), error);
|
|
21742
24200
|
process.exit(1);
|
|
@@ -21765,6 +24223,29 @@ program2.command("adr").description("Generate Architecture Decision Record for A
|
|
|
21765
24223
|
process.exit(1);
|
|
21766
24224
|
}
|
|
21767
24225
|
});
|
|
24226
|
+
program2.command("path").description("Generate strategic AI enablement roadmap with phased approach").argument("<repo-path>", "Path to the repository to analyze").option("-o, --output <path>", "Output directory for roadmap files").option("--format <format>", "Output format (markdown, json, both)", "markdown").option("--persona <type>", "Analysis persona (consultant, evangelist, team-lead)", "consultant").option("--timeframe <months>", "Target timeframe for full enablement in months", "12").option("--pace <speed>", "Implementation pace (aggressive, moderate, conservative)", "moderate").option("--include-metrics", "Include success metrics and KPIs").action(async (repoPath, options) => {
|
|
24227
|
+
try {
|
|
24228
|
+
const engine = new AssessmentEngine({
|
|
24229
|
+
repoPath,
|
|
24230
|
+
includeRecommendations: true,
|
|
24231
|
+
generateADR: false,
|
|
24232
|
+
outputFormat: "json",
|
|
24233
|
+
persona: options.persona
|
|
24234
|
+
});
|
|
24235
|
+
const spinner = import_ora.default("Analyzing repository and generating roadmap...").start();
|
|
24236
|
+
const result = await engine.execute();
|
|
24237
|
+
spinner.text = "Creating strategic roadmap...";
|
|
24238
|
+
const roadmap = await generateRoadmap(result, options);
|
|
24239
|
+
spinner.succeed("Roadmap generated!");
|
|
24240
|
+
displayRoadmapSummary(roadmap);
|
|
24241
|
+
if (options.output) {
|
|
24242
|
+
await saveRoadmap(roadmap, options.output, options.format);
|
|
24243
|
+
}
|
|
24244
|
+
} catch (error) {
|
|
24245
|
+
console.error(import_chalk.default.red("\u274C Roadmap generation failed:"), error);
|
|
24246
|
+
process.exit(1);
|
|
24247
|
+
}
|
|
24248
|
+
});
|
|
21768
24249
|
process.on("uncaughtException", (error) => {
|
|
21769
24250
|
console.error(import_chalk.default.red("\u274C Uncaught exception:"), error);
|
|
21770
24251
|
process.exit(1);
|