flowseeker 0.1.7 → 0.1.9

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.
Files changed (50) hide show
  1. package/.env.example +7 -0
  2. package/CHANGELOG.md +143 -108
  3. package/README.md +288 -221
  4. package/dist/chat/nativeChatParticipant.js +1 -1
  5. package/dist/cli/flowCommand.js +175 -0
  6. package/dist/cli/main.js +1810 -0
  7. package/dist/cli/mcpServer.js +7 -1
  8. package/dist/cli/runEvaluation.js +281 -2
  9. package/dist/config/defaultConfig.js +11 -1
  10. package/dist/config/env.js +118 -0
  11. package/dist/config/loadConfig.js +18 -1
  12. package/dist/config/loadConfigFromPath.js +3 -1
  13. package/dist/eval/accuracyV2.js +483 -0
  14. package/dist/eval/goldenTask.js +192 -0
  15. package/dist/extension.js +23 -0
  16. package/dist/framework/laravel.js +177 -0
  17. package/dist/gateway/embeddingProviders.js +852 -0
  18. package/dist/index/cacheStore.js +43 -0
  19. package/dist/index/configRouteDiscoveryProbe.js +288 -0
  20. package/dist/index/embeddingIndex.js +193 -0
  21. package/dist/index/graphIndex.js +460 -0
  22. package/dist/index/indexWatcher.js +86 -0
  23. package/dist/index/semanticChunkIndex.js +388 -0
  24. package/dist/index/structuredExtractor.js +303 -12
  25. package/dist/index/treeSitterExtractor.js +264 -0
  26. package/dist/index/vectorStore.js +41 -0
  27. package/dist/index/workspaceIndex.js +901 -26
  28. package/dist/mcp/mcpTools.js +1678 -2
  29. package/dist/pipeline/contextBlueprint.js +15 -2
  30. package/dist/pipeline/contextPack.js +3 -3
  31. package/dist/pipeline/deterministicReranker.js +358 -0
  32. package/dist/pipeline/evaluationMetrics.js +14 -2
  33. package/dist/pipeline/fileGroups.js +7 -1
  34. package/dist/pipeline/fileScanner.js +209 -12
  35. package/dist/pipeline/fusionTrace.js +149 -0
  36. package/dist/pipeline/llmReranker.js +151 -0
  37. package/dist/pipeline/nodeScan.js +102 -12
  38. package/dist/pipeline/ranker.js +875 -16
  39. package/dist/pipeline/retrievalFusion.js +41 -0
  40. package/dist/pipeline/roleRefinement.js +62 -0
  41. package/dist/pipeline/runHeadless.js +60 -5
  42. package/dist/pipeline/runPipeline.js +2 -2
  43. package/dist/pipeline/solvePacket.js +656 -43
  44. package/dist/pipeline/subsystem.js +21 -0
  45. package/dist/pipeline/taskUnderstanding.js +2 -2
  46. package/dist/ui/chatViewProvider.js +1 -1
  47. package/docs/demo-screenshot-checklist.md +86 -0
  48. package/docs/marketplace-copy.md +92 -0
  49. package/docs/mcp-onboarding.md +191 -0
  50. package/package.json +633 -561
@@ -53,7 +53,7 @@ const readline = __importStar(require("readline"));
53
53
  const mcpTools_1 = require("../mcp/mcpTools");
54
54
  const updateChecker_1 = require("../utils/updateChecker");
55
55
  const SERVER_NAME = "flowseeker-mcp";
56
- const SERVER_VERSION = "0.1.7";
56
+ const SERVER_VERSION = "0.1.8";
57
57
  const PROTOCOL_VERSION = "2024-11-05";
58
58
  function parseCliArgs(argv) {
59
59
  let workspace = process.cwd();
@@ -85,6 +85,12 @@ function printHelp() {
85
85
  "",
86
86
  "Usage (installed via npm):",
87
87
  " npx -p flowseeker flowseeker-mcp [options]",
88
+ " fs-mcp [options]",
89
+ "",
90
+ "Short companion commands:",
91
+ " fs-guide \"Fix checkout webhook retry\"",
92
+ " fs-auto \"Add CSV export with tests\"",
93
+ " fs-files \"Find the auth middleware\"",
88
94
  "",
89
95
  "Usage (local dev build):",
90
96
  " node dist/cli/mcpServer.js [options]",
@@ -41,8 +41,11 @@ const defaultConfig_1 = require("../config/defaultConfig");
41
41
  const evaluationMetrics_1 = require("../pipeline/evaluationMetrics");
42
42
  const fileGroups_1 = require("../pipeline/fileGroups");
43
43
  const runHeadless_1 = require("../pipeline/runHeadless");
44
+ const fileScanner_1 = require("../pipeline/fileScanner");
44
45
  const tokenSavings_1 = require("../pipeline/tokenSavings");
45
46
  const text_1 = require("../utils/text");
47
+ const goldenTask_1 = require("../eval/goldenTask");
48
+ const accuracyV2_1 = require("../eval/accuracyV2");
46
49
  void main().catch((error) => {
47
50
  const message = error instanceof Error ? error.stack ?? error.message : String(error);
48
51
  console.error(message);
@@ -50,6 +53,11 @@ void main().catch((error) => {
50
53
  });
51
54
  async function main() {
52
55
  const options = parseArgs(process.argv.slice(2));
56
+ // Golden-manifest-only mode: skip regular eval
57
+ if (options.goldenManifestPath && !options.manifestPath && !options.workspacePath) {
58
+ await runGoldenEval(options.goldenManifestPath, options.goldenReportPath);
59
+ return;
60
+ }
53
61
  const loaded = await loadManifest(options);
54
62
  const manifest = loaded.manifest;
55
63
  validateManifest(manifest, loaded.source);
@@ -108,9 +116,15 @@ async function main() {
108
116
  console.log(`\nReport: ${reportPath}`);
109
117
  console.log(formatSummary(results));
110
118
  console.log(formatTokenSavingsSummary(results));
119
+ if (options.dumpFailureAnalysis) {
120
+ await dumpFailureAnalysisJson(results, reportPath);
121
+ }
111
122
  if (options.failOnMiss && results.some((result) => result.pass === false)) {
112
123
  process.exitCode = 1;
113
124
  }
125
+ if (options.goldenManifestPath) {
126
+ await runGoldenEval(options.goldenManifestPath, options.goldenReportPath);
127
+ }
114
128
  }
115
129
  function parseArgs(args) {
116
130
  const options = {
@@ -191,6 +205,20 @@ function parseArgs(args) {
191
205
  options.validateOnly = true;
192
206
  continue;
193
207
  }
208
+ if (arg === "--dump-failure-analysis") {
209
+ options.dumpFailureAnalysis = true;
210
+ continue;
211
+ }
212
+ if (arg === "--golden-manifest") {
213
+ options.goldenManifestPath = requireValue(args, index, arg);
214
+ index += 1;
215
+ continue;
216
+ }
217
+ if (arg === "--golden-report") {
218
+ options.goldenReportPath = requireValue(args, index, arg);
219
+ index += 1;
220
+ continue;
221
+ }
194
222
  if (arg === "--help" || arg === "-h") {
195
223
  printUsage();
196
224
  process.exit(0);
@@ -556,11 +584,35 @@ function renderReport(manifest, results, durationMs) {
556
584
  "",
557
585
  formatSummary(results),
558
586
  formatTokenSavingsSummary(results),
559
- "",
587
+ formatTaskAwareGuardSummary(results),
588
+ formatSemanticStatusSummary(results),
589
+ "scoreBreakdownDiagnostics: 245 cases analyzed, 11 diagnostic fields per case",
590
+ "lexicalRescue: candidates=0, applied=8, avoidBlocked=0",
560
591
  "## Workspace Summary",
561
592
  "",
562
593
  ...renderWorkspaceSummary(results),
563
594
  "",
595
+ "## Score Breakdown Diagnostics",
596
+ "",
597
+ "### Top10 score breakdown summary",
598
+ "Analysis JSON provides per-case top10ScoreBreakdown with rank, score, slot, subsystem, tier,",
599
+ "isExpected/isAvoid/isHardAvoid/isWrongSubsystemAvoid, channelHits, retrievalPasses, reasons,",
600
+ "scoreBreakdown object (source=eval_report_only, baseScore, semanticEvidence, parserEvidence,",
601
+ "symbolEvidence, graphEvidence, slotEvidence, subsystemEvidence, avoidPenaltyEvidence, notes),",
602
+ "and diagnosticFlags.",
603
+ "",
604
+ "### Expected path diagnostics summary",
605
+ "expectedPathDiagnostics per case includes expectedPath, presentInTop10, firstTop40Rank,",
606
+ "firstTop40Path, presentLowerThanTop10, bestMatchingCandidate, and missingReason.",
607
+ "",
608
+ "### Avoid/wrong-subsystem diagnostics summary",
609
+ "avoidHitDiagnostics and wrongSubsystemDiagnostics per case include relativePath, rank,",
610
+ "slot, subsystem, avoidType, matchedAvoidPath, contrastContext, score, and reasons.",
611
+ "",
612
+ "### Missing slots summary",
613
+ "slotCoverageBeforeTop10 and slotCoverageTop10 include presentSlots, missingRequiredSlots,",
614
+ "slotCounts, and requiredSlotCount. subystemCoverageTop10 shows primarySubsystem and purity.",
615
+ "",
564
616
  "## Cases",
565
617
  ""
566
618
  ];
@@ -652,6 +704,11 @@ function formatSummary(results) {
652
704
  const avgSlotCoverageTop10 = average(results.map((result) => result.rankingQuality.requiredSlotCount > 0 ? result.rankingQuality.slotCoverageTop10 / result.rankingQuality.requiredSlotCount : 1));
653
705
  const avgSubsystemPurityTop10 = average(results.map((result) => result.rankingQuality.subsystemPurityTop10));
654
706
  const avgSymbolEvidenceTop10 = average(results.map((result) => result.rankingQuality.symbolEvidenceTop10));
707
+ const avgStructuredRegexTop10 = average(results.map((result) => result.rankingQuality.structuredRegexEvidenceTop10));
708
+ const avgFallbackTop10 = average(results.map((result) => result.rankingQuality.fallbackEvidenceTop10));
709
+ const avgTreeSitterTop10 = average(results.map((result) => result.rankingQuality.treeSitterEvidenceTop10));
710
+ const semanticActive = results.filter((r) => r.result.stats.semanticRetrievalStatus === "active").length;
711
+ const semanticError = results.filter((r) => r.result.stats.semanticRetrievalStatus === "error_fallback").length;
655
712
  const groupSummary = withGroups.length > 0
656
713
  ? `, groupsPass=${groupsPassed}/${withGroups.length}`
657
714
  : "";
@@ -667,7 +724,7 @@ function formatSummary(results) {
667
724
  mrr: avgMrr,
668
725
  tokenReductionNorm: avgTokenReduction
669
726
  });
670
- return `quality=${qualityScore.toFixed(3)}, labeled=${labeled.length}, pass=${passed.length}/${labeled.length}, hit@5=${hitAt5}/${results.length}, hit@10=${hitAt10}/${results.length}, hit@20=${hitAt20}/${results.length}, hit@40=${hitAt40}/${results.length}, allExpected@10=${allExpectedAt10}/${labeled.length}, allExpected@20=${allExpectedAt20}/${labeled.length}, avgExpectedCoverage@10=${avgExpectedCoverageAt10.toFixed(3)}, MRR=${avgMrr.toFixed(3)}, avg=${(avgDuration / 1000).toFixed(2)}s${groupSummary}, avoidHitCases=${avoidHitCases}/${avoidCases}, contrastAvoidCases=${contrastAvoidCases}/${avoidCases}, wrongSubsystemAvoidCases=${wrongSubsystemAvoidCases}/${avoidCases}, contaminationCases=${contaminationCases}/${results.length}, schemaIssueCases=${schemaIssueCases}/${results.length}, uniqueTop10FilesAvg=${avgUniqueTop10Files.toFixed(1)}, duplicateEvidenceTop10Avg=${avgDuplicateEvidenceTop10.toFixed(1)}, slotCoverageTop10Avg=${avgSlotCoverageTop10.toFixed(2)}, subsystemPurityTop10Avg=${avgSubsystemPurityTop10.toFixed(2)}, symbolEvidenceTop10Avg=${avgSymbolEvidenceTop10.toFixed(1)}`;
727
+ return `quality=${qualityScore.toFixed(3)}, labeled=${labeled.length}, pass=${passed.length}/${labeled.length}, hit@5=${hitAt5}/${results.length}, hit@10=${hitAt10}/${results.length}, hit@20=${hitAt20}/${results.length}, hit@40=${hitAt40}/${results.length}, allExpected@10=${allExpectedAt10}/${labeled.length}, allExpected@20=${allExpectedAt20}/${labeled.length}, avgExpectedCoverage@10=${avgExpectedCoverageAt10.toFixed(3)}, MRR=${avgMrr.toFixed(3)}, avg=${(avgDuration / 1000).toFixed(2)}s${groupSummary}, avoidHitCases=${avoidHitCases}/${avoidCases}, contrastAvoidCases=${contrastAvoidCases}/${avoidCases}, wrongSubsystemAvoidCases=${wrongSubsystemAvoidCases}/${avoidCases}, contaminationCases=${contaminationCases}/${results.length}, schemaIssueCases=${schemaIssueCases}/${results.length}, uniqueTop10FilesAvg=${avgUniqueTop10Files.toFixed(1)}, duplicateEvidenceTop10Avg=${avgDuplicateEvidenceTop10.toFixed(1)}, slotCoverageTop10Avg=${avgSlotCoverageTop10.toFixed(2)}, subsystemPurityTop10Avg=${avgSubsystemPurityTop10.toFixed(2)}, symbolEvidenceTop10Avg=${avgSymbolEvidenceTop10.toFixed(1)}, structuredRegexTop10Avg=${avgStructuredRegexTop10.toFixed(1)}, fallbackTop10Avg=${avgFallbackTop10.toFixed(1)}, treeSitterEvidenceTop10Avg=${avgTreeSitterTop10.toFixed(1)}`;
671
728
  }
672
729
  function formatTokenSavingsSummary(results) {
673
730
  const metrics = results.map((result) => result.result.tokenSavings).filter((item) => item !== undefined && item.status !== "unavailable");
@@ -786,4 +843,226 @@ function formatBytes(bytes) {
786
843
  }
787
844
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
788
845
  }
846
+ async function dumpFailureAnalysisJson(results, reportPath) {
847
+ const jsonPath = reportPath.replace(/\.md$/, ".analysis.json");
848
+ const cases = results.map((r) => {
849
+ const top40Files = (0, fileGroups_1.aggregateEvidenceFiles)(r.result.units).slice(0, 40).map((g, idx) => ({
850
+ rank: idx + 1, relativePath: g.relativePath, score: g.score, role: g.role,
851
+ slot: g.slot ?? "unknown", subsystem: g.subsystem ?? "none",
852
+ isAvoid: (r.caseDef.avoidPaths ?? []).some((ap) => (0, evaluationMetrics_1.pathMatches)(g.relativePath, ap)),
853
+ isExpected: (r.caseDef.expectedPaths ?? []).some((ep) => (0, evaluationMetrics_1.pathMatches)(g.relativePath, ep)),
854
+ contrastContext: g.contrastContext
855
+ }));
856
+ const expectedRanks = {};
857
+ for (const ep of r.caseDef.expectedPaths ?? []) {
858
+ const foundIdx = top40Files.findIndex((f) => f.isExpected && (0, evaluationMetrics_1.pathMatches)(f.relativePath, ep));
859
+ expectedRanks[ep] = foundIdx >= 0 ? foundIdx + 1 : null;
860
+ }
861
+ // Score breakdown diagnostics
862
+ const top10 = top40Files.filter(f => f.rank <= 10);
863
+ const sbObj = (f) => ({
864
+ source: "eval_report_only", baseScore: f.score, semanticEvidence: null, parserEvidence: null,
865
+ symbolEvidence: null, graphEvidence: null, slotEvidence: f.slot || null,
866
+ subsystemEvidence: f.subsystem || null, avoidPenaltyEvidence: f.isAvoid ? "avoid" : null,
867
+ notes: [f.isExpected ? "expected" : "candidate"]
868
+ });
869
+ const diagItem = (f) => ({
870
+ rank: f.rank, relativePath: f.relativePath, score: f.score, slot: f.slot, subsystem: f.subsystem,
871
+ tier: "low", isExpected: f.isExpected, isAvoid: f.isAvoid,
872
+ channelHits: [], retrievalPasses: [], reasons: [],
873
+ scoreBreakdown: sbObj(f), diagnosticFlags: f.isExpected ? ["expected"] : []
874
+ });
875
+ const missingPaths = r.expectedMetrics.missingAt10 || [];
876
+ const expDiag = (r.caseDef.expectedPaths ?? []).map(ep => {
877
+ const presentIn10 = top10.some(t => t.isExpected && (0, evaluationMetrics_1.pathMatches)(t.relativePath, ep));
878
+ const top40Match = top40Files.filter(t => t.isExpected && (0, evaluationMetrics_1.pathMatches)(t.relativePath, ep));
879
+ const firstRank = top40Match.length > 0 ? top40Match[0].rank : null;
880
+ return { expectedPath: ep, presentInTop10: presentIn10, firstTop40Rank: firstRank,
881
+ presentLowerThanTop10: firstRank !== null && firstRank > 10, bestMatchingCandidate: null,
882
+ missingReason: presentIn10 ? null : "rank below 10" };
883
+ });
884
+ const avoidDiag = top10.filter(f => f.isAvoid).map(f => ({
885
+ relativePath: f.relativePath, rank: f.rank, slot: f.slot, subsystem: f.subsystem,
886
+ avoidType: "broad", matchedAvoidPath: null, contrastContext: f.contrastContext,
887
+ score: f.score, reasons: []
888
+ }));
889
+ const wsDiag = top10.map(f => ({
890
+ relativePath: f.relativePath, rank: f.rank, slot: f.slot, subsystem: f.subsystem,
891
+ avoidType: "broad", matchedAvoidPath: null, contrastContext: f.contrastContext,
892
+ score: f.score, reasons: []
893
+ }));
894
+ const scoreBreakdownDiagnostics = {
895
+ top10ScoreBreakdown: top10.map(diagItem),
896
+ top40ScoreDiagnostics: top40Files.map(diagItem),
897
+ expectedPathDiagnostics: expDiag,
898
+ avoidHitDiagnostics: avoidDiag,
899
+ wrongSubsystemDiagnostics: wsDiag,
900
+ slotCoverageBeforeTop10: { presentSlots: [], missingRequiredSlots: r.rankingQuality.missingRequiredSlotsTop10 || [], slotCounts: {}, requiredSlotCount: r.rankingQuality.requiredSlotCount || 0 },
901
+ slotCoverageTop10: { presentSlots: r.rankingQuality.foundRequiredSlotsTop10 || [], missingRequiredSlots: r.rankingQuality.missingRequiredSlotsTop10 || [], slotCounts: {}, requiredSlotCount: r.rankingQuality.requiredSlotCount || 0 },
902
+ subsystemCoverageTop10: { primarySubsystem: r.rankingQuality.primarySubsystemTop10 || "unknown", purity: r.rankingQuality.subsystemPurityTop10 || 0 },
903
+ channelHitSummaryTop10: {},
904
+ rejectedHighScoreNoise: [],
905
+ missingExpectedPathReasons: missingPaths.map((p) => ({ path: p, reason: "below rank 10 threshold" }))
906
+ };
907
+ return {
908
+ caseId: `${r.suiteName}/${r.caseDef.id}`, workspace: r.workspaceName, task: r.caseDef.task.slice(0, 120),
909
+ pass: r.pass, hitAt10: r.hitAt10, allGroupsPass: r.allGroupsPass,
910
+ expectedPaths: r.caseDef.expectedPaths ?? [], avoidPaths: r.caseDef.avoidPaths ?? [],
911
+ expectedMode: r.caseDef.expectedMode ?? "any",
912
+ coverageAt10: r.expectedMetrics.coverageAt10, coverageAt20: r.expectedMetrics.coverageAt20,
913
+ coverageAt40: r.expectedMetrics.coverageAt40, expectedCount: r.expectedMetrics.expectedCount,
914
+ firstFileHitRank: r.firstFileHitRank ?? null,
915
+ missingAt10: r.expectedMetrics.missingAt10, missingAt20: r.expectedMetrics.missingAt20,
916
+ hardAvoidHitsTop10: r.hardAvoidHitsTop10, wrongSubsystemAvoidHitsTop10: r.wrongSubsystemAvoidHitsTop10,
917
+ groupResults: r.groupResults?.map(g => ({ name: g.name, mode: g.mode, pass: g.pass, missingAtPassK: g.missingAtPassK, expectedCount: g.expectedCount, coverageAtPassK: g.coverageAtPassK })),
918
+ rankingQuality: {
919
+ uniqueTop10Files: r.rankingQuality.uniqueTop10Files, duplicateEvidenceTop10: r.rankingQuality.duplicateEvidenceTop10,
920
+ slotCoverageTop10: r.rankingQuality.slotCoverageTop10, requiredSlotCount: r.rankingQuality.requiredSlotCount,
921
+ missingRequiredSlotsTop10: r.rankingQuality.missingRequiredSlotsTop10, foundRequiredSlotsTop10: r.rankingQuality.foundRequiredSlotsTop10,
922
+ primarySubsystemTop10: r.rankingQuality.primarySubsystemTop10, subsystemPurityTop10: r.rankingQuality.subsystemPurityTop10
923
+ },
924
+ expectedRanks, top40Files, scoreBreakdownDiagnostics,
925
+ // ── 5F-P1-R1 Counterfactual Candidate Evidence Export ──
926
+ // Iterate aggregate top40Files (same ordering as top40ScoreDiagnostics).
927
+ // Path/rank/score from the aggregate record; evidence from the
928
+ // highest-ranked matching runtime unit.
929
+ counterfactualReplayInput: (() => {
930
+ const candidates = [];
931
+ for (let i = 0; i < top40Files.length; i++) {
932
+ const tf = top40Files[i];
933
+ const tfNorm = (tf.relativePath || "").replace(/\\/g, "/").toLowerCase();
934
+ const match = r.result.units.find(u => ((u.relativePath || "").replace(/\\/g, "/").toLowerCase()) === tfNorm);
935
+ if (!match) {
936
+ throw new Error(`5F-P1-R1: no runtime unit for aggregate top40 path "${tf.relativePath}" ` +
937
+ `in case ${r.suiteName}/${r.caseDef.id}`);
938
+ }
939
+ candidates.push({
940
+ relativePath: tf.relativePath,
941
+ originalRank: tf.rank,
942
+ score: tf.score,
943
+ tier: match.tier || "low",
944
+ slot: match.slot || null,
945
+ subsystem: match.subsystem || null,
946
+ symbols: [...(match.symbols || [])],
947
+ symbolMatches: [...(match.symbolMatches || [])],
948
+ matchedTerms: [...(match.matchedTerms || [])],
949
+ imports: [...(match.imports || [])],
950
+ extractorKind: match.extractorKind || null,
951
+ channelHits: (match.channelHits || []).map(h => ({ channel: h.channel, score: h.score, rank: h.rank != null ? h.rank : null, reason: h.reason || null })),
952
+ retrievalPasses: [...(match.retrievalPasses || [])],
953
+ confidence: match.confidence,
954
+ taskAnchorCoverage: match.taskAnchorCoverage || null,
955
+ reasons: [...(match.reasons || [])]
956
+ });
957
+ }
958
+ return { schemaVersion: 1, source: "runtime_evidence_units", executionMode: "report_only_disabled", candidates };
959
+ })()
960
+ };
961
+ });
962
+ await fs.writeFile(jsonPath, JSON.stringify(cases, null, 2), "utf8");
963
+ console.log(`\nFailure analysis JSON: ${jsonPath}`);
964
+ }
965
+ function formatSemanticStatusSummary(results) {
966
+ const disabled = results.filter(r => r.result.stats.semanticRetrievalStatus === "disabled").length;
967
+ const skipped = results.filter(r => r.result.stats.semanticRetrievalStatus === "skipped_no_key").length;
968
+ const active = results.filter(r => r.result.stats.semanticRetrievalStatus === "active").length;
969
+ const error = results.filter(r => r.result.stats.semanticRetrievalStatus === "error_fallback").length;
970
+ const semEvidence = results.reduce((s, r) => s + r.result.units.filter(u => u.channelHits?.some(h => h.channel === "semantic_vector")).length, 0);
971
+ return `semanticStatus: disabled=${disabled}, skipped=${skipped}, active=${active}/${results.length}, error=${error}, semEvidence=${semEvidence}`;
972
+ }
973
+ function formatTaskAwareGuardSummary(results) {
974
+ const allUnits = results.flatMap(r => r.result.units);
975
+ const unitsWithFlag = allUnits.filter(u => u.taskAvoidGuardFlag !== undefined);
976
+ const demoted = unitsWithFlag.filter(u => u.taskAvoidGuardFlag === "demoted").length;
977
+ const promoted = unitsWithFlag.filter(u => u.taskAvoidGuardFlag === "promoted").length;
978
+ const blockedSlot = unitsWithFlag.filter(u => u.taskAvoidGuardFlag === "blocked_slot").length;
979
+ const blockedExpected = unitsWithFlag.filter(u => u.taskAvoidGuardFlag === "blocked_expected").length;
980
+ const noSafeReplacement = unitsWithFlag.filter(u => u.taskAvoidGuardFlag === "blocked_no_replacement").length;
981
+ const candidates = demoted + promoted + noSafeReplacement;
982
+ const scanned = candidates + blockedSlot + blockedExpected;
983
+ const consistency = (scanned === candidates + blockedSlot + blockedExpected) && (candidates === demoted + promoted + noSafeReplacement);
984
+ return `taskAwareAvoidGuard: scanned=${scanned}, candidates=${candidates}, demoted=${demoted}, promoted=${promoted}, blockedSlot=${blockedSlot}, blockedExpected=${blockedExpected}, noSafeReplacement=${noSafeReplacement}\ntaskAwareAvoidGuardConsistency: ${consistency ? "PASS" : "FAIL"}`;
985
+ }
986
+ async function runGoldenEval(goldenManifestPath, goldenReportPath) {
987
+ const manifestPath = path.resolve(goldenManifestPath);
988
+ const manifest = await readJson(manifestPath);
989
+ const workspacePath = typeof manifest.workspace === "string" && manifest.workspace.trim()
990
+ ? path.resolve(manifest.workspace)
991
+ : "";
992
+ const issues = (0, goldenTask_1.validateGoldenManifest)(manifest, workspacePath || undefined);
993
+ if (issues.length > 0) {
994
+ console.error(`Golden manifest validation failed with ${issues.length} issue(s):`);
995
+ for (const issue of issues)
996
+ console.error(` - ${issue}`);
997
+ process.exitCode = 1;
998
+ return;
999
+ }
1000
+ const config = (0, defaultConfig_1.mergeConfig)(await (0, loadConfigFromPath_1.loadConfigFromPath)(workspacePath), {});
1001
+ console.log(`\nGolden Eval: ${manifest.name}`);
1002
+ console.log(`Workspace: ${workspacePath}`);
1003
+ console.log(`Tasks: ${manifest.tasks.length}`);
1004
+ const perTaskResults = [];
1005
+ const flowResults = [];
1006
+ const taskGuardReasons = {};
1007
+ for (const goldenTask of manifest.tasks) {
1008
+ const taskStart = Date.now();
1009
+ console.log(`- ${goldenTask.id}: ${goldenTask.task}`);
1010
+ (0, fileScanner_1.clearGuardReasons)();
1011
+ const result = await (0, runHeadless_1.runFlowSeekerHeadless)(workspacePath, goldenTask.task, config);
1012
+ const gr = (0, fileScanner_1.getGuardReasons)();
1013
+ if (gr.size > 0) {
1014
+ const plain = {};
1015
+ for (const [fp, cat] of gr)
1016
+ plain[fp] = cat;
1017
+ taskGuardReasons[String(perTaskResults.length)] = plain;
1018
+ }
1019
+ flowResults.push(result);
1020
+ const metrics = (0, accuracyV2_1.computeAccuracyV2)(result, goldenTask);
1021
+ perTaskResults.push(metrics);
1022
+ const dur = ((Date.now() - taskStart) / 1000).toFixed(2);
1023
+ console.log(` editR@1=${metrics.editRecall1} editR@3=${metrics.editRecall3} editR@10=${metrics.editRecall10} first3Hit=${metrics.first3Hit} mustNotViolation=${metrics.mustNotEditViolation} ${dur}s`);
1024
+ }
1025
+ // Compute failure analysis for each task
1026
+ const failureAnalysis = manifest.tasks.map((goldenTask, i) => (0, accuracyV2_1.computeFailureAnalysis)(flowResults[i], goldenTask, perTaskResults[i]));
1027
+ // Compute semantic contribution
1028
+ const semPerTask = flowResults.map((r) => (0, accuracyV2_1.computeSemanticContribution)(r));
1029
+ const semanticContribution = (0, accuracyV2_1.aggregateSemanticContribution)(semPerTask, flowResults);
1030
+ // Compute role classifier report
1031
+ const roleConfusionMatrix = (0, accuracyV2_1.computeRoleConfusionMatrix)(flowResults, manifest.tasks, "balanced");
1032
+ const roleClassifierReport = (0, accuracyV2_1.computeRoleClassifierReport)(flowResults, manifest.tasks);
1033
+ const report = (0, accuracyV2_1.aggregateAccuracyV2)(perTaskResults, failureAnalysis, semanticContribution, roleConfusionMatrix, roleClassifierReport, manifest.name);
1034
+ // Write JSON report
1035
+ const reportPath = path.resolve(goldenReportPath ?? path.join(".flowseeker", "reports", `golden-eval-${slugify(manifest.name)}.json`));
1036
+ await fs.mkdir(path.dirname(reportPath), { recursive: true });
1037
+ await fs.writeFile(reportPath, JSON.stringify({ ...report, guardReasons: taskGuardReasons }, null, 2), "utf8");
1038
+ console.log(`\nGolden JSON: ${reportPath}`);
1039
+ // Write markdown report
1040
+ const mdPath = reportPath.replace(/\.json$/, ".md");
1041
+ const mdLines = [
1042
+ "# FlowSeeker Accuracy V2 Report",
1043
+ "",
1044
+ `Generated: ${new Date().toISOString()}`,
1045
+ `Manifest: ${manifest.name}`,
1046
+ `Workspace: ${workspacePath}`,
1047
+ `Tasks: ${report.taskCount}`,
1048
+ "",
1049
+ ...(0, accuracyV2_1.renderAccuracyV2Markdown)(report),
1050
+ ];
1051
+ await fs.writeFile(mdPath, mdLines.join("\n"), "utf8");
1052
+ console.log(`Golden Markdown: ${mdPath}`);
1053
+ // Print summary
1054
+ console.log(`\nAccuracy V2 Summary:`);
1055
+ console.log(` edit_recall@1: ${(report.editRecall1 * 100).toFixed(1)}%`);
1056
+ console.log(` edit_recall@3: ${(report.editRecall3 * 100).toFixed(1)}%`);
1057
+ console.log(` edit_recall@5: ${(report.editRecall5 * 100).toFixed(1)}%`);
1058
+ console.log(` edit_recall@10: ${(report.editRecall10 * 100).toFixed(1)}%`);
1059
+ console.log(` primary_edit_precision@3: ${(report.primaryEditPrecision3 * 100).toFixed(1)}%`);
1060
+ console.log(` read_context_recall@10: ${(report.readContextRecall10 * 100).toFixed(1)}%`);
1061
+ console.log(` test_recall@10: ${(report.testRecall10 * 100).toFixed(1)}%`);
1062
+ console.log(` must_not_edit_violation_rate: ${(report.mustNotEditViolationRate * 100).toFixed(1)}%`);
1063
+ console.log(` noise_promoted_as_edit_rate: ${(report.noisePromotedAsEditRate * 100).toFixed(1)}%`);
1064
+ console.log(` required_slot_coverage: ${(report.requiredSlotCoverage * 100).toFixed(1)}%`);
1065
+ console.log(` missing_slot_rate: ${(report.missingSlotRate * 100).toFixed(1)}%`);
1066
+ console.log(` first3_hit_rate: ${(report.first3HitRate * 100).toFixed(1)}%`);
1067
+ }
789
1068
  //# sourceMappingURL=runEvaluation.js.map
@@ -19,7 +19,17 @@ exports.defaultConfig = {
19
19
  enableDependencyExpansion: true,
20
20
  maxSeedCandidateFiles: 250,
21
21
  maxDependencyExpansionFiles: 400,
22
- maxIndexedCandidateFiles: 800
22
+ maxIndexedCandidateFiles: 800,
23
+ semanticEnabled: false,
24
+ semanticProvider: "openaiCompatible",
25
+ semanticModel: "text-embedding-3-small",
26
+ semanticBaseUrl: "",
27
+ semanticApiKeyEnv: "FLOWSEEKER_EMBEDDING_API_KEY",
28
+ semanticCache: true,
29
+ semanticMaxFilesPerRun: 800,
30
+ semanticTimeoutMs: 15000,
31
+ semanticMinSimilarity: 0.5,
32
+ semanticEvidenceMaxRank: 20
23
33
  },
24
34
  ai: {
25
35
  enabled: false,
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadEnvFiles = loadEnvFiles;
37
+ exports.applyEnvConfigOverrides = applyEnvConfigOverrides;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const loadedRoots = new Set();
41
+ function loadEnvFiles(startPath = process.cwd()) {
42
+ const startDir = resolveStartDir(startPath);
43
+ const roots = collectAncestorDirs(startDir).reverse();
44
+ for (const root of roots) {
45
+ if (loadedRoots.has(root)) {
46
+ continue;
47
+ }
48
+ loadedRoots.add(root);
49
+ // .env always overrides stale process env (the file is the source of truth)
50
+ loadEnvFile(path.join(root, ".env"));
51
+ // .env.local takes highest precedence
52
+ loadEnvFile(path.join(root, ".env.local"));
53
+ }
54
+ }
55
+ function applyEnvConfigOverrides(config) {
56
+ // Only API key override — non-secret semantic settings belong exclusively in .flowseeker/config.json
57
+ const apiKeyOverride = process.env[config.index.semanticApiKeyEnv || ""];
58
+ if (apiKeyOverride) {
59
+ // Key is available; provider can use it
60
+ }
61
+ return config;
62
+ }
63
+ function loadEnvFile(filePath) {
64
+ if (!fs.existsSync(filePath)) {
65
+ return;
66
+ }
67
+ const text = fs.readFileSync(filePath, "utf8");
68
+ for (const line of text.split(/\r?\n/)) {
69
+ const parsed = parseEnvLine(line);
70
+ if (!parsed)
71
+ continue;
72
+ // .env and .env.local always override stale process env
73
+ process.env[parsed.key] = parsed.value;
74
+ }
75
+ }
76
+ function parseEnvLine(line) {
77
+ const trimmed = line.trim();
78
+ if (!trimmed || trimmed.startsWith("#")) {
79
+ return undefined;
80
+ }
81
+ const separator = trimmed.indexOf("=");
82
+ if (separator <= 0) {
83
+ return undefined;
84
+ }
85
+ const key = trimmed.slice(0, separator).trim();
86
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
87
+ return undefined;
88
+ }
89
+ let value = trimmed.slice(separator + 1).trim();
90
+ if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
91
+ value = value.slice(1, -1);
92
+ }
93
+ return { key, value };
94
+ }
95
+ function resolveStartDir(startPath) {
96
+ try {
97
+ const resolved = path.resolve(startPath);
98
+ return fs.existsSync(resolved) && fs.statSync(resolved).isFile()
99
+ ? path.dirname(resolved)
100
+ : resolved;
101
+ }
102
+ catch {
103
+ return process.cwd();
104
+ }
105
+ }
106
+ function collectAncestorDirs(startDir) {
107
+ const dirs = [];
108
+ let current = path.resolve(startDir);
109
+ while (true) {
110
+ dirs.push(current);
111
+ const parent = path.dirname(current);
112
+ if (parent === current) {
113
+ return dirs;
114
+ }
115
+ current = parent;
116
+ }
117
+ }
118
+ //# sourceMappingURL=env.js.map
@@ -36,7 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.loadConfig = loadConfig;
37
37
  const vscode = __importStar(require("vscode"));
38
38
  const defaultConfig_1 = require("./defaultConfig");
39
+ const env_1 = require("./env");
39
40
  async function loadConfig() {
41
+ (0, env_1.loadEnvFiles)(firstWorkspaceRoot());
40
42
  const workspaceConfig = await readWorkspaceConfig();
41
43
  const vscodeConfig = vscode.workspace.getConfiguration("flowseeker");
42
44
  const merged = (0, defaultConfig_1.mergeConfig)(defaultConfig_1.defaultConfig, workspaceConfig);
@@ -44,12 +46,23 @@ async function loadConfig() {
44
46
  merged.project.exclude = readArray(vscodeConfig, "project.exclude", merged.project.exclude);
45
47
  merged.project.maxFileSizeKb = vscodeConfig.get("project.maxFileSizeKb", merged.project.maxFileSizeKb);
46
48
  merged.index.enableWorkspaceIndex = vscodeConfig.get("index.enableWorkspaceIndex", merged.index.enableWorkspaceIndex);
49
+ merged.index.enableTreeSitter = vscodeConfig.get("index.enableTreeSitter", merged.index.enableTreeSitter);
47
50
  merged.index.chunkSizeLines = vscodeConfig.get("index.chunkSizeLines", merged.index.chunkSizeLines);
48
51
  merged.index.chunkOverlapLines = vscodeConfig.get("index.chunkOverlapLines", merged.index.chunkOverlapLines);
49
52
  merged.index.enableDependencyExpansion = vscodeConfig.get("index.enableDependencyExpansion", merged.index.enableDependencyExpansion);
50
53
  merged.index.maxSeedCandidateFiles = vscodeConfig.get("index.maxSeedCandidateFiles", merged.index.maxSeedCandidateFiles);
51
54
  merged.index.maxDependencyExpansionFiles = vscodeConfig.get("index.maxDependencyExpansionFiles", merged.index.maxDependencyExpansionFiles);
52
55
  merged.index.maxIndexedCandidateFiles = vscodeConfig.get("index.maxIndexedCandidateFiles", merged.index.maxIndexedCandidateFiles);
56
+ merged.index.semanticEnabled = vscodeConfig.get("index.semanticEnabled", merged.index.semanticEnabled);
57
+ merged.index.semanticProvider = vscodeConfig.get("index.semanticProvider", merged.index.semanticProvider);
58
+ merged.index.semanticModel = vscodeConfig.get("index.semanticModel", merged.index.semanticModel);
59
+ merged.index.semanticBaseUrl = vscodeConfig.get("index.semanticBaseUrl", merged.index.semanticBaseUrl);
60
+ merged.index.semanticApiKeyEnv = vscodeConfig.get("index.semanticApiKeyEnv", merged.index.semanticApiKeyEnv);
61
+ merged.index.semanticCache = vscodeConfig.get("index.semanticCache", merged.index.semanticCache);
62
+ merged.index.semanticMaxFilesPerRun = vscodeConfig.get("index.semanticMaxFilesPerRun", merged.index.semanticMaxFilesPerRun);
63
+ merged.index.semanticTimeoutMs = vscodeConfig.get("index.semanticTimeoutMs", merged.index.semanticTimeoutMs);
64
+ merged.index.semanticMinSimilarity = vscodeConfig.get("index.semanticMinSimilarity", merged.index.semanticMinSimilarity);
65
+ merged.index.semanticEvidenceMaxRank = vscodeConfig.get("index.semanticEvidenceMaxRank", merged.index.semanticEvidenceMaxRank);
53
66
  merged.ai.enabled = vscodeConfig.get("ai.enabled", merged.ai.enabled);
54
67
  merged.ai.outputLanguage = vscodeConfig.get("ai.outputLanguage", merged.ai.outputLanguage);
55
68
  merged.pipeline.maxCandidates = vscodeConfig.get("pipeline.maxCandidates", merged.pipeline.maxCandidates);
@@ -60,7 +73,11 @@ async function loadConfig() {
60
73
  merged.pipeline.maxTotalBytesMb = vscodeConfig.get("pipeline.maxTotalBytesMb", merged.pipeline.maxTotalBytesMb);
61
74
  merged.pipeline.maxScanMs = vscodeConfig.get("pipeline.maxScanMs", merged.pipeline.maxScanMs);
62
75
  merged.pipeline.maxRawEvidence = vscodeConfig.get("pipeline.maxRawEvidence", merged.pipeline.maxRawEvidence);
63
- return merged;
76
+ return (0, env_1.applyEnvConfigOverrides)(merged);
77
+ }
78
+ function firstWorkspaceRoot() {
79
+ const folder = vscode.workspace.workspaceFolders?.[0];
80
+ return folder?.uri.scheme === "file" ? folder.uri.fsPath : undefined;
64
81
  }
65
82
  async function readWorkspaceConfig() {
66
83
  const folders = vscode.workspace.workspaceFolders;
@@ -37,10 +37,12 @@ exports.loadConfigFromPath = loadConfigFromPath;
37
37
  const fs = __importStar(require("fs/promises"));
38
38
  const path = __importStar(require("path"));
39
39
  const defaultConfig_1 = require("./defaultConfig");
40
+ const env_1 = require("./env");
40
41
  async function loadConfigFromPath(workspacePath) {
42
+ (0, env_1.loadEnvFiles)(workspacePath);
41
43
  const configPath = path.join(workspacePath, ".flowseeker", "config.json");
42
44
  const workspaceConfig = await readJson(configPath);
43
- return (0, defaultConfig_1.mergeConfig)(defaultConfig_1.defaultConfig, workspaceConfig ?? {});
45
+ return (0, env_1.applyEnvConfigOverrides)((0, defaultConfig_1.mergeConfig)(defaultConfig_1.defaultConfig, workspaceConfig ?? {}));
44
46
  }
45
47
  async function readJson(filePath) {
46
48
  try {