glassbox 0.2.5 → 0.2.6

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.js CHANGED
@@ -9,6 +9,90 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/db/schema.ts
13
+ var SCHEMA_CORE_SQL, SCHEMA_AI_SQL;
14
+ var init_schema = __esm({
15
+ "src/db/schema.ts"() {
16
+ "use strict";
17
+ SCHEMA_CORE_SQL = `
18
+ CREATE TABLE IF NOT EXISTS reviews (
19
+ id TEXT PRIMARY KEY,
20
+ repo_path TEXT NOT NULL,
21
+ repo_name TEXT NOT NULL,
22
+ mode TEXT NOT NULL,
23
+ mode_args TEXT,
24
+ head_commit TEXT,
25
+ status TEXT NOT NULL DEFAULT 'in_progress',
26
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
27
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW()
28
+ );
29
+
30
+ CREATE TABLE IF NOT EXISTS review_files (
31
+ id TEXT PRIMARY KEY,
32
+ review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
33
+ file_path TEXT NOT NULL,
34
+ status TEXT NOT NULL DEFAULT 'pending',
35
+ diff_data TEXT,
36
+ created_at TIMESTAMP NOT NULL DEFAULT NOW()
37
+ );
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_review_files_review ON review_files(review_id);
40
+
41
+ CREATE TABLE IF NOT EXISTS annotations (
42
+ id TEXT PRIMARY KEY,
43
+ review_file_id TEXT NOT NULL REFERENCES review_files(id) ON DELETE CASCADE,
44
+ line_number INTEGER NOT NULL,
45
+ side TEXT NOT NULL DEFAULT 'new',
46
+ category TEXT NOT NULL DEFAULT 'note',
47
+ content TEXT NOT NULL,
48
+ is_stale BOOLEAN NOT NULL DEFAULT FALSE,
49
+ original_content TEXT,
50
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
51
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW()
52
+ );
53
+
54
+ CREATE INDEX IF NOT EXISTS idx_annotations_file ON annotations(review_file_id);
55
+ `;
56
+ SCHEMA_AI_SQL = `
57
+ CREATE TABLE IF NOT EXISTS ai_analyses (
58
+ id TEXT PRIMARY KEY,
59
+ review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
60
+ analysis_type TEXT NOT NULL,
61
+ status TEXT NOT NULL DEFAULT 'pending',
62
+ error_message TEXT,
63
+ progress_completed INTEGER NOT NULL DEFAULT 0,
64
+ progress_total INTEGER NOT NULL DEFAULT 0,
65
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
66
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW()
67
+ );
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_ai_analyses_review ON ai_analyses(review_id);
70
+
71
+ CREATE TABLE IF NOT EXISTS ai_file_scores (
72
+ id TEXT PRIMARY KEY,
73
+ analysis_id TEXT NOT NULL REFERENCES ai_analyses(id) ON DELETE CASCADE,
74
+ review_file_id TEXT NOT NULL,
75
+ file_path TEXT NOT NULL,
76
+ sort_order INTEGER NOT NULL DEFAULT 0,
77
+ aggregate_score REAL,
78
+ rationale TEXT,
79
+ dimension_scores TEXT,
80
+ notes TEXT,
81
+ created_at TIMESTAMP NOT NULL DEFAULT NOW()
82
+ );
83
+
84
+ CREATE INDEX IF NOT EXISTS idx_ai_file_scores_analysis ON ai_file_scores(analysis_id);
85
+
86
+ CREATE TABLE IF NOT EXISTS user_preferences (
87
+ id TEXT PRIMARY KEY DEFAULT 'singleton',
88
+ sort_mode TEXT NOT NULL DEFAULT 'folder',
89
+ risk_sort_dimension TEXT NOT NULL DEFAULT 'aggregate',
90
+ show_risk_scores BOOLEAN NOT NULL DEFAULT FALSE
91
+ );
92
+ `;
93
+ }
94
+ });
95
+
12
96
  // src/db/connection.ts
13
97
  var connection_exports = {};
14
98
  __export(connection_exports, {
@@ -44,80 +128,8 @@ async function getDb() {
44
128
  }
45
129
  }
46
130
  async function initSchema(db2) {
47
- await db2.exec(`
48
- CREATE TABLE IF NOT EXISTS reviews (
49
- id TEXT PRIMARY KEY,
50
- repo_path TEXT NOT NULL,
51
- repo_name TEXT NOT NULL,
52
- mode TEXT NOT NULL,
53
- mode_args TEXT,
54
- head_commit TEXT,
55
- status TEXT NOT NULL DEFAULT 'in_progress',
56
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
57
- updated_at TIMESTAMP NOT NULL DEFAULT NOW()
58
- );
59
-
60
- CREATE TABLE IF NOT EXISTS review_files (
61
- id TEXT PRIMARY KEY,
62
- review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
63
- file_path TEXT NOT NULL,
64
- status TEXT NOT NULL DEFAULT 'pending',
65
- diff_data TEXT,
66
- created_at TIMESTAMP NOT NULL DEFAULT NOW()
67
- );
68
-
69
- CREATE INDEX IF NOT EXISTS idx_review_files_review ON review_files(review_id);
70
-
71
- CREATE TABLE IF NOT EXISTS annotations (
72
- id TEXT PRIMARY KEY,
73
- review_file_id TEXT NOT NULL REFERENCES review_files(id) ON DELETE CASCADE,
74
- line_number INTEGER NOT NULL,
75
- side TEXT NOT NULL DEFAULT 'new',
76
- category TEXT NOT NULL DEFAULT 'note',
77
- content TEXT NOT NULL,
78
- is_stale BOOLEAN NOT NULL DEFAULT FALSE,
79
- original_content TEXT,
80
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
81
- updated_at TIMESTAMP NOT NULL DEFAULT NOW()
82
- );
83
-
84
- CREATE INDEX IF NOT EXISTS idx_annotations_file ON annotations(review_file_id);
85
- `);
86
- await db2.exec(`
87
- CREATE TABLE IF NOT EXISTS ai_analyses (
88
- id TEXT PRIMARY KEY,
89
- review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
90
- analysis_type TEXT NOT NULL,
91
- status TEXT NOT NULL DEFAULT 'pending',
92
- error_message TEXT,
93
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
94
- updated_at TIMESTAMP NOT NULL DEFAULT NOW()
95
- );
96
-
97
- CREATE INDEX IF NOT EXISTS idx_ai_analyses_review ON ai_analyses(review_id);
98
-
99
- CREATE TABLE IF NOT EXISTS ai_file_scores (
100
- id TEXT PRIMARY KEY,
101
- analysis_id TEXT NOT NULL REFERENCES ai_analyses(id) ON DELETE CASCADE,
102
- review_file_id TEXT NOT NULL,
103
- file_path TEXT NOT NULL,
104
- sort_order INTEGER NOT NULL DEFAULT 0,
105
- aggregate_score REAL,
106
- rationale TEXT,
107
- dimension_scores TEXT,
108
- notes TEXT,
109
- created_at TIMESTAMP NOT NULL DEFAULT NOW()
110
- );
111
-
112
- CREATE INDEX IF NOT EXISTS idx_ai_file_scores_analysis ON ai_file_scores(analysis_id);
113
-
114
- CREATE TABLE IF NOT EXISTS user_preferences (
115
- id TEXT PRIMARY KEY DEFAULT 'singleton',
116
- sort_mode TEXT NOT NULL DEFAULT 'folder',
117
- risk_sort_dimension TEXT NOT NULL DEFAULT 'aggregate',
118
- show_risk_scores BOOLEAN NOT NULL DEFAULT FALSE
119
- );
120
- `);
131
+ await db2.exec(SCHEMA_CORE_SQL);
132
+ await db2.exec(SCHEMA_AI_SQL);
121
133
  await addColumnIfMissing(db2, "reviews", "head_commit", "TEXT");
122
134
  await addColumnIfMissing(db2, "annotations", "is_stale", "BOOLEAN NOT NULL DEFAULT FALSE");
123
135
  await addColumnIfMissing(db2, "annotations", "original_content", "TEXT");
@@ -141,6 +153,7 @@ var dataDir, dbPath, db;
141
153
  var init_connection = __esm({
142
154
  "src/db/connection.ts"() {
143
155
  "use strict";
156
+ init_schema();
144
157
  dataDir = join(homedir(), ".glassbox", "data");
145
158
  mkdirSync(dataDir, { recursive: true });
146
159
  dbPath = join(dataDir, "reviews");
@@ -3639,74 +3652,172 @@ function DiffView({ file, diff, annotations, mode }) {
3639
3652
  diff.isBinary ? /* @__PURE__ */ jsx("div", { className: "hunk-separator", children: "Binary file" }) : diff.status === "added" || diff.status === "deleted" || mode === "unified" ? /* @__PURE__ */ jsx(UnifiedDiff, { hunks: diff.hunks, annotationsByLine }) : /* @__PURE__ */ jsx(SplitDiff, { hunks: diff.hunks, annotationsByLine })
3640
3653
  ] });
3641
3654
  }
3655
+ function getAnnotations(pair, annotationsByLine) {
3656
+ const leftAnns = pair.left ? annotationsByLine[`${pair.left.oldNum}:old`] ?? [] : [];
3657
+ const rightAnns = pair.right ? annotationsByLine[`${pair.right.newNum}:new`] ?? [] : [];
3658
+ return [...leftAnns, ...rightAnns];
3659
+ }
3642
3660
  function SplitDiff({ hunks, annotationsByLine }) {
3643
3661
  const lastHunk = hunks[hunks.length - 1];
3644
3662
  const tailStart = lastHunk ? lastHunk.newStart + lastHunk.newCount : 1;
3645
- return /* @__PURE__ */ jsx("div", { className: "diff-table-split", children: [
3646
- hunks.map((hunk, hunkIdx) => {
3647
- const pairs = pairLines(hunk.lines);
3648
- return /* @__PURE__ */ jsx("div", { className: "hunk-block", children: [
3649
- /* @__PURE__ */ jsx(
3663
+ const items = [];
3664
+ for (let hunkIdx = 0; hunkIdx < hunks.length; hunkIdx++) {
3665
+ const hunk = hunks[hunkIdx];
3666
+ const prevHunk = hunkIdx > 0 ? hunks[hunkIdx - 1] : null;
3667
+ const gapStart = prevHunk ? prevHunk.newStart + prevHunk.newCount : 1;
3668
+ const gapEnd = hunk.newStart - 1;
3669
+ items.push({ kind: "separator", hunkIdx, hunk, gapStart, gapEnd });
3670
+ for (const pair of pairLines(hunk.lines)) {
3671
+ const anns = getAnnotations(pair, annotationsByLine);
3672
+ if (anns.length > 0) {
3673
+ items.push({ kind: "annotated", pair, annotations: anns });
3674
+ } else {
3675
+ items.push({ kind: "pair", pair });
3676
+ }
3677
+ }
3678
+ }
3679
+ items.push({ kind: "tail", start: tailStart });
3680
+ const groups = [];
3681
+ let run = [];
3682
+ for (const item of items) {
3683
+ if (item.kind === "annotated") {
3684
+ if (run.length > 0) {
3685
+ groups.push({ type: "columns", items: run });
3686
+ run = [];
3687
+ }
3688
+ groups.push({ type: "annotated", pair: item.pair, annotations: item.annotations });
3689
+ } else {
3690
+ run.push(item);
3691
+ }
3692
+ }
3693
+ if (run.length > 0) groups.push({ type: "columns", items: run });
3694
+ return /* @__PURE__ */ jsx("div", { className: "diff-table-split", children: groups.map((group) => {
3695
+ if (group.type === "annotated") {
3696
+ return /* @__PURE__ */ jsx("div", { children: [
3697
+ /* @__PURE__ */ jsx("div", { className: "split-row", children: [
3698
+ /* @__PURE__ */ jsx(
3699
+ "div",
3700
+ {
3701
+ className: `diff-line split-left ${group.pair.left?.type || "empty"}`,
3702
+ "data-line": group.pair.left?.oldNum ?? "",
3703
+ "data-side": "old",
3704
+ "data-new-line": group.pair.left?.newNum ?? group.pair.right?.newNum ?? "",
3705
+ children: [
3706
+ /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": group.pair.left?.oldNum ?? "" }),
3707
+ /* @__PURE__ */ jsx("span", { className: "code", children: group.pair.left ? raw(escapeHtml(group.pair.left.content)) : "" })
3708
+ ]
3709
+ }
3710
+ ),
3711
+ /* @__PURE__ */ jsx(
3712
+ "div",
3713
+ {
3714
+ className: `diff-line split-right ${group.pair.right?.type || "empty"}`,
3715
+ "data-line": group.pair.right?.newNum ?? "",
3716
+ "data-side": "new",
3717
+ children: [
3718
+ /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": group.pair.right?.newNum ?? "" }),
3719
+ /* @__PURE__ */ jsx("span", { className: "code", children: group.pair.right ? raw(escapeHtml(group.pair.right.content)) : "" })
3720
+ ]
3721
+ }
3722
+ )
3723
+ ] }),
3724
+ /* @__PURE__ */ jsx(AnnotationRows, { annotations: group.annotations })
3725
+ ] });
3726
+ }
3727
+ return /* @__PURE__ */ jsx("div", { className: "split-columns", children: [
3728
+ /* @__PURE__ */ jsx("div", { className: "split-col split-col-left", children: group.items.map((item) => {
3729
+ if (item.kind === "separator") {
3730
+ const { hunk, hunkIdx, gapStart, gapEnd } = item;
3731
+ return /* @__PURE__ */ jsx(
3732
+ "div",
3733
+ {
3734
+ className: "hunk-separator",
3735
+ "data-hunk-idx": hunkIdx,
3736
+ "data-old-start": hunk.oldStart,
3737
+ "data-old-count": hunk.oldCount,
3738
+ "data-new-start": hunk.newStart,
3739
+ "data-new-count": hunk.newCount,
3740
+ "data-gap-start": gapStart,
3741
+ "data-gap-end": gapEnd,
3742
+ children: [
3743
+ "@@ -",
3744
+ hunk.oldStart,
3745
+ ",",
3746
+ hunk.oldCount,
3747
+ " +",
3748
+ hunk.newStart,
3749
+ ",",
3750
+ hunk.newCount,
3751
+ " @@"
3752
+ ]
3753
+ }
3754
+ );
3755
+ }
3756
+ if (item.kind === "tail") {
3757
+ return /* @__PURE__ */ jsx("div", { className: "hunk-separator hunk-expander-tail", "data-start": item.start, children: "\u2195 Show remaining lines" });
3758
+ }
3759
+ const { pair } = item;
3760
+ return /* @__PURE__ */ jsx(
3650
3761
  "div",
3651
3762
  {
3652
- className: "hunk-separator",
3653
- "data-hunk-idx": hunkIdx,
3654
- "data-old-start": hunk.oldStart,
3655
- "data-old-count": hunk.oldCount,
3656
- "data-new-start": hunk.newStart,
3657
- "data-new-count": hunk.newCount,
3763
+ className: `diff-line split-left ${pair.left?.type || "empty"}`,
3764
+ "data-line": pair.left?.oldNum ?? "",
3765
+ "data-side": "old",
3766
+ "data-new-line": pair.left?.newNum ?? pair.right?.newNum ?? "",
3658
3767
  children: [
3659
- "@@ -",
3660
- hunk.oldStart,
3661
- ",",
3662
- hunk.oldCount,
3663
- " +",
3664
- hunk.newStart,
3665
- ",",
3666
- hunk.newCount,
3667
- " @@"
3768
+ /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": pair.left?.oldNum ?? "" }),
3769
+ /* @__PURE__ */ jsx("span", { className: "code", children: pair.left ? raw(escapeHtml(pair.left.content)) : "" })
3668
3770
  ]
3669
3771
  }
3670
- ),
3671
- pairs.map((pair) => {
3672
- const leftAnns = pair.left ? annotationsByLine[`${pair.left.oldNum}:old`] ?? [] : [];
3673
- const rightAnns = pair.right ? annotationsByLine[`${pair.right.newNum}:new`] ?? [] : [];
3674
- const allAnns = [...leftAnns, ...rightAnns];
3675
- return /* @__PURE__ */ jsx("div", { children: [
3676
- /* @__PURE__ */ jsx("div", { className: "split-row", children: [
3677
- /* @__PURE__ */ jsx(
3678
- "div",
3679
- {
3680
- className: `diff-line split-left ${pair.left?.type || "empty"}`,
3681
- "data-line": pair.left?.oldNum ?? "",
3682
- "data-side": "old",
3683
- "data-new-line": pair.left?.newNum ?? pair.right?.newNum ?? "",
3684
- children: [
3685
- /* @__PURE__ */ jsx("span", { className: "gutter", children: pair.left?.oldNum ?? "" }),
3686
- /* @__PURE__ */ jsx("span", { className: "code", children: pair.left ? raw(escapeHtml(pair.left.content)) : "" })
3687
- ]
3688
- }
3689
- ),
3690
- /* @__PURE__ */ jsx(
3691
- "div",
3692
- {
3693
- className: `diff-line split-right ${pair.right?.type || "empty"}`,
3694
- "data-line": pair.right?.newNum ?? "",
3695
- "data-side": "new",
3696
- children: [
3697
- /* @__PURE__ */ jsx("span", { className: "gutter", children: pair.right?.newNum ?? "" }),
3698
- /* @__PURE__ */ jsx("span", { className: "code", children: pair.right ? raw(escapeHtml(pair.right.content)) : "" })
3699
- ]
3700
- }
3701
- )
3702
- ] }),
3703
- allAnns.length > 0 ? /* @__PURE__ */ jsx(AnnotationRows, { annotations: allAnns }) : null
3704
- ] });
3705
- })
3706
- ] });
3707
- }),
3708
- /* @__PURE__ */ jsx("div", { className: "hunk-separator hunk-expander-tail", "data-start": tailStart, children: "\u2195 Show remaining lines" })
3709
- ] });
3772
+ );
3773
+ }) }),
3774
+ /* @__PURE__ */ jsx("div", { className: "split-col split-col-right", children: group.items.map((item) => {
3775
+ if (item.kind === "separator") {
3776
+ const { hunk, hunkIdx, gapStart, gapEnd } = item;
3777
+ return /* @__PURE__ */ jsx(
3778
+ "div",
3779
+ {
3780
+ className: "hunk-separator",
3781
+ "data-hunk-idx": hunkIdx,
3782
+ "data-old-start": hunk.oldStart,
3783
+ "data-old-count": hunk.oldCount,
3784
+ "data-new-start": hunk.newStart,
3785
+ "data-new-count": hunk.newCount,
3786
+ "data-gap-start": gapStart,
3787
+ "data-gap-end": gapEnd,
3788
+ children: [
3789
+ "@@ -",
3790
+ hunk.oldStart,
3791
+ ",",
3792
+ hunk.oldCount,
3793
+ " +",
3794
+ hunk.newStart,
3795
+ ",",
3796
+ hunk.newCount,
3797
+ " @@"
3798
+ ]
3799
+ }
3800
+ );
3801
+ }
3802
+ if (item.kind === "tail") {
3803
+ return /* @__PURE__ */ jsx("div", { className: "hunk-separator hunk-expander-tail", "data-start": item.start, children: "\u2195 Show remaining lines" });
3804
+ }
3805
+ const { pair } = item;
3806
+ return /* @__PURE__ */ jsx(
3807
+ "div",
3808
+ {
3809
+ className: `diff-line split-right ${pair.right?.type || "empty"}`,
3810
+ "data-line": pair.right?.newNum ?? "",
3811
+ "data-side": "new",
3812
+ children: [
3813
+ /* @__PURE__ */ jsx("span", { className: "gutter", "data-line-number": pair.right?.newNum ?? "" }),
3814
+ /* @__PURE__ */ jsx("span", { className: "code", children: pair.right ? raw(escapeHtml(pair.right.content)) : "" })
3815
+ ]
3816
+ }
3817
+ );
3818
+ }) })
3819
+ ] });
3820
+ }) });
3710
3821
  }
3711
3822
  function pairLines(lines) {
3712
3823
  const pairs = [];
@@ -3780,8 +3891,8 @@ function UnifiedDiff({ hunks, annotationsByLine }) {
3780
3891
  "data-line": lineNum,
3781
3892
  "data-side": side,
3782
3893
  children: [
3783
- /* @__PURE__ */ jsx("span", { className: "gutter-old", children: line.oldNum ?? "" }),
3784
- /* @__PURE__ */ jsx("span", { className: "gutter-new", children: line.newNum ?? "" }),
3894
+ /* @__PURE__ */ jsx("span", { className: "gutter-old", "data-line-number": line.oldNum ?? "" }),
3895
+ /* @__PURE__ */ jsx("span", { className: "gutter-new", "data-line-number": line.newNum ?? "" }),
3785
3896
  /* @__PURE__ */ jsx("span", { className: "code", children: raw(escapeHtml(line.content)) })
3786
3897
  ]
3787
3898
  }
@@ -4264,19 +4375,136 @@ async function startServer(port, reviewId, repoRoot, options) {
4264
4375
  }
4265
4376
  }
4266
4377
 
4267
- // src/update-check.ts
4378
+ // src/skills.ts
4268
4379
  import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
4380
+ import { join as join7 } from "path";
4381
+ var SKILL_VERSION = 1;
4382
+ function versionHeader() {
4383
+ return `<!-- glassbox-skill-version: ${SKILL_VERSION} -->`;
4384
+ }
4385
+ function parseVersionHeader(content) {
4386
+ const match = content.match(/<!-- glassbox-skill-version: (\d+) -->/);
4387
+ if (!match) return null;
4388
+ return parseInt(match[1], 10);
4389
+ }
4390
+ function updateFile(path, content) {
4391
+ if (existsSync6(path)) {
4392
+ const existing = readFileSync7(path, "utf-8");
4393
+ const version = parseVersionHeader(existing);
4394
+ if (version !== null && version >= SKILL_VERSION) {
4395
+ return false;
4396
+ }
4397
+ }
4398
+ writeFileSync5(path, content, "utf-8");
4399
+ return true;
4400
+ }
4401
+ function skillBody() {
4402
+ return [
4403
+ "Read `.glassbox/latest-review.md` and apply the feedback.",
4404
+ "",
4405
+ "For each annotation, follow the instruction type:",
4406
+ "",
4407
+ "1. **bug** and **fix** \u2014 These indicate code that needs to be changed. Apply the suggested fixes.",
4408
+ "2. **style** \u2014 These indicate stylistic preferences. Apply them to the indicated lines and similar patterns nearby.",
4409
+ "3. **pattern-follow** \u2014 These highlight good patterns. Continue using these patterns in new code.",
4410
+ "4. **pattern-avoid** \u2014 These highlight anti-patterns. Refactor the indicated code and avoid the pattern elsewhere.",
4411
+ "5. **remember** \u2014 These are rules/preferences to persist. Update the project's AI configuration file (e.g., CLAUDE.md) with these.",
4412
+ "6. **note** \u2014 These are informational context. Consider them but they may not require code changes.",
4413
+ "",
4414
+ "Work through all annotated files methodically. For each file, read the source code first, then apply the feedback."
4415
+ ].join("\n");
4416
+ }
4417
+ function ensureClaudeSkills(cwd) {
4418
+ const dir = join7(cwd, ".claude", "skills", "glassbox");
4419
+ mkdirSync5(dir, { recursive: true });
4420
+ const content = [
4421
+ "---",
4422
+ "name: glassbox",
4423
+ "description: Read the latest Glassbox code review and apply all feedback annotations",
4424
+ "allowed-tools: Read, Grep, Glob, Edit, Write, Bash",
4425
+ "---",
4426
+ versionHeader(),
4427
+ "",
4428
+ skillBody(),
4429
+ ""
4430
+ ].join("\n");
4431
+ return updateFile(join7(dir, "SKILL.md"), content);
4432
+ }
4433
+ function ensureCursorRules(cwd) {
4434
+ const rulesDir = join7(cwd, ".cursor", "rules");
4435
+ mkdirSync5(rulesDir, { recursive: true });
4436
+ const content = [
4437
+ "---",
4438
+ "description: Read the latest Glassbox code review and apply all feedback annotations",
4439
+ "alwaysApply: false",
4440
+ "---",
4441
+ versionHeader(),
4442
+ "",
4443
+ skillBody(),
4444
+ ""
4445
+ ].join("\n");
4446
+ return updateFile(join7(rulesDir, "glassbox.mdc"), content);
4447
+ }
4448
+ function ensureCopilotPrompts(cwd) {
4449
+ const promptsDir = join7(cwd, ".github", "prompts");
4450
+ mkdirSync5(promptsDir, { recursive: true });
4451
+ const content = [
4452
+ "---",
4453
+ "description: Read the latest Glassbox code review and apply all feedback annotations",
4454
+ "---",
4455
+ versionHeader(),
4456
+ "",
4457
+ skillBody(),
4458
+ ""
4459
+ ].join("\n");
4460
+ return updateFile(join7(promptsDir, "glassbox.prompt.md"), content);
4461
+ }
4462
+ function ensureWindsurfRules(cwd) {
4463
+ const rulesDir = join7(cwd, ".windsurf", "rules");
4464
+ mkdirSync5(rulesDir, { recursive: true });
4465
+ const content = [
4466
+ "---",
4467
+ "trigger: manual",
4468
+ "description: Read the latest Glassbox code review and apply all feedback annotations",
4469
+ "---",
4470
+ versionHeader(),
4471
+ "",
4472
+ skillBody(),
4473
+ ""
4474
+ ].join("\n");
4475
+ return updateFile(join7(rulesDir, "glassbox.md"), content);
4476
+ }
4477
+ function ensureSkills() {
4478
+ const cwd = process.cwd();
4479
+ const platforms = [];
4480
+ if (existsSync6(join7(cwd, ".claude"))) {
4481
+ if (ensureClaudeSkills(cwd)) platforms.push("Claude Code");
4482
+ }
4483
+ if (existsSync6(join7(cwd, ".cursor"))) {
4484
+ if (ensureCursorRules(cwd)) platforms.push("Cursor");
4485
+ }
4486
+ if (existsSync6(join7(cwd, ".github", "prompts")) || existsSync6(join7(cwd, ".github", "copilot-instructions.md"))) {
4487
+ if (ensureCopilotPrompts(cwd)) platforms.push("GitHub Copilot");
4488
+ }
4489
+ if (existsSync6(join7(cwd, ".windsurf"))) {
4490
+ if (ensureWindsurfRules(cwd)) platforms.push("Windsurf");
4491
+ }
4492
+ return platforms;
4493
+ }
4494
+
4495
+ // src/update-check.ts
4496
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
4269
4497
  import { get } from "https";
4270
4498
  import { homedir as homedir4 } from "os";
4271
- import { dirname as dirname2, join as join7 } from "path";
4499
+ import { dirname as dirname2, join as join8 } from "path";
4272
4500
  import { fileURLToPath as fileURLToPath2 } from "url";
4273
- var DATA_DIR = join7(homedir4(), ".glassbox");
4274
- var CHECK_FILE = join7(DATA_DIR, "last-update-check");
4501
+ var DATA_DIR = join8(homedir4(), ".glassbox");
4502
+ var CHECK_FILE = join8(DATA_DIR, "last-update-check");
4275
4503
  var PACKAGE_NAME = "glassbox";
4276
4504
  function getCurrentVersion() {
4277
4505
  try {
4278
4506
  const dir = dirname2(fileURLToPath2(import.meta.url));
4279
- const pkg = JSON.parse(readFileSync7(join7(dir, "..", "package.json"), "utf-8"));
4507
+ const pkg = JSON.parse(readFileSync8(join8(dir, "..", "package.json"), "utf-8"));
4280
4508
  return pkg.version;
4281
4509
  } catch {
4282
4510
  return "0.0.0";
@@ -4284,16 +4512,16 @@ function getCurrentVersion() {
4284
4512
  }
4285
4513
  function getLastCheckDate() {
4286
4514
  try {
4287
- if (existsSync6(CHECK_FILE)) {
4288
- return readFileSync7(CHECK_FILE, "utf-8").trim();
4515
+ if (existsSync7(CHECK_FILE)) {
4516
+ return readFileSync8(CHECK_FILE, "utf-8").trim();
4289
4517
  }
4290
4518
  } catch {
4291
4519
  }
4292
4520
  return null;
4293
4521
  }
4294
4522
  function saveCheckDate() {
4295
- mkdirSync5(DATA_DIR, { recursive: true });
4296
- writeFileSync5(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
4523
+ mkdirSync6(DATA_DIR, { recursive: true });
4524
+ writeFileSync6(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
4297
4525
  }
4298
4526
  function isFirstUseToday() {
4299
4527
  const last = getLastCheckDate();
@@ -4513,17 +4741,17 @@ async function main() {
4513
4741
  console.log("AI service test mode enabled \u2014 using mock AI responses");
4514
4742
  }
4515
4743
  if (debug) {
4516
- console.log(`[debug] Build timestamp: ${"2026-03-14T00:01:26.096Z"}`);
4744
+ console.log(`[debug] Build timestamp: ${"2026-03-15T23:17:06.265Z"}`);
4517
4745
  }
4518
4746
  if (projectDir) {
4519
4747
  process.chdir(projectDir);
4520
4748
  }
4521
4749
  if (demo === null) {
4522
4750
  const { homedir: homedir5 } = await import("os");
4523
- const { join: join8 } = await import("path");
4524
- const { mkdirSync: mkdirSync6 } = await import("fs");
4525
- const dataDir2 = join8(homedir5(), ".glassbox");
4526
- mkdirSync6(dataDir2, { recursive: true });
4751
+ const { join: join9 } = await import("path");
4752
+ const { mkdirSync: mkdirSync7 } = await import("fs");
4753
+ const dataDir2 = join9(homedir5(), ".glassbox");
4754
+ mkdirSync7(dataDir2, { recursive: true });
4527
4755
  acquireLock(dataDir2);
4528
4756
  }
4529
4757
  if (demo !== null) {
@@ -4546,6 +4774,10 @@ async function main() {
4546
4774
  }
4547
4775
  await checkForUpdates(forceUpdateCheck);
4548
4776
  const cwd = process.cwd();
4777
+ const skillPlatforms = ensureSkills();
4778
+ if (skillPlatforms.length > 0) {
4779
+ console.log(`AI tool skills created/updated for: ${skillPlatforms.join(", ")}`);
4780
+ }
4549
4781
  if (!isGitRepo(cwd)) {
4550
4782
  console.error("Error: Not a git repository. Run this from inside a git repo.");
4551
4783
  process.exit(1);