glassbox 1.0.0 → 1.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +767 -384
- package/dist/client/app.global.js +19 -18
- package/dist/client/styles.css +1 -1
- package/dist/plugins/mermaid/index.js +1 -1
- package/dist/plugins/plantuml/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -49,148 +49,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
49
49
|
mod
|
|
50
50
|
));
|
|
51
51
|
|
|
52
|
-
// src/review-notes/types.ts
|
|
53
|
-
function isNoteKind(value) {
|
|
54
|
-
return NOTE_KINDS.includes(value);
|
|
55
|
-
}
|
|
56
|
-
var NOTE_KINDS, CONFIDENCE_PROPERTY_KEY, ANCHOR_FINGERPRINT_KEY, DEFAULT_PRODUCER, DEFAULT_SHARD_CAP;
|
|
57
|
-
var init_types = __esm({
|
|
58
|
-
"src/review-notes/types.ts"() {
|
|
59
|
-
"use strict";
|
|
60
|
-
NOTE_KINDS = [
|
|
61
|
-
"rationale",
|
|
62
|
-
"proof",
|
|
63
|
-
"assumption",
|
|
64
|
-
"alternative-considered",
|
|
65
|
-
"risk",
|
|
66
|
-
"test-evidence"
|
|
67
|
-
];
|
|
68
|
-
CONFIDENCE_PROPERTY_KEY = "ext-ai-tool-confidence";
|
|
69
|
-
ANCHOR_FINGERPRINT_KEY = "prNoteAnchor/v1";
|
|
70
|
-
DEFAULT_PRODUCER = "unknown-ai-tool";
|
|
71
|
-
DEFAULT_SHARD_CAP = 1e4;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// src/review-notes/instructions.ts
|
|
76
|
-
function reviewNoteInstructions() {
|
|
77
|
-
return [
|
|
78
|
-
"# Emitting AI review notes (`.pr-notes/`)",
|
|
79
|
-
"",
|
|
80
|
-
"As you write or change code, leave **line-anchored review notes** that explain",
|
|
81
|
-
"your reasoning and prove your work \u2014 the rationale a careful reviewer (human or",
|
|
82
|
-
"the next AI session) would want, anchored to the exact lines it concerns. Notes",
|
|
83
|
-
"are committed to the repo as tool-neutral SARIF under `.pr-notes/` and shown",
|
|
84
|
-
"review-comment-style in Glassbox. Write them **while editing**, at the moment of",
|
|
85
|
-
"richest context \u2014 not reconstructed afterward.",
|
|
86
|
-
"",
|
|
87
|
-
"## When to emit a note",
|
|
88
|
-
"",
|
|
89
|
-
'Emit one when a future reader would otherwise have to ask "why?". Good triggers:',
|
|
90
|
-
"",
|
|
91
|
-
"- A **non-obvious decision** \u2014 why this approach over the obvious one.",
|
|
92
|
-
"- **Proof** a change is correct \u2014 the reasoning, test output, or invariant that backs it.",
|
|
93
|
-
"- An **assumption** you relied on that could later be wrong.",
|
|
94
|
-
"- An **alternative you considered and rejected**, and why.",
|
|
95
|
-
"- A **risk** or sharp edge a reviewer should scrutinize.",
|
|
96
|
-
"- **Test evidence** demonstrating the change works.",
|
|
97
|
-
"",
|
|
98
|
-
"Do **not** narrate the obvious or restate what the diff already says. One note per",
|
|
99
|
-
"genuine decision or claim \u2014 not per line.",
|
|
100
|
-
"",
|
|
101
|
-
"## How to emit a note",
|
|
102
|
-
"",
|
|
103
|
-
"Shell out to the `glassbox note` CLI (it owns the on-disk format, fingerprint,",
|
|
104
|
-
"and commit provenance \u2014 you never write the SARIF by hand):",
|
|
105
|
-
"",
|
|
106
|
-
"```",
|
|
107
|
-
"glassbox note add \\",
|
|
108
|
-
" --file <repo-relative path> \\",
|
|
109
|
-
" --lines <A[-B]> \\",
|
|
110
|
-
` --kind <${NOTE_KINDS.join("|")}> \\`,
|
|
111
|
-
' --body - \\ # body read from stdin (use --body "text" for short notes)',
|
|
112
|
-
" [--confidence 0..1] [--rank 0..100] [--ticket <id|url>] \\",
|
|
113
|
-
" [--artifact <path>]\u2026 \\ # attach committed proof artifacts (repeatable)",
|
|
114
|
-
' [--producer "<your tool/agent name>"] [--producer-version <v>]',
|
|
115
|
-
"```",
|
|
116
|
-
"",
|
|
117
|
-
"Always set `--producer` so the note records who wrote it, and `--ticket` when the",
|
|
118
|
-
"work traces to a tracked task. Keep the body focused: the claim and its support.",
|
|
119
|
-
"",
|
|
120
|
-
"## Proof artifacts \u2014 diagrams, test output, screenshots",
|
|
121
|
-
"",
|
|
122
|
-
"When a claim is clearer shown than told, save the evidence under",
|
|
123
|
-
"`.pr-notes/artifacts/` and attach it with `--artifact <path>` (repeatable):",
|
|
124
|
-
"",
|
|
125
|
-
"- **When a diagram is the clearest proof or rationale** \u2014 a state machine, a",
|
|
126
|
-
" data/control flow, a sequence of interactions, an architecture relationship \u2014",
|
|
127
|
-
" **write it as Mermaid SOURCE** (a `.mmd` file) and attach that. Diagram source",
|
|
128
|
-
" stays diffable and reviewable; never attach a rendered diagram image, and",
|
|
129
|
-
" never fall back to ASCII art in the note body.",
|
|
130
|
-
"- **Test output, logs, and measurements** are attached the same way as plain",
|
|
131
|
-
" text files.",
|
|
132
|
-
"- **Screenshots** are for genuinely visual claims only \u2014 downscale and compress",
|
|
133
|
-
" (WebP/AVIF; stored via Git LFS automatically).",
|
|
134
|
-
"",
|
|
135
|
-
"Prefer text/diagram-source over images, and attach one artifact per *claim*,",
|
|
136
|
-
"not per step.",
|
|
137
|
-
"",
|
|
138
|
-
"## Revise and consolidate",
|
|
139
|
-
"",
|
|
140
|
-
"Notes should reflect the **final** state of your change, not an obsolete",
|
|
141
|
-
"intermediate step:",
|
|
142
|
-
"",
|
|
143
|
-
"- `glassbox note add` prints a stable note id (guid).",
|
|
144
|
-
"- `glassbox note update --id <guid> [--body -|--kind \u2026|--confidence \u2026|--rank \u2026|--ticket \u2026]`",
|
|
145
|
-
" to correct a note as the work evolves.",
|
|
146
|
-
"- `glassbox note remove --id <guid>` to drop one that no longer applies.",
|
|
147
|
-
"- When the task is done, run `glassbox note coalesce` to drop redundant notes",
|
|
148
|
-
" (identical anchor + kind + body, keeping the most recent).",
|
|
149
|
-
"",
|
|
150
|
-
"## Final consolidation pass",
|
|
151
|
-
"",
|
|
152
|
-
"After the mechanical `coalesce`, do one **cross-cutting pass over all the notes",
|
|
153
|
-
"you wrote this session** \u2014 the relationships that only become visible once the",
|
|
154
|
-
"whole change is in front of you, not while editing a single file. `coalesce`",
|
|
155
|
-
"only catches byte-identical duplicates; this pass is the judgment it cannot make:",
|
|
156
|
-
"",
|
|
157
|
-
"- **Merge near-duplicates.** Two notes that make the *same* point in different",
|
|
158
|
-
" words (e.g. the same rationale restated on two files) aren't caught by",
|
|
159
|
-
" `coalesce`. Keep the clearest one \u2014 `update` it to the best wording, widen its",
|
|
160
|
-
" anchor or `--ticket` if helpful \u2014 and `remove` the rest.",
|
|
161
|
-
"- **Link related notes across files.** When several notes are facets of one",
|
|
162
|
-
" decision spanning multiple files, make that explicit: name the related file(s)",
|
|
163
|
-
' and the connecting idea in each note body (a short "see also `path/to/file`"',
|
|
164
|
-
" line), and give them a shared `--ticket` so a reader can pivot between them.",
|
|
165
|
-
" Bodies render as Markdown, so an inline `` `path` `` reference reads cleanly.",
|
|
166
|
-
"- **Prune what the finished change made obvious.** A note that justified an",
|
|
167
|
-
" intermediate step the final diff no longer shows is noise \u2014 `remove` it.",
|
|
168
|
-
"",
|
|
169
|
-
"The goal is the smallest set of notes that still proves the work: no restated",
|
|
170
|
-
"point twice, and every cross-file relationship spelled out where a reviewer will",
|
|
171
|
-
"see it.",
|
|
172
|
-
"",
|
|
173
|
-
"If your tool can't shell out, write the SARIF directly under `.pr-notes/` per the",
|
|
174
|
-
"format in `docs/20-ai-review-notes.md` \u2014 but prefer the CLI."
|
|
175
|
-
].join("\n");
|
|
176
|
-
}
|
|
177
|
-
var init_instructions = __esm({
|
|
178
|
-
"src/review-notes/instructions.ts"() {
|
|
179
|
-
"use strict";
|
|
180
|
-
init_types();
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// src/db/ids.ts
|
|
185
|
-
function generateId() {
|
|
186
|
-
return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
|
|
187
|
-
}
|
|
188
|
-
var init_ids = __esm({
|
|
189
|
-
"src/db/ids.ts"() {
|
|
190
|
-
"use strict";
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
52
|
// node_modules/zod/v4/core/core.js
|
|
195
53
|
// @__NO_SIDE_EFFECTS__
|
|
196
54
|
function $constructor(name, initializer3, params) {
|
|
@@ -15351,6 +15209,247 @@ var init_zod = __esm({
|
|
|
15351
15209
|
}
|
|
15352
15210
|
});
|
|
15353
15211
|
|
|
15212
|
+
// src/app-version.ts
|
|
15213
|
+
import { readFileSync } from "fs";
|
|
15214
|
+
import { dirname, join } from "path";
|
|
15215
|
+
import { fileURLToPath } from "url";
|
|
15216
|
+
function getCurrentVersion() {
|
|
15217
|
+
try {
|
|
15218
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
15219
|
+
const raw2 = JSON.parse(readFileSync(join(dir, "..", "package.json"), "utf-8"));
|
|
15220
|
+
return VersionPayloadSchema.parse(raw2).version;
|
|
15221
|
+
} catch {
|
|
15222
|
+
return "0.0.0";
|
|
15223
|
+
}
|
|
15224
|
+
}
|
|
15225
|
+
var VersionPayloadSchema;
|
|
15226
|
+
var init_app_version = __esm({
|
|
15227
|
+
"src/app-version.ts"() {
|
|
15228
|
+
"use strict";
|
|
15229
|
+
init_zod();
|
|
15230
|
+
VersionPayloadSchema = external_exports.object({ version: external_exports.string() });
|
|
15231
|
+
}
|
|
15232
|
+
});
|
|
15233
|
+
|
|
15234
|
+
// src/review-notes/types.ts
|
|
15235
|
+
function isNoteKind(value) {
|
|
15236
|
+
return NOTE_KINDS.includes(value);
|
|
15237
|
+
}
|
|
15238
|
+
var NOTE_KINDS, CONFIDENCE_PROPERTY_KEY, ANCHOR_FINGERPRINT_KEY, DEFAULT_PRODUCER, DEFAULT_SHARD_CAP;
|
|
15239
|
+
var init_types = __esm({
|
|
15240
|
+
"src/review-notes/types.ts"() {
|
|
15241
|
+
"use strict";
|
|
15242
|
+
NOTE_KINDS = [
|
|
15243
|
+
"rationale",
|
|
15244
|
+
"proof",
|
|
15245
|
+
"assumption",
|
|
15246
|
+
"alternative-considered",
|
|
15247
|
+
"risk",
|
|
15248
|
+
"test-evidence"
|
|
15249
|
+
];
|
|
15250
|
+
CONFIDENCE_PROPERTY_KEY = "ext-ai-tool-confidence";
|
|
15251
|
+
ANCHOR_FINGERPRINT_KEY = "prNoteAnchor/v1";
|
|
15252
|
+
DEFAULT_PRODUCER = "unknown-ai-tool";
|
|
15253
|
+
DEFAULT_SHARD_CAP = 1e4;
|
|
15254
|
+
}
|
|
15255
|
+
});
|
|
15256
|
+
|
|
15257
|
+
// src/review-notes/instructions.ts
|
|
15258
|
+
function reviewNoteInstructions() {
|
|
15259
|
+
return [
|
|
15260
|
+
"# Emitting AI review notes (`.pr-notes/`)",
|
|
15261
|
+
"",
|
|
15262
|
+
"As you write or change code, leave **line-anchored review notes** that explain",
|
|
15263
|
+
"your reasoning and prove your work \u2014 the rationale a careful reviewer (human or",
|
|
15264
|
+
"the next AI session) would want, anchored to the exact lines it concerns. Notes",
|
|
15265
|
+
"are committed to the repo as tool-neutral SARIF under `.pr-notes/` and shown",
|
|
15266
|
+
"review-comment-style in Glassbox. Write them **while editing**, at the moment of",
|
|
15267
|
+
"richest context \u2014 not reconstructed afterward.",
|
|
15268
|
+
"",
|
|
15269
|
+
"## When to emit a note",
|
|
15270
|
+
"",
|
|
15271
|
+
'Emit one when a future reader would otherwise have to ask "why?". Good triggers:',
|
|
15272
|
+
"",
|
|
15273
|
+
"- A **non-obvious decision** \u2014 why this approach over the obvious one.",
|
|
15274
|
+
"- **Proof** a change is correct \u2014 the reasoning, test output, or invariant that backs it.",
|
|
15275
|
+
"- An **assumption** you relied on that could later be wrong.",
|
|
15276
|
+
"- An **alternative you considered and rejected**, and why.",
|
|
15277
|
+
"- A **risk** or sharp edge a reviewer should scrutinize.",
|
|
15278
|
+
"- **Test evidence** demonstrating the change works.",
|
|
15279
|
+
"",
|
|
15280
|
+
"Do **not** narrate the obvious or restate what the diff already says. One note per",
|
|
15281
|
+
"genuine decision or claim \u2014 not per line.",
|
|
15282
|
+
"",
|
|
15283
|
+
"## How to emit a note",
|
|
15284
|
+
"",
|
|
15285
|
+
"Shell out to the `glassbox note` CLI (it owns the on-disk format, fingerprint,",
|
|
15286
|
+
"and commit provenance \u2014 you never write the SARIF by hand):",
|
|
15287
|
+
"",
|
|
15288
|
+
"```",
|
|
15289
|
+
"glassbox note add \\",
|
|
15290
|
+
" --file <repo-relative path> \\",
|
|
15291
|
+
" --lines <A[-B]> \\",
|
|
15292
|
+
` --kind <${NOTE_KINDS.join("|")}> \\`,
|
|
15293
|
+
' --body - \\ # body read from stdin (use --body "text" for short notes)',
|
|
15294
|
+
" [--confidence 0..1] [--rank 0..100] [--ticket <id|url>] \\",
|
|
15295
|
+
" [--artifact <path>]\u2026 \\ # attach committed proof artifacts (repeatable)",
|
|
15296
|
+
" [--related <file:line>]\u2026 \\ # code locations the body links to (repeatable)",
|
|
15297
|
+
' [--producer "<your tool/agent name>"] [--producer-version <v>]',
|
|
15298
|
+
"```",
|
|
15299
|
+
"",
|
|
15300
|
+
"Always set `--producer` so the note records who wrote it, and `--ticket` when the",
|
|
15301
|
+
"work traces to a tracked task. Keep the body focused: the claim and its support.",
|
|
15302
|
+
"",
|
|
15303
|
+
"## Writing the body",
|
|
15304
|
+
"",
|
|
15305
|
+
"**Lead with a one-sentence summary.** The body's first sentence is what a reader",
|
|
15306
|
+
"sees where space is tight \u2014 the export folds each note into a list item, and a",
|
|
15307
|
+
"viewer may show only the opening. Make it stand alone, then expand beneath it.",
|
|
15308
|
+
"",
|
|
15309
|
+
"Bodies are **markdown** and may span multiple lines. Glassbox renders:",
|
|
15310
|
+
"",
|
|
15311
|
+
"- Headings (`### Root cause`), paragraphs, and thematic breaks (`---`)",
|
|
15312
|
+
"- Unordered and ordered lists, including nested ones",
|
|
15313
|
+
"- Fenced code blocks (```` ``` ```` or `~~~`) \u2014 use these for snippets and",
|
|
15314
|
+
" command output; indented code blocks are **not** rendered as code",
|
|
15315
|
+
"- Blockquotes (`> \u2026`)",
|
|
15316
|
+
"- Inline `` `code` ``, **bold**, _italic_, and http(s)/mailto links",
|
|
15317
|
+
"",
|
|
15318
|
+
"**Link to other code instead of describing it.** Pass `--related <file:line>`",
|
|
15319
|
+
"(repeatable) and reference the Nth one from the body as `[text](N)`, 0-based \u2014",
|
|
15320
|
+
"it renders as a click-to-jump link to that line. Prefer this over spelling out",
|
|
15321
|
+
'"see src/foo.ts line 120" in prose.',
|
|
15322
|
+
"",
|
|
15323
|
+
"Not rendered: tables, reference-style links, and raw HTML (a body is escaped",
|
|
15324
|
+
"before formatting, so any HTML shows as literal text). A single newline inside a",
|
|
15325
|
+
"paragraph renders as a visible line break, so short line-oriented prose reads the",
|
|
15326
|
+
"way you wrote it.",
|
|
15327
|
+
"",
|
|
15328
|
+
"## Proof artifacts \u2014 diagrams, test output, screenshots",
|
|
15329
|
+
"",
|
|
15330
|
+
"When a claim is clearer shown than told, save the evidence under",
|
|
15331
|
+
"`.pr-notes/artifacts/` and attach it with `--artifact <path>` (repeatable):",
|
|
15332
|
+
"",
|
|
15333
|
+
"- **When a diagram is the clearest proof or rationale** \u2014 a state machine, a",
|
|
15334
|
+
" data/control flow, a sequence of interactions, an architecture relationship \u2014",
|
|
15335
|
+
" **write it as Mermaid SOURCE** (a `.mmd` file) and attach that. Diagram source",
|
|
15336
|
+
" stays diffable and reviewable; never attach a rendered diagram image, and",
|
|
15337
|
+
" never fall back to ASCII art in the note body.",
|
|
15338
|
+
"- **Test output, logs, and measurements** are attached the same way as plain",
|
|
15339
|
+
" text files.",
|
|
15340
|
+
"- **Screenshots** are for genuinely visual claims only \u2014 downscale and compress",
|
|
15341
|
+
" (WebP/AVIF; stored via Git LFS automatically).",
|
|
15342
|
+
"",
|
|
15343
|
+
"Prefer text/diagram-source over images, and attach one artifact per *claim*,",
|
|
15344
|
+
"not per step.",
|
|
15345
|
+
"",
|
|
15346
|
+
"## Revise and consolidate",
|
|
15347
|
+
"",
|
|
15348
|
+
"Notes should reflect the **final** state of your change, not an obsolete",
|
|
15349
|
+
"intermediate step:",
|
|
15350
|
+
"",
|
|
15351
|
+
"- `glassbox note add` prints a stable note id (guid).",
|
|
15352
|
+
"- `glassbox note update --id <guid> [--body -|--kind \u2026|--confidence \u2026|--rank \u2026|--ticket \u2026]`",
|
|
15353
|
+
" to correct a note as the work evolves.",
|
|
15354
|
+
"- `glassbox note remove --id <guid>` to drop one that no longer applies.",
|
|
15355
|
+
"- When the task is done, run `glassbox note coalesce` to drop redundant notes",
|
|
15356
|
+
" (identical anchor + kind + body, keeping the most recent).",
|
|
15357
|
+
"",
|
|
15358
|
+
"## Final consolidation pass",
|
|
15359
|
+
"",
|
|
15360
|
+
"After the mechanical `coalesce`, do one **cross-cutting pass over all the notes",
|
|
15361
|
+
"you wrote this session** \u2014 the relationships that only become visible once the",
|
|
15362
|
+
"whole change is in front of you, not while editing a single file. `coalesce`",
|
|
15363
|
+
"only catches byte-identical duplicates; this pass is the judgment it cannot make:",
|
|
15364
|
+
"",
|
|
15365
|
+
"- **Merge near-duplicates.** Two notes that make the *same* point in different",
|
|
15366
|
+
" words (e.g. the same rationale restated on two files) aren't caught by",
|
|
15367
|
+
" `coalesce`. Keep the clearest one \u2014 `update` it to the best wording, widen its",
|
|
15368
|
+
" anchor or `--ticket` if helpful \u2014 and `remove` the rest.",
|
|
15369
|
+
"- **Link related notes across files.** When several notes are facets of one",
|
|
15370
|
+
" decision spanning multiple files, make that explicit: name the related file(s)",
|
|
15371
|
+
' and the connecting idea in each note body (a short "see also `path/to/file`"',
|
|
15372
|
+
" line), and give them a shared `--ticket` so a reader can pivot between them.",
|
|
15373
|
+
" Bodies render as Markdown, so an inline `` `path` `` reference reads cleanly.",
|
|
15374
|
+
"- **Prune what the finished change made obvious.** A note that justified an",
|
|
15375
|
+
" intermediate step the final diff no longer shows is noise \u2014 `remove` it.",
|
|
15376
|
+
"",
|
|
15377
|
+
"The goal is the smallest set of notes that still proves the work: no restated",
|
|
15378
|
+
"point twice, and every cross-file relationship spelled out where a reviewer will",
|
|
15379
|
+
"see it.",
|
|
15380
|
+
"",
|
|
15381
|
+
"If your tool can't shell out, write the SARIF directly under `.pr-notes/` per the",
|
|
15382
|
+
"format in `docs/20-ai-review-notes.md` \u2014 but prefer the CLI."
|
|
15383
|
+
].join("\n");
|
|
15384
|
+
}
|
|
15385
|
+
var init_instructions = __esm({
|
|
15386
|
+
"src/review-notes/instructions.ts"() {
|
|
15387
|
+
"use strict";
|
|
15388
|
+
init_types();
|
|
15389
|
+
}
|
|
15390
|
+
});
|
|
15391
|
+
|
|
15392
|
+
// src/db/ids.ts
|
|
15393
|
+
function generateId() {
|
|
15394
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
|
|
15395
|
+
}
|
|
15396
|
+
var init_ids = __esm({
|
|
15397
|
+
"src/db/ids.ts"() {
|
|
15398
|
+
"use strict";
|
|
15399
|
+
}
|
|
15400
|
+
});
|
|
15401
|
+
|
|
15402
|
+
// src/utils/lfs.ts
|
|
15403
|
+
function isLfsPointer(data) {
|
|
15404
|
+
const length = typeof data === "string" ? data.length : data.byteLength;
|
|
15405
|
+
if (length === 0 || length > MAX_POINTER_BYTES) return false;
|
|
15406
|
+
const head = typeof data === "string" ? data.slice(0, VERSION_LINE.length) : data.subarray(0, VERSION_LINE.length).toString("latin1");
|
|
15407
|
+
if (head !== VERSION_LINE) return false;
|
|
15408
|
+
const text = typeof data === "string" ? data : data.toString("utf-8");
|
|
15409
|
+
return OID_RE.test(text) && SIZE_RE.test(text);
|
|
15410
|
+
}
|
|
15411
|
+
var MAX_POINTER_BYTES, VERSION_LINE, OID_RE, SIZE_RE;
|
|
15412
|
+
var init_lfs = __esm({
|
|
15413
|
+
"src/utils/lfs.ts"() {
|
|
15414
|
+
"use strict";
|
|
15415
|
+
MAX_POINTER_BYTES = 1024;
|
|
15416
|
+
VERSION_LINE = "version https://git-lfs.github.com/spec/v1";
|
|
15417
|
+
OID_RE = /(^|\n)oid sha256:[0-9a-f]{64}(\r?\n|$)/;
|
|
15418
|
+
SIZE_RE = /(^|\n)size \d+(\r?\n|$)/;
|
|
15419
|
+
}
|
|
15420
|
+
});
|
|
15421
|
+
|
|
15422
|
+
// src/utils/flattenMarkdown.ts
|
|
15423
|
+
function stripInline(line) {
|
|
15424
|
+
return line.replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, "$1 ($2)").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(^|[^*])\*([^*\s][^*]*)\*/g, "$1$2").replace(/(^|[^_])_([^_\s][^_]*)_/g, "$1$2");
|
|
15425
|
+
}
|
|
15426
|
+
function flattenMarkdown(body) {
|
|
15427
|
+
const out = [];
|
|
15428
|
+
let inFence = false;
|
|
15429
|
+
for (const line of body.split("\n")) {
|
|
15430
|
+
if (FENCE_RE.test(line)) {
|
|
15431
|
+
inFence = !inFence;
|
|
15432
|
+
continue;
|
|
15433
|
+
}
|
|
15434
|
+
if (inFence) {
|
|
15435
|
+
out.push(line);
|
|
15436
|
+
continue;
|
|
15437
|
+
}
|
|
15438
|
+
const heading = HEADING_RE.exec(line);
|
|
15439
|
+
const text = heading !== null ? heading[1].replace(/\s+#+\s*$/, "") : line;
|
|
15440
|
+
out.push(stripInline(text));
|
|
15441
|
+
}
|
|
15442
|
+
return out.join("\n");
|
|
15443
|
+
}
|
|
15444
|
+
var FENCE_RE, HEADING_RE;
|
|
15445
|
+
var init_flattenMarkdown = __esm({
|
|
15446
|
+
"src/utils/flattenMarkdown.ts"() {
|
|
15447
|
+
"use strict";
|
|
15448
|
+
FENCE_RE = /^ {0,3}(?:`{3,}|~{3,})/;
|
|
15449
|
+
HEADING_RE = /^ {0,3}#{1,6}\s+(.*)$/;
|
|
15450
|
+
}
|
|
15451
|
+
});
|
|
15452
|
+
|
|
15354
15453
|
// src/review-notes/sarif.ts
|
|
15355
15454
|
function emptyLog(producer, opts = {}) {
|
|
15356
15455
|
return { $schema: SARIF_SCHEMA_URL, version: "2.1.0", runs: [newRun(producer, opts)] };
|
|
@@ -15379,6 +15478,10 @@ function newRun(producer, opts = {}) {
|
|
|
15379
15478
|
}
|
|
15380
15479
|
return run;
|
|
15381
15480
|
}
|
|
15481
|
+
function noteMessage(body) {
|
|
15482
|
+
const flat = flattenMarkdown(body);
|
|
15483
|
+
return { text: flat === "" ? body : flat, markdown: body };
|
|
15484
|
+
}
|
|
15382
15485
|
function buildResult(input, meta3) {
|
|
15383
15486
|
const region = { startLine: input.startLine, endLine: input.endLine };
|
|
15384
15487
|
if (meta3.snippet !== void 0) region.snippet = { text: meta3.snippet };
|
|
@@ -15390,7 +15493,7 @@ function buildResult(input, meta3) {
|
|
|
15390
15493
|
kind: "informational",
|
|
15391
15494
|
level: input.kind === "risk" ? "warning" : "none",
|
|
15392
15495
|
guid: meta3.guid,
|
|
15393
|
-
message:
|
|
15496
|
+
message: noteMessage(input.body),
|
|
15394
15497
|
locations: [{
|
|
15395
15498
|
physicalLocation: {
|
|
15396
15499
|
artifactLocation: { uri: input.file },
|
|
@@ -15399,6 +15502,15 @@ function buildResult(input, meta3) {
|
|
|
15399
15502
|
}],
|
|
15400
15503
|
properties
|
|
15401
15504
|
};
|
|
15505
|
+
if (input.related !== void 0 && input.related.length > 0) {
|
|
15506
|
+
result.relatedLocations = input.related.map((loc, index) => ({
|
|
15507
|
+
id: index,
|
|
15508
|
+
physicalLocation: {
|
|
15509
|
+
artifactLocation: { uri: loc.uri },
|
|
15510
|
+
region: { startLine: loc.line }
|
|
15511
|
+
}
|
|
15512
|
+
}));
|
|
15513
|
+
}
|
|
15402
15514
|
if (input.rank !== void 0) result.rank = input.rank;
|
|
15403
15515
|
if (input.ticket !== void 0 && input.ticket !== "") result.workItemUris = [input.ticket];
|
|
15404
15516
|
if (meta3.fingerprint !== void 0) result.partialFingerprints = { [ANCHOR_FINGERPRINT_KEY]: meta3.fingerprint };
|
|
@@ -15417,6 +15529,7 @@ var init_sarif = __esm({
|
|
|
15417
15529
|
"src/review-notes/sarif.ts"() {
|
|
15418
15530
|
"use strict";
|
|
15419
15531
|
init_zod();
|
|
15532
|
+
init_flattenMarkdown();
|
|
15420
15533
|
init_types();
|
|
15421
15534
|
SARIF_SCHEMA_URL = "https://json.schemastore.org/sarif-2.1.0.json";
|
|
15422
15535
|
REVIEW_NOTE_RULE_ID = "review-note";
|
|
@@ -15451,17 +15564,17 @@ var init_view = __esm({
|
|
|
15451
15564
|
// src/review-notes/store.ts
|
|
15452
15565
|
import { spawnSync } from "child_process";
|
|
15453
15566
|
import { createHash } from "crypto";
|
|
15454
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
15455
|
-
import { dirname, join } from "path";
|
|
15567
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync } from "fs";
|
|
15568
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
15456
15569
|
function sanitizeRel(file2) {
|
|
15457
15570
|
const rel = file2.replace(/\\/g, "/").replace(/^\/+/, "").replace(/(^|\/)\.\.(?=\/|$)/g, "$1_");
|
|
15458
15571
|
return rel === "" ? "file" : rel;
|
|
15459
15572
|
}
|
|
15460
15573
|
function shardPath(repoRoot, safeRel, index) {
|
|
15461
|
-
return
|
|
15574
|
+
return join2(repoRoot, NOTES_SUBDIR, `${safeRel}.${String(index).padStart(6, "0")}.sarif`);
|
|
15462
15575
|
}
|
|
15463
15576
|
function listShardIndices(repoRoot, safeRel) {
|
|
15464
|
-
const dir =
|
|
15577
|
+
const dir = join2(repoRoot, NOTES_SUBDIR, dirname2(safeRel));
|
|
15465
15578
|
if (!existsSync(dir)) return [];
|
|
15466
15579
|
const base = safeRel.split("/").pop() ?? safeRel;
|
|
15467
15580
|
const indices = [];
|
|
@@ -15476,7 +15589,7 @@ function totalResults(log) {
|
|
|
15476
15589
|
return log.runs.reduce((sum, run) => sum + run.results.length, 0);
|
|
15477
15590
|
}
|
|
15478
15591
|
function readLog(path) {
|
|
15479
|
-
const raw2 = JSON.parse(
|
|
15592
|
+
const raw2 = JSON.parse(readFileSync2(path, "utf-8"));
|
|
15480
15593
|
const parsed = SarifLogShapeSchema.safeParse(raw2);
|
|
15481
15594
|
if (!parsed.success) {
|
|
15482
15595
|
throw new Error(`existing notes shard is not a SARIF log we recognize, refusing to overwrite: ${path}`);
|
|
@@ -15502,7 +15615,7 @@ function gitValue(repoRoot, args) {
|
|
|
15502
15615
|
}
|
|
15503
15616
|
function anchorSnippet(repoRoot, safeRel, startLine, endLine) {
|
|
15504
15617
|
try {
|
|
15505
|
-
const lines =
|
|
15618
|
+
const lines = readFileSync2(join2(repoRoot, safeRel), "utf-8").split("\n");
|
|
15506
15619
|
const slice = lines.slice(Math.max(0, startLine - 1), Math.max(startLine, endLine));
|
|
15507
15620
|
if (slice.length === 0) return {};
|
|
15508
15621
|
const snippet = slice.join("\n");
|
|
@@ -15551,10 +15664,13 @@ function writeReviewNote(repoRoot, input, opts = {}) {
|
|
|
15551
15664
|
log = emptyLog(producer, { producerVersion: input.producerVersion, ...vcs });
|
|
15552
15665
|
}
|
|
15553
15666
|
findOrAddRun(log, producer, input.producerVersion, vcs).results.push(result);
|
|
15554
|
-
mkdirSync(
|
|
15667
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
15555
15668
|
writeFileSync(path, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
15556
15669
|
return { path, guid: guid3 };
|
|
15557
15670
|
}
|
|
15671
|
+
function noteBody(r) {
|
|
15672
|
+
return r.message?.markdown ?? r.message?.text ?? "";
|
|
15673
|
+
}
|
|
15558
15674
|
function writeLog(path, log) {
|
|
15559
15675
|
writeFileSync(path, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
15560
15676
|
}
|
|
@@ -15564,19 +15680,19 @@ function hashArtifacts(repoRoot, artifacts) {
|
|
|
15564
15680
|
const safe = uri.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
15565
15681
|
if (/(^|\/)\.\.(\/|$)/.test(safe)) continue;
|
|
15566
15682
|
try {
|
|
15567
|
-
const abs =
|
|
15683
|
+
const abs = join2(repoRoot, safe);
|
|
15568
15684
|
const stat = statSync(abs);
|
|
15569
15685
|
if (!stat.isFile() || stat.size > ARTIFACT_HASH_MAX_BYTES) continue;
|
|
15570
|
-
out[uri] = createHash("sha256").update(
|
|
15686
|
+
out[uri] = createHash("sha256").update(readFileSync2(abs)).digest("hex");
|
|
15571
15687
|
} catch {
|
|
15572
15688
|
}
|
|
15573
15689
|
}
|
|
15574
15690
|
return out;
|
|
15575
15691
|
}
|
|
15576
15692
|
function ensureArtifactLfsFilter(repoRoot) {
|
|
15577
|
-
const path =
|
|
15693
|
+
const path = join2(repoRoot, ".gitattributes");
|
|
15578
15694
|
try {
|
|
15579
|
-
const existing = existsSync(path) ?
|
|
15695
|
+
const existing = existsSync(path) ? readFileSync2(path, "utf-8") : "";
|
|
15580
15696
|
if (existing.includes(".pr-notes/artifacts/**")) return;
|
|
15581
15697
|
const prefix = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
15582
15698
|
writeFileSync(path, `${existing}${prefix}${LFS_FILTER_LINE}
|
|
@@ -15588,10 +15704,10 @@ function listShardPaths(repoRoot, safeRel) {
|
|
|
15588
15704
|
return listShardIndices(repoRoot, safeRel).map((i) => shardPath(repoRoot, safeRel, i));
|
|
15589
15705
|
}
|
|
15590
15706
|
function allShardPaths(repoRoot) {
|
|
15591
|
-
const root =
|
|
15707
|
+
const root = join2(repoRoot, NOTES_SUBDIR);
|
|
15592
15708
|
if (!existsSync(root)) return [];
|
|
15593
15709
|
const entries = readdirSync(root, { recursive: true });
|
|
15594
|
-
return entries.filter((e) => SHARD_RE.test(e)).map((e) =>
|
|
15710
|
+
return entries.filter((e) => SHARD_RE.test(e)).map((e) => join2(root, e));
|
|
15595
15711
|
}
|
|
15596
15712
|
function persistOrDelete(path, log) {
|
|
15597
15713
|
log.runs = log.runs.filter((run) => run.results.length > 0);
|
|
@@ -15623,7 +15739,7 @@ function updateNote(repoRoot, guid3, patch, file2) {
|
|
|
15623
15739
|
for (const run of log.runs) {
|
|
15624
15740
|
const result = run.results.find((r) => r.guid === guid3);
|
|
15625
15741
|
if (result !== void 0) {
|
|
15626
|
-
if (patch.body !== void 0) result.message =
|
|
15742
|
+
if (patch.body !== void 0) result.message = noteMessage(patch.body);
|
|
15627
15743
|
if (patch.kind !== void 0) {
|
|
15628
15744
|
result.properties = { ...result.properties, tags: [patch.kind] };
|
|
15629
15745
|
result.level = patch.kind === "risk" ? "warning" : "none";
|
|
@@ -15647,7 +15763,7 @@ function noteKey(r) {
|
|
|
15647
15763
|
loc?.region?.startLine,
|
|
15648
15764
|
loc?.region?.endLine,
|
|
15649
15765
|
r.properties?.tags,
|
|
15650
|
-
r
|
|
15766
|
+
noteBody(r)
|
|
15651
15767
|
]);
|
|
15652
15768
|
}
|
|
15653
15769
|
function coalesceFile(repoRoot, file2) {
|
|
@@ -15683,7 +15799,7 @@ function coalesceFile(repoRoot, file2) {
|
|
|
15683
15799
|
return toRemove.length;
|
|
15684
15800
|
}
|
|
15685
15801
|
function coalesceAll(repoRoot) {
|
|
15686
|
-
const root =
|
|
15802
|
+
const root = join2(repoRoot, NOTES_SUBDIR);
|
|
15687
15803
|
if (!existsSync(root)) return 0;
|
|
15688
15804
|
const sources = /* @__PURE__ */ new Set();
|
|
15689
15805
|
for (const entry of readdirSync(root, { recursive: true })) {
|
|
@@ -15698,16 +15814,27 @@ function readArtifactText(repoRoot, uri) {
|
|
|
15698
15814
|
const safe = uri.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
15699
15815
|
if (/(^|\/)\.\.(\/|$)/.test(safe)) return void 0;
|
|
15700
15816
|
try {
|
|
15701
|
-
const abs =
|
|
15817
|
+
const abs = join2(repoRoot, safe);
|
|
15702
15818
|
const stat = statSync(abs);
|
|
15703
15819
|
if (!stat.isFile() || stat.size > ARTIFACT_MAX_BYTES) return void 0;
|
|
15704
|
-
const buf =
|
|
15820
|
+
const buf = readFileSync2(abs);
|
|
15705
15821
|
if (buf.includes(0)) return void 0;
|
|
15822
|
+
if (isLfsPointer(buf)) return void 0;
|
|
15706
15823
|
return buf.toString("utf-8");
|
|
15707
15824
|
} catch {
|
|
15708
15825
|
return void 0;
|
|
15709
15826
|
}
|
|
15710
15827
|
}
|
|
15828
|
+
function readRelated(result) {
|
|
15829
|
+
const raw2 = result.relatedLocations;
|
|
15830
|
+
if (raw2 === void 0 || raw2.length === 0) return void 0;
|
|
15831
|
+
return raw2.map((entry) => {
|
|
15832
|
+
const uri = entry.physicalLocation?.artifactLocation?.uri;
|
|
15833
|
+
const line = entry.physicalLocation?.region?.startLine;
|
|
15834
|
+
if (typeof uri !== "string" || uri === "" || typeof line !== "number") return { uri: "", line: 0 };
|
|
15835
|
+
return { uri, line };
|
|
15836
|
+
});
|
|
15837
|
+
}
|
|
15711
15838
|
function readArtifacts(repoRoot, result) {
|
|
15712
15839
|
const out = [];
|
|
15713
15840
|
for (const att of result.attachments ?? []) {
|
|
@@ -15745,10 +15872,11 @@ function loadReviewNotesForFile(repoRoot, file2) {
|
|
|
15745
15872
|
line: startLine,
|
|
15746
15873
|
side: "new",
|
|
15747
15874
|
kind,
|
|
15748
|
-
body: r
|
|
15875
|
+
body: noteBody(r),
|
|
15749
15876
|
confidence: typeof confidence === "number" ? confidence : void 0,
|
|
15750
15877
|
producer: producer === "" ? void 0 : producer,
|
|
15751
15878
|
snippet: region?.snippet?.text,
|
|
15879
|
+
related: readRelated(r),
|
|
15752
15880
|
artifacts: readArtifacts(repoRoot, r)
|
|
15753
15881
|
});
|
|
15754
15882
|
}
|
|
@@ -15761,10 +15889,11 @@ var init_store = __esm({
|
|
|
15761
15889
|
"src/review-notes/store.ts"() {
|
|
15762
15890
|
"use strict";
|
|
15763
15891
|
init_ids();
|
|
15892
|
+
init_lfs();
|
|
15764
15893
|
init_sarif();
|
|
15765
15894
|
init_types();
|
|
15766
15895
|
init_view();
|
|
15767
|
-
NOTES_SUBDIR =
|
|
15896
|
+
NOTES_SUBDIR = join2(".pr-notes", "notes");
|
|
15768
15897
|
SHARD_RE = /\.(\d{6})\.sarif$/;
|
|
15769
15898
|
ARTIFACT_HASH_MAX_BYTES = 5e7;
|
|
15770
15899
|
LFS_FILTER_LINE = ".pr-notes/artifacts/** filter=lfs diff=lfs merge=lfs -text";
|
|
@@ -15775,17 +15904,22 @@ var init_store = __esm({
|
|
|
15775
15904
|
// src/review-notes/cli.ts
|
|
15776
15905
|
var cli_exports = {};
|
|
15777
15906
|
__export(cli_exports, {
|
|
15907
|
+
UnknownFlagError: () => UnknownFlagError,
|
|
15778
15908
|
parseNoteAdd: () => parseNoteAdd,
|
|
15779
15909
|
runNoteCli: () => runNoteCli
|
|
15780
15910
|
});
|
|
15781
15911
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
15782
15912
|
import { relative, resolve } from "path";
|
|
15783
|
-
function parseFlags(args) {
|
|
15913
|
+
function parseFlags(args, sub) {
|
|
15914
|
+
const known = KNOWN_FLAGS[sub];
|
|
15784
15915
|
const flags = /* @__PURE__ */ new Map();
|
|
15785
15916
|
for (let i = 0; i < args.length; i++) {
|
|
15786
15917
|
const a = args[i];
|
|
15787
15918
|
if (!a.startsWith("--")) throw new Error(`unexpected argument: ${a}`);
|
|
15788
15919
|
const key = a.slice(2);
|
|
15920
|
+
if (!known.includes(key)) {
|
|
15921
|
+
throw new UnknownFlagError(`unknown flag --${key} for 'glassbox note ${sub}' (accepted: ${known.map((f) => `--${f}`).join(", ")})`);
|
|
15922
|
+
}
|
|
15789
15923
|
const next = args.at(i + 1);
|
|
15790
15924
|
if (next === void 0 || next.startsWith("--")) throw new Error(`missing value for --${key}`);
|
|
15791
15925
|
flags.set(key, next);
|
|
@@ -15827,11 +15961,21 @@ add \u2014 options:
|
|
|
15827
15961
|
--producer-version <v>
|
|
15828
15962
|
--artifact <path> Attach a committed proof artifact (test output, log,
|
|
15829
15963
|
diagram source); repeatable, repo-relative path
|
|
15964
|
+
--related <file:line> A code location the body links to; repeatable. Reference
|
|
15965
|
+
the Nth one from the body as [text](N), 0-based \u2014 SARIF's
|
|
15966
|
+
embedded-link syntax, rendered as a jump-to-line link
|
|
15830
15967
|
|
|
15831
15968
|
update/remove use the guid returned by 'add' (--file scopes the search; omit to search all notes).
|
|
15832
15969
|
coalesce drops redundant notes (identical anchor + kind + body), keeping the most recent.
|
|
15833
15970
|
`;
|
|
15834
15971
|
}
|
|
15972
|
+
function parseRelated(value) {
|
|
15973
|
+
const match = /^(.+):(\d+)$/.exec(value);
|
|
15974
|
+
if (match === null) throw new Error(`--related must be <file:line> (e.g. src/a.ts:42), got "${value}"`);
|
|
15975
|
+
const line = parseInteger("--related line", match[2]);
|
|
15976
|
+
if (line < 1) throw new Error("--related line must be 1-based");
|
|
15977
|
+
return { uri: match[1], line };
|
|
15978
|
+
}
|
|
15835
15979
|
function parseInteger(label, value) {
|
|
15836
15980
|
const n = Number(value);
|
|
15837
15981
|
if (!Number.isInteger(n)) throw new Error(`${label} must be an integer, got "${value}"`);
|
|
@@ -15854,7 +15998,7 @@ function parseConfidenceRank(flags) {
|
|
|
15854
15998
|
return out;
|
|
15855
15999
|
}
|
|
15856
16000
|
function parseNoteAdd(args) {
|
|
15857
|
-
const flags = parseFlags(args);
|
|
16001
|
+
const flags = parseFlags(args, "add");
|
|
15858
16002
|
const file2 = flags.get("file");
|
|
15859
16003
|
const lines = flags.get("lines");
|
|
15860
16004
|
const kindRaw = flags.get("kind");
|
|
@@ -15868,7 +16012,15 @@ function parseNoteAdd(args) {
|
|
|
15868
16012
|
const endRaw = lineMatch.at(2);
|
|
15869
16013
|
const endLine = endRaw !== void 0 ? parseInteger("--lines end", endRaw) : startLine;
|
|
15870
16014
|
if (startLine < 1 || endLine < startLine) throw new Error("--lines must be 1-based with start <= end");
|
|
15871
|
-
const parsed = {
|
|
16015
|
+
const parsed = {
|
|
16016
|
+
file: file2,
|
|
16017
|
+
startLine,
|
|
16018
|
+
endLine,
|
|
16019
|
+
kind: kindRaw,
|
|
16020
|
+
artifacts: collectRepeatable(args, "artifact"),
|
|
16021
|
+
related: collectRepeatable(args, "related").map(parseRelated),
|
|
16022
|
+
bodyStdin: false
|
|
16023
|
+
};
|
|
15872
16024
|
Object.assign(parsed, parseConfidenceRank(flags));
|
|
15873
16025
|
if (flags.has("ticket")) parsed.ticket = flags.get("ticket");
|
|
15874
16026
|
if (flags.has("producer")) parsed.producer = flags.get("producer");
|
|
@@ -15927,7 +16079,8 @@ async function runAdd(args, cwd) {
|
|
|
15927
16079
|
ticket: parsed.ticket,
|
|
15928
16080
|
producer: parsed.producer,
|
|
15929
16081
|
producerVersion: parsed.producerVersion,
|
|
15930
|
-
artifacts: parsed.artifacts.length > 0 ? parsed.artifacts : void 0
|
|
16082
|
+
artifacts: parsed.artifacts.length > 0 ? parsed.artifacts : void 0,
|
|
16083
|
+
related: parsed.related.length > 0 ? parsed.related : void 0
|
|
15931
16084
|
};
|
|
15932
16085
|
warnIfPrNotesIgnored(repoRoot);
|
|
15933
16086
|
const { path, guid: guid3 } = writeReviewNote(repoRoot, input);
|
|
@@ -15938,7 +16091,7 @@ function scopedFile(flags, repoRoot, cwd) {
|
|
|
15938
16091
|
return file2 === void 0 ? void 0 : toRepoRelative(repoRoot, cwd, file2);
|
|
15939
16092
|
}
|
|
15940
16093
|
function runRemove(args, cwd) {
|
|
15941
|
-
const flags = parseFlags(args);
|
|
16094
|
+
const flags = parseFlags(args, "remove");
|
|
15942
16095
|
const id = flags.get("id");
|
|
15943
16096
|
if (id === void 0) throw new Error("--id <guid> is required");
|
|
15944
16097
|
const repoRoot = findRepoRoot(cwd);
|
|
@@ -15947,7 +16100,7 @@ function runRemove(args, cwd) {
|
|
|
15947
16100
|
console.log(`Removed review note ${id}`);
|
|
15948
16101
|
}
|
|
15949
16102
|
async function runUpdate(args, cwd) {
|
|
15950
|
-
const flags = parseFlags(args);
|
|
16103
|
+
const flags = parseFlags(args, "update");
|
|
15951
16104
|
const id = flags.get("id");
|
|
15952
16105
|
if (id === void 0) throw new Error("--id <guid> is required");
|
|
15953
16106
|
const patch = {};
|
|
@@ -15967,7 +16120,7 @@ async function runUpdate(args, cwd) {
|
|
|
15967
16120
|
console.log(`Updated review note ${id}`);
|
|
15968
16121
|
}
|
|
15969
16122
|
function runCoalesce(args, cwd) {
|
|
15970
|
-
const flags = parseFlags(args);
|
|
16123
|
+
const flags = parseFlags(args, "coalesce");
|
|
15971
16124
|
const repoRoot = findRepoRoot(cwd);
|
|
15972
16125
|
const file2 = flags.get("file");
|
|
15973
16126
|
const removed = file2 !== void 0 ? coalesceFile(repoRoot, toRepoRelative(repoRoot, cwd, file2)) : coalesceAll(repoRoot);
|
|
@@ -15981,32 +16134,50 @@ async function runNoteCli(args, ctx = {}) {
|
|
|
15981
16134
|
return;
|
|
15982
16135
|
}
|
|
15983
16136
|
const rest = args.slice(1);
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16137
|
+
try {
|
|
16138
|
+
switch (sub) {
|
|
16139
|
+
case "add":
|
|
16140
|
+
await runAdd(rest, cwd);
|
|
16141
|
+
return;
|
|
16142
|
+
case "remove":
|
|
16143
|
+
runRemove(rest, cwd);
|
|
16144
|
+
return;
|
|
16145
|
+
case "update":
|
|
16146
|
+
await runUpdate(rest, cwd);
|
|
16147
|
+
return;
|
|
16148
|
+
case "coalesce":
|
|
16149
|
+
runCoalesce(rest, cwd);
|
|
16150
|
+
return;
|
|
16151
|
+
case "instructions":
|
|
16152
|
+
console.log(reviewNoteInstructions());
|
|
16153
|
+
return;
|
|
16154
|
+
default:
|
|
16155
|
+
throw new Error(`unknown 'note' subcommand: ${sub} (expected add/update/remove/coalesce/instructions)`);
|
|
16156
|
+
}
|
|
16157
|
+
} catch (err) {
|
|
16158
|
+
if (err instanceof UnknownFlagError) {
|
|
16159
|
+
throw new UnknownFlagError(`${err.message}
|
|
16160
|
+
This is glassbox ${getCurrentVersion()} \u2014 a flag added in a newer release is not recognized here.`);
|
|
16161
|
+
}
|
|
16162
|
+
throw err;
|
|
16002
16163
|
}
|
|
16003
16164
|
}
|
|
16165
|
+
var UnknownFlagError, KNOWN_FLAGS;
|
|
16004
16166
|
var init_cli = __esm({
|
|
16005
16167
|
"src/review-notes/cli.ts"() {
|
|
16006
16168
|
"use strict";
|
|
16169
|
+
init_app_version();
|
|
16007
16170
|
init_instructions();
|
|
16008
16171
|
init_store();
|
|
16009
16172
|
init_types();
|
|
16173
|
+
UnknownFlagError = class extends Error {
|
|
16174
|
+
};
|
|
16175
|
+
KNOWN_FLAGS = {
|
|
16176
|
+
add: ["file", "lines", "kind", "body", "confidence", "rank", "ticket", "producer", "producer-version", "artifact", "related"],
|
|
16177
|
+
update: ["id", "file", "body", "kind", "confidence", "rank", "ticket"],
|
|
16178
|
+
remove: ["id", "file"],
|
|
16179
|
+
coalesce: ["file"]
|
|
16180
|
+
};
|
|
16010
16181
|
}
|
|
16011
16182
|
});
|
|
16012
16183
|
|
|
@@ -16240,12 +16411,12 @@ __export(manifest_exports, {
|
|
|
16240
16411
|
GroundTruthSetSchema: () => GroundTruthSetSchema,
|
|
16241
16412
|
loadGroundTruthManifest: () => loadGroundTruthManifest
|
|
16242
16413
|
});
|
|
16243
|
-
import { readFileSync as
|
|
16244
|
-
import { basename, dirname as
|
|
16414
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
16415
|
+
import { basename, dirname as dirname3, isAbsolute, resolve as resolve2 } from "path";
|
|
16245
16416
|
function loadGroundTruthManifest(manifestPath) {
|
|
16246
16417
|
let raw2;
|
|
16247
16418
|
try {
|
|
16248
|
-
raw2 =
|
|
16419
|
+
raw2 = readFileSync3(manifestPath, "utf-8");
|
|
16249
16420
|
} catch {
|
|
16250
16421
|
throw new Error(`Cannot read ground-truth manifest: ${manifestPath}`);
|
|
16251
16422
|
}
|
|
@@ -16268,7 +16439,7 @@ function loadGroundTruthManifest(manifestPath) {
|
|
|
16268
16439
|
const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
16269
16440
|
throw new Error(`Ground-truth manifest is invalid (${manifestPath}): ${detail}`);
|
|
16270
16441
|
}
|
|
16271
|
-
const baseDir =
|
|
16442
|
+
const baseDir = dirname3(resolve2(manifestPath));
|
|
16272
16443
|
const resolvePath = (p) => isAbsolute(p) ? p : resolve2(baseDir, p);
|
|
16273
16444
|
const used = /* @__PURE__ */ new Set();
|
|
16274
16445
|
const dedupe = (key) => {
|
|
@@ -16363,7 +16534,7 @@ __export(promote_exports, {
|
|
|
16363
16534
|
promoteGroundTruthBaselines: () => promoteGroundTruthBaselines
|
|
16364
16535
|
});
|
|
16365
16536
|
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
16366
|
-
import { dirname as
|
|
16537
|
+
import { dirname as dirname4 } from "path";
|
|
16367
16538
|
function promoteGroundTruthBaselines(manifestPath) {
|
|
16368
16539
|
const entries = loadGroundTruthManifest(manifestPath);
|
|
16369
16540
|
const result = { promoted: [], skipped: [] };
|
|
@@ -16379,7 +16550,7 @@ function promoteGroundTruthBaselines(manifestPath) {
|
|
|
16379
16550
|
result.skipped.push({ key: entry.key, reason: `actual image not found: ${entry.actualPath}` });
|
|
16380
16551
|
continue;
|
|
16381
16552
|
}
|
|
16382
|
-
mkdirSync2(
|
|
16553
|
+
mkdirSync2(dirname4(entry.expectedPath), { recursive: true });
|
|
16383
16554
|
copyFileSync(entry.actualPath, entry.expectedPath);
|
|
16384
16555
|
result.promoted.push({ key: entry.key, from: entry.actualPath, to: entry.expectedPath });
|
|
16385
16556
|
}
|
|
@@ -16612,12 +16783,12 @@ var init_ddl = __esm({
|
|
|
16612
16783
|
// src/db/connection.ts
|
|
16613
16784
|
import { PGlite } from "@electric-sql/pglite";
|
|
16614
16785
|
import { mkdirSync as mkdirSync3, rmSync } from "fs";
|
|
16615
|
-
import { join as
|
|
16786
|
+
import { join as join3 } from "path";
|
|
16616
16787
|
function setDataDir(dataDir) {
|
|
16617
16788
|
currentDataDir = dataDir;
|
|
16618
|
-
const dbDir =
|
|
16789
|
+
const dbDir = join3(dataDir, "data");
|
|
16619
16790
|
mkdirSync3(dbDir, { recursive: true });
|
|
16620
|
-
currentDbPath =
|
|
16791
|
+
currentDbPath = join3(dbDir, "reviews");
|
|
16621
16792
|
}
|
|
16622
16793
|
function getDataDir() {
|
|
16623
16794
|
return currentDataDir;
|
|
@@ -16692,10 +16863,10 @@ var init_connection = __esm({
|
|
|
16692
16863
|
// src/attachments/store.ts
|
|
16693
16864
|
import { createHash as createHash2 } from "crypto";
|
|
16694
16865
|
import { existsSync as existsSync3, mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16695
|
-
import { basename as basename2, join as
|
|
16866
|
+
import { basename as basename2, join as join4 } from "path";
|
|
16696
16867
|
function attachmentsDir() {
|
|
16697
16868
|
const dataDir = getDataDir();
|
|
16698
|
-
return dataDir === null ? null :
|
|
16869
|
+
return dataDir === null ? null : join4(dataDir, "attachments");
|
|
16699
16870
|
}
|
|
16700
16871
|
function sanitizeFilename(name) {
|
|
16701
16872
|
const base = basename2(name.replace(/\\/g, "/"));
|
|
@@ -16706,7 +16877,7 @@ function writeAttachmentFile(id, originalFilename, bytes) {
|
|
|
16706
16877
|
const dir = attachmentsDir();
|
|
16707
16878
|
if (dir === null) throw new Error("No data directory for attachments");
|
|
16708
16879
|
mkdirSync4(dir, { recursive: true });
|
|
16709
|
-
const storedPath =
|
|
16880
|
+
const storedPath = join4(dir, `${id}-${sanitizeFilename(originalFilename)}`);
|
|
16710
16881
|
writeFileSync2(storedPath, bytes);
|
|
16711
16882
|
const sha256 = createHash2("sha256").update(bytes).digest("hex");
|
|
16712
16883
|
return { storedPath, size: bytes.length, sha256 };
|
|
@@ -17213,18 +17384,18 @@ var init_queries = __esm({
|
|
|
17213
17384
|
});
|
|
17214
17385
|
|
|
17215
17386
|
// src/global-config.ts
|
|
17216
|
-
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as
|
|
17387
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
17217
17388
|
import { homedir } from "os";
|
|
17218
|
-
import { join as
|
|
17389
|
+
import { join as join5 } from "path";
|
|
17219
17390
|
function resolveGlobalConfigDir() {
|
|
17220
17391
|
const override = process.env.GLASSBOX_CONFIG_DIR;
|
|
17221
17392
|
if (override !== void 0 && override.trim() !== "") return override;
|
|
17222
|
-
return
|
|
17393
|
+
return join5(homedir(), ".glassbox");
|
|
17223
17394
|
}
|
|
17224
17395
|
function readGlobalConfig() {
|
|
17225
17396
|
try {
|
|
17226
17397
|
if (existsSync4(GLOBAL_CONFIG_PATH)) {
|
|
17227
|
-
const raw2 = JSON.parse(
|
|
17398
|
+
const raw2 = JSON.parse(readFileSync4(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
17228
17399
|
const parsed = GlobalConfigSchema.safeParse(raw2);
|
|
17229
17400
|
if (parsed.success) return parsed.data;
|
|
17230
17401
|
}
|
|
@@ -17252,7 +17423,7 @@ var init_global_config = __esm({
|
|
|
17252
17423
|
init_zod();
|
|
17253
17424
|
GlobalConfigSchema = external_exports.record(external_exports.string(), external_exports.unknown());
|
|
17254
17425
|
GLOBAL_CONFIG_DIR = resolveGlobalConfigDir();
|
|
17255
|
-
GLOBAL_CONFIG_PATH =
|
|
17426
|
+
GLOBAL_CONFIG_PATH = join5(GLOBAL_CONFIG_DIR, "config.json");
|
|
17256
17427
|
}
|
|
17257
17428
|
});
|
|
17258
17429
|
|
|
@@ -17391,10 +17562,10 @@ __export(image_blobs_exports, {
|
|
|
17391
17562
|
readImageBlob: () => readImageBlob,
|
|
17392
17563
|
writeImageBlob: () => writeImageBlob
|
|
17393
17564
|
});
|
|
17394
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as
|
|
17395
|
-
import { join as
|
|
17565
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
17566
|
+
import { join as join6 } from "path";
|
|
17396
17567
|
function blobDir(dataDir) {
|
|
17397
|
-
return
|
|
17568
|
+
return join6(dataDir, "image-blobs");
|
|
17398
17569
|
}
|
|
17399
17570
|
function blobName(fileId, side) {
|
|
17400
17571
|
return `${fileId.replace(/[^a-z0-9]/gi, "")}-${side}`;
|
|
@@ -17403,13 +17574,13 @@ function writeImageBlob(dataDir, fileId, side, bytes) {
|
|
|
17403
17574
|
if (bytes.length === 0) return;
|
|
17404
17575
|
const dir = blobDir(dataDir);
|
|
17405
17576
|
mkdirSync6(dir, { recursive: true });
|
|
17406
|
-
writeFileSync4(
|
|
17577
|
+
writeFileSync4(join6(dir, blobName(fileId, side)), bytes);
|
|
17407
17578
|
}
|
|
17408
17579
|
function readImageBlob(dataDir, fileId, side) {
|
|
17409
|
-
const path =
|
|
17580
|
+
const path = join6(blobDir(dataDir), blobName(fileId, side));
|
|
17410
17581
|
if (!existsSync5(path)) return null;
|
|
17411
17582
|
try {
|
|
17412
|
-
return
|
|
17583
|
+
return readFileSync5(path);
|
|
17413
17584
|
} catch {
|
|
17414
17585
|
return null;
|
|
17415
17586
|
}
|
|
@@ -17509,16 +17680,16 @@ var init_project_settings = __esm({
|
|
|
17509
17680
|
});
|
|
17510
17681
|
|
|
17511
17682
|
// src/project-settings-store.ts
|
|
17512
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as
|
|
17513
|
-
import { join as
|
|
17683
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
17684
|
+
import { join as join11 } from "path";
|
|
17514
17685
|
function settingsPath(repoRoot) {
|
|
17515
|
-
return
|
|
17686
|
+
return join11(repoRoot, ".glassbox", "settings.json");
|
|
17516
17687
|
}
|
|
17517
17688
|
function readProjectSettings(repoRoot) {
|
|
17518
17689
|
try {
|
|
17519
17690
|
const path = settingsPath(repoRoot);
|
|
17520
17691
|
if (existsSync10(path)) {
|
|
17521
|
-
const raw2 = JSON.parse(
|
|
17692
|
+
const raw2 = JSON.parse(readFileSync10(path, "utf-8"));
|
|
17522
17693
|
const parsed = ProjectSettingsSchema.safeParse(raw2);
|
|
17523
17694
|
if (parsed.success) return parsed.data;
|
|
17524
17695
|
}
|
|
@@ -17530,9 +17701,9 @@ function updateProjectSettings2(repoRoot, mutator) {
|
|
|
17530
17701
|
const current = readProjectSettings(repoRoot);
|
|
17531
17702
|
const result = mutator(current);
|
|
17532
17703
|
const next = result === void 0 ? current : result;
|
|
17533
|
-
const dir =
|
|
17704
|
+
const dir = join11(repoRoot, ".glassbox");
|
|
17534
17705
|
mkdirSync8(dir, { recursive: true });
|
|
17535
|
-
writeFileSync9(
|
|
17706
|
+
writeFileSync9(join11(dir, "settings.json"), JSON.stringify(next, null, 2), "utf-8");
|
|
17536
17707
|
}
|
|
17537
17708
|
var init_project_settings_store = __esm({
|
|
17538
17709
|
"src/project-settings-store.ts"() {
|
|
@@ -17886,8 +18057,8 @@ var init_settings = __esm({
|
|
|
17886
18057
|
});
|
|
17887
18058
|
|
|
17888
18059
|
// src/plugins/loader.ts
|
|
17889
|
-
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as
|
|
17890
|
-
import { join as
|
|
18060
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
18061
|
+
import { join as join12 } from "path";
|
|
17891
18062
|
import { pathToFileURL } from "url";
|
|
17892
18063
|
function getConfigLabelOverride(pluginId, labelId) {
|
|
17893
18064
|
return configLabelOverrides.get(`${pluginId}:${labelId}`);
|
|
@@ -17908,7 +18079,7 @@ function clearAllPluginUIElements() {
|
|
|
17908
18079
|
pluginUIElements.clear();
|
|
17909
18080
|
}
|
|
17910
18081
|
function pluginsDir() {
|
|
17911
|
-
return
|
|
18082
|
+
return join12(GLOBAL_CONFIG_DIR, "plugins");
|
|
17912
18083
|
}
|
|
17913
18084
|
function discoverPluginDirs(root = pluginsDir()) {
|
|
17914
18085
|
if (!existsSync11(root)) return [];
|
|
@@ -17921,7 +18092,7 @@ function discoverPluginDirs(root = pluginsDir()) {
|
|
|
17921
18092
|
const out = [];
|
|
17922
18093
|
for (const name of entries) {
|
|
17923
18094
|
if (name.startsWith(".")) continue;
|
|
17924
|
-
const dir =
|
|
18095
|
+
const dir = join12(root, name);
|
|
17925
18096
|
try {
|
|
17926
18097
|
if (statSync3(dir).isDirectory()) out.push(dir);
|
|
17927
18098
|
} catch {
|
|
@@ -17930,18 +18101,18 @@ function discoverPluginDirs(root = pluginsDir()) {
|
|
|
17930
18101
|
return out.sort();
|
|
17931
18102
|
}
|
|
17932
18103
|
function readManifest(dir) {
|
|
17933
|
-
const manifestPath =
|
|
18104
|
+
const manifestPath = join12(dir, "manifest.json");
|
|
17934
18105
|
if (existsSync11(manifestPath)) {
|
|
17935
18106
|
try {
|
|
17936
|
-
return parseManifest(JSON.parse(
|
|
18107
|
+
return parseManifest(JSON.parse(readFileSync11(manifestPath, "utf-8")));
|
|
17937
18108
|
} catch {
|
|
17938
18109
|
return null;
|
|
17939
18110
|
}
|
|
17940
18111
|
}
|
|
17941
|
-
const pkgPath =
|
|
18112
|
+
const pkgPath = join12(dir, "package.json");
|
|
17942
18113
|
if (existsSync11(pkgPath)) {
|
|
17943
18114
|
try {
|
|
17944
|
-
const pkg = JSON.parse(
|
|
18115
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
17945
18116
|
const gb = pkg.glassbox;
|
|
17946
18117
|
if (gb !== null && typeof gb === "object") {
|
|
17947
18118
|
return parseManifest({ id: pkg.name, version: pkg.version, entry: pkg.main, ...gb });
|
|
@@ -17984,7 +18155,7 @@ async function loadPluginDir(dir, registry3, isEnabled = ALWAYS_ENABLED, repoRoo
|
|
|
17984
18155
|
return { id: manifest.id, dir, manifest, status: "disabled", disabledScope: enablement.scope };
|
|
17985
18156
|
}
|
|
17986
18157
|
try {
|
|
17987
|
-
const entry =
|
|
18158
|
+
const entry = join12(dir, manifest.entry ?? "index.js");
|
|
17988
18159
|
if (!existsSync11(entry)) throw new Error(`entry not found: ${manifest.entry ?? "index.js"}`);
|
|
17989
18160
|
const mod = await import(pathToFileURL(entry).href);
|
|
17990
18161
|
const plugin = mod.default ?? (typeof mod.activate === "function" ? { activate: mod.activate, onAction: mod.onAction } : void 0);
|
|
@@ -18029,18 +18200,18 @@ var init_loader = __esm({
|
|
|
18029
18200
|
|
|
18030
18201
|
// src/plugins/install.ts
|
|
18031
18202
|
import { createHash as createHash3 } from "crypto";
|
|
18032
|
-
import { cpSync, existsSync as existsSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as
|
|
18033
|
-
import { dirname as
|
|
18034
|
-
import { fileURLToPath as
|
|
18203
|
+
import { cpSync, existsSync as existsSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync12, rmSync as rmSync6, statSync as statSync4, symlinkSync, writeFileSync as writeFileSync10 } from "fs";
|
|
18204
|
+
import { dirname as dirname8, join as join13 } from "path";
|
|
18205
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18035
18206
|
function dismissedPathFor(userDir) {
|
|
18036
|
-
return
|
|
18207
|
+
return join13(dirname8(userDir), DISMISSED_FILE);
|
|
18037
18208
|
}
|
|
18038
18209
|
function bundledPluginsDir() {
|
|
18039
18210
|
const override = process.env.GLASSBOX_BUNDLED_PLUGINS_DIR;
|
|
18040
18211
|
if (override !== void 0 && override.trim() !== "") return override;
|
|
18041
|
-
const sibling =
|
|
18212
|
+
const sibling = join13(dirname8(fileURLToPath3(import.meta.url)), "plugins");
|
|
18042
18213
|
if (existsSync12(sibling)) return sibling;
|
|
18043
|
-
return
|
|
18214
|
+
return join13(process.cwd(), "dist", "plugins");
|
|
18044
18215
|
}
|
|
18045
18216
|
function discoverBundledPlugins(bundledDir = bundledPluginsDir()) {
|
|
18046
18217
|
if (!existsSync12(bundledDir)) return [];
|
|
@@ -18052,7 +18223,7 @@ function discoverBundledPlugins(bundledDir = bundledPluginsDir()) {
|
|
|
18052
18223
|
}
|
|
18053
18224
|
const out = [];
|
|
18054
18225
|
for (const name of entries.sort()) {
|
|
18055
|
-
const dir =
|
|
18226
|
+
const dir = join13(bundledDir, name);
|
|
18056
18227
|
try {
|
|
18057
18228
|
if (!statSync4(dir).isDirectory()) continue;
|
|
18058
18229
|
const manifest = readManifest(dir);
|
|
@@ -18064,7 +18235,7 @@ function discoverBundledPlugins(bundledDir = bundledPluginsDir()) {
|
|
|
18064
18235
|
}
|
|
18065
18236
|
function readDismissed(userDir = pluginsDir()) {
|
|
18066
18237
|
try {
|
|
18067
|
-
const raw2 = JSON.parse(
|
|
18238
|
+
const raw2 = JSON.parse(readFileSync12(dismissedPathFor(userDir), "utf-8"));
|
|
18068
18239
|
return Array.isArray(raw2) ? raw2.filter((x) => typeof x === "string") : [];
|
|
18069
18240
|
} catch {
|
|
18070
18241
|
return [];
|
|
@@ -18072,19 +18243,19 @@ function readDismissed(userDir = pluginsDir()) {
|
|
|
18072
18243
|
}
|
|
18073
18244
|
function writeDismissed(ids, userDir) {
|
|
18074
18245
|
const path = dismissedPathFor(userDir);
|
|
18075
|
-
mkdirSync9(
|
|
18246
|
+
mkdirSync9(dirname8(path), { recursive: true });
|
|
18076
18247
|
writeFileSync10(path, JSON.stringify([...new Set(ids)], null, 2), "utf-8");
|
|
18077
18248
|
}
|
|
18078
18249
|
function hashPluginDir(dir) {
|
|
18079
18250
|
const h = createHash3("sha1");
|
|
18080
18251
|
const walk = (d, rel) => {
|
|
18081
18252
|
for (const name of readdirSync3(d).sort()) {
|
|
18082
|
-
const abs =
|
|
18253
|
+
const abs = join13(d, name);
|
|
18083
18254
|
const r = rel === "" ? name : `${rel}/${name}`;
|
|
18084
18255
|
if (statSync4(abs).isDirectory()) walk(abs, r);
|
|
18085
18256
|
else {
|
|
18086
18257
|
h.update(r);
|
|
18087
|
-
h.update(
|
|
18258
|
+
h.update(readFileSync12(abs));
|
|
18088
18259
|
}
|
|
18089
18260
|
}
|
|
18090
18261
|
};
|
|
@@ -18116,13 +18287,13 @@ function installBundledPlugins(opts) {
|
|
|
18116
18287
|
return;
|
|
18117
18288
|
}
|
|
18118
18289
|
for (const name of entries) {
|
|
18119
|
-
const src =
|
|
18290
|
+
const src = join13(bundledDir, name);
|
|
18120
18291
|
try {
|
|
18121
18292
|
if (!statSync4(src).isDirectory()) continue;
|
|
18122
18293
|
const manifest = readManifest(src);
|
|
18123
18294
|
if (manifest === null || dismissed.has(manifest.id)) continue;
|
|
18124
18295
|
if (manifest.autoInstall === false) continue;
|
|
18125
|
-
const dest =
|
|
18296
|
+
const dest = join13(userDir, manifest.id);
|
|
18126
18297
|
if (!shouldInstall(src, dest, manifest.version)) continue;
|
|
18127
18298
|
mkdirSync9(userDir, { recursive: true });
|
|
18128
18299
|
rmSync6(dest, { recursive: true, force: true });
|
|
@@ -18136,7 +18307,7 @@ function installPluginFromDisk(sourceDir, opts) {
|
|
|
18136
18307
|
if (manifest === null) throw new Error("The selected folder is not a Glassbox plugin (no manifest.json).");
|
|
18137
18308
|
const userDir = opts?.userDir ?? pluginsDir();
|
|
18138
18309
|
mkdirSync9(userDir, { recursive: true });
|
|
18139
|
-
const dest =
|
|
18310
|
+
const dest = join13(userDir, manifest.id);
|
|
18140
18311
|
rmSync6(dest, { recursive: true, force: true });
|
|
18141
18312
|
try {
|
|
18142
18313
|
symlinkSync(sourceDir, dest, "dir");
|
|
@@ -18151,7 +18322,7 @@ function undismissPlugin(id, userDir = pluginsDir()) {
|
|
|
18151
18322
|
}
|
|
18152
18323
|
function uninstallPlugin(id, opts) {
|
|
18153
18324
|
const userDir = opts?.userDir ?? pluginsDir();
|
|
18154
|
-
rmSync6(
|
|
18325
|
+
rmSync6(join13(userDir, id), { recursive: true, force: true });
|
|
18155
18326
|
writeDismissed([...readDismissed(userDir), id], userDir);
|
|
18156
18327
|
}
|
|
18157
18328
|
var DISMISSED_FILE;
|
|
@@ -18566,17 +18737,17 @@ __export(difftool_discovery_exports, {
|
|
|
18566
18737
|
tryAcquireStartingLock: () => tryAcquireStartingLock,
|
|
18567
18738
|
writeDiscovery: () => writeDiscovery
|
|
18568
18739
|
});
|
|
18569
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync16, readFileSync as
|
|
18740
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync16, readFileSync as readFileSync23, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync16 } from "fs";
|
|
18570
18741
|
import { homedir as homedir3 } from "os";
|
|
18571
|
-
import { join as
|
|
18742
|
+
import { join as join24 } from "path";
|
|
18572
18743
|
function difftoolHome() {
|
|
18573
|
-
return
|
|
18744
|
+
return join24(homedir3(), ".glassbox");
|
|
18574
18745
|
}
|
|
18575
18746
|
function discoveryPath(home = difftoolHome()) {
|
|
18576
|
-
return
|
|
18747
|
+
return join24(home, "difftool.lock");
|
|
18577
18748
|
}
|
|
18578
18749
|
function startingLockPath(home = difftoolHome()) {
|
|
18579
|
-
return
|
|
18750
|
+
return join24(home, "difftool-starting.lock");
|
|
18580
18751
|
}
|
|
18581
18752
|
function parseDiscovery(raw2) {
|
|
18582
18753
|
let parsed;
|
|
@@ -18592,7 +18763,7 @@ function readDiscovery(home = difftoolHome()) {
|
|
|
18592
18763
|
const path = discoveryPath(home);
|
|
18593
18764
|
if (!existsSync19(path)) return null;
|
|
18594
18765
|
try {
|
|
18595
|
-
return parseDiscovery(
|
|
18766
|
+
return parseDiscovery(readFileSync23(path, "utf-8"));
|
|
18596
18767
|
} catch {
|
|
18597
18768
|
return null;
|
|
18598
18769
|
}
|
|
@@ -23083,12 +23254,12 @@ __export(perceptual_diff_exports, {
|
|
|
23083
23254
|
decodeImage: () => decodeImage,
|
|
23084
23255
|
isIdentical: () => isIdentical
|
|
23085
23256
|
});
|
|
23086
|
-
import { readFileSync as
|
|
23257
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
23087
23258
|
import { extname as extname3 } from "path";
|
|
23088
23259
|
function decodeImage(path) {
|
|
23089
23260
|
let buf;
|
|
23090
23261
|
try {
|
|
23091
|
-
buf =
|
|
23262
|
+
buf = readFileSync24(path);
|
|
23092
23263
|
} catch {
|
|
23093
23264
|
return null;
|
|
23094
23265
|
}
|
|
@@ -23112,7 +23283,7 @@ async function decodeWithFallback(path, pluginDecode) {
|
|
|
23112
23283
|
if (core !== null) return core;
|
|
23113
23284
|
let buf;
|
|
23114
23285
|
try {
|
|
23115
|
-
buf =
|
|
23286
|
+
buf = readFileSync24(path);
|
|
23116
23287
|
} catch {
|
|
23117
23288
|
return null;
|
|
23118
23289
|
}
|
|
@@ -23163,7 +23334,7 @@ var init_perceptual_diff = __esm({
|
|
|
23163
23334
|
// src/cli.ts
|
|
23164
23335
|
import { existsSync as existsSync20, mkdirSync as mkdirSync17, realpathSync, statSync as statSync8 } from "fs";
|
|
23165
23336
|
import { tmpdir as tmpdir2 } from "os";
|
|
23166
|
-
import { basename as basename5, join as
|
|
23337
|
+
import { basename as basename5, join as join25, resolve as resolve12 } from "path";
|
|
23167
23338
|
|
|
23168
23339
|
// src/cli-subcommands.ts
|
|
23169
23340
|
import { resolve as resolve3 } from "path";
|
|
@@ -24133,12 +24304,17 @@ function seedSvgBlobs(fileId, diff) {
|
|
|
24133
24304
|
function demoReviewNotes(filePath) {
|
|
24134
24305
|
if (filePath !== "src/auth/session.ts") return [];
|
|
24135
24306
|
return [
|
|
24136
|
-
|
|
24307
|
+
// The body carries a SARIF embedded link — `[text](0)` indexes `related` —
|
|
24308
|
+
// which renders as a jump-to-line link into the other changed file (doc 20
|
|
24309
|
+
// §20.6).
|
|
24310
|
+
{ guid: "demo-note-rationale", line: 14, side: "new", kind: "rationale", body: "`createSession` is **async** now because session state moved from an in-process `Map` to [the Redis client](0); callers must `await` it.", confidence: 0.9, producer: "Claude Code", related: [{ uri: "src/db/redis.ts", line: 1 }] },
|
|
24137
24311
|
// The proof note carries two artifacts: mocked test output (always a code
|
|
24138
24312
|
// block) and a Mermaid sequence diagram — with the mermaid content plugin
|
|
24139
24313
|
// installed it renders inline as a diagram (doc 29); without it, the
|
|
24140
24314
|
// fail-soft code block still tells the story.
|
|
24141
|
-
|
|
24315
|
+
// Body deliberately uses block markdown (paragraph + list) to showcase the
|
|
24316
|
+
// renderer's block support (doc 20 §20.6).
|
|
24317
|
+
{ guid: "demo-note-proof", line: 23, side: "new", kind: "proof", body: "The TTL is written atomically with the value via the EX option, so a session can never be stored without an expiry.\n\n- `SET` carries `EX 3600` in a single round trip\n- No window exists where a session is stored without one", producer: "Claude Code", artifacts: [
|
|
24142
24318
|
{ uri: ".pr-notes/artifacts/session-ttl.test.txt", content: "PASS session.test.ts\n \u2713 createSession writes value and TTL atomically (4 ms)\n \u2713 a session always has an expiry (2 ms)\n\nTests: 2 passed, 2 total" },
|
|
24143
24319
|
{ uri: ".pr-notes/artifacts/session-flow.mmd", content: "sequenceDiagram\n participant C as Caller\n participant S as createSession\n participant R as Redis\n C->>S: createSession(userId)\n S->>S: id = randomUUID()\n S->>R: SET session:id {userId} EX 3600\n Note over R: value + TTL written atomically\n R-->>S: OK\n S-->>C: id" }
|
|
24144
24320
|
] },
|
|
@@ -24279,11 +24455,12 @@ async function setupReviewNoteReply(fileIdMap) {
|
|
|
24279
24455
|
|
|
24280
24456
|
// src/git/diff.ts
|
|
24281
24457
|
init_zod();
|
|
24282
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as
|
|
24458
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync6, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
24283
24459
|
import { tmpdir } from "os";
|
|
24284
|
-
import { basename as basename3, dirname as
|
|
24460
|
+
import { basename as basename3, dirname as dirname5, join as join7, resolve as resolve4 } from "path";
|
|
24285
24461
|
|
|
24286
24462
|
// src/git/parseDiff.ts
|
|
24463
|
+
init_lfs();
|
|
24287
24464
|
function parseDiff(raw2) {
|
|
24288
24465
|
const files = [];
|
|
24289
24466
|
const fileChunks = raw2.split(/^diff --git /m).filter(Boolean);
|
|
@@ -24317,10 +24494,20 @@ function parseDiff(raw2) {
|
|
|
24317
24494
|
continue;
|
|
24318
24495
|
}
|
|
24319
24496
|
const hunks = parseHunks(chunk.slice(headerEnd));
|
|
24497
|
+
if (hunksAreLfsPointer(hunks)) {
|
|
24498
|
+
files.push({ filePath, oldPath, status, hunks: [], isBinary: true });
|
|
24499
|
+
continue;
|
|
24500
|
+
}
|
|
24320
24501
|
files.push({ filePath, oldPath, status, hunks, isBinary: false });
|
|
24321
24502
|
}
|
|
24322
24503
|
return files;
|
|
24323
24504
|
}
|
|
24505
|
+
function hunksAreLfsPointer(hunks) {
|
|
24506
|
+
if (hunks.length !== 1) return false;
|
|
24507
|
+
const lines = hunks[0].lines;
|
|
24508
|
+
const side = (types) => lines.filter((l) => types.includes(l.type)).map((l) => l.content).join("\n") + "\n";
|
|
24509
|
+
return isLfsPointer(side(["context", "add"])) || isLfsPointer(side(["context", "remove"]));
|
|
24510
|
+
}
|
|
24324
24511
|
function parseHunks(raw2) {
|
|
24325
24512
|
const hunks = [];
|
|
24326
24513
|
const hunkRegex = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@(.*)/gm;
|
|
@@ -24501,9 +24688,9 @@ function getDiffArgs(mode) {
|
|
|
24501
24688
|
function directComparisonRoots(mode) {
|
|
24502
24689
|
const rootOf = (p) => {
|
|
24503
24690
|
try {
|
|
24504
|
-
return statSync2(p).isDirectory() ? p :
|
|
24691
|
+
return statSync2(p).isDirectory() ? p : dirname5(p);
|
|
24505
24692
|
} catch {
|
|
24506
|
-
return
|
|
24693
|
+
return dirname5(p);
|
|
24507
24694
|
}
|
|
24508
24695
|
};
|
|
24509
24696
|
return { rootA: rootOf(mode.pathA), rootB: rootOf(mode.pathB) };
|
|
@@ -24531,14 +24718,14 @@ function getDirectComparisonFiles(mode, cwd) {
|
|
|
24531
24718
|
function diffRawContent(displayPath, oldContent, newContent) {
|
|
24532
24719
|
const rel = displayPath.replace(/\\/g, "/").replace(/^\/+/, "").replace(/(^|\/)\.\.(?=\/|$)/g, "$1_");
|
|
24533
24720
|
const safeRel = rel === "" ? "file" : rel;
|
|
24534
|
-
const work = mkdtempSync(
|
|
24535
|
-
const rootA =
|
|
24536
|
-
const rootB =
|
|
24537
|
-
const oldAbs =
|
|
24538
|
-
const newAbs =
|
|
24721
|
+
const work = mkdtempSync(join7(tmpdir(), "glassbox-difftool-append-"));
|
|
24722
|
+
const rootA = join7(work, "a");
|
|
24723
|
+
const rootB = join7(work, "b");
|
|
24724
|
+
const oldAbs = join7(rootA, safeRel);
|
|
24725
|
+
const newAbs = join7(rootB, safeRel);
|
|
24539
24726
|
try {
|
|
24540
|
-
mkdirSync7(
|
|
24541
|
-
mkdirSync7(
|
|
24727
|
+
mkdirSync7(dirname5(oldAbs), { recursive: true });
|
|
24728
|
+
mkdirSync7(dirname5(newAbs), { recursive: true });
|
|
24542
24729
|
writeFileSync5(oldAbs, oldContent);
|
|
24543
24730
|
writeFileSync5(newAbs, newContent);
|
|
24544
24731
|
const rawDiff = gitOrEmpty(["diff", "--no-index", "-U3", toGitArg(oldAbs), toGitArg(newAbs)], work);
|
|
@@ -24602,7 +24789,7 @@ function getAllFiles(repoRoot) {
|
|
|
24602
24789
|
function createNewFileDiff(filePath, repoRoot) {
|
|
24603
24790
|
let content;
|
|
24604
24791
|
try {
|
|
24605
|
-
const buf =
|
|
24792
|
+
const buf = readFileSync6(resolve4(repoRoot, filePath));
|
|
24606
24793
|
const checkLen = Math.min(buf.length, 8192);
|
|
24607
24794
|
for (let i = 0; i < checkLen; i++) {
|
|
24608
24795
|
if (buf[i] === 0) {
|
|
@@ -24638,7 +24825,7 @@ function getFileContent(filePath, ref, cwd) {
|
|
|
24638
24825
|
const repoRoot = getRepoRoot(cwd);
|
|
24639
24826
|
try {
|
|
24640
24827
|
if (ref === "working") {
|
|
24641
|
-
return
|
|
24828
|
+
return readFileSync6(resolve4(repoRoot, filePath), "utf-8");
|
|
24642
24829
|
}
|
|
24643
24830
|
return git(["show", `${ref}:${filePath}`], repoRoot);
|
|
24644
24831
|
} catch {
|
|
@@ -24648,9 +24835,9 @@ function getFileContent(filePath, ref, cwd) {
|
|
|
24648
24835
|
function getModeFileContent(mode, filePath, side, cwd) {
|
|
24649
24836
|
if (mode.type === "diff") {
|
|
24650
24837
|
const { rootA, rootB } = directComparisonRoots(mode);
|
|
24651
|
-
const abs =
|
|
24838
|
+
const abs = join7(side === "old" ? rootA : rootB, filePath);
|
|
24652
24839
|
try {
|
|
24653
|
-
return
|
|
24840
|
+
return readFileSync6(abs, "utf-8");
|
|
24654
24841
|
} catch {
|
|
24655
24842
|
return "";
|
|
24656
24843
|
}
|
|
@@ -24695,8 +24882,8 @@ function getSingleFileDiff(mode, filePath, repoRoot, extraFlags = "") {
|
|
|
24695
24882
|
if (mode.type === "ground-truth") return null;
|
|
24696
24883
|
if (mode.type === "diff") {
|
|
24697
24884
|
const { rootA, rootB } = directComparisonRoots(mode);
|
|
24698
|
-
const oldAbs =
|
|
24699
|
-
const newAbs =
|
|
24885
|
+
const oldAbs = join7(rootA, filePath);
|
|
24886
|
+
const newAbs = join7(rootB, filePath);
|
|
24700
24887
|
if (!existsSync6(oldAbs) || !existsSync6(newAbs)) return null;
|
|
24701
24888
|
const args2 = ["diff", "--no-index", "-U3"];
|
|
24702
24889
|
if (extraFlags) args2.push(...extraFlags.split(" ").filter(Boolean));
|
|
@@ -24762,8 +24949,8 @@ function getModeArgs(mode) {
|
|
|
24762
24949
|
}
|
|
24763
24950
|
|
|
24764
24951
|
// src/git/gitignore.ts
|
|
24765
|
-
import { existsSync as existsSync7, readFileSync as
|
|
24766
|
-
import { basename as basename4, dirname as
|
|
24952
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
24953
|
+
import { basename as basename4, dirname as dirname6, join as join8 } from "path";
|
|
24767
24954
|
var GLASSBOX_GITIGNORE_LINES = ["/.glassbox/*", "!/.glassbox/settings.json"];
|
|
24768
24955
|
function isGlassboxPattern(token) {
|
|
24769
24956
|
const core = token.replace(/^!/, "").replace(/^\//, "").trim();
|
|
@@ -24814,10 +25001,10 @@ function computeGitignore(existing) {
|
|
|
24814
25001
|
}
|
|
24815
25002
|
function ensureGlassboxGitignored(glassboxDir) {
|
|
24816
25003
|
if (basename4(glassboxDir) !== ".glassbox") return { changed: false };
|
|
24817
|
-
const targetDir =
|
|
25004
|
+
const targetDir = dirname6(glassboxDir);
|
|
24818
25005
|
if (!isGitRepo(targetDir)) return { changed: false };
|
|
24819
|
-
const gitignorePath =
|
|
24820
|
-
const existing = existsSync7(gitignorePath) ?
|
|
25006
|
+
const gitignorePath = join8(targetDir, ".gitignore");
|
|
25007
|
+
const existing = existsSync7(gitignorePath) ? readFileSync7(gitignorePath, "utf-8") : null;
|
|
24821
25008
|
const { changed, content } = computeGitignore(existing);
|
|
24822
25009
|
if (changed) writeFileSync6(gitignorePath, content, "utf-8");
|
|
24823
25010
|
return { changed };
|
|
@@ -24825,15 +25012,15 @@ function ensureGlassboxGitignored(glassboxDir) {
|
|
|
24825
25012
|
|
|
24826
25013
|
// src/lock.ts
|
|
24827
25014
|
init_zod();
|
|
24828
|
-
import { existsSync as existsSync8, readFileSync as
|
|
24829
|
-
import { join as
|
|
25015
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
25016
|
+
import { join as join9 } from "path";
|
|
24830
25017
|
var lockPath = null;
|
|
24831
25018
|
var LockFileSchema = external_exports.object({ pid: external_exports.number().int() });
|
|
24832
25019
|
function acquireLock(dataDir) {
|
|
24833
|
-
lockPath =
|
|
25020
|
+
lockPath = join9(dataDir, "glassbox.lock");
|
|
24834
25021
|
if (existsSync8(lockPath)) {
|
|
24835
25022
|
try {
|
|
24836
|
-
const raw2 = JSON.parse(
|
|
25023
|
+
const raw2 = JSON.parse(readFileSync8(lockPath, "utf-8"));
|
|
24837
25024
|
const contents = LockFileSchema.parse(raw2);
|
|
24838
25025
|
const pid = contents.pid;
|
|
24839
25026
|
try {
|
|
@@ -25003,23 +25190,23 @@ async function updateReviewDiffs(reviewId, newDiffs, headCommit) {
|
|
|
25003
25190
|
|
|
25004
25191
|
// src/server.ts
|
|
25005
25192
|
import { serve } from "@hono/node-server";
|
|
25006
|
-
import { existsSync as existsSync16, readFileSync as
|
|
25193
|
+
import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
|
|
25007
25194
|
import { Hono as Hono21 } from "hono";
|
|
25008
|
-
import { dirname as
|
|
25009
|
-
import { fileURLToPath as
|
|
25195
|
+
import { dirname as dirname9, join as join21 } from "path";
|
|
25196
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
25010
25197
|
|
|
25011
25198
|
// src/channel-config.ts
|
|
25012
25199
|
init_zod();
|
|
25013
|
-
import { existsSync as existsSync9, readFileSync as
|
|
25014
|
-
import { dirname as
|
|
25015
|
-
import { fileURLToPath } from "url";
|
|
25200
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
25201
|
+
import { dirname as dirname7, join as join10, resolve as resolve5 } from "path";
|
|
25202
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
25016
25203
|
var MCP_SERVER_KEY = "glassbox-channel";
|
|
25017
25204
|
var McpConfigSchema = external_exports.object({
|
|
25018
25205
|
mcpServers: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
25019
25206
|
}).loose();
|
|
25020
25207
|
var HealthResponseSchema = external_exports.object({ ok: external_exports.boolean() });
|
|
25021
25208
|
function getChannelServerPath() {
|
|
25022
|
-
const thisDir =
|
|
25209
|
+
const thisDir = dirname7(fileURLToPath2(import.meta.url));
|
|
25023
25210
|
const distPath = resolve5(thisDir, "channel.js");
|
|
25024
25211
|
if (existsSync9(distPath)) {
|
|
25025
25212
|
return { command: process.execPath, args: [distPath] };
|
|
@@ -25035,12 +25222,12 @@ function projectRoot(dataDir) {
|
|
|
25035
25222
|
}
|
|
25036
25223
|
function registerChannel(dataDir) {
|
|
25037
25224
|
const root = projectRoot(dataDir);
|
|
25038
|
-
const mcpPath =
|
|
25225
|
+
const mcpPath = join10(root, ".mcp.json");
|
|
25039
25226
|
const { command, args } = getChannelServerPath();
|
|
25040
25227
|
let config2 = {};
|
|
25041
25228
|
if (existsSync9(mcpPath)) {
|
|
25042
25229
|
try {
|
|
25043
|
-
const raw2 = JSON.parse(
|
|
25230
|
+
const raw2 = JSON.parse(readFileSync9(mcpPath, "utf-8"));
|
|
25044
25231
|
const parsed = McpConfigSchema.safeParse(raw2);
|
|
25045
25232
|
if (parsed.success) config2 = parsed.data;
|
|
25046
25233
|
} catch {
|
|
@@ -25055,10 +25242,10 @@ function registerChannel(dataDir) {
|
|
|
25055
25242
|
}
|
|
25056
25243
|
function unregisterChannel(dataDir) {
|
|
25057
25244
|
const root = projectRoot(dataDir);
|
|
25058
|
-
const mcpPath =
|
|
25245
|
+
const mcpPath = join10(root, ".mcp.json");
|
|
25059
25246
|
if (!existsSync9(mcpPath)) return;
|
|
25060
25247
|
try {
|
|
25061
|
-
const raw2 = JSON.parse(
|
|
25248
|
+
const raw2 = JSON.parse(readFileSync9(mcpPath, "utf-8"));
|
|
25062
25249
|
const parsed = McpConfigSchema.safeParse(raw2);
|
|
25063
25250
|
if (!parsed.success) return;
|
|
25064
25251
|
const config2 = parsed.data;
|
|
@@ -25073,7 +25260,7 @@ function unregisterChannel(dataDir) {
|
|
|
25073
25260
|
}
|
|
25074
25261
|
function getChannelPort(dataDir) {
|
|
25075
25262
|
try {
|
|
25076
|
-
const portStr =
|
|
25263
|
+
const portStr = readFileSync9(join10(dataDir, "channel-port"), "utf-8").trim();
|
|
25077
25264
|
const port = parseInt(portStr, 10);
|
|
25078
25265
|
return isNaN(port) ? null : port;
|
|
25079
25266
|
} catch {
|
|
@@ -25094,7 +25281,7 @@ async function isChannelAlive(dataDir) {
|
|
|
25094
25281
|
}
|
|
25095
25282
|
function getChannelSecret(dataDir) {
|
|
25096
25283
|
try {
|
|
25097
|
-
const secret =
|
|
25284
|
+
const secret = readFileSync9(join10(dataDir, "channel-secret"), "utf-8").trim();
|
|
25098
25285
|
return secret === "" ? null : secret;
|
|
25099
25286
|
} catch {
|
|
25100
25287
|
return null;
|
|
@@ -25203,6 +25390,42 @@ init_zod();
|
|
|
25203
25390
|
// src/review-notes/format.ts
|
|
25204
25391
|
init_store();
|
|
25205
25392
|
init_view();
|
|
25393
|
+
var EXPORT_MIN_HEADING = 4;
|
|
25394
|
+
var ITEM_INDENT = " ";
|
|
25395
|
+
var FENCE_RE2 = /^\s*(?:```|~~~)/;
|
|
25396
|
+
var ATX_RE = /^( {0,3})(#{1,6})(\s)/;
|
|
25397
|
+
function indentLines(text, indent) {
|
|
25398
|
+
return text.split("\n").map((line) => line.trim() === "" ? "" : indent + line);
|
|
25399
|
+
}
|
|
25400
|
+
function demoteHeadings(body, minLevel) {
|
|
25401
|
+
const lines = body.split("\n");
|
|
25402
|
+
const levels = [];
|
|
25403
|
+
let inFence = false;
|
|
25404
|
+
for (const line of lines) {
|
|
25405
|
+
if (FENCE_RE2.test(line)) inFence = !inFence;
|
|
25406
|
+
else if (!inFence) {
|
|
25407
|
+
const m = ATX_RE.exec(line);
|
|
25408
|
+
if (m !== null) levels.push(m[2].length);
|
|
25409
|
+
}
|
|
25410
|
+
}
|
|
25411
|
+
if (levels.length === 0) return body;
|
|
25412
|
+
const shift = minLevel - Math.min(...levels);
|
|
25413
|
+
if (shift <= 0) return body;
|
|
25414
|
+
inFence = false;
|
|
25415
|
+
return lines.map((line) => {
|
|
25416
|
+
if (FENCE_RE2.test(line)) {
|
|
25417
|
+
inFence = !inFence;
|
|
25418
|
+
return line;
|
|
25419
|
+
}
|
|
25420
|
+
if (inFence) return line;
|
|
25421
|
+
return line.replace(ATX_RE, (_m, pad, hashes, space) => `${pad}${"#".repeat(Math.min(6, hashes.length + shift))}${space}`);
|
|
25422
|
+
}).join("\n");
|
|
25423
|
+
}
|
|
25424
|
+
function noteListItem(label, body) {
|
|
25425
|
+
const trimmed = body.replace(/\s+$/, "");
|
|
25426
|
+
if (!trimmed.includes("\n")) return [`- ${label}: ${trimmed}`];
|
|
25427
|
+
return [`- ${label}`, "", ...indentLines(trimmed, ITEM_INDENT), ""];
|
|
25428
|
+
}
|
|
25206
25429
|
function notesByFile(repoRoot, filePaths) {
|
|
25207
25430
|
const out = [];
|
|
25208
25431
|
for (const file2 of filePaths) {
|
|
@@ -25222,7 +25445,7 @@ function reviewNotesPromptSection(repoRoot, filePaths) {
|
|
|
25222
25445
|
for (const { file: file2, notes } of grouped) {
|
|
25223
25446
|
lines.push(`${file2}:`);
|
|
25224
25447
|
for (const n of notes) {
|
|
25225
|
-
lines.push(
|
|
25448
|
+
lines.push(...noteListItem(`[${REVIEW_NOTE_LABELS[n.kind] ?? n.kind}, L${String(n.line)}]`, n.body));
|
|
25226
25449
|
}
|
|
25227
25450
|
lines.push("");
|
|
25228
25451
|
}
|
|
@@ -25242,7 +25465,7 @@ function reviewNotesExportSection(repoRoot, filePaths) {
|
|
|
25242
25465
|
lines.push("");
|
|
25243
25466
|
for (const n of notes) {
|
|
25244
25467
|
const who = n.producer !== void 0 ? ` _(${n.producer})_` : "";
|
|
25245
|
-
lines.push(
|
|
25468
|
+
lines.push(...noteListItem(`**Line ${String(n.line)}** [${n.kind}]${who}`, demoteHeadings(n.body, EXPORT_MIN_HEADING)));
|
|
25246
25469
|
}
|
|
25247
25470
|
lines.push("");
|
|
25248
25471
|
}
|
|
@@ -28295,8 +28518,8 @@ init_queries();
|
|
|
28295
28518
|
init_attachment_queries();
|
|
28296
28519
|
init_queries();
|
|
28297
28520
|
init_schemas3();
|
|
28298
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as
|
|
28299
|
-
import { join as
|
|
28521
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
28522
|
+
import { join as join14 } from "path";
|
|
28300
28523
|
init_image_metadata();
|
|
28301
28524
|
|
|
28302
28525
|
// src/utils/formatReviewMode.ts
|
|
@@ -28591,9 +28814,9 @@ function buildReviewMarkdown(args) {
|
|
|
28591
28814
|
|
|
28592
28815
|
// src/export/generate.ts
|
|
28593
28816
|
function deleteReviewExport(reviewId, repoRoot) {
|
|
28594
|
-
const exportDir =
|
|
28817
|
+
const exportDir = join14(repoRoot, ".glassbox");
|
|
28595
28818
|
for (const ext of ["md", "json"]) {
|
|
28596
|
-
const archivePath =
|
|
28819
|
+
const archivePath = join14(exportDir, `review-${reviewId}.${ext}`);
|
|
28597
28820
|
if (existsSync13(archivePath)) unlinkSync2(archivePath);
|
|
28598
28821
|
}
|
|
28599
28822
|
}
|
|
@@ -28621,7 +28844,7 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
28621
28844
|
if (!review) throw new Error("Review not found");
|
|
28622
28845
|
const files = await getReviewFiles(reviewId);
|
|
28623
28846
|
const annotations = await getAnnotationsForReview(reviewId);
|
|
28624
|
-
const exportDir =
|
|
28847
|
+
const exportDir = join14(repoRoot, ".glassbox");
|
|
28625
28848
|
mkdirSync10(exportDir, { recursive: true });
|
|
28626
28849
|
const modeLabel = formatReviewMode(review.mode, review.mode_args);
|
|
28627
28850
|
const exportDate = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -28654,20 +28877,20 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
28654
28877
|
resolveDims: resolveImageDims
|
|
28655
28878
|
});
|
|
28656
28879
|
const json2 = JSON.stringify(exportData, null, 2);
|
|
28657
|
-
const archivePath =
|
|
28880
|
+
const archivePath = join14(exportDir, `review-${review.id}.md`);
|
|
28658
28881
|
writeFileSync11(archivePath, content, "utf-8");
|
|
28659
|
-
writeFileSync11(
|
|
28882
|
+
writeFileSync11(join14(exportDir, `review-${review.id}.json`), json2, "utf-8");
|
|
28660
28883
|
if (isCurrent) {
|
|
28661
|
-
const latestPath =
|
|
28884
|
+
const latestPath = join14(exportDir, "latest-review.md");
|
|
28662
28885
|
writeFileSync11(latestPath, content, "utf-8");
|
|
28663
|
-
writeFileSync11(
|
|
28886
|
+
writeFileSync11(join14(exportDir, "latest-review.json"), json2, "utf-8");
|
|
28664
28887
|
return latestPath;
|
|
28665
28888
|
}
|
|
28666
28889
|
return archivePath;
|
|
28667
28890
|
}
|
|
28668
28891
|
function resolveImageDims(absPath) {
|
|
28669
28892
|
try {
|
|
28670
|
-
const meta3 = extractMetadata(
|
|
28893
|
+
const meta3 = extractMetadata(readFileSync13(absPath), absPath);
|
|
28671
28894
|
if (meta3.width !== null && meta3.height !== null) {
|
|
28672
28895
|
return { width: meta3.width, height: meta3.height };
|
|
28673
28896
|
}
|
|
@@ -28775,7 +28998,7 @@ init_store2();
|
|
|
28775
28998
|
init_attachment_queries();
|
|
28776
28999
|
init_ids();
|
|
28777
29000
|
init_queries();
|
|
28778
|
-
import { readFileSync as
|
|
29001
|
+
import { readFileSync as readFileSync14, statSync as statSync5 } from "fs";
|
|
28779
29002
|
import { Hono as Hono5 } from "hono";
|
|
28780
29003
|
|
|
28781
29004
|
// src/utils/mime.ts
|
|
@@ -28940,7 +29163,7 @@ attachmentsRoutes.get("/attachments/:id/raw", async (c) => {
|
|
|
28940
29163
|
try {
|
|
28941
29164
|
const stat = statSync5(attachment.stored_path);
|
|
28942
29165
|
if (!stat.isFile()) return c.text("Not found", 404);
|
|
28943
|
-
const bytes =
|
|
29166
|
+
const bytes = readFileSync14(attachment.stored_path);
|
|
28944
29167
|
const asciiName = attachment.original_filename.replace(/[^\x20-\x7E]/g, "_").replace(/["\\]/g, "_");
|
|
28945
29168
|
const utf8Name = encodeURIComponent(attachment.original_filename);
|
|
28946
29169
|
return new Response(new Uint8Array(bytes), {
|
|
@@ -29151,9 +29374,10 @@ init_queries();
|
|
|
29151
29374
|
import { Hono as Hono8 } from "hono";
|
|
29152
29375
|
|
|
29153
29376
|
// src/git/image.ts
|
|
29377
|
+
init_lfs();
|
|
29154
29378
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
29155
|
-
import { readFileSync as
|
|
29156
|
-
import { join as
|
|
29379
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
29380
|
+
import { join as join15, resolve as resolve8 } from "path";
|
|
29157
29381
|
init_image_metadata();
|
|
29158
29382
|
function getOldRef(mode) {
|
|
29159
29383
|
switch (mode.type) {
|
|
@@ -29213,18 +29437,26 @@ function gitShowFile(ref, filePath, repoRoot) {
|
|
|
29213
29437
|
const spec = ref === ":" ? `:${filePath}` : `${ref}:${filePath}`;
|
|
29214
29438
|
const result = spawnSync8("git", ["show", spec], { cwd: repoRoot, maxBuffer: 50 * 1024 * 1024, env: scrubbedGitEnv() });
|
|
29215
29439
|
if (result.status !== 0 || result.stdout.length === 0) return null;
|
|
29440
|
+
if (isLfsPointer(result.stdout)) {
|
|
29441
|
+
const smudged = spawnSync8("git", ["cat-file", "--filters", spec], { cwd: repoRoot, maxBuffer: 50 * 1024 * 1024, env: scrubbedGitEnv() });
|
|
29442
|
+
if (smudged.status === 0 && smudged.stdout.length > 0 && !isLfsPointer(smudged.stdout)) {
|
|
29443
|
+
return smudged.stdout;
|
|
29444
|
+
}
|
|
29445
|
+
return null;
|
|
29446
|
+
}
|
|
29216
29447
|
return result.stdout;
|
|
29217
29448
|
}
|
|
29218
29449
|
function readWorkingFile(filePath, repoRoot) {
|
|
29219
29450
|
try {
|
|
29220
|
-
|
|
29451
|
+
const data = readFileSync15(resolve8(repoRoot, filePath));
|
|
29452
|
+
return isLfsPointer(data) ? null : data;
|
|
29221
29453
|
} catch {
|
|
29222
29454
|
return null;
|
|
29223
29455
|
}
|
|
29224
29456
|
}
|
|
29225
29457
|
function readDiskImage(absPath) {
|
|
29226
29458
|
try {
|
|
29227
|
-
const data =
|
|
29459
|
+
const data = readFileSync15(absPath);
|
|
29228
29460
|
return { data, size: data.length };
|
|
29229
29461
|
} catch {
|
|
29230
29462
|
return null;
|
|
@@ -29235,7 +29467,7 @@ function groundTruthEntry(mode, filePath) {
|
|
|
29235
29467
|
}
|
|
29236
29468
|
function getOldImage(mode, filePath, oldPath, repoRoot) {
|
|
29237
29469
|
if (mode.type === "diff") {
|
|
29238
|
-
return readDiskImage(
|
|
29470
|
+
return readDiskImage(join15(directComparisonRoots(mode).rootA, oldPath ?? filePath));
|
|
29239
29471
|
}
|
|
29240
29472
|
if (mode.type === "ground-truth") {
|
|
29241
29473
|
const entry = groundTruthEntry(mode, filePath);
|
|
@@ -29255,7 +29487,7 @@ function getOldImage(mode, filePath, oldPath, repoRoot) {
|
|
|
29255
29487
|
}
|
|
29256
29488
|
function getNewImage(mode, filePath, repoRoot) {
|
|
29257
29489
|
if (mode.type === "diff") {
|
|
29258
|
-
return readDiskImage(
|
|
29490
|
+
return readDiskImage(join15(directComparisonRoots(mode).rootB, filePath));
|
|
29259
29491
|
}
|
|
29260
29492
|
if (mode.type === "ground-truth") {
|
|
29261
29493
|
const entry = groundTruthEntry(mode, filePath);
|
|
@@ -29356,7 +29588,7 @@ imageRoutes.get("/image/:fileId/:side", async (c) => {
|
|
|
29356
29588
|
// src/routes/api/outline.ts
|
|
29357
29589
|
init_queries();
|
|
29358
29590
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
29359
|
-
import { readFileSync as
|
|
29591
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
29360
29592
|
import { Hono as Hono9 } from "hono";
|
|
29361
29593
|
import { resolve as resolve9 } from "path";
|
|
29362
29594
|
|
|
@@ -29734,7 +29966,7 @@ outlineRoutes.get("/symbol-definition", async (c) => {
|
|
|
29734
29966
|
}
|
|
29735
29967
|
let content = "";
|
|
29736
29968
|
try {
|
|
29737
|
-
content =
|
|
29969
|
+
content = readFileSync16(resolve9(repoRoot, filePath), "utf-8");
|
|
29738
29970
|
} catch {
|
|
29739
29971
|
continue;
|
|
29740
29972
|
}
|
|
@@ -29776,7 +30008,7 @@ import { Hono as Hono10 } from "hono";
|
|
|
29776
30008
|
init_install();
|
|
29777
30009
|
init_loader();
|
|
29778
30010
|
import { existsSync as existsSync14 } from "fs";
|
|
29779
|
-
import { join as
|
|
30011
|
+
import { join as join16 } from "path";
|
|
29780
30012
|
|
|
29781
30013
|
// src/plugins/readiness.ts
|
|
29782
30014
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -29832,7 +30064,7 @@ function listAvailablePlugins(opts) {
|
|
|
29832
30064
|
const out = [];
|
|
29833
30065
|
for (const { manifest } of discoverBundledPlugins(bundledDir)) {
|
|
29834
30066
|
if (manifest.autoInstall !== false) continue;
|
|
29835
|
-
if (existsSync14(
|
|
30067
|
+
if (existsSync14(join16(userDir, manifest.id))) continue;
|
|
29836
30068
|
out.push(describeAvailablePlugin(manifest, opts?.runProbe));
|
|
29837
30069
|
}
|
|
29838
30070
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -29849,7 +30081,7 @@ init_loader();
|
|
|
29849
30081
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
29850
30082
|
import { createHash as createHash4 } from "crypto";
|
|
29851
30083
|
import { cpSync as cpSync2, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
29852
|
-
import { join as
|
|
30084
|
+
import { join as join17 } from "path";
|
|
29853
30085
|
var defaultFetchBytes = async (url2) => {
|
|
29854
30086
|
const res = await fetch(url2);
|
|
29855
30087
|
if (!res.ok) throw new Error(`HTTP ${String(res.status)}`);
|
|
@@ -29872,7 +30104,7 @@ async function runProvisionStep(step, installDir, requirements, deps) {
|
|
|
29872
30104
|
return { step: label2, ok: false, skipped: false, detail: `checksum mismatch (expected ${step.sha256})` };
|
|
29873
30105
|
}
|
|
29874
30106
|
}
|
|
29875
|
-
writeFileSync12(
|
|
30107
|
+
writeFileSync12(join17(installDir, step.dest), bytes);
|
|
29876
30108
|
return { step: label2, ok: true, skipped: false, detail: `saved ${step.dest}` };
|
|
29877
30109
|
} catch (e) {
|
|
29878
30110
|
return { step: label2, ok: false, skipped: false, detail: e instanceof Error ? e.message : "download failed" };
|
|
@@ -29907,7 +30139,7 @@ async function installAvailablePlugin(id, deps = {}) {
|
|
|
29907
30139
|
return { id, installed: false, status: "error", requirements: [], provisioned: [], instructions: [], error: "Not a bundled plugin available to install." };
|
|
29908
30140
|
}
|
|
29909
30141
|
const manifest = bundled.manifest;
|
|
29910
|
-
const dest =
|
|
30142
|
+
const dest = join17(userDir, id);
|
|
29911
30143
|
try {
|
|
29912
30144
|
mkdirSync11(userDir, { recursive: true });
|
|
29913
30145
|
cpSync2(bundled.dir, dest, { recursive: true });
|
|
@@ -30027,7 +30259,8 @@ projectSettingsRoutes.patch("/project-settings", async (c) => {
|
|
|
30027
30259
|
|
|
30028
30260
|
// src/routes/api/review-notes.ts
|
|
30029
30261
|
init_store();
|
|
30030
|
-
|
|
30262
|
+
init_lfs();
|
|
30263
|
+
import { readFileSync as readFileSync17, statSync as statSync6 } from "fs";
|
|
30031
30264
|
import { Hono as Hono12 } from "hono";
|
|
30032
30265
|
import { extname as extname2, relative as relative2, resolve as resolve10 } from "path";
|
|
30033
30266
|
var reviewNotesRoutes = new Hono12();
|
|
@@ -30054,7 +30287,8 @@ reviewNotesRoutes.get("/review-notes/artifact", (c) => {
|
|
|
30054
30287
|
try {
|
|
30055
30288
|
const stat = statSync6(abs);
|
|
30056
30289
|
if (!stat.isFile() || stat.size > ARTIFACT_SERVE_MAX_BYTES) return c.text("Not found", 404);
|
|
30057
|
-
const body =
|
|
30290
|
+
const body = readFileSync17(abs);
|
|
30291
|
+
if (isLfsPointer(body)) return c.text("Not found", 404);
|
|
30058
30292
|
return c.body(body, 200, { "Content-Type": contentType });
|
|
30059
30293
|
} catch {
|
|
30060
30294
|
return c.text("Not found", 404);
|
|
@@ -30076,12 +30310,12 @@ import { Hono as Hono13 } from "hono";
|
|
|
30076
30310
|
// src/export/on-complete-hook.ts
|
|
30077
30311
|
import { spawn as spawn2 } from "child_process";
|
|
30078
30312
|
import { appendFileSync } from "fs";
|
|
30079
|
-
import { join as
|
|
30313
|
+
import { join as join18 } from "path";
|
|
30080
30314
|
function runOnCompleteHook(command, ctx) {
|
|
30081
30315
|
if (command === null || command.trim() === "") {
|
|
30082
30316
|
return Promise.resolve({ ran: false, ok: true, exitCode: 0 });
|
|
30083
30317
|
}
|
|
30084
|
-
const logPath =
|
|
30318
|
+
const logPath = join18(ctx.repoRoot, ".glassbox", "on-complete.log");
|
|
30085
30319
|
const log = (s) => {
|
|
30086
30320
|
try {
|
|
30087
30321
|
appendFileSync(logPath, s);
|
|
@@ -30298,7 +30532,7 @@ apiRoutes.route("/", systemRoutes);
|
|
|
30298
30532
|
import { spawnSync as spawnSync12 } from "child_process";
|
|
30299
30533
|
import { mkdirSync as mkdirSync12 } from "fs";
|
|
30300
30534
|
import { Hono as Hono17 } from "hono";
|
|
30301
|
-
import { join as
|
|
30535
|
+
import { join as join19 } from "path";
|
|
30302
30536
|
init_global_config();
|
|
30303
30537
|
init_compareVersions();
|
|
30304
30538
|
var MIN_CLAUDE_VERSION = "2.1.80";
|
|
@@ -30307,7 +30541,7 @@ channelApiRoutes.get("/status", async (c) => {
|
|
|
30307
30541
|
const config2 = readGlobalConfig();
|
|
30308
30542
|
const enabled = config2.channelEnabled === true;
|
|
30309
30543
|
const repoRoot = c.get("repoRoot");
|
|
30310
|
-
const dataDir =
|
|
30544
|
+
const dataDir = join19(repoRoot, ".glassbox");
|
|
30311
30545
|
const connected = enabled ? await isChannelAlive(dataDir) : false;
|
|
30312
30546
|
return c.json({ enabled, connected });
|
|
30313
30547
|
});
|
|
@@ -30316,7 +30550,7 @@ channelApiRoutes.post("/enable", (c) => {
|
|
|
30316
30550
|
config2.channelEnabled = true;
|
|
30317
30551
|
});
|
|
30318
30552
|
const repoRoot = c.get("repoRoot");
|
|
30319
|
-
const dataDir =
|
|
30553
|
+
const dataDir = join19(repoRoot, ".glassbox");
|
|
30320
30554
|
mkdirSync12(dataDir, { recursive: true });
|
|
30321
30555
|
registerChannel(dataDir);
|
|
30322
30556
|
return c.json({ ok: true });
|
|
@@ -30326,7 +30560,7 @@ channelApiRoutes.post("/disable", (c) => {
|
|
|
30326
30560
|
config2.channelEnabled = false;
|
|
30327
30561
|
});
|
|
30328
30562
|
const repoRoot = c.get("repoRoot");
|
|
30329
|
-
const dataDir =
|
|
30563
|
+
const dataDir = join19(repoRoot, ".glassbox");
|
|
30330
30564
|
unregisterChannel(dataDir);
|
|
30331
30565
|
return c.json({ ok: true });
|
|
30332
30566
|
});
|
|
@@ -30334,7 +30568,7 @@ channelApiRoutes.post("/trigger", async (c) => {
|
|
|
30334
30568
|
const parsed = await parseBody(c, TriggerChannelReqSchema);
|
|
30335
30569
|
if (!parsed.ok) return parsed.response;
|
|
30336
30570
|
const repoRoot = c.get("repoRoot");
|
|
30337
|
-
const dataDir =
|
|
30571
|
+
const dataDir = join19(repoRoot, ".glassbox");
|
|
30338
30572
|
const sent = await triggerChannel(dataDir, parsed.data.message);
|
|
30339
30573
|
if (!sent) {
|
|
30340
30574
|
return errorResponse(c, "Channel not connected", 503);
|
|
@@ -30441,7 +30675,7 @@ difftoolApiRoutes.post("/end", (c) => {
|
|
|
30441
30675
|
});
|
|
30442
30676
|
|
|
30443
30677
|
// src/routes/pages.tsx
|
|
30444
|
-
import { readFileSync as
|
|
30678
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
30445
30679
|
import { Hono as Hono19 } from "hono";
|
|
30446
30680
|
import { relative as relative3, resolve as resolve11 } from "path";
|
|
30447
30681
|
|
|
@@ -30636,12 +30870,23 @@ function truncateDiffLine(content) {
|
|
|
30636
30870
|
function escapeHtml(str) {
|
|
30637
30871
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
30638
30872
|
}
|
|
30873
|
+
function escapeAttr(str) {
|
|
30874
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
30875
|
+
}
|
|
30639
30876
|
|
|
30640
30877
|
// src/utils/noteMarkdown.ts
|
|
30641
30878
|
var SAFE_URL = /^(https?:\/\/|mailto:)/i;
|
|
30642
|
-
function
|
|
30879
|
+
function renderEmbeddedLink(text, destination, ctx) {
|
|
30880
|
+
if (!/^\d+$/.test(destination)) return null;
|
|
30881
|
+
const target = ctx.related.at(Number(destination));
|
|
30882
|
+
if (target === void 0 || target.uri === "" || target.line < 1) return null;
|
|
30883
|
+
return `<a class="ai-note-loclink" data-loc-file="${escapeAttr(target.uri)}" data-loc-line="${String(target.line)}" title="${escapeAttr(`${target.uri}:${String(target.line)}`)}">${text}</a>`;
|
|
30884
|
+
}
|
|
30885
|
+
function renderInline(escaped, ctx) {
|
|
30643
30886
|
let out = escaped.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
30644
30887
|
out = out.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (match, text, url2) => {
|
|
30888
|
+
const embedded = renderEmbeddedLink(text, url2, ctx);
|
|
30889
|
+
if (embedded !== null) return embedded;
|
|
30645
30890
|
if (!SAFE_URL.test(url2)) return match;
|
|
30646
30891
|
return `<a href="${url2}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
|
30647
30892
|
});
|
|
@@ -30650,8 +30895,155 @@ function renderInline(escaped) {
|
|
|
30650
30895
|
out = out.replace(/(^|[^_])_([^_\s][^_]*)_/g, "$1<em>$2</em>");
|
|
30651
30896
|
return out;
|
|
30652
30897
|
}
|
|
30653
|
-
|
|
30654
|
-
|
|
30898
|
+
var MAX_BLOCK_DEPTH = 6;
|
|
30899
|
+
var HEADING_BASE = 3;
|
|
30900
|
+
var FENCE_RE3 = /^ {0,3}(`{3,}|~{3,})\s*(.*)$/;
|
|
30901
|
+
var HEADING_RE2 = /^ {0,3}(#{1,6})\s+(.*)$/;
|
|
30902
|
+
var HR_RE = /^ {0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/;
|
|
30903
|
+
var QUOTE_RE = /^ {0,3}>[ \t]?(.*)$/;
|
|
30904
|
+
var ITEM_RE = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/;
|
|
30905
|
+
var CONTINUATION_RE = /^\s{2,}\S/;
|
|
30906
|
+
function matchItem(line) {
|
|
30907
|
+
const m = ITEM_RE.exec(line);
|
|
30908
|
+
if (m === null) return null;
|
|
30909
|
+
if (HR_RE.test(line)) return null;
|
|
30910
|
+
const ordered = /^\d/.test(m[2]);
|
|
30911
|
+
return {
|
|
30912
|
+
indent: m[1].length,
|
|
30913
|
+
ordered,
|
|
30914
|
+
number: ordered ? parseInt(m[2], 10) : 1,
|
|
30915
|
+
text: m[3]
|
|
30916
|
+
};
|
|
30917
|
+
}
|
|
30918
|
+
function isBlockStart(line) {
|
|
30919
|
+
return FENCE_RE3.test(line) || HEADING_RE2.test(line) || HR_RE.test(line) || QUOTE_RE.test(line) || matchItem(line) !== null;
|
|
30920
|
+
}
|
|
30921
|
+
function dedent(lines) {
|
|
30922
|
+
let min = Infinity;
|
|
30923
|
+
for (const line of lines) {
|
|
30924
|
+
if (line.trim() === "") continue;
|
|
30925
|
+
min = Math.min(min, line.length - line.trimStart().length);
|
|
30926
|
+
}
|
|
30927
|
+
if (min === Infinity || min === 0) return lines;
|
|
30928
|
+
return lines.map((line) => line.trim() === "" ? "" : line.slice(min));
|
|
30929
|
+
}
|
|
30930
|
+
function renderParagraph(lines, ctx) {
|
|
30931
|
+
return `<p>${lines.map((line) => renderInline(line, ctx)).join("<br>")}</p>`;
|
|
30932
|
+
}
|
|
30933
|
+
function renderCodeBlock(lines) {
|
|
30934
|
+
return `<pre><code>${lines.join("\n")}</code></pre>`;
|
|
30935
|
+
}
|
|
30936
|
+
function renderList(run, first, depth, ctx) {
|
|
30937
|
+
const base = first.indent;
|
|
30938
|
+
const items = [];
|
|
30939
|
+
for (const line of run) {
|
|
30940
|
+
const m = matchItem(line);
|
|
30941
|
+
if (m !== null && m.indent <= base) {
|
|
30942
|
+
items.push({ lines: [m.text], nested: [] });
|
|
30943
|
+
continue;
|
|
30944
|
+
}
|
|
30945
|
+
const item = items[items.length - 1];
|
|
30946
|
+
if (item.nested.length > 0 || m !== null) item.nested.push(line);
|
|
30947
|
+
else if (line.trim() === "") item.nested.push(line);
|
|
30948
|
+
else item.lines.push(line.trim());
|
|
30949
|
+
}
|
|
30950
|
+
const tag = first.ordered ? "ol" : "ul";
|
|
30951
|
+
const start = first.ordered && first.number !== 1 ? ` start="${String(first.number)}"` : "";
|
|
30952
|
+
const body = items.map((item) => {
|
|
30953
|
+
const text = item.lines.map((line) => renderInline(line, ctx)).join("<br>");
|
|
30954
|
+
const nested = dedent(item.nested).filter((l, i, a) => !(l === "" && i === a.length - 1));
|
|
30955
|
+
const inner = nested.length > 0 ? renderBlocks(nested, depth + 1, ctx) : "";
|
|
30956
|
+
return `<li>${text}${inner}</li>`;
|
|
30957
|
+
}).join("");
|
|
30958
|
+
return `<${tag}${start}>${body}</${tag}>`;
|
|
30959
|
+
}
|
|
30960
|
+
function renderBlocks(lines, depth, ctx) {
|
|
30961
|
+
if (depth > MAX_BLOCK_DEPTH) {
|
|
30962
|
+
const text = lines.filter((l) => l.trim() !== "");
|
|
30963
|
+
return text.length > 0 ? renderParagraph(text, ctx) : "";
|
|
30964
|
+
}
|
|
30965
|
+
const out = [];
|
|
30966
|
+
let i = 0;
|
|
30967
|
+
while (i < lines.length) {
|
|
30968
|
+
const line = lines[i];
|
|
30969
|
+
if (line.trim() === "") {
|
|
30970
|
+
i++;
|
|
30971
|
+
continue;
|
|
30972
|
+
}
|
|
30973
|
+
const fence = FENCE_RE3.exec(line);
|
|
30974
|
+
if (fence !== null) {
|
|
30975
|
+
const marker = fence[1][0];
|
|
30976
|
+
const body = [];
|
|
30977
|
+
i++;
|
|
30978
|
+
while (i < lines.length) {
|
|
30979
|
+
const close = FENCE_RE3.exec(lines[i]);
|
|
30980
|
+
if (close !== null && close[1][0] === marker && close[2] === "") {
|
|
30981
|
+
i++;
|
|
30982
|
+
break;
|
|
30983
|
+
}
|
|
30984
|
+
body.push(lines[i]);
|
|
30985
|
+
i++;
|
|
30986
|
+
}
|
|
30987
|
+
out.push(renderCodeBlock(body));
|
|
30988
|
+
continue;
|
|
30989
|
+
}
|
|
30990
|
+
const heading = HEADING_RE2.exec(line);
|
|
30991
|
+
if (heading !== null) {
|
|
30992
|
+
const level = Math.min(6, heading[1].length + HEADING_BASE);
|
|
30993
|
+
out.push(`<h${String(level)}>${renderInline(heading[2].replace(/\s+#+\s*$/, ""), ctx)}</h${String(level)}>`);
|
|
30994
|
+
i++;
|
|
30995
|
+
continue;
|
|
30996
|
+
}
|
|
30997
|
+
if (HR_RE.test(line)) {
|
|
30998
|
+
out.push("<hr>");
|
|
30999
|
+
i++;
|
|
31000
|
+
continue;
|
|
31001
|
+
}
|
|
31002
|
+
const quote = QUOTE_RE.exec(line);
|
|
31003
|
+
if (quote !== null) {
|
|
31004
|
+
const inner = [];
|
|
31005
|
+
while (i < lines.length) {
|
|
31006
|
+
const m = QUOTE_RE.exec(lines[i]);
|
|
31007
|
+
if (m === null) break;
|
|
31008
|
+
inner.push(m[1]);
|
|
31009
|
+
i++;
|
|
31010
|
+
}
|
|
31011
|
+
out.push(`<blockquote>${renderBlocks(inner, depth + 1, ctx)}</blockquote>`);
|
|
31012
|
+
continue;
|
|
31013
|
+
}
|
|
31014
|
+
const item = matchItem(line);
|
|
31015
|
+
if (item !== null) {
|
|
31016
|
+
const run = [];
|
|
31017
|
+
while (i < lines.length) {
|
|
31018
|
+
const current = lines[i];
|
|
31019
|
+
if (current.trim() === "") {
|
|
31020
|
+
let k = i;
|
|
31021
|
+
while (k < lines.length && lines[k].trim() === "") k++;
|
|
31022
|
+
if (k < lines.length && (matchItem(lines[k]) !== null || CONTINUATION_RE.test(lines[k]))) {
|
|
31023
|
+
run.push(...lines.slice(i, k));
|
|
31024
|
+
i = k;
|
|
31025
|
+
continue;
|
|
31026
|
+
}
|
|
31027
|
+
break;
|
|
31028
|
+
}
|
|
31029
|
+
if (matchItem(current) === null && !CONTINUATION_RE.test(current)) break;
|
|
31030
|
+
run.push(current);
|
|
31031
|
+
i++;
|
|
31032
|
+
}
|
|
31033
|
+
out.push(renderList(run, item, depth, ctx));
|
|
31034
|
+
continue;
|
|
31035
|
+
}
|
|
31036
|
+
const para = [];
|
|
31037
|
+
while (i < lines.length && lines[i].trim() !== "" && !(para.length > 0 && isBlockStart(lines[i]))) {
|
|
31038
|
+
para.push(lines[i]);
|
|
31039
|
+
i++;
|
|
31040
|
+
}
|
|
31041
|
+
out.push(renderParagraph(para, ctx));
|
|
31042
|
+
}
|
|
31043
|
+
return out.join("");
|
|
31044
|
+
}
|
|
31045
|
+
function renderNoteMarkdown(text, related = []) {
|
|
31046
|
+
return renderBlocks(escapeHtml(text).split("\n"), 0, { related });
|
|
30655
31047
|
}
|
|
30656
31048
|
|
|
30657
31049
|
// src/components/imageDiff.tsx
|
|
@@ -31225,7 +31617,7 @@ function ReviewNoteRows({ notes, repliesByNote }) {
|
|
|
31225
31617
|
/* @__PURE__ */ jsxs4("div", { className: "ai-note-item", children: [
|
|
31226
31618
|
/* @__PURE__ */ jsx4("span", { className: `ai-note-label ai-note-label-${n.kind}`, children: REVIEW_NOTE_LABELS[n.kind] ?? n.kind }),
|
|
31227
31619
|
n.stale === true ? /* @__PURE__ */ jsx4("span", { className: "ai-note-stale-tag", title: "The code this note referred to has changed", children: "outdated" }) : null,
|
|
31228
|
-
/* @__PURE__ */ jsx4("
|
|
31620
|
+
/* @__PURE__ */ jsx4("div", { className: "ai-note-text", children: raw(renderNoteMarkdown(n.body, n.related)) }),
|
|
31229
31621
|
n.producer !== void 0 ? /* @__PURE__ */ jsx4("span", { className: "ai-note-producer", children: n.producer }) : null,
|
|
31230
31622
|
n.guid !== void 0 ? /* @__PURE__ */ jsx4("button", { className: "ai-note-reply-btn", "data-line": String(n.line), children: "Reply" }) : null,
|
|
31231
31623
|
n.stale === true && n.guid !== void 0 ? /* @__PURE__ */ jsxs4("span", { className: "ai-note-stale-actions", children: [
|
|
@@ -31326,10 +31718,10 @@ function AnnotationRows({ annotations }) {
|
|
|
31326
31718
|
|
|
31327
31719
|
// src/themes/config.ts
|
|
31328
31720
|
init_zod();
|
|
31329
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readdirSync as readdirSync4, readFileSync as
|
|
31330
|
-
import { join as
|
|
31721
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readdirSync as readdirSync4, readFileSync as readFileSync18, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
31722
|
+
import { join as join20 } from "path";
|
|
31331
31723
|
init_global_config();
|
|
31332
|
-
var THEMES_DIR =
|
|
31724
|
+
var THEMES_DIR = join20(GLOBAL_CONFIG_DIR, "themes");
|
|
31333
31725
|
var ThemeConfigSliceSchema = external_exports.object({ active: external_exports.string().optional() }).loose();
|
|
31334
31726
|
function getActiveThemeId() {
|
|
31335
31727
|
const config2 = readGlobalConfig();
|
|
@@ -31344,7 +31736,7 @@ function setActiveThemeId(id) {
|
|
|
31344
31736
|
}
|
|
31345
31737
|
function readStoredTheme(filePath) {
|
|
31346
31738
|
try {
|
|
31347
|
-
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(
|
|
31739
|
+
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(readFileSync18(filePath, "utf-8")));
|
|
31348
31740
|
if (!parsed.success) return void 0;
|
|
31349
31741
|
const d = parsed.data;
|
|
31350
31742
|
return { id: d.id, name: d.name, colors: d.colors, builtIn: false, baseTheme: d.baseTheme ?? "" };
|
|
@@ -31357,7 +31749,7 @@ function loadCustomThemes() {
|
|
|
31357
31749
|
const themes = [];
|
|
31358
31750
|
try {
|
|
31359
31751
|
for (const file2 of readdirSync4(THEMES_DIR).filter((f) => f.endsWith(".json"))) {
|
|
31360
|
-
const theme = readStoredTheme(
|
|
31752
|
+
const theme = readStoredTheme(join20(THEMES_DIR, file2));
|
|
31361
31753
|
if (theme !== void 0) themes.push(theme);
|
|
31362
31754
|
}
|
|
31363
31755
|
} catch {
|
|
@@ -31366,17 +31758,17 @@ function loadCustomThemes() {
|
|
|
31366
31758
|
}
|
|
31367
31759
|
function saveCustomTheme(theme) {
|
|
31368
31760
|
mkdirSync13(THEMES_DIR, { recursive: true });
|
|
31369
|
-
const filePath =
|
|
31761
|
+
const filePath = join20(THEMES_DIR, `${theme.id}.json`);
|
|
31370
31762
|
writeFileSync13(filePath, JSON.stringify(theme, null, 2), "utf-8");
|
|
31371
31763
|
}
|
|
31372
31764
|
function deleteCustomTheme(id) {
|
|
31373
|
-
const filePath =
|
|
31765
|
+
const filePath = join20(THEMES_DIR, `${id}.json`);
|
|
31374
31766
|
if (existsSync15(filePath)) {
|
|
31375
31767
|
unlinkSync3(filePath);
|
|
31376
31768
|
}
|
|
31377
31769
|
}
|
|
31378
31770
|
function getCustomTheme(id) {
|
|
31379
|
-
const filePath =
|
|
31771
|
+
const filePath = join20(THEMES_DIR, `${id}.json`);
|
|
31380
31772
|
if (!existsSync15(filePath)) return void 0;
|
|
31381
31773
|
return readStoredTheme(filePath);
|
|
31382
31774
|
}
|
|
@@ -31855,7 +32247,7 @@ pageRoutes.get("/file-raw", (c) => {
|
|
|
31855
32247
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/")) return c.text("Forbidden", 403);
|
|
31856
32248
|
let content;
|
|
31857
32249
|
try {
|
|
31858
|
-
content =
|
|
32250
|
+
content = readFileSync19(abs, "utf-8");
|
|
31859
32251
|
} catch {
|
|
31860
32252
|
return c.text("File not found", 404);
|
|
31861
32253
|
}
|
|
@@ -32043,22 +32435,22 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
32043
32435
|
c.set("onCompleteCommand", onCompleteCommand);
|
|
32044
32436
|
await next();
|
|
32045
32437
|
});
|
|
32046
|
-
const selfDir =
|
|
32047
|
-
const distDir = existsSync16(
|
|
32438
|
+
const selfDir = dirname9(fileURLToPath4(import.meta.url));
|
|
32439
|
+
const distDir = existsSync16(join21(selfDir, "client", "styles.css")) ? join21(selfDir, "client") : join21(selfDir, "..", "dist", "client");
|
|
32048
32440
|
app.get("/static/styles.css", (c) => {
|
|
32049
|
-
const css =
|
|
32441
|
+
const css = readFileSync20(join21(distDir, "styles.css"), "utf-8");
|
|
32050
32442
|
return c.text(css, 200, { "Content-Type": "text/css", "Cache-Control": "no-cache" });
|
|
32051
32443
|
});
|
|
32052
32444
|
app.get("/static/app.js", (c) => {
|
|
32053
|
-
const js =
|
|
32445
|
+
const js = readFileSync20(join21(distDir, "app.global.js"), "utf-8");
|
|
32054
32446
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
32055
32447
|
});
|
|
32056
32448
|
app.get("/static/history.js", (c) => {
|
|
32057
|
-
const js =
|
|
32449
|
+
const js = readFileSync20(join21(distDir, "history.global.js"), "utf-8");
|
|
32058
32450
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
32059
32451
|
});
|
|
32060
32452
|
app.get("/favicon.svg", (c) => {
|
|
32061
|
-
const svg =
|
|
32453
|
+
const svg = readFileSync20(join21(distDir, "favicon.svg"), "utf-8");
|
|
32062
32454
|
return c.body(svg, 200, { "Content-Type": "image/svg+xml", "Cache-Control": "no-cache" });
|
|
32063
32455
|
});
|
|
32064
32456
|
app.get("/favicon.ico", (c) => c.body(null, 204));
|
|
@@ -32102,7 +32494,7 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
32102
32494
|
try {
|
|
32103
32495
|
const globalConfig2 = readGlobalConfig();
|
|
32104
32496
|
if (globalConfig2.channelEnabled === true) {
|
|
32105
|
-
const dataDir =
|
|
32497
|
+
const dataDir = join21(repoRoot, ".glassbox");
|
|
32106
32498
|
registerChannel(dataDir);
|
|
32107
32499
|
}
|
|
32108
32500
|
} catch {
|
|
@@ -32117,8 +32509,8 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
32117
32509
|
}
|
|
32118
32510
|
|
|
32119
32511
|
// src/skills.ts
|
|
32120
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as
|
|
32121
|
-
import { join as
|
|
32512
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync14 } from "fs";
|
|
32513
|
+
import { join as join22 } from "path";
|
|
32122
32514
|
var SKILL_VERSION = 1;
|
|
32123
32515
|
function versionHeader() {
|
|
32124
32516
|
return `<!-- glassbox-skill-version: ${SKILL_VERSION} -->`;
|
|
@@ -32130,7 +32522,7 @@ function parseVersionHeader(content) {
|
|
|
32130
32522
|
}
|
|
32131
32523
|
function updateFile(path, content) {
|
|
32132
32524
|
if (existsSync17(path)) {
|
|
32133
|
-
const existing =
|
|
32525
|
+
const existing = readFileSync21(path, "utf-8");
|
|
32134
32526
|
const version2 = parseVersionHeader(existing);
|
|
32135
32527
|
if (version2 !== null && version2 >= SKILL_VERSION) {
|
|
32136
32528
|
return false;
|
|
@@ -32156,7 +32548,7 @@ function skillBody() {
|
|
|
32156
32548
|
].join("\n");
|
|
32157
32549
|
}
|
|
32158
32550
|
function ensureClaudeSkills(cwd) {
|
|
32159
|
-
const dir =
|
|
32551
|
+
const dir = join22(cwd, ".claude", "skills", "glassbox");
|
|
32160
32552
|
mkdirSync14(dir, { recursive: true });
|
|
32161
32553
|
const content = [
|
|
32162
32554
|
"---",
|
|
@@ -32169,10 +32561,10 @@ function ensureClaudeSkills(cwd) {
|
|
|
32169
32561
|
skillBody(),
|
|
32170
32562
|
""
|
|
32171
32563
|
].join("\n");
|
|
32172
|
-
return updateFile(
|
|
32564
|
+
return updateFile(join22(dir, "SKILL.md"), content);
|
|
32173
32565
|
}
|
|
32174
32566
|
function ensureCursorRules(cwd) {
|
|
32175
|
-
const rulesDir =
|
|
32567
|
+
const rulesDir = join22(cwd, ".cursor", "rules");
|
|
32176
32568
|
mkdirSync14(rulesDir, { recursive: true });
|
|
32177
32569
|
const content = [
|
|
32178
32570
|
"---",
|
|
@@ -32184,10 +32576,10 @@ function ensureCursorRules(cwd) {
|
|
|
32184
32576
|
skillBody(),
|
|
32185
32577
|
""
|
|
32186
32578
|
].join("\n");
|
|
32187
|
-
return updateFile(
|
|
32579
|
+
return updateFile(join22(rulesDir, "glassbox.mdc"), content);
|
|
32188
32580
|
}
|
|
32189
32581
|
function ensureCopilotPrompts(cwd) {
|
|
32190
|
-
const promptsDir =
|
|
32582
|
+
const promptsDir = join22(cwd, ".github", "prompts");
|
|
32191
32583
|
mkdirSync14(promptsDir, { recursive: true });
|
|
32192
32584
|
const content = [
|
|
32193
32585
|
"---",
|
|
@@ -32198,10 +32590,10 @@ function ensureCopilotPrompts(cwd) {
|
|
|
32198
32590
|
skillBody(),
|
|
32199
32591
|
""
|
|
32200
32592
|
].join("\n");
|
|
32201
|
-
return updateFile(
|
|
32593
|
+
return updateFile(join22(promptsDir, "glassbox.prompt.md"), content);
|
|
32202
32594
|
}
|
|
32203
32595
|
function ensureWindsurfRules(cwd) {
|
|
32204
|
-
const rulesDir =
|
|
32596
|
+
const rulesDir = join22(cwd, ".windsurf", "rules");
|
|
32205
32597
|
mkdirSync14(rulesDir, { recursive: true });
|
|
32206
32598
|
const content = [
|
|
32207
32599
|
"---",
|
|
@@ -32213,21 +32605,21 @@ function ensureWindsurfRules(cwd) {
|
|
|
32213
32605
|
skillBody(),
|
|
32214
32606
|
""
|
|
32215
32607
|
].join("\n");
|
|
32216
|
-
return updateFile(
|
|
32608
|
+
return updateFile(join22(rulesDir, "glassbox.md"), content);
|
|
32217
32609
|
}
|
|
32218
32610
|
function ensureSkills() {
|
|
32219
32611
|
const cwd = process.cwd();
|
|
32220
32612
|
const platforms = [];
|
|
32221
|
-
if (existsSync17(
|
|
32613
|
+
if (existsSync17(join22(cwd, ".claude"))) {
|
|
32222
32614
|
if (ensureClaudeSkills(cwd)) platforms.push("Claude Code");
|
|
32223
32615
|
}
|
|
32224
|
-
if (existsSync17(
|
|
32616
|
+
if (existsSync17(join22(cwd, ".cursor"))) {
|
|
32225
32617
|
if (ensureCursorRules(cwd)) platforms.push("Cursor");
|
|
32226
32618
|
}
|
|
32227
|
-
if (existsSync17(
|
|
32619
|
+
if (existsSync17(join22(cwd, ".github", "prompts")) || existsSync17(join22(cwd, ".github", "copilot-instructions.md"))) {
|
|
32228
32620
|
if (ensureCopilotPrompts(cwd)) platforms.push("GitHub Copilot");
|
|
32229
32621
|
}
|
|
32230
|
-
if (existsSync17(
|
|
32622
|
+
if (existsSync17(join22(cwd, ".windsurf"))) {
|
|
32231
32623
|
if (ensureWindsurfRules(cwd)) platforms.push("Windsurf");
|
|
32232
32624
|
}
|
|
32233
32625
|
return platforms;
|
|
@@ -32235,29 +32627,20 @@ function ensureSkills() {
|
|
|
32235
32627
|
|
|
32236
32628
|
// src/update-check.ts
|
|
32237
32629
|
init_zod();
|
|
32630
|
+
init_app_version();
|
|
32238
32631
|
init_compareVersions();
|
|
32239
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync15, readFileSync as
|
|
32632
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync15, readFileSync as readFileSync22, writeFileSync as writeFileSync15 } from "fs";
|
|
32240
32633
|
import { get } from "https";
|
|
32241
32634
|
import { homedir as homedir2 } from "os";
|
|
32242
|
-
import {
|
|
32243
|
-
|
|
32244
|
-
var
|
|
32245
|
-
var CHECK_FILE = join22(DATA_DIR, "last-update-check");
|
|
32635
|
+
import { join as join23 } from "path";
|
|
32636
|
+
var DATA_DIR = join23(homedir2(), ".glassbox");
|
|
32637
|
+
var CHECK_FILE = join23(DATA_DIR, "last-update-check");
|
|
32246
32638
|
var PACKAGE_NAME = "glassbox";
|
|
32247
|
-
var
|
|
32248
|
-
function getCurrentVersion() {
|
|
32249
|
-
try {
|
|
32250
|
-
const dir = dirname9(fileURLToPath4(import.meta.url));
|
|
32251
|
-
const raw2 = JSON.parse(readFileSync21(join22(dir, "..", "package.json"), "utf-8"));
|
|
32252
|
-
return VersionPayloadSchema.parse(raw2).version;
|
|
32253
|
-
} catch {
|
|
32254
|
-
return "0.0.0";
|
|
32255
|
-
}
|
|
32256
|
-
}
|
|
32639
|
+
var VersionPayloadSchema2 = external_exports.object({ version: external_exports.string() });
|
|
32257
32640
|
function getLastCheckDate() {
|
|
32258
32641
|
try {
|
|
32259
32642
|
if (existsSync18(CHECK_FILE)) {
|
|
32260
|
-
return
|
|
32643
|
+
return readFileSync22(CHECK_FILE, "utf-8").trim();
|
|
32261
32644
|
}
|
|
32262
32645
|
} catch {
|
|
32263
32646
|
}
|
|
@@ -32287,7 +32670,7 @@ function fetchLatestVersion() {
|
|
|
32287
32670
|
res.on("end", () => {
|
|
32288
32671
|
try {
|
|
32289
32672
|
const raw2 = JSON.parse(data);
|
|
32290
|
-
resolve13(
|
|
32673
|
+
resolve13(VersionPayloadSchema2.parse(raw2).version);
|
|
32291
32674
|
} catch {
|
|
32292
32675
|
resolve13(null);
|
|
32293
32676
|
}
|
|
@@ -32626,7 +33009,7 @@ async function main() {
|
|
|
32626
33009
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
32627
33010
|
}
|
|
32628
33011
|
if (debug) {
|
|
32629
|
-
console.log(`[debug] Build timestamp: ${"2026-07-
|
|
33012
|
+
console.log(`[debug] Build timestamp: ${"2026-07-24T02:30:22.046Z"}`);
|
|
32630
33013
|
}
|
|
32631
33014
|
if (projectDir !== null) {
|
|
32632
33015
|
if (!existsSync20(projectDir) || !statSync8(projectDir).isDirectory()) {
|
|
@@ -32636,7 +33019,7 @@ async function main() {
|
|
|
32636
33019
|
process.chdir(projectDir);
|
|
32637
33020
|
}
|
|
32638
33021
|
if (dataDir === null) {
|
|
32639
|
-
dataDir =
|
|
33022
|
+
dataDir = join25(process.cwd(), ".glassbox");
|
|
32640
33023
|
}
|
|
32641
33024
|
if (difftoolServe) {
|
|
32642
33025
|
await runDifftoolServe(dataDir, port, { noOpen, strictPort, onComplete });
|
|
@@ -32652,7 +33035,7 @@ async function main() {
|
|
|
32652
33035
|
}
|
|
32653
33036
|
process.exit(1);
|
|
32654
33037
|
}
|
|
32655
|
-
dataDir =
|
|
33038
|
+
dataDir = join25(tmpdir2(), `glassbox-demo-${demo}-${Date.now()}`);
|
|
32656
33039
|
setDemoMode(demo);
|
|
32657
33040
|
console.log(`
|
|
32658
33041
|
DEMO MODE: ${scenario.label}
|