codeloop-mcp-server 0.1.14 → 0.1.15

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 (77) hide show
  1. package/dist/auth/local_mode.d.ts +32 -0
  2. package/dist/auth/local_mode.d.ts.map +1 -0
  3. package/dist/auth/local_mode.js +56 -0
  4. package/dist/auth/local_mode.js.map +1 -0
  5. package/dist/auth/usage_tracker.d.ts +11 -1
  6. package/dist/auth/usage_tracker.d.ts.map +1 -1
  7. package/dist/auth/usage_tracker.js +51 -4
  8. package/dist/auth/usage_tracker.js.map +1 -1
  9. package/dist/environment/presets.d.ts +46 -0
  10. package/dist/environment/presets.d.ts.map +1 -0
  11. package/dist/environment/presets.js +109 -0
  12. package/dist/environment/presets.js.map +1 -0
  13. package/dist/evidence/baseline_governance.d.ts +62 -0
  14. package/dist/evidence/baseline_governance.d.ts.map +1 -0
  15. package/dist/evidence/baseline_governance.js +113 -0
  16. package/dist/evidence/baseline_governance.js.map +1 -0
  17. package/dist/evidence/run_lineage.d.ts +66 -0
  18. package/dist/evidence/run_lineage.d.ts.map +1 -0
  19. package/dist/evidence/run_lineage.js +138 -0
  20. package/dist/evidence/run_lineage.js.map +1 -0
  21. package/dist/evidence/screenshot_diff.d.ts +6 -2
  22. package/dist/evidence/screenshot_diff.d.ts.map +1 -1
  23. package/dist/evidence/screenshot_diff.js +40 -3
  24. package/dist/evidence/screenshot_diff.js.map +1 -1
  25. package/dist/evidence/visual_attribution.d.ts +32 -0
  26. package/dist/evidence/visual_attribution.d.ts.map +1 -0
  27. package/dist/evidence/visual_attribution.js +88 -0
  28. package/dist/evidence/visual_attribution.js.map +1 -0
  29. package/dist/index.js +150 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/prompt_manager/index.d.ts +27 -0
  32. package/dist/prompt_manager/index.d.ts.map +1 -0
  33. package/dist/prompt_manager/index.js +28 -0
  34. package/dist/prompt_manager/index.js.map +1 -0
  35. package/dist/prompt_manager/state_machine.d.ts +55 -0
  36. package/dist/prompt_manager/state_machine.d.ts.map +1 -0
  37. package/dist/prompt_manager/state_machine.js +60 -0
  38. package/dist/prompt_manager/state_machine.js.map +1 -0
  39. package/dist/prompt_manager/templates.d.ts +43 -0
  40. package/dist/prompt_manager/templates.d.ts.map +1 -0
  41. package/dist/prompt_manager/templates.js +177 -0
  42. package/dist/prompt_manager/templates.js.map +1 -0
  43. package/dist/runners/base.d.ts +1 -1
  44. package/dist/runners/base.d.ts.map +1 -1
  45. package/dist/runners/base.js +2 -2
  46. package/dist/runners/base.js.map +1 -1
  47. package/dist/runners/figma_spec_generator.d.ts +54 -0
  48. package/dist/runners/figma_spec_generator.d.ts.map +1 -0
  49. package/dist/runners/figma_spec_generator.js +227 -0
  50. package/dist/runners/figma_spec_generator.js.map +1 -0
  51. package/dist/runners/playwright.d.ts +9 -1
  52. package/dist/runners/playwright.d.ts.map +1 -1
  53. package/dist/runners/playwright.js +18 -3
  54. package/dist/runners/playwright.js.map +1 -1
  55. package/dist/runners/plugin_sdk.d.ts +25 -0
  56. package/dist/runners/plugin_sdk.d.ts.map +1 -0
  57. package/dist/runners/plugin_sdk.js +86 -0
  58. package/dist/runners/plugin_sdk.js.map +1 -0
  59. package/dist/state/dependency_graph.d.ts +23 -0
  60. package/dist/state/dependency_graph.d.ts.map +1 -0
  61. package/dist/state/dependency_graph.js +127 -0
  62. package/dist/state/dependency_graph.js.map +1 -0
  63. package/dist/state/section_registry.d.ts.map +1 -1
  64. package/dist/state/section_registry.js +11 -5
  65. package/dist/state/section_registry.js.map +1 -1
  66. package/dist/tools/design_compare.js +2 -2
  67. package/dist/tools/design_compare.js.map +1 -1
  68. package/dist/tools/update_baseline.d.ts +3 -0
  69. package/dist/tools/update_baseline.d.ts.map +1 -1
  70. package/dist/tools/update_baseline.js +18 -2
  71. package/dist/tools/update_baseline.js.map +1 -1
  72. package/dist/tools/verify.d.ts.map +1 -1
  73. package/dist/tools/verify.js +66 -4
  74. package/dist/tools/verify.js.map +1 -1
  75. package/dist/tools/visual_review.js +1 -1
  76. package/dist/tools/visual_review.js.map +1 -1
  77. package/package.json +1 -1
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Run lineage — enriches every run with ancestry and Git context so that
3
+ * artifacts can be traced back to the commit, branch, and section that
4
+ * produced them.
5
+ *
6
+ * Also maintains `run_index.json` at the artifacts root for fast lookups
7
+ * without scanning every `meta.json` file.
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "fs";
10
+ import { join } from "path";
11
+ import { execSync } from "child_process";
12
+ const INDEX_FILE = "run_index.json";
13
+ function gitField(cwd, cmd) {
14
+ try {
15
+ return execSync(cmd, { cwd, encoding: "utf-8", timeout: 3000 }).trim() || undefined;
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ }
21
+ export function captureGitContext(cwd) {
22
+ return {
23
+ commit_sha: gitField(cwd, "git rev-parse --short HEAD"),
24
+ branch: gitField(cwd, "git rev-parse --abbrev-ref HEAD"),
25
+ };
26
+ }
27
+ export function loadRunIndex(artifactsBase) {
28
+ const p = join(artifactsBase, INDEX_FILE);
29
+ if (!existsSync(p)) {
30
+ return { version: 1, entries: [], updated_at: new Date().toISOString() };
31
+ }
32
+ try {
33
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
34
+ if (!Array.isArray(raw.entries)) {
35
+ return { version: 1, entries: [], updated_at: new Date().toISOString() };
36
+ }
37
+ return raw;
38
+ }
39
+ catch {
40
+ return { version: 1, entries: [], updated_at: new Date().toISOString() };
41
+ }
42
+ }
43
+ export function saveRunIndex(artifactsBase, index) {
44
+ mkdirSync(artifactsBase, { recursive: true });
45
+ index.updated_at = new Date().toISOString();
46
+ writeFileSync(join(artifactsBase, INDEX_FILE), JSON.stringify(index, null, 2));
47
+ }
48
+ export function appendToIndex(artifactsBase, entry) {
49
+ const index = loadRunIndex(artifactsBase);
50
+ const existing = index.entries.findIndex((e) => e.run_id === entry.run_id);
51
+ if (existing >= 0) {
52
+ index.entries[existing] = { ...index.entries[existing], ...entry };
53
+ }
54
+ else {
55
+ index.entries.push(entry);
56
+ }
57
+ saveRunIndex(artifactsBase, index);
58
+ return index;
59
+ }
60
+ export function buildLineage(artifactsBase, opts) {
61
+ const index = loadRunIndex(artifactsBase);
62
+ const git = captureGitContext(opts.cwd ?? process.cwd());
63
+ return {
64
+ parent_run_id: opts.parent_run_id,
65
+ section_id: opts.section_id,
66
+ commit_sha: git.commit_sha,
67
+ branch: git.branch,
68
+ trigger: opts.trigger,
69
+ run_index: index.entries.length + 1,
70
+ };
71
+ }
72
+ /**
73
+ * Rebuild `run_index.json` by scanning every `meta.json` in the artifacts base.
74
+ * Useful for migrating existing runs that pre-date the lineage feature.
75
+ */
76
+ export function rebuildIndex(artifactsBase) {
77
+ const entries = [];
78
+ if (!existsSync(artifactsBase)) {
79
+ return { version: 1, entries, updated_at: new Date().toISOString() };
80
+ }
81
+ const dirs = readdirSync(artifactsBase, { withFileTypes: true })
82
+ .filter((d) => d.isDirectory() && d.name.startsWith("run_"))
83
+ .map((d) => d.name)
84
+ .sort();
85
+ for (const dir of dirs) {
86
+ const metaPath = join(artifactsBase, dir, "meta.json");
87
+ if (!existsSync(metaPath))
88
+ continue;
89
+ try {
90
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
91
+ entries.push({
92
+ run_id: meta.run_id ?? dir,
93
+ started_at: meta.started_at ?? "",
94
+ finished_at: meta.finished_at,
95
+ trigger: meta.lineage?.trigger ?? "manual",
96
+ section_id: meta.lineage?.section_id ?? meta.section_id,
97
+ parent_run_id: meta.lineage?.parent_run_id,
98
+ commit_sha: meta.lineage?.commit_sha,
99
+ branch: meta.lineage?.branch,
100
+ confidence: meta.confidence,
101
+ pass_count: meta.test_summary?.passed ?? meta.deterministic_results?.tests?.passed,
102
+ fail_count: meta.test_summary?.failed ?? meta.deterministic_results?.tests?.failed,
103
+ platform: meta.platform,
104
+ });
105
+ }
106
+ catch { /* skip corrupt metas */ }
107
+ }
108
+ const index = { version: 1, entries, updated_at: new Date().toISOString() };
109
+ saveRunIndex(artifactsBase, index);
110
+ return index;
111
+ }
112
+ export function queryRunHistory(artifactsBase, query = {}) {
113
+ const index = loadRunIndex(artifactsBase);
114
+ let filtered = index.entries;
115
+ if (query.section_id) {
116
+ filtered = filtered.filter((e) => e.section_id === query.section_id);
117
+ }
118
+ if (query.branch) {
119
+ filtered = filtered.filter((e) => e.branch === query.branch);
120
+ }
121
+ if (query.since) {
122
+ const since = new Date(query.since).getTime();
123
+ filtered = filtered.filter((e) => {
124
+ const t = new Date(e.started_at).getTime();
125
+ return !isNaN(t) && t >= since;
126
+ });
127
+ }
128
+ filtered.sort((a, b) => {
129
+ const ta = new Date(a.started_at).getTime();
130
+ const tb = new Date(b.started_at).getTime();
131
+ return tb - ta;
132
+ });
133
+ const limit = query.limit ?? 50;
134
+ const total = filtered.length;
135
+ const entries = filtered.slice(0, limit);
136
+ return { total, returned: entries.length, entries };
137
+ }
138
+ //# sourceMappingURL=run_lineage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run_lineage.js","sourceRoot":"","sources":["../../src/evidence/run_lineage.ts"],"names":[],"mappings":"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"}
@@ -15,13 +15,17 @@ export interface ScreenshotDiffSummary {
15
15
  /**
16
16
  * Compare two PNG images pixel-by-pixel.
17
17
  * Size mismatches are handled by padding both images to the larger width and height (white fill), then comparing.
18
+ *
19
+ * When `cwd` is provided, region masks defined in `.codeloop/baseline_masks.json`
20
+ * for the matching screen are applied to BOTH buffers before pixelmatch.
21
+ * This neutralises dynamic UI regions (timestamps, avatars, ads).
18
22
  */
19
- export declare function compareScreenshot(screenshotPath: string, baselinePath: string, diffOutputPath: string, threshold?: number): DiffResult;
23
+ export declare function compareScreenshot(screenshotPath: string, baselinePath: string, diffOutputPath: string, threshold?: number, cwd?: string): DiffResult;
20
24
  /**
21
25
  * Compare every PNG in `screenshotsDir` to the same filename under `baselinesDir`.
22
26
  * Writes diff images to `diffsDir` using the same base filename.
23
27
  */
24
- export declare function diffAllScreenshots(screenshotsDir: string, baselinesDir: string, diffsDir: string, threshold?: number): ScreenshotDiffSummary;
28
+ export declare function diffAllScreenshots(screenshotsDir: string, baselinesDir: string, diffsDir: string, threshold?: number, cwd?: string): ScreenshotDiffSummary;
25
29
  /**
26
30
  * Copy current screenshots into `baselinesDir` (creates the directory if needed).
27
31
  * If `screens` is set, only those screen names (without `.png`) are updated; otherwise all PNGs are copied.
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot_diff.d.ts","sourceRoot":"","sources":["../../src/evidence/screenshot_diff.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAqDD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,SAAS,GAAE,MAA0B,GACpC,UAAU,CAmCZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAA0B,GACpC,qBAAqB,CA+CvB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,EAAE,CAwBV"}
1
+ {"version":3,"file":"screenshot_diff.d.ts","sourceRoot":"","sources":["../../src/evidence/screenshot_diff.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAqDD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,SAAS,GAAE,MAA0B,EACrC,GAAG,CAAC,EAAE,MAAM,GACX,UAAU,CAoEZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAA0B,EACrC,GAAG,CAAC,EAAE,MAAM,GACX,qBAAqB,CA+CvB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,EAAE,CAwBV"}
@@ -2,6 +2,7 @@ import pixelmatch from "pixelmatch";
2
2
  import { PNG } from "pngjs";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, } from "fs";
4
4
  import { join, basename, dirname } from "path";
5
+ import { loadMasks, getMaskForScreen, applyMaskToBuffer, } from "./baseline_governance.js";
5
6
  const DEFAULT_THRESHOLD = 0.1;
6
7
  function screenNameFromPath(filePath) {
7
8
  const base = basename(filePath);
@@ -42,8 +43,12 @@ function prepareCompareBuffers(a, b) {
42
43
  /**
43
44
  * Compare two PNG images pixel-by-pixel.
44
45
  * Size mismatches are handled by padding both images to the larger width and height (white fill), then comparing.
46
+ *
47
+ * When `cwd` is provided, region masks defined in `.codeloop/baseline_masks.json`
48
+ * for the matching screen are applied to BOTH buffers before pixelmatch.
49
+ * This neutralises dynamic UI regions (timestamps, avatars, ads).
45
50
  */
46
- export function compareScreenshot(screenshotPath, baselinePath, diffOutputPath, threshold = DEFAULT_THRESHOLD) {
51
+ export function compareScreenshot(screenshotPath, baselinePath, diffOutputPath, threshold = DEFAULT_THRESHOLD, cwd) {
47
52
  const screen = screenNameFromPath(screenshotPath);
48
53
  if (!existsSync(screenshotPath) || !existsSync(baselinePath)) {
49
54
  throw new Error(`compareScreenshot: missing file(s) — screenshot: ${screenshotPath}, baseline: ${baselinePath}`);
@@ -53,6 +58,38 @@ export function compareScreenshot(screenshotPath, baselinePath, diffOutputPath,
53
58
  const { width, height, buf1, buf2 } = prepareCompareBuffers(img1, img2);
54
59
  const totalPixels = width * height;
55
60
  mkdirSync(dirname(diffOutputPath), { recursive: true });
61
+ // Apply region masks (if any) to both buffers before pixel matching.
62
+ // Both buffers MUST be masked identically so pixelmatch sees zeros on
63
+ // both sides of any dynamic region and never counts them as diffs.
64
+ if (cwd) {
65
+ const masks = loadMasks(cwd);
66
+ const regions = getMaskForScreen(masks, screen);
67
+ if (regions.length > 0) {
68
+ // Buffers from prepareCompareBuffers may share memory with the source
69
+ // PNG.data when no padding was needed; clone first to avoid mutating
70
+ // the original images for downstream consumers.
71
+ const safeBuf1 = Buffer.from(buf1);
72
+ const safeBuf2 = Buffer.from(buf2);
73
+ applyMaskToBuffer(safeBuf1, width, height, regions);
74
+ applyMaskToBuffer(safeBuf2, width, height, regions);
75
+ const diff = Buffer.alloc(totalPixels * 4);
76
+ const diffPixels = pixelmatch(safeBuf1, safeBuf2, diff, width, height, {
77
+ threshold,
78
+ });
79
+ const outPng = new PNG({ width, height });
80
+ diff.copy(outPng.data);
81
+ writeFileSync(diffOutputPath, PNG.sync.write(outPng));
82
+ const diffScore = totalPixels === 0 ? 0 : diffPixels / totalPixels;
83
+ return {
84
+ screen,
85
+ diffScore,
86
+ diffPixels,
87
+ totalPixels,
88
+ diffImagePath: diffOutputPath,
89
+ baselineExists: true,
90
+ };
91
+ }
92
+ }
56
93
  const diff = Buffer.alloc(totalPixels * 4);
57
94
  const diffPixels = pixelmatch(buf1, buf2, diff, width, height, {
58
95
  threshold,
@@ -74,7 +111,7 @@ export function compareScreenshot(screenshotPath, baselinePath, diffOutputPath,
74
111
  * Compare every PNG in `screenshotsDir` to the same filename under `baselinesDir`.
75
112
  * Writes diff images to `diffsDir` using the same base filename.
76
113
  */
77
- export function diffAllScreenshots(screenshotsDir, baselinesDir, diffsDir, threshold = DEFAULT_THRESHOLD) {
114
+ export function diffAllScreenshots(screenshotsDir, baselinesDir, diffsDir, threshold = DEFAULT_THRESHOLD, cwd) {
78
115
  const pngFiles = listPngFiles(screenshotsDir);
79
116
  const results = [];
80
117
  let allHaveBaselines = true;
@@ -104,7 +141,7 @@ export function diffAllScreenshots(screenshotsDir, baselinesDir, diffsDir, thres
104
141
  continue;
105
142
  }
106
143
  const diffOutputPath = join(diffsDir, file);
107
- const result = compareScreenshot(screenshotPath, baselinePath, diffOutputPath, threshold);
144
+ const result = compareScreenshot(screenshotPath, baselinePath, diffOutputPath, threshold, cwd);
108
145
  results.push(result);
109
146
  }
110
147
  const screensAboveThreshold = results
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot_diff.js","sourceRoot":"","sources":["../../src/evidence/screenshot_diff.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"screenshot_diff.js","sourceRoot":"","sources":["../../src/evidence/screenshot_diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Visual change attribution — maps visual diffs to the commit, branch,
3
+ * and section that introduced them. Uses the run lineage index to
4
+ * correlate diff scores across consecutive runs.
5
+ */
6
+ export interface VisualAttribution {
7
+ screen: string;
8
+ diff_score: number;
9
+ introduced_by: {
10
+ run_id: string;
11
+ commit_sha?: string;
12
+ branch?: string;
13
+ section_id?: string;
14
+ started_at: string;
15
+ };
16
+ previous_run_id?: string;
17
+ previous_diff_score?: number;
18
+ }
19
+ export interface AttributionReport {
20
+ attributions: VisualAttribution[];
21
+ total_screens: number;
22
+ screens_with_changes: number;
23
+ }
24
+ /**
25
+ * Build a visual attribution report by comparing diff scores across
26
+ * consecutive runs in the index.
27
+ */
28
+ export declare function buildAttribution(artifactsBase: string, opts?: {
29
+ section_id?: string;
30
+ limit?: number;
31
+ }): AttributionReport;
32
+ //# sourceMappingURL=visual_attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual_attribution.d.ts","sourceRoot":"","sources":["../../src/evidence/visual_attribution.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE;QACb,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAiCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,GAAE;IACJ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CACX,GACL,iBAAiB,CAuDnB"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Visual change attribution — maps visual diffs to the commit, branch,
3
+ * and section that introduced them. Uses the run lineage index to
4
+ * correlate diff scores across consecutive runs.
5
+ */
6
+ import { loadRunIndex } from "./run_lineage.js";
7
+ import { existsSync, readFileSync, readdirSync } from "fs";
8
+ import { join, basename } from "path";
9
+ function loadDiffScores(runDir) {
10
+ const scores = new Map();
11
+ const diffsDir = join(runDir, "diffs");
12
+ if (!existsSync(diffsDir))
13
+ return scores;
14
+ const summaryPath = join(runDir, "design_compare_summary.json");
15
+ if (existsSync(summaryPath)) {
16
+ try {
17
+ const summary = JSON.parse(readFileSync(summaryPath, "utf-8"));
18
+ if (Array.isArray(summary.per_screen)) {
19
+ for (const s of summary.per_screen) {
20
+ if (s.screen && typeof s.score === "number") {
21
+ scores.set(s.screen, 1 - s.score);
22
+ }
23
+ }
24
+ }
25
+ return scores;
26
+ }
27
+ catch { /* fall through to PNG scanning */ }
28
+ }
29
+ try {
30
+ const files = readdirSync(diffsDir).filter((f) => f.endsWith(".png"));
31
+ for (const f of files) {
32
+ const screen = basename(f, ".png");
33
+ scores.set(screen, -1);
34
+ }
35
+ }
36
+ catch { /* empty */ }
37
+ return scores;
38
+ }
39
+ /**
40
+ * Build a visual attribution report by comparing diff scores across
41
+ * consecutive runs in the index.
42
+ */
43
+ export function buildAttribution(artifactsBase, opts = {}) {
44
+ const index = loadRunIndex(artifactsBase);
45
+ let entries = [...index.entries].sort((a, b) => new Date(a.started_at).getTime() - new Date(b.started_at).getTime());
46
+ if (opts.section_id) {
47
+ entries = entries.filter((e) => e.section_id === opts.section_id);
48
+ }
49
+ const attributions = [];
50
+ const allScreens = new Set();
51
+ for (let i = 0; i < entries.length; i++) {
52
+ const entry = entries[i];
53
+ const runDir = join(artifactsBase, entry.run_id);
54
+ const current = loadDiffScores(runDir);
55
+ let previous = new Map();
56
+ if (i > 0) {
57
+ const prevDir = join(artifactsBase, entries[i - 1].run_id);
58
+ previous = loadDiffScores(prevDir);
59
+ }
60
+ for (const [screen, score] of current.entries()) {
61
+ allScreens.add(screen);
62
+ const prevScore = previous.get(screen);
63
+ if (score > 0 && (prevScore === undefined || prevScore === 0 || score > prevScore * 1.1)) {
64
+ attributions.push({
65
+ screen,
66
+ diff_score: score,
67
+ introduced_by: {
68
+ run_id: entry.run_id,
69
+ commit_sha: entry.commit_sha,
70
+ branch: entry.branch,
71
+ section_id: entry.section_id,
72
+ started_at: entry.started_at,
73
+ },
74
+ previous_run_id: i > 0 ? entries[i - 1].run_id : undefined,
75
+ previous_diff_score: prevScore,
76
+ });
77
+ }
78
+ }
79
+ }
80
+ const limit = opts.limit ?? 50;
81
+ attributions.sort((a, b) => b.diff_score - a.diff_score);
82
+ return {
83
+ attributions: attributions.slice(0, limit),
84
+ total_screens: allScreens.size,
85
+ screens_with_changes: new Set(attributions.map((a) => a.screen)).size,
86
+ };
87
+ }
88
+ //# sourceMappingURL=visual_attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual_attribution.js","sourceRoot":"","sources":["../../src/evidence/visual_attribution.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { join, basename } from "path";
7
7
  import { loadConfig } from "./config.js";
8
8
  import { validateApiKey, isActivationRequired } from "./auth/api_key.js";
9
9
  import { trackUsage } from "./auth/usage_tracker.js";
10
+ import { isLocalMode } from "./auth/local_mode.js";
10
11
  import { discoverProjectDir } from "./project-discovery.js";
11
12
  function readImageAsBase64(path) {
12
13
  if (!existsSync(path))
@@ -59,6 +60,12 @@ const server = new McpServer({
59
60
  ].join(" "),
60
61
  });
61
62
  async function withAuth(fn) {
63
+ // Local / self-hosted mode (CODELOOP_MODE=local): skip API-key validation
64
+ // entirely. All cloud-side checks are bypassed; usage events are queued
65
+ // to disk via trackUsage's local-mode branch.
66
+ if (isLocalMode()) {
67
+ return fn();
68
+ }
62
69
  const result = await validateApiKey(apiKey);
63
70
  if (isActivationRequired(result)) {
64
71
  return result;
@@ -490,6 +497,121 @@ Returns: list of affected sections, new states, and recommended next actions.`,
490
497
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
491
498
  };
492
499
  });
500
+ server.tool("codeloop_visual_attribution", TOOL_BOOTSTRAP + `Identify which commit, branch, and section introduced each visual diff. Use this tool when:
501
+ - You want to understand why a screenshot diff appeared
502
+ - You need to trace a visual regression to its source commit
503
+ - You want a report of all visual changes across runs
504
+ Returns: list of visual changes attributed to specific commits and sections.`, {
505
+ section_id: z.string().optional(),
506
+ limit: z.number().optional(),
507
+ }, async (params) => {
508
+ const result = await withAuth(async () => {
509
+ const { buildAttribution } = await import("./evidence/visual_attribution.js");
510
+ const { getArtifactsBaseDir } = await import("./evidence/artifacts.js");
511
+ return buildAttribution(getArtifactsBaseDir(projectDir), {
512
+ section_id: params.section_id,
513
+ limit: params.limit,
514
+ });
515
+ });
516
+ return {
517
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
518
+ };
519
+ });
520
+ server.tool("codeloop_generate_spec", TOOL_BOOTSTRAP + `Generate a design specification from Figma design tokens. Use this tool when:
521
+ - A .codeloop/figma.json exists and you want to extract colors, typography, and spacing tokens
522
+ - You need to create docs/specs/design_tokens.json and docs/ui_rules.md from the Figma file
523
+ - You want to enforce design consistency by comparing code against extracted tokens
524
+ Returns: extracted tokens, generated file paths, and any errors from the Figma API.`, {}, async () => {
525
+ const result = await withAuth(async () => {
526
+ const { generateSpec } = await import("./runners/figma_spec_generator.js");
527
+ return generateSpec(projectDir);
528
+ });
529
+ return {
530
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
531
+ };
532
+ });
533
+ server.tool("codeloop_list_env_presets", TOOL_BOOTSTRAP + `List available environment normalization presets. Use this tool when:
534
+ - You need to know available viewport sizes (mobile_se, tablet_portrait, desktop_1920, etc.)
535
+ - You want to configure network throttling (3g, 4g, wifi, offline)
536
+ - You need locale/timezone presets (en_us, ja_jp, de_de, etc.)
537
+ - You want to see simulator targets (iphone_15, pixel_7, etc.)
538
+ Returns: lists of named presets for viewports, networks, locales, simulators, seed data, and API modes.`, {}, async () => {
539
+ const result = await withAuth(async () => {
540
+ const { listPresets } = await import("./environment/presets.js");
541
+ return listPresets();
542
+ });
543
+ return {
544
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
545
+ };
546
+ });
547
+ server.tool("codeloop_run_history", TOOL_BOOTSTRAP + `Query the run history for this project. Use this tool when:
548
+ - You need to review past verification runs and their outcomes
549
+ - You want to track confidence trends over time
550
+ - You need to find a specific run by section, branch, or date range
551
+ Returns: list of runs with lineage fields (commit, branch, section, parent run), confidence, and pass/fail counts.`, {
552
+ section_id: z.string().optional(),
553
+ branch: z.string().optional(),
554
+ limit: z.number().optional(),
555
+ since: z.string().optional(),
556
+ rebuild_index: z.boolean().optional(),
557
+ }, async (params) => {
558
+ const result = await withAuth(async () => {
559
+ const { queryRunHistory, rebuildIndex } = await import("./evidence/run_lineage.js");
560
+ const { getArtifactsBaseDir } = await import("./evidence/artifacts.js");
561
+ const base = getArtifactsBaseDir(projectDir);
562
+ if (params.rebuild_index) {
563
+ rebuildIndex(base);
564
+ }
565
+ return queryRunHistory(base, {
566
+ section_id: params.section_id,
567
+ branch: params.branch,
568
+ limit: params.limit,
569
+ since: params.since,
570
+ });
571
+ });
572
+ return {
573
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
574
+ };
575
+ });
576
+ server.tool("codeloop_get_prompt", TOOL_BOOTSTRAP + `Retrieve a context-aware prompt template for the current stage of multi-section app development. Use this tool when:
577
+ - You need guidance on what to do next (planning, implementing, repairing, or integrating)
578
+ - You want a structured prompt for a specific development phase
579
+ - codeloop_section_status or codeloop_diagnose suggested calling this tool
580
+
581
+ Layers:
582
+ master_human — top-level framing when a user goal is first received
583
+ planning — translate master spec into an ordered build plan
584
+ section_implement — per-section build prompt with acceptance criteria
585
+ repair — constrained fix loop when verify/gate fails
586
+ integration — cross-section verification at checkpoints
587
+
588
+ Returns: rendered prompt text with metadata about any missing required variables.`, {
589
+ layer: z.enum(["master_human", "planning", "section_implement", "repair", "integration"]),
590
+ context: z.record(z.unknown()).optional(),
591
+ }, async (params) => {
592
+ const result = await withAuth(async () => {
593
+ const { getPrompt } = await import("./prompt_manager/index.js");
594
+ return getPrompt({
595
+ layer: params.layer,
596
+ context: params.context ?? {},
597
+ });
598
+ });
599
+ return {
600
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
601
+ };
602
+ });
603
+ server.tool("codeloop_list_prompts", TOOL_BOOTSTRAP + `List all available prompt template layers and their metadata. Use this tool when:
604
+ - You want to understand what prompt layers are available
605
+ - You need to know the required variables for a specific layer before calling codeloop_get_prompt
606
+ Returns: array of prompt layers with IDs, descriptions, and required variables.`, {}, async () => {
607
+ const result = await withAuth(async () => {
608
+ const { describeAllPrompts } = await import("./prompt_manager/index.js");
609
+ return describeAllPrompts();
610
+ });
611
+ return {
612
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
613
+ };
614
+ });
493
615
  server.tool("codeloop_interaction_replay", TOOL_BOOTSTRAP + `Analyze a recorded video of a user interaction flow to verify it completes as expected. Use this tool when:
494
616
  - You have recorded yourself interacting with ALL elements of the app via codeloop_start_recording
495
617
  - You want to verify that every page loaded, every button worked, every form submitted
@@ -1918,6 +2040,34 @@ project. After it completes, proceed directly with \`codeloop_verify\`.`, {
1918
2040
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1919
2041
  };
1920
2042
  });
2043
+ server.tool("codeloop_flush_usage", TOOL_BOOTSTRAP + `Drain the persisted offline usage queue and POST events to the CodeLoop backend.
2044
+ Use this tool when:
2045
+ - The MCP server was running in local/self-hosted mode and the user has switched to cloud
2046
+ - A previous session had networking issues and queued events to disk
2047
+ - You want to confirm there is no pending billing data to reconcile
2048
+
2049
+ Returns: counts for attempted / succeeded / requeued events and the queue location.`, {
2050
+ project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR or cwd."),
2051
+ }, async (params) => {
2052
+ const cwd = params.project_dir || projectDir;
2053
+ const { flushPersistedUsage } = await import("./auth/usage_tracker.js");
2054
+ const result = await flushPersistedUsage(cwd);
2055
+ return {
2056
+ content: [
2057
+ {
2058
+ type: "text",
2059
+ text: JSON.stringify({
2060
+ ...result,
2061
+ local_mode: isLocalMode(),
2062
+ project_dir: cwd,
2063
+ }, null, 2),
2064
+ },
2065
+ ],
2066
+ };
2067
+ });
2068
+ if (isLocalMode()) {
2069
+ console.error(`[CodeLoop] Running in local mode (CODELOOP_MODE=local). API-key validation skipped; usage events queued to .codeloop/offline_queue.json.`);
2070
+ }
1921
2071
  // ── Start Server ─────────────────────────────────────────────────
1922
2072
  const transport = new StdioServerTransport();
1923
2073
  await server.connect(transport);