gyojeong 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +3 -0
- package/README.md +43 -7
- package/apps/review-web/dist/assets/{feedback-contract-CMrinGBH.js → feedback-contract-_524rCEj.js} +1 -1
- package/apps/review-web/dist/assets/feedback-wrapper-BPcSoKgo.js +3 -0
- package/apps/review-web/dist/assets/feedback-wrapper-gbapxzQa.css +1 -0
- package/apps/review-web/dist/assets/{index-DOaH7bry.css → index-CxnczdwI.css} +1 -1
- package/apps/review-web/dist/assets/index-DlzIyqhR.js +25 -0
- package/apps/review-web/dist/feedback-wrapper.html +3 -3
- package/apps/review-web/dist/index.html +3 -3
- package/bin/gyojeong.mjs +2 -1
- package/package.json +1 -1
- package/scripts/check.mjs +168 -29
- package/scripts/review-cli.mjs +1248 -136
- package/apps/review-web/dist/assets/feedback-wrapper-Bhgs7aQS.js +0 -3
- package/apps/review-web/dist/assets/feedback-wrapper-DWKFHwo5.css +0 -1
- package/apps/review-web/dist/assets/index-CY49dpth.js +0 -17
package/scripts/review-cli.mjs
CHANGED
|
@@ -9,8 +9,8 @@ var __export = (target, all) => {
|
|
|
9
9
|
// ../../scripts/review-cli.mjs
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
12
|
-
import { closeSync, existsSync as
|
|
13
|
-
import
|
|
12
|
+
import { chmodSync as chmodSync3, closeSync, existsSync as existsSync5, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13
|
+
import path5 from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
|
|
16
16
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
|
|
@@ -256,10 +256,10 @@ function mergeDefs(...defs) {
|
|
|
256
256
|
function cloneDef(schema) {
|
|
257
257
|
return mergeDefs(schema._zod.def);
|
|
258
258
|
}
|
|
259
|
-
function getElementAtPath(obj,
|
|
260
|
-
if (!
|
|
259
|
+
function getElementAtPath(obj, path6) {
|
|
260
|
+
if (!path6)
|
|
261
261
|
return obj;
|
|
262
|
-
return
|
|
262
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
263
263
|
}
|
|
264
264
|
function promiseAllObject(promisesObj) {
|
|
265
265
|
const keys = Object.keys(promisesObj);
|
|
@@ -668,11 +668,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
668
668
|
}
|
|
669
669
|
return false;
|
|
670
670
|
}
|
|
671
|
-
function prefixIssues(
|
|
671
|
+
function prefixIssues(path6, issues) {
|
|
672
672
|
return issues.map((iss) => {
|
|
673
673
|
var _a3;
|
|
674
674
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
675
|
-
iss.path.unshift(
|
|
675
|
+
iss.path.unshift(path6);
|
|
676
676
|
return iss;
|
|
677
677
|
});
|
|
678
678
|
}
|
|
@@ -819,16 +819,16 @@ function flattenError(error2, mapper = (issue2) => issue2.message) {
|
|
|
819
819
|
}
|
|
820
820
|
function formatError(error2, mapper = (issue2) => issue2.message) {
|
|
821
821
|
const fieldErrors = { _errors: [] };
|
|
822
|
-
const processError = (error3,
|
|
822
|
+
const processError = (error3, path6 = []) => {
|
|
823
823
|
for (const issue2 of error3.issues) {
|
|
824
824
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
825
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
825
|
+
issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
|
|
826
826
|
} else if (issue2.code === "invalid_key") {
|
|
827
|
-
processError({ issues: issue2.issues }, [...
|
|
827
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
828
828
|
} else if (issue2.code === "invalid_element") {
|
|
829
|
-
processError({ issues: issue2.issues }, [...
|
|
829
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
830
830
|
} else {
|
|
831
|
-
const fullpath = [...
|
|
831
|
+
const fullpath = [...path6, ...issue2.path];
|
|
832
832
|
if (fullpath.length === 0) {
|
|
833
833
|
fieldErrors._errors.push(mapper(issue2));
|
|
834
834
|
} else {
|
|
@@ -2072,7 +2072,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
2072
2072
|
const _normalized = cached(() => normalizeDef(def));
|
|
2073
2073
|
const generateFastpass = (shape) => {
|
|
2074
2074
|
const doc = new Doc(["shape", "payload", "ctx"]);
|
|
2075
|
-
const
|
|
2075
|
+
const normalized2 = _normalized.value;
|
|
2076
2076
|
const parseStr = (key) => {
|
|
2077
2077
|
const k = esc(key);
|
|
2078
2078
|
return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
|
|
@@ -2080,11 +2080,11 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
2080
2080
|
doc.write(`const input = payload.value;`);
|
|
2081
2081
|
const ids = /* @__PURE__ */ Object.create(null);
|
|
2082
2082
|
let counter = 0;
|
|
2083
|
-
for (const key of
|
|
2083
|
+
for (const key of normalized2.keys) {
|
|
2084
2084
|
ids[key] = `key_${counter++}`;
|
|
2085
2085
|
}
|
|
2086
2086
|
doc.write(`const newResult = {};`);
|
|
2087
|
-
for (const key of
|
|
2087
|
+
for (const key of normalized2.keys) {
|
|
2088
2088
|
const id = ids[key];
|
|
2089
2089
|
const k = esc(key);
|
|
2090
2090
|
const schema = shape[key];
|
|
@@ -5296,10 +5296,107 @@ function trimUrl(url3) {
|
|
|
5296
5296
|
function maskCode(text) {
|
|
5297
5297
|
return text.replace(CODE_SPAN_RE, " ");
|
|
5298
5298
|
}
|
|
5299
|
-
|
|
5299
|
+
var HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([
|
|
5300
|
+
"bash",
|
|
5301
|
+
"dockerfile",
|
|
5302
|
+
"make",
|
|
5303
|
+
"makefile",
|
|
5304
|
+
"perl",
|
|
5305
|
+
"php",
|
|
5306
|
+
"pl",
|
|
5307
|
+
"powershell",
|
|
5308
|
+
"ps1",
|
|
5309
|
+
"py",
|
|
5310
|
+
"python",
|
|
5311
|
+
"r",
|
|
5312
|
+
"rb",
|
|
5313
|
+
"ruby",
|
|
5314
|
+
"sh",
|
|
5315
|
+
"shell",
|
|
5316
|
+
"toml",
|
|
5317
|
+
"yaml",
|
|
5318
|
+
"yml",
|
|
5319
|
+
"zsh"
|
|
5320
|
+
]);
|
|
5321
|
+
var SHELL_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["bash", "sh", "shell", "zsh"]);
|
|
5322
|
+
var WHITESPACE_HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["yaml", "yml"]);
|
|
5323
|
+
var ANYWHERE_HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([
|
|
5324
|
+
"perl",
|
|
5325
|
+
"php",
|
|
5326
|
+
"pl",
|
|
5327
|
+
"powershell",
|
|
5328
|
+
"ps1",
|
|
5329
|
+
"rb",
|
|
5330
|
+
"ruby"
|
|
5331
|
+
]);
|
|
5332
|
+
var MAKE_LANGUAGES = /* @__PURE__ */ new Set(["make", "makefile"]);
|
|
5333
|
+
var PHP_ATTRIBUTE_LANGUAGES = /* @__PURE__ */ new Set(["php"]);
|
|
5334
|
+
var LINE_HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["dockerfile"]);
|
|
5335
|
+
var SLASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([
|
|
5336
|
+
"c",
|
|
5337
|
+
"c++",
|
|
5338
|
+
"cc",
|
|
5339
|
+
"cpp",
|
|
5340
|
+
"cs",
|
|
5341
|
+
"csharp",
|
|
5342
|
+
"cxx",
|
|
5343
|
+
"go",
|
|
5344
|
+
"h",
|
|
5345
|
+
"hpp",
|
|
5346
|
+
"java",
|
|
5347
|
+
"javascript",
|
|
5348
|
+
"js",
|
|
5349
|
+
"jsonc",
|
|
5350
|
+
"jsx",
|
|
5351
|
+
"kotlin",
|
|
5352
|
+
"kt",
|
|
5353
|
+
"less",
|
|
5354
|
+
"php",
|
|
5355
|
+
"rs",
|
|
5356
|
+
"rust",
|
|
5357
|
+
"scala",
|
|
5358
|
+
"scss",
|
|
5359
|
+
"swift",
|
|
5360
|
+
"ts",
|
|
5361
|
+
"tsx",
|
|
5362
|
+
"typescript"
|
|
5363
|
+
]);
|
|
5364
|
+
var C_STYLE_BLOCK_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([...SLASH_COMMENT_LANGUAGES, "css"]);
|
|
5365
|
+
var HTML_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["html", "markdown", "md", "svelte", "svg", "vue", "xml"]);
|
|
5366
|
+
var PAIRED_DELIMITERS = /* @__PURE__ */ new Map([["(", ")"], ["{", "}"], ["[", "]"], ["<", ">"]]);
|
|
5367
|
+
var delimitedLiteralStart = (rest, previous, language) => {
|
|
5368
|
+
if (language === "ruby" || language === "rb") {
|
|
5369
|
+
return rest.match(/^%(?:[qQrwWixI])?([^\w\s])/u);
|
|
5370
|
+
}
|
|
5371
|
+
if ((language === "perl" || language === "pl") && !/[\w$@%]/u.test(previous)) {
|
|
5372
|
+
return rest.match(/^(?:qq|qw|qx|qr|q)([^\w\s])/u);
|
|
5373
|
+
}
|
|
5374
|
+
return null;
|
|
5375
|
+
};
|
|
5376
|
+
var slashLiteralStart = (text, index, language) => {
|
|
5377
|
+
if (!["perl", "pl", "rb", "ruby"].includes(language) || text[index] !== "/") return false;
|
|
5378
|
+
const beforeText = text.slice(0, index);
|
|
5379
|
+
const before = beforeText.match(/\S\s*$/u)?.[0].trim() ?? "";
|
|
5380
|
+
if (before === "" || /[=(:,!\[\{;~]/u.test(before.at(-1) ?? "")) return true;
|
|
5381
|
+
const word = beforeText.match(/([A-Za-z]+)\s*$/u)?.[1]?.toLowerCase();
|
|
5382
|
+
if (["rb", "ruby"].includes(language)) {
|
|
5383
|
+
return ["and", "if", "not", "or", "return", "then", "unless", "when", "while"].includes(word ?? "");
|
|
5384
|
+
}
|
|
5385
|
+
return ["m", "qr", "s"].includes(word ?? "");
|
|
5386
|
+
};
|
|
5387
|
+
function stripComments(text, language = "") {
|
|
5388
|
+
const normalizedLanguage = language.toLowerCase();
|
|
5389
|
+
const hashComments = HASH_COMMENT_LANGUAGES.has(normalizedLanguage);
|
|
5390
|
+
const slashComments = SLASH_COMMENT_LANGUAGES.has(normalizedLanguage);
|
|
5391
|
+
const cStyleBlockComments = C_STYLE_BLOCK_COMMENT_LANGUAGES.has(normalizedLanguage);
|
|
5392
|
+
const htmlComments = HTML_COMMENT_LANGUAGES.has(normalizedLanguage);
|
|
5393
|
+
const knownLanguage = hashComments || slashComments || cStyleBlockComments || htmlComments;
|
|
5300
5394
|
let out = "";
|
|
5301
5395
|
let quote = null;
|
|
5396
|
+
let quoteOpen = null;
|
|
5397
|
+
let quoteDepth = 0;
|
|
5302
5398
|
let block = null;
|
|
5399
|
+
let lineStart = 0;
|
|
5303
5400
|
for (let i = 0; i < text.length; i += 1) {
|
|
5304
5401
|
const rest = text.slice(i);
|
|
5305
5402
|
if (block) {
|
|
@@ -5307,48 +5404,92 @@ function stripComments(text) {
|
|
|
5307
5404
|
i += block.length - 1;
|
|
5308
5405
|
block = null;
|
|
5309
5406
|
}
|
|
5407
|
+
if (text[i] === "\n") {
|
|
5408
|
+
lineStart = i + 1;
|
|
5409
|
+
}
|
|
5310
5410
|
continue;
|
|
5311
5411
|
}
|
|
5312
5412
|
const char = text[i];
|
|
5413
|
+
const atLineStart = /^\s*$/.test(text.slice(lineStart, i));
|
|
5414
|
+
const genericSlashComment = !knownLanguage && atLineStart && rest.startsWith("//");
|
|
5415
|
+
const genericHashComment = !knownLanguage && atLineStart && char === "#" && !rest.startsWith("#!") && !/^#[\w-]+\s*\{/u.test(rest);
|
|
5416
|
+
const genericCStyleBlockComment = !knownLanguage && atLineStart && rest.startsWith("/*");
|
|
5417
|
+
const genericHtmlComment = !knownLanguage && atLineStart && rest.startsWith("<!--");
|
|
5418
|
+
const previous = text[i - 1] ?? "";
|
|
5419
|
+
const makeRecipe = MAKE_LANGUAGES.has(normalizedLanguage) && text[lineStart] === " ";
|
|
5420
|
+
const shellHashComment = (SHELL_COMMENT_LANGUAGES.has(normalizedLanguage) || makeRecipe) && char === "#" && (i === 0 || /[\s;&|()<>]/u.test(previous));
|
|
5421
|
+
const whitespaceHashComment = WHITESPACE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) && char === "#" && (i === 0 || /\s/u.test(previous)) && !(PHP_ATTRIBUTE_LANGUAGES.has(normalizedLanguage) && rest.startsWith("#["));
|
|
5422
|
+
const lineHashComment = LINE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) && char === "#" && atLineStart;
|
|
5423
|
+
const anywhereHashComment = ANYWHERE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) && char === "#" && !(PHP_ATTRIBUTE_LANGUAGES.has(normalizedLanguage) && rest.startsWith("#["));
|
|
5424
|
+
const makeHashComment = MAKE_LANGUAGES.has(normalizedLanguage) && char === "#" && (makeRecipe ? shellHashComment : true);
|
|
5425
|
+
const languageHashComment = hashComments && char === "#" && (SHELL_COMMENT_LANGUAGES.has(normalizedLanguage) || makeRecipe ? shellHashComment : WHITESPACE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) ? whitespaceHashComment : LINE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) ? lineHashComment : MAKE_LANGUAGES.has(normalizedLanguage) ? makeHashComment : anywhereHashComment);
|
|
5313
5426
|
if (quote) {
|
|
5314
5427
|
out += char;
|
|
5315
5428
|
if (char === "\\") {
|
|
5316
5429
|
out += text[i + 1] ?? "";
|
|
5317
5430
|
i += 1;
|
|
5431
|
+
} else if (quoteOpen && char === quoteOpen) {
|
|
5432
|
+
quoteDepth += 1;
|
|
5318
5433
|
} else if (char === quote) {
|
|
5319
|
-
|
|
5434
|
+
if (quoteDepth > 0) {
|
|
5435
|
+
quoteDepth -= 1;
|
|
5436
|
+
} else {
|
|
5437
|
+
quote = null;
|
|
5438
|
+
quoteOpen = null;
|
|
5439
|
+
}
|
|
5320
5440
|
}
|
|
5321
5441
|
continue;
|
|
5322
5442
|
}
|
|
5443
|
+
const delimitedLiteral = delimitedLiteralStart(rest, previous, normalizedLanguage);
|
|
5444
|
+
if (delimitedLiteral) {
|
|
5445
|
+
const opener = delimitedLiteral[1];
|
|
5446
|
+
const token = delimitedLiteral[0];
|
|
5447
|
+
quoteOpen = PAIRED_DELIMITERS.has(opener) ? opener : null;
|
|
5448
|
+
quote = PAIRED_DELIMITERS.get(opener) ?? opener;
|
|
5449
|
+
quoteDepth = 0;
|
|
5450
|
+
out += token;
|
|
5451
|
+
i += token.length - 1;
|
|
5452
|
+
continue;
|
|
5453
|
+
}
|
|
5323
5454
|
if (char === '"' || char === "'" || char === "`") {
|
|
5324
5455
|
quote = char;
|
|
5325
5456
|
out += char;
|
|
5326
5457
|
continue;
|
|
5327
5458
|
}
|
|
5328
|
-
if (
|
|
5459
|
+
if (slashLiteralStart(text, i, normalizedLanguage)) {
|
|
5460
|
+
quote = "/";
|
|
5461
|
+
out += char;
|
|
5462
|
+
continue;
|
|
5463
|
+
}
|
|
5464
|
+
if (cStyleBlockComments && rest.startsWith("/*") || genericCStyleBlockComment) {
|
|
5329
5465
|
block = "*/";
|
|
5330
5466
|
continue;
|
|
5331
5467
|
}
|
|
5332
|
-
if (rest.startsWith("<!--")) {
|
|
5468
|
+
if (htmlComments && rest.startsWith("<!--") || genericHtmlComment) {
|
|
5333
5469
|
block = "-->";
|
|
5334
5470
|
continue;
|
|
5335
5471
|
}
|
|
5336
|
-
if (rest.startsWith("//") ||
|
|
5472
|
+
if (slashComments && rest.startsWith("//") || languageHashComment || genericSlashComment || genericHashComment) {
|
|
5337
5473
|
const end = text.indexOf("\n", i);
|
|
5338
5474
|
if (end === -1) break;
|
|
5339
5475
|
i = end - 1;
|
|
5476
|
+
lineStart = end;
|
|
5340
5477
|
continue;
|
|
5341
5478
|
}
|
|
5342
5479
|
out += char;
|
|
5480
|
+
if (char === "\n") {
|
|
5481
|
+
lineStart = i + 1;
|
|
5482
|
+
}
|
|
5343
5483
|
}
|
|
5344
5484
|
return out;
|
|
5345
5485
|
}
|
|
5346
5486
|
function normalizeCodeBlock(text) {
|
|
5347
5487
|
const raw = text.split("\n");
|
|
5348
5488
|
const open = raw[0] ?? "";
|
|
5489
|
+
const language = open.match(/^\s*(?:`{3,}|~{3,})\s*([^\s{]+)/)?.[1] ?? "";
|
|
5349
5490
|
const closes = raw.length > 1 && /^\s*(`{3,}|~{3,})\s*$/.test(raw[raw.length - 1] ?? "");
|
|
5350
5491
|
const body = raw.slice(1, closes ? -1 : void 0).join("\n");
|
|
5351
|
-
const lines = [open, ...stripComments(body).split("\n")];
|
|
5492
|
+
const lines = [open, ...stripComments(body, language).split("\n")];
|
|
5352
5493
|
const indent = lines[0].match(/^\s*/)[0].length;
|
|
5353
5494
|
return lines.map((line) => line.slice(Math.min(indent, line.match(/^\s*/)[0].length))).map((line) => line.replace(/\s+$/, "")).filter((line) => line.trim() !== "").join("\n");
|
|
5354
5495
|
}
|
|
@@ -5463,25 +5604,18 @@ var ENGLISH_NUMBER_WORDS = {
|
|
|
5463
5604
|
triple: 3,
|
|
5464
5605
|
both: 2,
|
|
5465
5606
|
dozen: 12,
|
|
5466
|
-
half: 50
|
|
5467
|
-
hex: 16,
|
|
5468
|
-
hexadecimal: 16,
|
|
5469
|
-
octal: 8,
|
|
5470
|
-
binary: 2,
|
|
5471
|
-
decimal: 10,
|
|
5472
|
-
january: 1,
|
|
5473
|
-
february: 2,
|
|
5474
|
-
march: 3,
|
|
5475
|
-
april: 4,
|
|
5476
|
-
may: 5,
|
|
5477
|
-
june: 6,
|
|
5478
|
-
july: 7,
|
|
5479
|
-
august: 8,
|
|
5480
|
-
september: 9,
|
|
5481
|
-
october: 10,
|
|
5482
|
-
november: 11,
|
|
5483
|
-
december: 12
|
|
5607
|
+
half: 50
|
|
5484
5608
|
};
|
|
5609
|
+
var MONTHS = { january: 1, february: 2, march: 3, april: 4, may: 5, june: 6, july: 7, august: 8, september: 9, october: 10, november: 11, december: 12 };
|
|
5610
|
+
var MONTH_DATE_RE = new RegExp(`\\b(${Object.keys(MONTHS).join("|")})\\s+\\d|\\d\\s+(${Object.keys(MONTHS).join("|")})\\b`, "g");
|
|
5611
|
+
var SECOND_UNIT_RE = /\b(?:\d+(?:\.\d+)?|a|an|one|per|each|every)\s+second\b|\bseconds\b/g;
|
|
5612
|
+
var KOREAN_BASE_RE = /(\d+)\s*진/g;
|
|
5613
|
+
var ENGLISH_BASES = { hex: 16, hexadecimal: 16, octal: 8, binary: 2, decimal: 10 };
|
|
5614
|
+
var ENGLISH_BASE_RE = new RegExp(`\\b(${Object.keys(ENGLISH_BASES).join("|")})\\b`, "g");
|
|
5615
|
+
function sourceBases(prose, language) {
|
|
5616
|
+
if (language === "en") return new Set([...prose.toLowerCase().matchAll(ENGLISH_BASE_RE)].map((m) => String(ENGLISH_BASES[m[1]])));
|
|
5617
|
+
return new Set([...prose.matchAll(KOREAN_BASE_RE)].map((m) => normalizeNumber(m[1])));
|
|
5618
|
+
}
|
|
5485
5619
|
var KOREAN_NUMBER_WORDS = { \uD55C: 1, \uD558\uB098: 1, \uB450: 2, \uB458: 2, \uC138: 3, \uC14B: 3, \uB124: 4, \uB137: 4, \uB2E4\uC12F: 5, \uC5EC\uC12F: 6, \uC77C\uACF1: 7, \uC5EC\uB35F: 8, \uC544\uD649: 9, \uC5F4: 10 };
|
|
5486
5620
|
var KOREAN_NUMERAL = "\uD55C|\uD558\uB098|\uB450|\uB458|\uC138|\uC14B|\uB124|\uB137|\uB2E4\uC12F|\uC5EC\uC12F|\uC77C\uACF1|\uC5EC\uB35F|\uC544\uD649|\uC5F4";
|
|
5487
5621
|
var KOREAN_COUNTER = "\uAC1C|\uBC88|\uAC00\uC9C0|\uBA85|\uB300|\uC904|\uB2E8\uACC4|\uBC30|\uC2DC\uAC04|\uBD84|\uCD08|\uC77C|\uC8FC|\uB2EC|\uACF3|\uCABD|\uC7A5|\uCE78";
|
|
@@ -5500,13 +5634,15 @@ function normalizeNumber(raw) {
|
|
|
5500
5634
|
return /^\d+(\.\d+)?$/.test(plain) ? String(Number(plain)) : plain;
|
|
5501
5635
|
}
|
|
5502
5636
|
function digitNumbers(prose) {
|
|
5503
|
-
const
|
|
5504
|
-
return new Set((
|
|
5637
|
+
const normalized2 = prose.replace(KOREAN_DATE_RE, (_, y, m, d) => ` ${y} ${Number(m)} ${Number(d)} `).replace(KOREAN_UNIT_RE, (_, n, unit) => ` ${Number(n) * KOREAN_UNITS[unit]} `);
|
|
5638
|
+
return new Set((normalized2.match(NUM_RE) ?? []).map(normalizeNumber));
|
|
5505
5639
|
}
|
|
5506
5640
|
function justifiedNumbers(prose, language) {
|
|
5507
5641
|
const out = /* @__PURE__ */ new Set();
|
|
5508
5642
|
if (language === "en") {
|
|
5509
|
-
|
|
5643
|
+
const lower = prose.toLowerCase();
|
|
5644
|
+
for (const m of lower.matchAll(MONTH_DATE_RE)) out.add(String(MONTHS[m[1] ?? m[2]]));
|
|
5645
|
+
for (const m of lower.replace(SECOND_UNIT_RE, " ").matchAll(/\b[a-z]+\b/g)) {
|
|
5510
5646
|
if (Object.hasOwn(ENGLISH_NUMBER_WORDS, m[0])) out.add(String(ENGLISH_NUMBER_WORDS[m[0]]));
|
|
5511
5647
|
}
|
|
5512
5648
|
if (/\bnon-?negative\b|\bzero-based\b/i.test(prose)) out.add("0");
|
|
@@ -5521,14 +5657,17 @@ function justifiedNumbers(prose, language) {
|
|
|
5521
5657
|
}
|
|
5522
5658
|
function checkNumbers(sourceBlocks, draftBlocks, sourceLanguage, add) {
|
|
5523
5659
|
const sourceProse = numberProse(sourceBlocks);
|
|
5524
|
-
const
|
|
5660
|
+
const draftText = numberProse(draftBlocks);
|
|
5661
|
+
const bases = sourceBases(sourceProse, sourceLanguage);
|
|
5662
|
+
const draftBases = new Set([...draftText.matchAll(KOREAN_BASE_RE)].map((m) => normalizeNumber(m[1])));
|
|
5663
|
+
const draftProse = draftText.replace(KOREAN_BASE_RE, " ");
|
|
5525
5664
|
const sourceDigits = digitNumbers(sourceProse);
|
|
5526
5665
|
const draftDigits = digitNumbers(draftProse);
|
|
5527
5666
|
const justified = /* @__PURE__ */ new Set([...sourceDigits, ...justifiedNumbers(sourceProse, sourceLanguage)]);
|
|
5528
5667
|
const draftWords = justifiedNumbers(draftProse, "ko");
|
|
5529
|
-
const draftAll = /* @__PURE__ */ new Set([...draftDigits, ...draftWords]);
|
|
5668
|
+
const draftAll = /* @__PURE__ */ new Set([...draftDigits, ...draftWords, ...draftBases]);
|
|
5530
5669
|
const addedWords = [...draftWords].filter((n) => Number(n) >= 3);
|
|
5531
|
-
for (const n of [...draftDigits, ...addedWords]) {
|
|
5670
|
+
for (const n of [...draftDigits, ...addedWords, ...[...draftBases].filter((b) => !bases.has(b))]) {
|
|
5532
5671
|
if (!justified.has(n)) add("fail", "number", `\uC6D0\uBB38\uC5D0 \uC5C6\uB294 \uC22B\uC790\uAC00 \uC0DD\uACBC\uC2B5\uB2C8\uB2E4: ${n}`, { token: n });
|
|
5533
5672
|
}
|
|
5534
5673
|
for (const n of sourceDigits) {
|
|
@@ -5539,7 +5678,7 @@ var EN_WEAK_PROHIBITION = [/\bavoid(?:s|ing)?\b/i, /\b(?:forbid|forbids|disallow
|
|
|
5539
5678
|
var EN_PERMISSION = /\b(?:may|allowed|permitted|optional|optionally|free to|fine|okay|ok|need not|needn't|not required|no need|(?:do|does)n't need|(?:do|does) not need|(?:do|does)n't have to|(?:do|does) not have to|acceptable)\b/i;
|
|
5540
5679
|
var EN = {
|
|
5541
5680
|
prohibition: [
|
|
5542
|
-
/(?:^|[
|
|
5681
|
+
/(?:^|[.!?:;]\s+|\n\s*(?:[-*+]|\d+[.)])?\s*|\*\*|>\s*)(?:do not|don't|never|avoid)\b(?!\s+(?:need|have to|require))/i,
|
|
5543
5682
|
// "should not", and "should, however, not"
|
|
5544
5683
|
/\b(?:must|shall|should)(?:\s+not|n't|,[^,.;!?\n]{1,25},\s+not)\b/i,
|
|
5545
5684
|
/\b(?:is|are|isn't|aren't)\s+not\s+(?:allowed|permitted)\b/i,
|
|
@@ -5913,30 +6052,156 @@ var occurrenceDelta = (beforeText, afterText, value) => ({
|
|
|
5913
6052
|
before: countOccurrences(beforeText, value),
|
|
5914
6053
|
after: countOccurrences(afterText, value)
|
|
5915
6054
|
});
|
|
5916
|
-
var
|
|
6055
|
+
var overlappingRanges = (text, value) => {
|
|
6056
|
+
const ranges = [];
|
|
6057
|
+
let cursor = 0;
|
|
6058
|
+
while (cursor <= text.length - value.length) {
|
|
6059
|
+
const start = text.indexOf(value, cursor);
|
|
6060
|
+
if (start === -1) {
|
|
6061
|
+
break;
|
|
6062
|
+
}
|
|
6063
|
+
ranges.push({ start, end: start + value.length });
|
|
6064
|
+
cursor = start + 1;
|
|
6065
|
+
}
|
|
6066
|
+
return ranges;
|
|
6067
|
+
};
|
|
6068
|
+
var sourceInsideReplacementStatus = (region, source, replacement) => {
|
|
6069
|
+
if (replacement === source || !replacement.includes(source)) {
|
|
6070
|
+
return { replaced: false, ambiguous: false };
|
|
6071
|
+
}
|
|
6072
|
+
const replacementRanges = overlappingRanges(region, replacement);
|
|
6073
|
+
const ambiguous = replacementRanges.some((range, index) => index > 0 && range.start < replacementRanges[index - 1].end);
|
|
6074
|
+
if (ambiguous) {
|
|
6075
|
+
return { replaced: false, ambiguous: true };
|
|
6076
|
+
}
|
|
6077
|
+
const sourceRanges = overlappingRanges(region, source);
|
|
6078
|
+
const replaced = sourceRanges.length > 0 && sourceRanges.every(
|
|
6079
|
+
(candidate) => replacementRanges.some((container) => candidate.start >= container.start && candidate.end <= container.end)
|
|
6080
|
+
);
|
|
6081
|
+
return { replaced, ambiguous: false };
|
|
6082
|
+
};
|
|
6083
|
+
var nearestIndexOf = (text, value, expected, from = 0) => {
|
|
6084
|
+
let best = -1;
|
|
6085
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
6086
|
+
let clusters = 0;
|
|
6087
|
+
let clusterEnd = -1;
|
|
6088
|
+
let tied = false;
|
|
6089
|
+
let cursor = from;
|
|
6090
|
+
while (cursor <= text.length - value.length) {
|
|
6091
|
+
const found = text.indexOf(value, cursor);
|
|
6092
|
+
if (found === -1) {
|
|
6093
|
+
break;
|
|
6094
|
+
}
|
|
6095
|
+
if (found >= clusterEnd) {
|
|
6096
|
+
clusters += 1;
|
|
6097
|
+
}
|
|
6098
|
+
clusterEnd = Math.max(clusterEnd, found + value.length);
|
|
6099
|
+
const distance = Math.abs(found - expected);
|
|
6100
|
+
if (distance < bestDistance) {
|
|
6101
|
+
best = found;
|
|
6102
|
+
bestDistance = distance;
|
|
6103
|
+
tied = false;
|
|
6104
|
+
} else if (distance === bestDistance) {
|
|
6105
|
+
tied = true;
|
|
6106
|
+
}
|
|
6107
|
+
cursor = found + 1;
|
|
6108
|
+
}
|
|
6109
|
+
return { index: best, ambiguous: tied || clusters > 1 };
|
|
6110
|
+
};
|
|
6111
|
+
var locateContext = (afterText, context, expected, side, from = 0) => {
|
|
6112
|
+
const points = Array.from(context);
|
|
6113
|
+
const minimum = Math.min(8, points.length);
|
|
6114
|
+
for (let length = points.length; length >= minimum; length -= 1) {
|
|
6115
|
+
const value = side === "prefix" ? points.slice(-length).join("") : points.slice(0, length).join("");
|
|
6116
|
+
const found = nearestIndexOf(afterText, value, expected, from);
|
|
6117
|
+
if (found.ambiguous) {
|
|
6118
|
+
return null;
|
|
6119
|
+
}
|
|
6120
|
+
if (found.index !== -1) {
|
|
6121
|
+
return side === "prefix" ? found.index + value.length : found.index;
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
return null;
|
|
6125
|
+
};
|
|
6126
|
+
var revisedSelectionRegion = (beforeText, afterText, selection) => {
|
|
6127
|
+
const before = Array.from(beforeText);
|
|
6128
|
+
const unchangedSelectionRegion = () => {
|
|
6129
|
+
const selectedText = selection.selected_text;
|
|
6130
|
+
const selectedAt = afterText.indexOf(selectedText);
|
|
6131
|
+
if (selectedAt === -1 || afterText.indexOf(selectedText, selectedAt + selectedText.length) !== -1) {
|
|
6132
|
+
return null;
|
|
6133
|
+
}
|
|
6134
|
+
return afterText.slice(selectedAt, selectedAt + selectedText.length);
|
|
6135
|
+
};
|
|
6136
|
+
const context = 48;
|
|
6137
|
+
const prefix = before.slice(Math.max(0, selection.start - context), selection.start).join("");
|
|
6138
|
+
const suffix = before.slice(selection.end, Math.min(before.length, selection.end + context)).join("");
|
|
6139
|
+
const selectionStart = before.slice(0, selection.start).join("").length;
|
|
6140
|
+
const selectionEnd = before.slice(0, selection.end).join("").length;
|
|
6141
|
+
const start = prefix.length === 0 ? 0 : locateContext(afterText, prefix, selectionStart - prefix.length, "prefix");
|
|
6142
|
+
if (start === null) {
|
|
6143
|
+
return unchangedSelectionRegion();
|
|
6144
|
+
}
|
|
6145
|
+
const end = suffix.length === 0 ? afterText.length : locateContext(afterText, suffix, selectionEnd, "suffix", start);
|
|
6146
|
+
if (end === null || end < start) {
|
|
6147
|
+
return unchangedSelectionRegion();
|
|
6148
|
+
}
|
|
6149
|
+
return afterText.slice(start, end);
|
|
6150
|
+
};
|
|
6151
|
+
var exactReplacementStatus = (beforeText, afterText, feedback2, correction) => {
|
|
6152
|
+
const selectedText = feedback2.selection.selected_text;
|
|
6153
|
+
let region = revisedSelectionRegion(beforeText, afterText, feedback2.selection);
|
|
6154
|
+
if (region === null) {
|
|
6155
|
+
const selection = feedback2.selection;
|
|
6156
|
+
const beforePoints = Array.from(beforeText);
|
|
6157
|
+
const leading = beforePoints.slice(0, selection.start).join("");
|
|
6158
|
+
const trailing = beforePoints.slice(selection.end).join("");
|
|
6159
|
+
const boundaryOnly = (value) => /^[\s\p{P}]*$/u.test(value);
|
|
6160
|
+
const atStart = boundaryOnly(leading) && afterText.startsWith(leading + correction);
|
|
6161
|
+
const atEnd = boundaryOnly(trailing) && afterText.endsWith(correction + trailing);
|
|
6162
|
+
if (atStart || atEnd) {
|
|
6163
|
+
region = correction;
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
if (region === null) {
|
|
6167
|
+
return { resolved: false, selectedReduced: false, notVerifiable: true, checks: ["selection_location_not_verifiable"] };
|
|
6168
|
+
}
|
|
5917
6169
|
const selected = occurrenceDelta(beforeText, afterText, selectedText);
|
|
5918
6170
|
const corrected = occurrenceDelta(beforeText, afterText, correction);
|
|
5919
|
-
const correctionAdded = corrected.after > corrected.before;
|
|
6171
|
+
const correctionAdded = corrected.after > corrected.before && region.includes(correction);
|
|
5920
6172
|
const selectedReduced = selected.after < selected.before;
|
|
5921
|
-
const
|
|
6173
|
+
const containment = correction.includes(selectedText) ? sourceInsideReplacementStatus(region, selectedText, correction) : null;
|
|
6174
|
+
if (containment?.ambiguous) {
|
|
6175
|
+
return { resolved: false, selectedReduced, notVerifiable: true, checks: ["overlapping_replacement_not_verifiable"] };
|
|
6176
|
+
}
|
|
6177
|
+
const selectedReplaced = containment ? containment.replaced : selectedReduced && !region.includes(selectedText);
|
|
5922
6178
|
return {
|
|
5923
|
-
resolved: correctionAdded &&
|
|
6179
|
+
resolved: correctionAdded && selectedReplaced,
|
|
5924
6180
|
selectedReduced,
|
|
5925
6181
|
checks: [
|
|
5926
6182
|
...correctionAdded ? ["correction_occurrence_increased"] : ["correction_occurrence_not_increased"],
|
|
5927
|
-
...
|
|
6183
|
+
...selectedReplaced ? ["selected_occurrence_replaced"] : ["selected_occurrence_not_reduced"]
|
|
5928
6184
|
]
|
|
5929
6185
|
};
|
|
5930
6186
|
};
|
|
5931
|
-
var inlineReplacementStatus = (beforeText, afterText,
|
|
6187
|
+
var inlineReplacementStatus = (beforeText, afterText, feedback2, correction) => {
|
|
6188
|
+
const selectedText = feedback2.selection.selected_text;
|
|
5932
6189
|
const match = correction.match(/^\s*(.+?)\s*(?:->|→)\s*(.+?)\s*$/su);
|
|
5933
6190
|
if (!match || !selectedText.includes(match[1])) {
|
|
5934
6191
|
return null;
|
|
5935
6192
|
}
|
|
6193
|
+
const region = revisedSelectionRegion(beforeText, afterText, feedback2.selection);
|
|
6194
|
+
if (region === null) {
|
|
6195
|
+
return { resolved: false, sourceReduced: false, notVerifiable: true, checks: ["selection_location_not_verifiable"] };
|
|
6196
|
+
}
|
|
5936
6197
|
const source = occurrenceDelta(beforeText, afterText, match[1]);
|
|
5937
6198
|
const target = occurrenceDelta(beforeText, afterText, match[2]);
|
|
5938
|
-
const
|
|
5939
|
-
|
|
6199
|
+
const containment = match[2].includes(match[1]) ? sourceInsideReplacementStatus(region, match[1], match[2]) : null;
|
|
6200
|
+
if (containment?.ambiguous) {
|
|
6201
|
+
return { resolved: false, sourceReduced: false, notVerifiable: true, checks: ["overlapping_replacement_not_verifiable"] };
|
|
6202
|
+
}
|
|
6203
|
+
const sourceReduced = containment ? containment.replaced : source.after < source.before && !region.includes(match[1]);
|
|
6204
|
+
const targetAdded = target.after > target.before && region.includes(match[2]);
|
|
5940
6205
|
return {
|
|
5941
6206
|
resolved: sourceReduced && targetAdded,
|
|
5942
6207
|
sourceReduced,
|
|
@@ -5953,8 +6218,20 @@ var classifyFeedback = (beforeText, afterText, feedback2, correction) => {
|
|
|
5953
6218
|
}
|
|
5954
6219
|
if (feedback2.category === "deletion") {
|
|
5955
6220
|
const target = feedback2.term ?? selectedText;
|
|
6221
|
+
const region = revisedSelectionRegion(beforeText, afterText, feedback2.selection);
|
|
6222
|
+
if (region === null) {
|
|
6223
|
+
if (countOccurrences(beforeText, target) === 1) {
|
|
6224
|
+
const occurrences2 = occurrenceDelta(beforeText, afterText, target);
|
|
6225
|
+
const resolved2 = occurrences2.after < occurrences2.before;
|
|
6226
|
+
return {
|
|
6227
|
+
status: resolved2 ? "resolved" : "unresolved",
|
|
6228
|
+
checks: [resolved2 ? "unique_target_occurrence_decreased" : "unique_target_occurrence_not_decreased"]
|
|
6229
|
+
};
|
|
6230
|
+
}
|
|
6231
|
+
return { status: "not_verifiable", checks: ["selection_location_not_verifiable"] };
|
|
6232
|
+
}
|
|
5956
6233
|
const occurrences = occurrenceDelta(beforeText, afterText, target);
|
|
5957
|
-
const resolved = occurrences.after < occurrences.before;
|
|
6234
|
+
const resolved = occurrences.after < occurrences.before && !region.includes(target);
|
|
5958
6235
|
return {
|
|
5959
6236
|
status: resolved ? "resolved" : "unresolved",
|
|
5960
6237
|
checks: [resolved ? "target_occurrence_decreased" : "target_occurrence_not_decreased"]
|
|
@@ -5969,7 +6246,10 @@ var classifyFeedback = (beforeText, afterText, feedback2, correction) => {
|
|
|
5969
6246
|
checks: [selectedReduced ? "selected_occurrence_decreased" : "selected_occurrence_not_decreased"]
|
|
5970
6247
|
};
|
|
5971
6248
|
}
|
|
5972
|
-
const exact2 = exactReplacementStatus(beforeText, afterText,
|
|
6249
|
+
const exact2 = exactReplacementStatus(beforeText, afterText, feedback2, correction.correction);
|
|
6250
|
+
if (exact2.notVerifiable) {
|
|
6251
|
+
return { status: "not_verifiable", checks: exact2.checks };
|
|
6252
|
+
}
|
|
5973
6253
|
return { status: exact2.resolved ? "resolved" : "unresolved", checks: exact2.checks };
|
|
5974
6254
|
}
|
|
5975
6255
|
if (!correction) {
|
|
@@ -5978,10 +6258,13 @@ var classifyFeedback = (beforeText, afterText, feedback2, correction) => {
|
|
|
5978
6258
|
const inlineReplacement = inlineReplacementStatus(
|
|
5979
6259
|
beforeText,
|
|
5980
6260
|
afterText,
|
|
5981
|
-
|
|
6261
|
+
feedback2,
|
|
5982
6262
|
correction.correction
|
|
5983
6263
|
);
|
|
5984
6264
|
if (inlineReplacement) {
|
|
6265
|
+
if (inlineReplacement.notVerifiable) {
|
|
6266
|
+
return { status: "not_verifiable", checks: inlineReplacement.checks };
|
|
6267
|
+
}
|
|
5985
6268
|
if (!inlineReplacement.resolved && feedback2.category === "unspecified" && inlineReplacement.sourceReduced) {
|
|
5986
6269
|
return {
|
|
5987
6270
|
status: "not_verifiable",
|
|
@@ -5994,8 +6277,12 @@ var classifyFeedback = (beforeText, afterText, feedback2, correction) => {
|
|
|
5994
6277
|
};
|
|
5995
6278
|
}
|
|
5996
6279
|
if (feedback2.category === "omission") {
|
|
6280
|
+
const region = revisedSelectionRegion(beforeText, afterText, feedback2.selection);
|
|
6281
|
+
if (region === null) {
|
|
6282
|
+
return { status: "not_verifiable", checks: ["selection_location_not_verifiable"] };
|
|
6283
|
+
}
|
|
5997
6284
|
const corrected = occurrenceDelta(beforeText, afterText, correction.correction);
|
|
5998
|
-
const resolved = corrected.after > corrected.before;
|
|
6285
|
+
const resolved = corrected.after > corrected.before && region.includes(correction.correction);
|
|
5999
6286
|
return {
|
|
6000
6287
|
status: resolved ? "resolved" : "unresolved",
|
|
6001
6288
|
checks: [resolved ? "correction_occurrence_increased" : "correction_occurrence_not_increased"]
|
|
@@ -6003,10 +6290,18 @@ var classifyFeedback = (beforeText, afterText, feedback2, correction) => {
|
|
|
6003
6290
|
}
|
|
6004
6291
|
if (feedback2.category === "terminology") {
|
|
6005
6292
|
const target = feedback2.term ?? selectedText;
|
|
6293
|
+
const region = revisedSelectionRegion(beforeText, afterText, feedback2.selection);
|
|
6294
|
+
if (region === null) {
|
|
6295
|
+
return { status: "not_verifiable", checks: ["selection_location_not_verifiable"] };
|
|
6296
|
+
}
|
|
6006
6297
|
const targetDelta = occurrenceDelta(beforeText, afterText, target);
|
|
6007
6298
|
const correctionDelta = occurrenceDelta(beforeText, afterText, correction.correction);
|
|
6008
|
-
const correctionAdded = correctionDelta.after > correctionDelta.before;
|
|
6009
|
-
const
|
|
6299
|
+
const correctionAdded = correctionDelta.after > correctionDelta.before && region.includes(correction.correction);
|
|
6300
|
+
const containment = correction.correction.includes(target) ? sourceInsideReplacementStatus(region, target, correction.correction) : null;
|
|
6301
|
+
if (containment?.ambiguous) {
|
|
6302
|
+
return { status: "not_verifiable", checks: ["overlapping_replacement_not_verifiable"] };
|
|
6303
|
+
}
|
|
6304
|
+
const targetReplaced = containment ? containment.replaced : targetDelta.after < targetDelta.before && !region.includes(target);
|
|
6010
6305
|
const resolved = correctionAdded && targetReplaced;
|
|
6011
6306
|
return {
|
|
6012
6307
|
status: resolved ? "resolved" : "unresolved",
|
|
@@ -6016,7 +6311,10 @@ var classifyFeedback = (beforeText, afterText, feedback2, correction) => {
|
|
|
6016
6311
|
]
|
|
6017
6312
|
};
|
|
6018
6313
|
}
|
|
6019
|
-
const exact = exactReplacementStatus(beforeText, afterText,
|
|
6314
|
+
const exact = exactReplacementStatus(beforeText, afterText, feedback2, correction.correction);
|
|
6315
|
+
if (exact.notVerifiable) {
|
|
6316
|
+
return { status: "not_verifiable", checks: exact.checks };
|
|
6317
|
+
}
|
|
6020
6318
|
if (!exact.resolved && feedback2.category === "unspecified" && exact.selectedReduced) {
|
|
6021
6319
|
return {
|
|
6022
6320
|
status: "not_verifiable",
|
|
@@ -6067,21 +6365,777 @@ var loadIdleConfig = (env = process.env, configPath = path.join(homedir(), ".con
|
|
|
6067
6365
|
};
|
|
6068
6366
|
};
|
|
6069
6367
|
|
|
6368
|
+
// ../../scripts/gist-rules.mjs
|
|
6369
|
+
import { spawnSync } from "node:child_process";
|
|
6370
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6371
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "node:fs";
|
|
6372
|
+
import { homedir as homedir2, hostname, tmpdir } from "node:os";
|
|
6373
|
+
import path2 from "node:path";
|
|
6374
|
+
var GIST_ID = /^[a-f0-9]{20,64}$/iu;
|
|
6375
|
+
var CONFIG_DIR = path2.join(homedir2(), ".config", "gyojeong");
|
|
6376
|
+
var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
|
|
6377
|
+
var DOCUMENT_ONLY_CATEGORIES = /* @__PURE__ */ new Set(["deletion", "omission", "addition", "meaning_shift"]);
|
|
6378
|
+
var runGh = (args, input) => {
|
|
6379
|
+
const result = spawnSync("gh", args, { encoding: "utf8", input, maxBuffer: 32 * 1024 * 1024 });
|
|
6380
|
+
if (result.error?.code === "ENOENT") {
|
|
6381
|
+
throw new Error("gh_unavailable: install GitHub CLI from https://cli.github.com/ and run `gh auth login`");
|
|
6382
|
+
}
|
|
6383
|
+
if (result.error) throw new Error(`gh_unavailable: ${result.error.message}`);
|
|
6384
|
+
if (result.status !== 0) throw new Error(`gist_request_failed: ${(result.stderr || result.stdout).trim()}`);
|
|
6385
|
+
return result.stdout;
|
|
6386
|
+
};
|
|
6387
|
+
var runGit = (args, cwd) => {
|
|
6388
|
+
const result = spawnSync("git", [
|
|
6389
|
+
"-c",
|
|
6390
|
+
"credential.helper=",
|
|
6391
|
+
"-c",
|
|
6392
|
+
"credential.helper=!gh auth git-credential",
|
|
6393
|
+
...args
|
|
6394
|
+
], { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
6395
|
+
if (result.error) throw new Error(`git_unavailable: ${result.error.message}`);
|
|
6396
|
+
if (result.status !== 0) throw new Error(`gist_git_failed: ${(result.stderr || result.stdout).trim()}`);
|
|
6397
|
+
return result.stdout;
|
|
6398
|
+
};
|
|
6399
|
+
var readLocalConfig = () => {
|
|
6400
|
+
if (!existsSync2(CONFIG_PATH)) return {};
|
|
6401
|
+
return JSON.parse(readFileSync2(CONFIG_PATH, "utf8"));
|
|
6402
|
+
};
|
|
6403
|
+
var readConfig = () => {
|
|
6404
|
+
const local = readLocalConfig();
|
|
6405
|
+
if (process.env.GYOJEONG_GIST_ID !== void 0) {
|
|
6406
|
+
const gistId = process.env.GYOJEONG_GIST_ID.trim();
|
|
6407
|
+
if (!GIST_ID.test(gistId)) throw new Error("invalid_GYOJEONG_GIST_ID");
|
|
6408
|
+
return { ...local, gist_index_id: gistId };
|
|
6409
|
+
}
|
|
6410
|
+
return local;
|
|
6411
|
+
};
|
|
6412
|
+
var writeConfig = (config2) => {
|
|
6413
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
6414
|
+
const current = existsSync2(CONFIG_PATH) ? JSON.parse(readFileSync2(CONFIG_PATH, "utf8")) : {};
|
|
6415
|
+
writeFileSync(CONFIG_PATH, `${JSON.stringify({ ...current, ...config2, storage: "gist" }, null, 2)}
|
|
6416
|
+
`, { mode: 384 });
|
|
6417
|
+
chmodSync(CONFIG_PATH, 384);
|
|
6418
|
+
};
|
|
6419
|
+
var assertPrivateOwnedGist = ({ isPublic, owner }, viewer) => {
|
|
6420
|
+
if (isPublic !== false) throw new Error("gist_must_be_secret");
|
|
6421
|
+
if (!owner || !viewer || owner.toLowerCase() !== viewer.toLowerCase()) {
|
|
6422
|
+
throw new Error("gist_must_be_owned_by_authenticated_user");
|
|
6423
|
+
}
|
|
6424
|
+
};
|
|
6425
|
+
var getVerifiedGist = (gistId) => {
|
|
6426
|
+
const gist = JSON.parse(runGh(["api", `gists/${gistId}`]));
|
|
6427
|
+
const viewer = runGh(["api", "user", "--jq", ".login"]).trim();
|
|
6428
|
+
assertPrivateOwnedGist({ isPublic: gist.public, owner: gist.owner?.login }, viewer);
|
|
6429
|
+
return gist;
|
|
6430
|
+
};
|
|
6431
|
+
var validateConfiguredGist = (gistId) => {
|
|
6432
|
+
getVerifiedGist(gistId);
|
|
6433
|
+
};
|
|
6434
|
+
var ensureRuleGist = () => {
|
|
6435
|
+
const found = readConfig();
|
|
6436
|
+
if (GIST_ID.test(found?.gist_index_id ?? "")) {
|
|
6437
|
+
validateConfiguredGist(found.gist_index_id);
|
|
6438
|
+
writeConfig({ gist_index_id: found.gist_index_id });
|
|
6439
|
+
return found.gist_index_id;
|
|
6440
|
+
}
|
|
6441
|
+
const state = { schema_version: 3, preferences: {} };
|
|
6442
|
+
const body = JSON.stringify({
|
|
6443
|
+
description: "gyojeong index (personal rules)",
|
|
6444
|
+
public: false,
|
|
6445
|
+
files: { "index.json": { content: JSON.stringify(state, null, 2) } }
|
|
6446
|
+
});
|
|
6447
|
+
const id = runGh(["api", "gists", "-X", "POST", "--input", "-", "--jq", ".id"], body).trim();
|
|
6448
|
+
if (!GIST_ID.test(id)) throw new Error("invalid_gist_id");
|
|
6449
|
+
writeConfig({ gist_index_id: id });
|
|
6450
|
+
return id;
|
|
6451
|
+
};
|
|
6452
|
+
var readIndex = (gistId) => {
|
|
6453
|
+
const directory = mkdtempSync(path2.join(tmpdir(), "gyojeong-rules-"));
|
|
6454
|
+
try {
|
|
6455
|
+
runGit(["clone", "--quiet", `https://gist.github.com/${gistId}.git`, directory]);
|
|
6456
|
+
} catch (error2) {
|
|
6457
|
+
rmSync(directory, { recursive: true, force: true });
|
|
6458
|
+
throw error2;
|
|
6459
|
+
}
|
|
6460
|
+
try {
|
|
6461
|
+
const indexPath = path2.join(directory, "index.json");
|
|
6462
|
+
if (!existsSync2(indexPath)) throw new Error("missing_gist_index");
|
|
6463
|
+
const state = JSON.parse(readFileSync2(indexPath, "utf8"));
|
|
6464
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) throw new Error("invalid_gist_index");
|
|
6465
|
+
state.preferences ??= {};
|
|
6466
|
+
return { state, directory, indexPath, summaryPath: path2.join(directory, "rules.md") };
|
|
6467
|
+
} catch (error2) {
|
|
6468
|
+
rmSync(directory, { recursive: true, force: true });
|
|
6469
|
+
throw error2;
|
|
6470
|
+
}
|
|
6471
|
+
};
|
|
6472
|
+
var parseGistIndex = (gist) => {
|
|
6473
|
+
const file = gist?.files?.["index.json"];
|
|
6474
|
+
if (!file) throw new Error("missing_gist_index");
|
|
6475
|
+
if (file.truncated || typeof file.content !== "string") throw new Error("gist_index_truncated");
|
|
6476
|
+
const state = JSON.parse(file.content);
|
|
6477
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) throw new Error("invalid_gist_index");
|
|
6478
|
+
state.preferences ??= {};
|
|
6479
|
+
return state;
|
|
6480
|
+
};
|
|
6481
|
+
var readGistRules = () => {
|
|
6482
|
+
const found = readConfig();
|
|
6483
|
+
let gistId;
|
|
6484
|
+
let gist;
|
|
6485
|
+
if (GIST_ID.test(found?.gist_index_id ?? "")) {
|
|
6486
|
+
gistId = found.gist_index_id;
|
|
6487
|
+
gist = getVerifiedGist(gistId);
|
|
6488
|
+
} else {
|
|
6489
|
+
gistId = ensureRuleGist();
|
|
6490
|
+
gist = getVerifiedGist(gistId);
|
|
6491
|
+
}
|
|
6492
|
+
let state;
|
|
6493
|
+
try {
|
|
6494
|
+
state = parseGistIndex(gist);
|
|
6495
|
+
} catch (error2) {
|
|
6496
|
+
if (error2.message !== "gist_index_truncated") throw error2;
|
|
6497
|
+
const cloned = readIndex(gistId);
|
|
6498
|
+
try {
|
|
6499
|
+
state = cloned.state;
|
|
6500
|
+
} finally {
|
|
6501
|
+
rmSync(cloned.directory, { recursive: true, force: true });
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
const { state: current } = migrateRuleState(state);
|
|
6505
|
+
return {
|
|
6506
|
+
gist_id: gistId,
|
|
6507
|
+
onboarding_required: !isRulesOnboardingComplete(readConfig()),
|
|
6508
|
+
active_rules: appliedRulebook(current.preferences)
|
|
6509
|
+
};
|
|
6510
|
+
};
|
|
6511
|
+
var commitIndex = (state, directory, indexPath, summaryPath, message, removeOldHistory = false) => {
|
|
6512
|
+
writeFileSync(indexPath, `${JSON.stringify(state)}
|
|
6513
|
+
`, { mode: 384 });
|
|
6514
|
+
writeFileSync(summaryPath, renderRuleSummary(state.preferences), { mode: 384 });
|
|
6515
|
+
if (removeOldHistory) rmSync(path2.join(directory, "review-history.json"), { force: true });
|
|
6516
|
+
runGit(["config", "user.name", "gyojeong"], directory);
|
|
6517
|
+
runGit(["config", "user.email", "gyojeong@local"], directory);
|
|
6518
|
+
runGit(["add", "-A"], directory);
|
|
6519
|
+
runGit(["commit", "--quiet", "-m", message], directory);
|
|
6520
|
+
};
|
|
6521
|
+
var normalized = (value) => String(value ?? "").normalize("NFC").trim().replace(/\s+/gu, " ").toLowerCase();
|
|
6522
|
+
var rulesEnvironmentKey = ({
|
|
6523
|
+
machine = hostname(),
|
|
6524
|
+
platform = process.platform,
|
|
6525
|
+
home = homedir2(),
|
|
6526
|
+
codexHome = process.env.CODEX_HOME || path2.join(home, ".codex"),
|
|
6527
|
+
claudeHome = process.env.CLAUDE_CONFIG_DIR || path2.join(home, ".claude")
|
|
6528
|
+
} = {}) => createHash2("sha256").update(JSON.stringify([machine, platform, path2.resolve(home), path2.resolve(codexHome), path2.resolve(claudeHome)])).digest("hex").slice(0, 24);
|
|
6529
|
+
var isRulesOnboardingComplete = (config2, environmentKey = rulesEnvironmentKey()) => {
|
|
6530
|
+
return Boolean(config2?.rules_onboarding_environments?.[environmentKey]?.completed_at);
|
|
6531
|
+
};
|
|
6532
|
+
var preferenceKey = (preference) => {
|
|
6533
|
+
const category = preference?.structured_value?.category ?? preference?.category ?? "unspecified";
|
|
6534
|
+
const subject = preference?.term || preference?.selected_text || "";
|
|
6535
|
+
return `${category}\0${normalized(subject)}\0${normalized(preference?.correction)}`;
|
|
6536
|
+
};
|
|
6537
|
+
var distillRuleState = (state) => {
|
|
6538
|
+
const preferences = {};
|
|
6539
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6540
|
+
const mappedRules = new Set(Object.values(state.preferences ?? {}).filter((value) => value.rule_status === "active" && (value.mappings?.length || value.term)).map((value) => value.rule));
|
|
6541
|
+
for (const [id, value] of Object.entries(state.preferences ?? {})) {
|
|
6542
|
+
if (!["active", "candidate"].includes(value.rule_status) || !value.rule) continue;
|
|
6543
|
+
if (value.rule_status === "candidate" && (!value.repeatable || !value.evidence?.length)) continue;
|
|
6544
|
+
if (value.rule_status === "active" && !value.term && !value.mappings?.length && !value.context && mappedRules.has(value.rule)) continue;
|
|
6545
|
+
const rawMappings = value.mappings ?? (value.term && value.correction ? [{ from: value.term, to: value.correction }] : void 0);
|
|
6546
|
+
const mappings = rawMappings?.map((mapping) => ({
|
|
6547
|
+
from: mapping.from,
|
|
6548
|
+
to: mapping.to,
|
|
6549
|
+
...mapping.context || value.context ? { context: mapping.context ?? value.context } : {}
|
|
6550
|
+
}));
|
|
6551
|
+
const glossary = value.rule_status === "active" && mappings?.length;
|
|
6552
|
+
const rule = glossary ? "\uAC80\uC218\uB41C \uC6A9\uC5B4 \uD45C\uAE30\uB97C \uBB38\uB9E5\uC5D0 \uB9DE\uAC8C \uC77C\uAD00\uB418\uAC8C \uC0AC\uC6A9\uD55C\uB2E4." : value.rule;
|
|
6553
|
+
const groupKey = glossary ? "active-glossary" : `${value.rule_status}\0${normalized(rule)}\0${normalized(value.context)}\0${value.repeatable === true}`;
|
|
6554
|
+
const existingId = groups.get(groupKey);
|
|
6555
|
+
if (existingId) {
|
|
6556
|
+
const existing = preferences[existingId];
|
|
6557
|
+
for (const mapping of mappings ?? []) {
|
|
6558
|
+
if (!existing.mappings?.some((item) => item.from === mapping.from && item.to === mapping.to && item.context === mapping.context)) (existing.mappings ??= []).push(mapping);
|
|
6559
|
+
}
|
|
6560
|
+
for (const evidence of value.rule_status === "candidate" ? value.evidence ?? [] : []) {
|
|
6561
|
+
if (!existing.evidence?.some((item) => item.review_id === evidence.review_id && item.feedback_id === evidence.feedback_id)) {
|
|
6562
|
+
(existing.evidence ??= []).push(evidence);
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
for (const sourceId of value.source_ids ?? (value.selected_text ? [id] : [])) {
|
|
6566
|
+
if (!existing.source_ids?.includes(sourceId)) (existing.source_ids ??= []).push(sourceId);
|
|
6567
|
+
}
|
|
6568
|
+
existing.occurrences = Math.max(
|
|
6569
|
+
existing.occurrences ?? 1,
|
|
6570
|
+
value.occurrences ?? 1,
|
|
6571
|
+
new Set((existing.evidence ?? []).map((item) => item.review_id)).size
|
|
6572
|
+
);
|
|
6573
|
+
continue;
|
|
6574
|
+
}
|
|
6575
|
+
groups.set(groupKey, id);
|
|
6576
|
+
preferences[id] = {
|
|
6577
|
+
category: value.category,
|
|
6578
|
+
rule,
|
|
6579
|
+
...!glossary && value.context ? { context: value.context } : {},
|
|
6580
|
+
...mappings ? { mappings } : {},
|
|
6581
|
+
...value.repeatable ? { repeatable: true } : {},
|
|
6582
|
+
...value.rule_status === "candidate" && value.evidence ? { evidence: value.evidence } : {},
|
|
6583
|
+
...value.source_ids?.length || value.selected_text ? { source_ids: value.source_ids ?? [id] } : {},
|
|
6584
|
+
rule_status: value.rule_status,
|
|
6585
|
+
occurrences: value.occurrences,
|
|
6586
|
+
promotion_reason: value.promotion_reason,
|
|
6587
|
+
rule_updated_at: value.rule_updated_at
|
|
6588
|
+
};
|
|
6589
|
+
}
|
|
6590
|
+
return { schema_version: 3, preferences };
|
|
6591
|
+
};
|
|
6592
|
+
var LEGACY_FIELDS = /* @__PURE__ */ new Set([
|
|
6593
|
+
"owner_id",
|
|
6594
|
+
"project_id",
|
|
6595
|
+
"kind",
|
|
6596
|
+
"source_language",
|
|
6597
|
+
"target_language",
|
|
6598
|
+
"domain",
|
|
6599
|
+
"structured_value",
|
|
6600
|
+
"selected_text",
|
|
6601
|
+
"term",
|
|
6602
|
+
"correction",
|
|
6603
|
+
"created_at",
|
|
6604
|
+
"evidence",
|
|
6605
|
+
"occurrences",
|
|
6606
|
+
"promotion_reason",
|
|
6607
|
+
"rule_status",
|
|
6608
|
+
"rule_updated_at",
|
|
6609
|
+
"rule",
|
|
6610
|
+
"context",
|
|
6611
|
+
"category",
|
|
6612
|
+
"repeatable",
|
|
6613
|
+
"mappings",
|
|
6614
|
+
"source_ids"
|
|
6615
|
+
]);
|
|
6616
|
+
var compactRuleState = (state) => {
|
|
6617
|
+
const allowedTop = /* @__PURE__ */ new Set(["schema_version", "runs", "job_index", "idempotency", "preferences", "rules"]);
|
|
6618
|
+
for (const key of Object.keys(state)) {
|
|
6619
|
+
if (!allowedTop.has(key)) throw new Error(`unknown_gist_index_field: ${key}`);
|
|
6620
|
+
}
|
|
6621
|
+
const preferences = {};
|
|
6622
|
+
for (const [id, value] of Object.entries(state.preferences ?? {})) {
|
|
6623
|
+
for (const key of Object.keys(value)) {
|
|
6624
|
+
if (!LEGACY_FIELDS.has(key)) throw new Error(`unknown_gist_preference_field: ${key}`);
|
|
6625
|
+
}
|
|
6626
|
+
const shadow = state.rules?.[id];
|
|
6627
|
+
if (shadow && (shadow.status !== value.rule_status || shadow.occurrences !== value.occurrences || shadow.promotion_reason !== value.promotion_reason)) {
|
|
6628
|
+
throw new Error(`conflicting_gist_rule: ${id}`);
|
|
6629
|
+
}
|
|
6630
|
+
preferences[id] = {
|
|
6631
|
+
category: value.category ?? value.structured_value?.category,
|
|
6632
|
+
selected_text: value.selected_text,
|
|
6633
|
+
...value.term ? { term: value.term } : {},
|
|
6634
|
+
correction: value.correction,
|
|
6635
|
+
...value.rule ? { rule: value.rule } : {},
|
|
6636
|
+
...value.context ? { context: value.context } : {},
|
|
6637
|
+
...value.mappings ? { mappings: value.mappings } : {},
|
|
6638
|
+
...value.repeatable ? { repeatable: true } : {},
|
|
6639
|
+
...value.source_ids ? { source_ids: value.source_ids } : {},
|
|
6640
|
+
created_at: value.created_at,
|
|
6641
|
+
...value.evidence ? { evidence: value.evidence } : {},
|
|
6642
|
+
rule_status: value.rule_status,
|
|
6643
|
+
occurrences: value.occurrences,
|
|
6644
|
+
promotion_reason: value.promotion_reason,
|
|
6645
|
+
rule_updated_at: value.rule_updated_at
|
|
6646
|
+
};
|
|
6647
|
+
}
|
|
6648
|
+
if (state.rules && Object.keys(state.rules).some((id) => !preferences[id])) {
|
|
6649
|
+
throw new Error("orphan_gist_rule");
|
|
6650
|
+
}
|
|
6651
|
+
return { schema_version: 3, preferences };
|
|
6652
|
+
};
|
|
6653
|
+
var migrateRuleState = (state, now = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
6654
|
+
state.preferences ??= {};
|
|
6655
|
+
const entries = Object.entries(state.preferences ?? {});
|
|
6656
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6657
|
+
for (const [, preference] of entries) {
|
|
6658
|
+
if (!preference.selected_text || !preference.correction) continue;
|
|
6659
|
+
const key = preferenceKey(preference);
|
|
6660
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
6661
|
+
}
|
|
6662
|
+
const semanticEvidence = /* @__PURE__ */ new Map();
|
|
6663
|
+
for (const [id, preference] of entries) {
|
|
6664
|
+
if (!preference.repeatable || !preference.rule || DOCUMENT_ONLY_CATEGORIES.has(preference.category)) continue;
|
|
6665
|
+
const key = `${normalized(preference.rule)}\0${normalized(preference.context)}`;
|
|
6666
|
+
const sources = semanticEvidence.get(key) ?? /* @__PURE__ */ new Set();
|
|
6667
|
+
for (const item of preference.evidence ?? []) sources.add(item.review_id ?? id);
|
|
6668
|
+
if (!preference.evidence?.length) sources.add(id);
|
|
6669
|
+
semanticEvidence.set(key, sources);
|
|
6670
|
+
}
|
|
6671
|
+
let active = 0;
|
|
6672
|
+
let candidates = 0;
|
|
6673
|
+
for (const [id, preference] of entries) {
|
|
6674
|
+
const category = preference?.structured_value?.category ?? preference?.category;
|
|
6675
|
+
const occurrences = preference.selected_text && preference.correction ? counts.get(preferenceKey(preference)) ?? 1 : preference.occurrences ?? 1;
|
|
6676
|
+
const excluded = DOCUMENT_ONLY_CATEGORIES.has(category);
|
|
6677
|
+
const semanticCount = semanticEvidence.get(`${normalized(preference.rule)}\0${normalized(preference.context)}`)?.size ?? 0;
|
|
6678
|
+
const repeatedSemanticRule = preference.repeatable && semanticCount >= 3;
|
|
6679
|
+
const status = excluded ? "excluded" : repeatedSemanticRule ? "active" : preference.rule_status ?? "candidate";
|
|
6680
|
+
preference.rule_status = status;
|
|
6681
|
+
preference.occurrences = Math.max(preference.occurrences ?? 1, occurrences, semanticCount);
|
|
6682
|
+
preference.rule_updated_at ??= preference.created_at ?? now;
|
|
6683
|
+
const automaticReason = excluded ? "document_only_category" : repeatedSemanticRule ? "repeated_semantic" : occurrences >= 2 ? "repeated_pending_agent" : "awaiting_repeat_or_agent";
|
|
6684
|
+
if (excluded || repeatedSemanticRule || !preference.promotion_reason || preference.promotion_reason === "repeated_pending_agent" || preference.promotion_reason === "awaiting_repeat_or_agent") {
|
|
6685
|
+
preference.promotion_reason = automaticReason;
|
|
6686
|
+
}
|
|
6687
|
+
if (status === "active") active += 1;
|
|
6688
|
+
else if (status === "candidate") candidates += 1;
|
|
6689
|
+
}
|
|
6690
|
+
return { state: compactRuleState(state), active, candidates };
|
|
6691
|
+
};
|
|
6692
|
+
var preferenceId = (reviewId, feedbackId) => createHash2("sha256").update(`gyojeong-rule:${reviewId}:${feedbackId}`).digest("hex").slice(0, 32);
|
|
6693
|
+
var ruleCurationPreview = (reviewId, feedback2) => feedback2.filter(isDurableFeedback).map((item) => ({
|
|
6694
|
+
id: preferenceId(reviewId, item.id),
|
|
6695
|
+
category: item.category,
|
|
6696
|
+
...item.term ? { term: item.term } : {},
|
|
6697
|
+
selected_text: item.selection.selected_text,
|
|
6698
|
+
correction: item.correction
|
|
6699
|
+
}));
|
|
6700
|
+
var assertReviewCurations = (reviewId, feedback2, curations) => {
|
|
6701
|
+
const curatedIds = new Set(curations.map((item) => item.id));
|
|
6702
|
+
const missing = ruleCurationPreview(reviewId, feedback2).map((item) => item.id).filter((id) => !curatedIds.has(id));
|
|
6703
|
+
if (missing.length) throw new Error(`missing_rule_curations: ${missing.join(",")}`);
|
|
6704
|
+
};
|
|
6705
|
+
var retainedReviewFeedback = (reviewId, feedback2, curations) => {
|
|
6706
|
+
const decisions = new Map(curations.map((item) => [item.id, item.status]));
|
|
6707
|
+
return feedback2.filter((item) => decisions.get(preferenceId(reviewId, item.id)) !== "discard");
|
|
6708
|
+
};
|
|
6709
|
+
var isDurableFeedback = (item) => item?.scope === "personal" && Boolean(item?.correction) && !DOCUMENT_ONLY_CATEGORIES.has(item?.category);
|
|
6710
|
+
var syncReviewRules = (state, reviewId, feedback2, now = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
6711
|
+
state = migrateRuleState(state, now).state;
|
|
6712
|
+
const seen = new Set(Object.entries(state.preferences).flatMap(([id, value]) => [id, ...value.source_ids ?? []]));
|
|
6713
|
+
let added = 0;
|
|
6714
|
+
for (const item of feedback2) {
|
|
6715
|
+
if (!isDurableFeedback(item)) continue;
|
|
6716
|
+
const id = preferenceId(reviewId, item.id);
|
|
6717
|
+
if (seen.has(id)) continue;
|
|
6718
|
+
state.preferences[id] = {
|
|
6719
|
+
category: item.category,
|
|
6720
|
+
selected_text: item.selection.selected_text,
|
|
6721
|
+
...item.term ? { term: item.term } : {},
|
|
6722
|
+
correction: item.correction,
|
|
6723
|
+
created_at: item.created_at ?? now,
|
|
6724
|
+
evidence: [{ review_id: reviewId, feedback_id: item.id }]
|
|
6725
|
+
};
|
|
6726
|
+
added += 1;
|
|
6727
|
+
}
|
|
6728
|
+
return { ...migrateRuleState(state, now), added };
|
|
6729
|
+
};
|
|
6730
|
+
var sessionFeedbackId = (entry) => createHash2("sha256").update(JSON.stringify([entry.source, entry.session_id, entry.selected_text, entry.correction, entry.term ?? ""])).digest("hex").slice(0, 32);
|
|
6731
|
+
var syncSessionCorrections = (state, entries, now = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
6732
|
+
if (!Array.isArray(entries) || entries.length === 0 || entries.length > 100) throw new Error("invalid_session_corrections");
|
|
6733
|
+
const ids = [];
|
|
6734
|
+
let added = 0;
|
|
6735
|
+
for (const raw of entries) {
|
|
6736
|
+
const entry = {
|
|
6737
|
+
source: String(raw?.source ?? "").trim(),
|
|
6738
|
+
session_id: String(raw?.session_id ?? "").trim(),
|
|
6739
|
+
category: String(raw?.category ?? "unspecified").trim(),
|
|
6740
|
+
selected_text: String(raw?.selected_text ?? "").normalize("NFC").trim(),
|
|
6741
|
+
correction: String(raw?.correction ?? "").normalize("NFC").trim(),
|
|
6742
|
+
term: raw?.term ? String(raw.term).normalize("NFC").trim() : void 0,
|
|
6743
|
+
rule: raw?.rule ? String(raw.rule).normalize("NFC").trim() : void 0,
|
|
6744
|
+
context: raw?.context ? String(raw.context).normalize("NFC").trim() : void 0,
|
|
6745
|
+
general: raw?.general === true,
|
|
6746
|
+
repeatable: raw?.repeatable === true
|
|
6747
|
+
};
|
|
6748
|
+
if (!["claude", "codex", "session"].includes(entry.source) || !/^[\w.:-]{1,200}$/u.test(entry.session_id) || !["unspecified", "awkward_expression", "terminology", "register"].includes(entry.category) || !entry.selected_text || !entry.correction || raw?.repeatable !== void 0 && typeof raw.repeatable !== "boolean" || entry.selected_text.length > 2e3 || entry.correction.length > 2e3 || entry.rule && entry.rule.length > 240 || entry.context && entry.context.length > 160 || /[\r\n]/u.test(entry.rule ?? "") || /[\r\n]/u.test(entry.context ?? "") || !entry.rule || entry.general && entry.repeatable) {
|
|
6749
|
+
throw new Error("invalid_session_correction");
|
|
6750
|
+
}
|
|
6751
|
+
const feedbackId = sessionFeedbackId(entry);
|
|
6752
|
+
const reviewId = `session:${entry.source}:${entry.session_id}`;
|
|
6753
|
+
const id = preferenceId(reviewId, feedbackId);
|
|
6754
|
+
const synced = syncReviewRules(state, reviewId, [{
|
|
6755
|
+
id: feedbackId,
|
|
6756
|
+
scope: "personal",
|
|
6757
|
+
category: entry.category,
|
|
6758
|
+
selection: { selected_text: entry.selected_text },
|
|
6759
|
+
correction: entry.correction,
|
|
6760
|
+
...entry.term ? { term: entry.term } : {},
|
|
6761
|
+
created_at: now
|
|
6762
|
+
}], now);
|
|
6763
|
+
state = synced.state;
|
|
6764
|
+
added += synced.added;
|
|
6765
|
+
const preference = state.preferences[id];
|
|
6766
|
+
if (preference && synced.added > 0) {
|
|
6767
|
+
if (entry.rule) preference.rule = entry.rule;
|
|
6768
|
+
if (entry.context) preference.context = entry.context;
|
|
6769
|
+
if (entry.repeatable) preference.repeatable = true;
|
|
6770
|
+
preference.rule_status = entry.general ? "active" : "candidate";
|
|
6771
|
+
preference.promotion_reason = entry.general ? "agent_general" : entry.repeatable ? "semantic_pending" : "agent_contextual";
|
|
6772
|
+
preference.rule_updated_at = now;
|
|
6773
|
+
}
|
|
6774
|
+
ids.push(id);
|
|
6775
|
+
}
|
|
6776
|
+
return { ...migrateRuleState(state, now), added, ids };
|
|
6777
|
+
};
|
|
6778
|
+
var curateRules = (state, updates, now = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
6779
|
+
if (!Array.isArray(updates) || updates.length === 0 || updates.length > 100) throw new Error("invalid_rule_curations");
|
|
6780
|
+
state = migrateRuleState(state, now).state;
|
|
6781
|
+
for (const raw of updates) {
|
|
6782
|
+
const preference = state.preferences[raw?.id];
|
|
6783
|
+
const rule = typeof raw?.rule === "string" ? raw.rule.normalize("NFC").trim() : "";
|
|
6784
|
+
const context = typeof raw?.context === "string" ? raw.context.normalize("NFC").trim() : "";
|
|
6785
|
+
const mappings = raw?.mappings?.map((mapping) => ({
|
|
6786
|
+
from: typeof mapping?.from === "string" ? mapping.from.normalize("NFC").trim() : "",
|
|
6787
|
+
to: typeof mapping?.to === "string" ? mapping.to.normalize("NFC").trim() : ""
|
|
6788
|
+
}));
|
|
6789
|
+
if (!preference || raw?.status !== "discard" && !rule || rule.length > 240 || context.length > 160 || /[\r\n]/u.test(rule) || /[\r\n]/u.test(context) || !["active", "candidate", "discard"].includes(raw?.status) || raw.repeatable !== void 0 && typeof raw.repeatable !== "boolean" || raw.mappings !== void 0 && (!Array.isArray(raw.mappings) || !mappings.length || mappings.length > 8 || mappings.some(({ from, to }) => !from || !to || from.length > 80 || to.length > 80 || /[\r\n]/u.test(`${from}${to}`)))) {
|
|
6790
|
+
throw new Error("invalid_rule_curation");
|
|
6791
|
+
}
|
|
6792
|
+
if (DOCUMENT_ONLY_CATEGORIES.has(preference.category) && raw.status === "active") {
|
|
6793
|
+
throw new Error(`document_only_preference: ${raw.id}`);
|
|
6794
|
+
}
|
|
6795
|
+
if (raw.status === "discard") {
|
|
6796
|
+
delete state.preferences[raw.id];
|
|
6797
|
+
continue;
|
|
6798
|
+
}
|
|
6799
|
+
preference.rule = rule;
|
|
6800
|
+
if (mappings) preference.mappings = mappings;
|
|
6801
|
+
if (context) preference.context = context;
|
|
6802
|
+
else delete preference.context;
|
|
6803
|
+
preference.rule_status = raw.status;
|
|
6804
|
+
if (raw.repeatable === true && raw.status === "candidate") preference.repeatable = true;
|
|
6805
|
+
else delete preference.repeatable;
|
|
6806
|
+
preference.promotion_reason = raw.status === "active" ? "agent_general" : preference.repeatable ? "semantic_pending" : "agent_contextual";
|
|
6807
|
+
preference.rule_updated_at = now;
|
|
6808
|
+
}
|
|
6809
|
+
return migrateRuleState(state, now);
|
|
6810
|
+
};
|
|
6811
|
+
var presentActiveRules = (preferences) => {
|
|
6812
|
+
const rules2 = /* @__PURE__ */ new Map();
|
|
6813
|
+
const terms = /* @__PURE__ */ new Map();
|
|
6814
|
+
const termRules = /* @__PURE__ */ new Set();
|
|
6815
|
+
for (const [id, value] of Object.entries(preferences)) {
|
|
6816
|
+
if (value.rule_status !== "active") continue;
|
|
6817
|
+
const mappings = value.mappings ?? (value.term ? [{ from: value.term, to: value.correction }] : []);
|
|
6818
|
+
for (const mapping of mappings) {
|
|
6819
|
+
const context = mapping.context ?? value.context;
|
|
6820
|
+
const key = `${mapping.from}\0${mapping.to}\0${context ?? ""}`;
|
|
6821
|
+
const entry = terms.get(key) ?? { ...mapping, ...context ? { context } : {}, ids: [] };
|
|
6822
|
+
entry.ids.push(id);
|
|
6823
|
+
terms.set(key, entry);
|
|
6824
|
+
}
|
|
6825
|
+
if (mappings.length && value.rule) termRules.add(value.rule);
|
|
6826
|
+
}
|
|
6827
|
+
if (terms.size) rules2.set("glossary", {
|
|
6828
|
+
rule: "\uAC80\uC218\uB41C \uC6A9\uC5B4 \uD45C\uAE30\uB97C \uBB38\uB9E5\uC5D0 \uB9DE\uAC8C \uC77C\uAD00\uB418\uAC8C \uC0AC\uC6A9\uD55C\uB2E4.",
|
|
6829
|
+
terms: [...terms.values()]
|
|
6830
|
+
});
|
|
6831
|
+
for (const [id, value] of Object.entries(preferences)) {
|
|
6832
|
+
if (value.rule_status !== "active") continue;
|
|
6833
|
+
if (value.term || value.mappings || value.rule && !value.context && termRules.has(value.rule)) continue;
|
|
6834
|
+
if (!value.rule) {
|
|
6835
|
+
rules2.set(id, {
|
|
6836
|
+
id,
|
|
6837
|
+
category: value.category,
|
|
6838
|
+
term: value.term,
|
|
6839
|
+
selected_text: value.selected_text,
|
|
6840
|
+
correction: value.correction,
|
|
6841
|
+
occurrences: value.occurrences,
|
|
6842
|
+
reason: value.promotion_reason
|
|
6843
|
+
});
|
|
6844
|
+
continue;
|
|
6845
|
+
}
|
|
6846
|
+
const key = `${value.rule}\0${value.context ?? ""}`;
|
|
6847
|
+
const existing = rules2.get(key);
|
|
6848
|
+
if (existing) {
|
|
6849
|
+
existing.ids.push(id);
|
|
6850
|
+
} else {
|
|
6851
|
+
rules2.set(key, { ids: [id], rule: value.rule, ...value.context ? { context: value.context } : {} });
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
return [...rules2.values()];
|
|
6855
|
+
};
|
|
6856
|
+
var renderRuleSummary = (preferences) => {
|
|
6857
|
+
const lines = ["# gyojeong active rules", "", "Apply these to Korean text you write, revise, or save. Candidate evidence is not a rule.", ""];
|
|
6858
|
+
for (const entry of presentActiveRules(preferences)) {
|
|
6859
|
+
if (entry.terms) {
|
|
6860
|
+
lines.push(`- ${entry.rule}`);
|
|
6861
|
+
lines.push(` ${entry.terms.map(({ from, to, context }) => `${from} -> ${to}${context ? ` (${context})` : ""}`).join(", ")}`);
|
|
6862
|
+
} else if (entry.rule) lines.push(`- ${entry.rule}${entry.context ? ` (${entry.context})` : ""}`);
|
|
6863
|
+
else lines.push(`- ${entry.term ?? entry.selected_text} -> ${entry.correction}`);
|
|
6864
|
+
}
|
|
6865
|
+
return `${lines.join("\n")}
|
|
6866
|
+
`;
|
|
6867
|
+
};
|
|
6868
|
+
var appliedRulebook = (preferences) => presentActiveRules(preferences).map((entry) => {
|
|
6869
|
+
if (entry.terms) return { rule: entry.rule, terms: entry.terms.map(({ ids: ids2, ...term }) => term) };
|
|
6870
|
+
const { id, ids, ...instruction } = entry;
|
|
6871
|
+
return instruction;
|
|
6872
|
+
});
|
|
6873
|
+
var updateGistRules = ({ reviewId, feedback: feedback2 = [], promoteIds = [], candidateIds = [], sessionCorrections = [], curations = [], completeOnboarding = false, includeCandidates = false, distill = false } = {}) => {
|
|
6874
|
+
if (reviewId) assertReviewCurations(reviewId, feedback2, curations);
|
|
6875
|
+
const gistId = ensureRuleGist();
|
|
6876
|
+
const environmentKey = rulesEnvironmentKey();
|
|
6877
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
6878
|
+
const { state, directory, indexPath, summaryPath } = readIndex(gistId);
|
|
6879
|
+
try {
|
|
6880
|
+
const before = JSON.stringify(state);
|
|
6881
|
+
const retainedFeedback = reviewId ? retainedReviewFeedback(reviewId, feedback2, curations) : [];
|
|
6882
|
+
let synced = reviewId ? syncReviewRules(state, reviewId, retainedFeedback) : migrateRuleState(state);
|
|
6883
|
+
let added = synced.added ?? 0;
|
|
6884
|
+
if (sessionCorrections.length) {
|
|
6885
|
+
synced = syncSessionCorrections(synced.state, sessionCorrections);
|
|
6886
|
+
added += synced.added;
|
|
6887
|
+
}
|
|
6888
|
+
const rawState = structuredClone(synced.state);
|
|
6889
|
+
if (curations.length) {
|
|
6890
|
+
const seen = new Set(Object.values(synced.state.preferences).flatMap((value) => value.source_ids ?? []));
|
|
6891
|
+
const pending = curations.filter((item) => (item.status !== "discard" || synced.state.preferences[item.id]) && (synced.state.preferences[item.id] || !seen.has(item.id)));
|
|
6892
|
+
if (pending.length) synced = curateRules(synced.state, pending);
|
|
6893
|
+
}
|
|
6894
|
+
for (const id of promoteIds) {
|
|
6895
|
+
const preference = synced.state.preferences[id];
|
|
6896
|
+
if (!preference) throw new Error(`unknown_preference: ${id}`);
|
|
6897
|
+
if (DOCUMENT_ONLY_CATEGORIES.has(preference?.structured_value?.category ?? preference?.category)) {
|
|
6898
|
+
throw new Error(`document_only_preference: ${id}`);
|
|
6899
|
+
}
|
|
6900
|
+
if (!preference.rule) throw new Error(`missing_rule_summary: ${id}`);
|
|
6901
|
+
preference.rule_status = "active";
|
|
6902
|
+
preference.promotion_reason = "agent_general";
|
|
6903
|
+
preference.rule_updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
6904
|
+
}
|
|
6905
|
+
for (const id of candidateIds) {
|
|
6906
|
+
const preference = synced.state.preferences[id];
|
|
6907
|
+
if (!preference) throw new Error(`unknown_preference: ${id}`);
|
|
6908
|
+
if (DOCUMENT_ONLY_CATEGORIES.has(preference?.structured_value?.category ?? preference?.category)) {
|
|
6909
|
+
throw new Error(`document_only_preference: ${id}`);
|
|
6910
|
+
}
|
|
6911
|
+
preference.rule_status = "candidate";
|
|
6912
|
+
preference.promotion_reason = "agent_contextual";
|
|
6913
|
+
preference.rule_updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
6914
|
+
}
|
|
6915
|
+
const shouldDistill = distill || added > 0 || curations.length > 0;
|
|
6916
|
+
if (shouldDistill) synced.state = distillRuleState(synced.state);
|
|
6917
|
+
const summary = renderRuleSummary(synced.state.preferences);
|
|
6918
|
+
const oldHistory = path2.join(directory, "review-history.json");
|
|
6919
|
+
const finalChanged = added > 0 || JSON.stringify(synced.state) !== before || !existsSync2(summaryPath) || readFileSync2(summaryPath, "utf8") !== summary || shouldDistill && existsSync2(oldHistory);
|
|
6920
|
+
if (finalChanged) {
|
|
6921
|
+
try {
|
|
6922
|
+
if (added > 0) commitIndex(rawState, directory, indexPath, summaryPath, "Archive correction evidence");
|
|
6923
|
+
commitIndex(synced.state, directory, indexPath, summaryPath, "Update distilled personal rules", shouldDistill);
|
|
6924
|
+
runGit(["push", "--quiet", "origin", "HEAD"], directory);
|
|
6925
|
+
} catch (error2) {
|
|
6926
|
+
if (/rejected|fetch first|non-fast-forward/iu.test(String(error2)) && attempt < 2) continue;
|
|
6927
|
+
throw error2;
|
|
6928
|
+
}
|
|
6929
|
+
}
|
|
6930
|
+
const preferences = Object.entries(synced.state.preferences);
|
|
6931
|
+
if (completeOnboarding) {
|
|
6932
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6933
|
+
const localConfig = readLocalConfig();
|
|
6934
|
+
writeConfig({
|
|
6935
|
+
rules_onboarding_environments: {
|
|
6936
|
+
...localConfig.rules_onboarding_environments ?? {},
|
|
6937
|
+
[environmentKey]: { completed_at: completedAt }
|
|
6938
|
+
}
|
|
6939
|
+
});
|
|
6940
|
+
}
|
|
6941
|
+
const currentConfig = readConfig();
|
|
6942
|
+
const onboardingComplete = isRulesOnboardingComplete(currentConfig, environmentKey);
|
|
6943
|
+
return {
|
|
6944
|
+
gist_id: gistId,
|
|
6945
|
+
storage: "gist",
|
|
6946
|
+
added,
|
|
6947
|
+
active: preferences.filter(([, value]) => value.rule_status === "active").length,
|
|
6948
|
+
candidates: preferences.filter(([, value]) => value.rule_status === "candidate").length,
|
|
6949
|
+
excluded: preferences.filter(([, value]) => value.rule_status === "excluded").length,
|
|
6950
|
+
rules_environment_id: environmentKey,
|
|
6951
|
+
onboarding_required: !onboardingComplete,
|
|
6952
|
+
session_rule_ids: synced.ids ?? [],
|
|
6953
|
+
active_rules: appliedRulebook(synced.state.preferences),
|
|
6954
|
+
...includeCandidates || reviewId ? { recent: preferences.filter(([, value]) => value.rule_status === "candidate").filter(([, value]) => !reviewId || value.evidence?.some((item) => item.review_id === reviewId)).sort((a, b) => String(b[1].rule_updated_at ?? b[1].created_at).localeCompare(String(a[1].rule_updated_at ?? a[1].created_at))).slice(0, 20).map(([id, value]) => ({
|
|
6955
|
+
id,
|
|
6956
|
+
status: value.rule_status,
|
|
6957
|
+
category: value.category,
|
|
6958
|
+
rule: value.rule,
|
|
6959
|
+
context: value.context,
|
|
6960
|
+
mappings: value.mappings,
|
|
6961
|
+
occurrences: value.occurrences,
|
|
6962
|
+
evidence_count: value.evidence?.length ?? 0,
|
|
6963
|
+
reason: value.promotion_reason
|
|
6964
|
+
})) } : {}
|
|
6965
|
+
};
|
|
6966
|
+
} finally {
|
|
6967
|
+
rmSync(directory, { recursive: true, force: true });
|
|
6968
|
+
}
|
|
6969
|
+
}
|
|
6970
|
+
throw new Error("gist_write_conflict");
|
|
6971
|
+
};
|
|
6972
|
+
|
|
6973
|
+
// ../../scripts/session-history.mjs
|
|
6974
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6975
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
6976
|
+
import { createReadStream, existsSync as existsSync3 } from "node:fs";
|
|
6977
|
+
import { opendir } from "node:fs/promises";
|
|
6978
|
+
import { homedir as homedir3 } from "node:os";
|
|
6979
|
+
import path3 from "node:path";
|
|
6980
|
+
import { createInterface } from "node:readline";
|
|
6981
|
+
var LANGUAGE_SIGNAL = /(영어|한글|원문|표현|단어|문구|말투|문장|번역|한국어|용어|윤문)/u;
|
|
6982
|
+
var CORRECTION_SIGNAL = /(해줘|써줘|바꿔|변경|고쳐|이상|어색|유지|그대로|하지\s*마|말고|통일|대신|풀어서)/u;
|
|
6983
|
+
var NON_LANGUAGE_SIGNAL = /(코드|구현|함수|클래스|파일|테스트|빌드|배포|서버|기능|버그|브랜치|커밋|프로젝트|api|ui|pr)/iu;
|
|
6984
|
+
var ARROW_REPLACEMENT = /.{1,80}\s*(?:->|=>|→)\s*.{1,80}/u;
|
|
6985
|
+
var EXPLICIT_REPLACEMENT = /(?:(?:대신에?|말고)\s*.{1,80}(?:써|사용|표현|바꿔|변경|고쳐))|(?:.{1,80}(?:로|으로)\s*(?:바꿔|변경|고쳐|써))/u;
|
|
6986
|
+
var SEARCH_SIGNAL = "(\uC601\uC5B4|\uD55C\uAE00|\uC6D0\uBB38|\uD45C\uD604|\uB2E8\uC5B4|\uBB38\uAD6C|\uB9D0\uD22C|\uBB38\uC7A5|\uBC88\uC5ED|\uD55C\uAD6D\uC5B4|\uC6A9\uC5B4|\uC724\uBB38|->|=>|\u2192|(\uB300\uC2E0\uC5D0?|\uB9D0\uACE0).{1,80}(\uC368|\uC0AC\uC6A9|\uD45C\uD604|\uBC14\uAFD4|\uBCC0\uACBD|\uACE0\uCCD0)|(\uB85C|\uC73C\uB85C)\\s*(\uBC14\uAFD4|\uBCC0\uACBD|\uACE0\uCCD0|\uC368))";
|
|
6987
|
+
var SEARCH_SIGNAL_JS = new RegExp(SEARCH_SIGNAL, "iu");
|
|
6988
|
+
var historyRecordPrefix = (source) => source === "codex" ? '^\\{(?=.{0,2048}$)(?=.*"type"\\s*:\\s*"response_item")(?=.*"role"\\s*:\\s*"user").*' : '^\\{(?=.{0,2048}$)(?=.*"type"\\s*:\\s*"user").*';
|
|
6989
|
+
var stringContent = (content) => {
|
|
6990
|
+
if (typeof content === "string") return content;
|
|
6991
|
+
if (!Array.isArray(content)) return "";
|
|
6992
|
+
return content.map((item) => typeof item === "string" ? item : item?.text ?? "").join("\n");
|
|
6993
|
+
};
|
|
6994
|
+
var sessionUserText = (record) => {
|
|
6995
|
+
if (record?.type === "response_item" && record?.payload?.role === "user") {
|
|
6996
|
+
return stringContent(record.payload.content);
|
|
6997
|
+
}
|
|
6998
|
+
if (record?.type === "user") return stringContent(record.content ?? record.message?.content);
|
|
6999
|
+
return "";
|
|
7000
|
+
};
|
|
7001
|
+
var isCorrectionCandidate = (text) => {
|
|
7002
|
+
const normalized2 = String(text ?? "").normalize("NFC").trim();
|
|
7003
|
+
return normalized2.length > 0 && normalized2.length <= 500 && !normalized2.startsWith("<") && (LANGUAGE_SIGNAL.test(normalized2) && CORRECTION_SIGNAL.test(normalized2) || ARROW_REPLACEMENT.test(normalized2) || !NON_LANGUAGE_SIGNAL.test(normalized2) && EXPLICIT_REPLACEMENT.test(normalized2));
|
|
7004
|
+
};
|
|
7005
|
+
var addRecord = (records, group, file, record) => {
|
|
7006
|
+
const text = sessionUserText(record).normalize("NFC").trim();
|
|
7007
|
+
if (!isCorrectionCandidate(text)) return;
|
|
7008
|
+
const sessionId = path3.basename(file, ".jsonl");
|
|
7009
|
+
const fingerprint = createHash3("sha256").update(text).digest("hex").slice(0, 16);
|
|
7010
|
+
const sessionKey = `${group.source}:${sessionId}`;
|
|
7011
|
+
const evidence = { source: group.source, session_id: sessionId, timestamp: record.timestamp ?? null };
|
|
7012
|
+
const existing = records.get(fingerprint);
|
|
7013
|
+
if (existing) {
|
|
7014
|
+
if (existing._session_keys.has(sessionKey)) return;
|
|
7015
|
+
existing._session_keys.add(sessionKey);
|
|
7016
|
+
existing.occurrences += 1;
|
|
7017
|
+
if (existing.evidence.length < 20) existing.evidence.push(evidence);
|
|
7018
|
+
if (String(record.timestamp ?? "") > String(existing.timestamp ?? "")) {
|
|
7019
|
+
existing.source = group.source;
|
|
7020
|
+
existing.session_id = sessionId;
|
|
7021
|
+
existing.timestamp = record.timestamp ?? null;
|
|
7022
|
+
}
|
|
7023
|
+
return;
|
|
7024
|
+
}
|
|
7025
|
+
records.set(fingerprint, {
|
|
7026
|
+
fingerprint,
|
|
7027
|
+
source: group.source,
|
|
7028
|
+
session_id: sessionId,
|
|
7029
|
+
timestamp: record.timestamp ?? null,
|
|
7030
|
+
text,
|
|
7031
|
+
occurrences: 1,
|
|
7032
|
+
evidence: [evidence],
|
|
7033
|
+
_session_keys: /* @__PURE__ */ new Set([sessionKey])
|
|
7034
|
+
});
|
|
7035
|
+
};
|
|
7036
|
+
async function* jsonlFiles(root) {
|
|
7037
|
+
let directory;
|
|
7038
|
+
try {
|
|
7039
|
+
directory = await opendir(root);
|
|
7040
|
+
} catch {
|
|
7041
|
+
return;
|
|
7042
|
+
}
|
|
7043
|
+
for await (const entry of directory) {
|
|
7044
|
+
const file = path3.join(root, entry.name);
|
|
7045
|
+
if (entry.isDirectory()) yield* jsonlFiles(file);
|
|
7046
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl")) yield file;
|
|
7047
|
+
}
|
|
7048
|
+
}
|
|
7049
|
+
var scanWithoutRipgrep = async (records, group, roots) => {
|
|
7050
|
+
for (const root of roots) {
|
|
7051
|
+
for await (const file of jsonlFiles(root)) {
|
|
7052
|
+
const lines = createInterface({ input: createReadStream(file), crlfDelay: Infinity });
|
|
7053
|
+
try {
|
|
7054
|
+
for await (const raw of lines) {
|
|
7055
|
+
if (raw.length > 2048 || !SEARCH_SIGNAL_JS.test(raw)) continue;
|
|
7056
|
+
let record;
|
|
7057
|
+
try {
|
|
7058
|
+
record = JSON.parse(raw);
|
|
7059
|
+
} catch {
|
|
7060
|
+
continue;
|
|
7061
|
+
}
|
|
7062
|
+
addRecord(records, group, file, record);
|
|
7063
|
+
}
|
|
7064
|
+
} catch {
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
};
|
|
7069
|
+
var scanSessionHistory = async (requested = "all") => {
|
|
7070
|
+
if (!["all", "claude", "codex"].includes(requested)) throw new Error("--history must be all, claude, or codex");
|
|
7071
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR || path3.join(homedir3(), ".claude");
|
|
7072
|
+
const codexHome = process.env.CODEX_HOME || path3.join(homedir3(), ".codex");
|
|
7073
|
+
const sourceRoots = [];
|
|
7074
|
+
if (requested !== "codex") {
|
|
7075
|
+
sourceRoots.push({ source: "claude", roots: [path3.join(claudeHome, "transcripts"), path3.join(claudeHome, "projects")] });
|
|
7076
|
+
}
|
|
7077
|
+
if (requested !== "claude") sourceRoots.push({ source: "codex", roots: [path3.join(codexHome, "sessions")] });
|
|
7078
|
+
const available = sourceRoots.flatMap((group) => group.roots).filter(existsSync3);
|
|
7079
|
+
if (!available.length) return { scanned: [], count: 0, candidates: [] };
|
|
7080
|
+
const unique = /* @__PURE__ */ new Map();
|
|
7081
|
+
for (const group of sourceRoots) {
|
|
7082
|
+
const roots = group.roots.filter(existsSync3);
|
|
7083
|
+
if (!roots.length) continue;
|
|
7084
|
+
const prefix = historyRecordPrefix(group.source);
|
|
7085
|
+
const result = spawnSync2("rg", [
|
|
7086
|
+
"--json",
|
|
7087
|
+
"--pcre2",
|
|
7088
|
+
"-g",
|
|
7089
|
+
"*.jsonl",
|
|
7090
|
+
`${prefix}${SEARCH_SIGNAL}`,
|
|
7091
|
+
...roots
|
|
7092
|
+
], { encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
7093
|
+
if (result.error?.code === "ENOENT") {
|
|
7094
|
+
await scanWithoutRipgrep(unique, group, roots);
|
|
7095
|
+
continue;
|
|
7096
|
+
}
|
|
7097
|
+
if (result.error) throw new Error(`history_scan_unavailable: ${result.error.message}`);
|
|
7098
|
+
if (result.status !== 0 && result.status !== 1) throw new Error(`history_scan_failed: ${result.stderr.trim()}`);
|
|
7099
|
+
for (const line of result.stdout.split("\n")) {
|
|
7100
|
+
if (!line) continue;
|
|
7101
|
+
let event;
|
|
7102
|
+
try {
|
|
7103
|
+
event = JSON.parse(line);
|
|
7104
|
+
} catch {
|
|
7105
|
+
continue;
|
|
7106
|
+
}
|
|
7107
|
+
if (event.type !== "match") continue;
|
|
7108
|
+
const file = event.data?.path?.text;
|
|
7109
|
+
const raw = event.data?.lines?.text;
|
|
7110
|
+
if (!file || !raw) continue;
|
|
7111
|
+
let record;
|
|
7112
|
+
try {
|
|
7113
|
+
record = JSON.parse(raw);
|
|
7114
|
+
} catch {
|
|
7115
|
+
continue;
|
|
7116
|
+
}
|
|
7117
|
+
addRecord(unique, group, file, record);
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
const candidates = [...unique.values()].sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? ""))).slice(0, 200).map(({ _session_keys, ...candidate }) => candidate);
|
|
7121
|
+
return { scanned: available, count: candidates.length, truncated: unique.size > candidates.length, candidates };
|
|
7122
|
+
};
|
|
7123
|
+
|
|
6070
7124
|
// ../../scripts/review-store.mjs
|
|
6071
7125
|
import { randomUUID } from "node:crypto";
|
|
6072
7126
|
import {
|
|
6073
|
-
chmodSync,
|
|
6074
|
-
existsSync as
|
|
6075
|
-
mkdirSync,
|
|
7127
|
+
chmodSync as chmodSync2,
|
|
7128
|
+
existsSync as existsSync4,
|
|
7129
|
+
mkdirSync as mkdirSync2,
|
|
6076
7130
|
readdirSync,
|
|
6077
|
-
readFileSync as
|
|
7131
|
+
readFileSync as readFileSync3,
|
|
6078
7132
|
renameSync,
|
|
6079
|
-
rmSync,
|
|
7133
|
+
rmSync as rmSync2,
|
|
6080
7134
|
statSync,
|
|
6081
|
-
writeFileSync
|
|
7135
|
+
writeFileSync as writeFileSync2
|
|
6082
7136
|
} from "node:fs";
|
|
6083
|
-
import { hostname, homedir as
|
|
6084
|
-
import
|
|
7137
|
+
import { hostname as hostname2, homedir as homedir4 } from "node:os";
|
|
7138
|
+
import path4 from "node:path";
|
|
6085
7139
|
|
|
6086
7140
|
// ../feedback-core/dist/index.js
|
|
6087
7141
|
var createSelectionValidator = (text) => {
|
|
@@ -6111,29 +7165,29 @@ var StoreProblem = class extends Error {
|
|
|
6111
7165
|
}
|
|
6112
7166
|
};
|
|
6113
7167
|
var problem = (status, code, message, nextAction) => new StoreProblem(status, code, message, nextAction);
|
|
6114
|
-
var storeHome = () =>
|
|
6115
|
-
var runsDir = (home) =>
|
|
7168
|
+
var storeHome = () => path4.resolve(process.env.GYOJEONG_HOME || path4.join(homedir4(), ".local", "share", "gyojeong"));
|
|
7169
|
+
var runsDir = (home) => path4.join(home, "runs");
|
|
6116
7170
|
var runDir = (home, id) => {
|
|
6117
7171
|
if (typeof id !== "string" || !REVIEW_ID.test(id)) {
|
|
6118
7172
|
throw problem(400, "invalid_request", "\uAC80\uC218 ID \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "\uD604\uC7AC \uAC80\uC218 \uB9C1\uD06C\uB97C \uB2E4\uC2DC \uC5F4\uC5B4 \uC8FC\uC138\uC694.");
|
|
6119
7173
|
}
|
|
6120
|
-
return
|
|
7174
|
+
return path4.join(runsDir(home), id);
|
|
6121
7175
|
};
|
|
6122
7176
|
var ensurePrivateDir = (dir) => {
|
|
6123
|
-
|
|
6124
|
-
|
|
7177
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
7178
|
+
chmodSync2(dir, 448);
|
|
6125
7179
|
};
|
|
6126
7180
|
var writeAtomic = (file, text) => {
|
|
6127
7181
|
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
6128
|
-
|
|
7182
|
+
writeFileSync2(temp, text, { mode: 384 });
|
|
6129
7183
|
renameSync(temp, file);
|
|
6130
7184
|
};
|
|
6131
7185
|
var readJson = (file, fallback) => {
|
|
6132
|
-
if (!
|
|
7186
|
+
if (!existsSync4(file)) return fallback;
|
|
6133
7187
|
try {
|
|
6134
|
-
return JSON.parse(
|
|
7188
|
+
return JSON.parse(readFileSync3(file, "utf8"));
|
|
6135
7189
|
} catch {
|
|
6136
|
-
throw problem(409, "storage_integrity", `${
|
|
7190
|
+
throw problem(409, "storage_integrity", `${path4.basename(file)} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`, "\uAC80\uC218 \uAE30\uB85D \uD30C\uC77C\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
|
|
6137
7191
|
}
|
|
6138
7192
|
};
|
|
6139
7193
|
var sleepSync = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
@@ -6147,19 +7201,19 @@ var processAlive = (pid) => {
|
|
|
6147
7201
|
};
|
|
6148
7202
|
var acquireLock = (lock) => {
|
|
6149
7203
|
const token = randomUUID();
|
|
6150
|
-
const owner = { pid: process.pid, host:
|
|
7204
|
+
const owner = { pid: process.pid, host: hostname2(), token, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
6151
7205
|
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
6152
7206
|
const readOwner = () => {
|
|
6153
7207
|
try {
|
|
6154
|
-
return JSON.parse(
|
|
7208
|
+
return JSON.parse(readFileSync3(path4.join(lock, "owner.json"), "utf8"));
|
|
6155
7209
|
} catch {
|
|
6156
7210
|
return null;
|
|
6157
7211
|
}
|
|
6158
7212
|
};
|
|
6159
7213
|
for (; ; ) {
|
|
6160
7214
|
try {
|
|
6161
|
-
|
|
6162
|
-
|
|
7215
|
+
mkdirSync2(lock);
|
|
7216
|
+
writeFileSync2(path4.join(lock, "owner.json"), JSON.stringify(owner), { mode: 384 });
|
|
6163
7217
|
break;
|
|
6164
7218
|
} catch (error2) {
|
|
6165
7219
|
if (error2?.code === "ENOENT") {
|
|
@@ -6173,12 +7227,12 @@ var acquireLock = (lock) => {
|
|
|
6173
7227
|
} catch {
|
|
6174
7228
|
continue;
|
|
6175
7229
|
}
|
|
6176
|
-
const dead = held?.host ===
|
|
7230
|
+
const dead = held?.host === hostname2() && Number.isInteger(held.pid) && !processAlive(held.pid);
|
|
6177
7231
|
if (dead || age > LOCK_STALE_MS) {
|
|
6178
7232
|
try {
|
|
6179
7233
|
const broken = `${lock}.${token}.broken`;
|
|
6180
7234
|
renameSync(lock, broken);
|
|
6181
|
-
|
|
7235
|
+
rmSync2(broken, { recursive: true, force: true });
|
|
6182
7236
|
continue;
|
|
6183
7237
|
} catch {
|
|
6184
7238
|
}
|
|
@@ -6190,7 +7244,7 @@ var acquireLock = (lock) => {
|
|
|
6190
7244
|
}
|
|
6191
7245
|
}
|
|
6192
7246
|
return () => {
|
|
6193
|
-
if (readOwner()?.token === token)
|
|
7247
|
+
if (readOwner()?.token === token) rmSync2(lock, { recursive: true, force: true });
|
|
6194
7248
|
};
|
|
6195
7249
|
};
|
|
6196
7250
|
var withLock = (lock, task) => {
|
|
@@ -6201,9 +7255,9 @@ var withLock = (lock, task) => {
|
|
|
6201
7255
|
release();
|
|
6202
7256
|
}
|
|
6203
7257
|
};
|
|
6204
|
-
var withRunLock = (dir, task) => withLock(
|
|
7258
|
+
var withRunLock = (dir, task) => withLock(path4.join(dir, ".lock"), task);
|
|
6205
7259
|
var assertRunExists = (dir) => {
|
|
6206
|
-
if (!
|
|
7260
|
+
if (!existsSync4(path4.join(dir, "content.json"))) {
|
|
6207
7261
|
throw problem(404, "wrong_run", "\uAC80\uC218 \uAE30\uB85D\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", "\uAC80\uC218\uAC00 \uC774\uBBF8 \uC885\uB8CC\uB410\uB294\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
|
|
6208
7262
|
}
|
|
6209
7263
|
};
|
|
@@ -6211,7 +7265,7 @@ var createRun = (home, input, { mode, check: check2 } = {}) => {
|
|
|
6211
7265
|
const id = input.run_id;
|
|
6212
7266
|
const dir = runDir(home, id);
|
|
6213
7267
|
ensurePrivateDir(runsDir(home));
|
|
6214
|
-
if (
|
|
7268
|
+
if (existsSync4(dir)) {
|
|
6215
7269
|
throw problem(409, "duplicate_run", "\uAC19\uC740 ID\uC758 \uAC80\uC218\uAC00 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4.", "\uC0C8 ID\uB85C \uB2E4\uC2DC \uAC8C\uC2DC\uD574 \uC8FC\uC138\uC694.");
|
|
6216
7270
|
}
|
|
6217
7271
|
const job = input.jobs[0];
|
|
@@ -6243,15 +7297,15 @@ var createRun = (home, input, { mode, check: check2 } = {}) => {
|
|
|
6243
7297
|
ensurePrivateDir(staging);
|
|
6244
7298
|
try {
|
|
6245
7299
|
(input.attachments ?? []).forEach((attachment, index) => {
|
|
6246
|
-
|
|
7300
|
+
writeFileSync2(path4.join(staging, `attachment-${index}.b64`), attachment.content_base64, { mode: 384 });
|
|
6247
7301
|
});
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
7302
|
+
writeFileSync2(path4.join(staging, "content.json"), serializeReviewContent(content), { mode: 384 });
|
|
7303
|
+
writeFileSync2(path4.join(staging, "feedback.json"), "[]", { mode: 384 });
|
|
7304
|
+
writeFileSync2(path4.join(staging, "state.json"), JSON.stringify({ processed_feedback_ids: [] }), { mode: 384 });
|
|
7305
|
+
writeFileSync2(path4.join(staging, "meta.json"), JSON.stringify({ created_at: createdAt }), { mode: 384 });
|
|
6252
7306
|
renameSync(staging, dir);
|
|
6253
7307
|
} catch (error2) {
|
|
6254
|
-
|
|
7308
|
+
rmSync2(staging, { recursive: true, force: true });
|
|
6255
7309
|
throw error2;
|
|
6256
7310
|
}
|
|
6257
7311
|
return { id, job_id: content.job.id, created_at: createdAt };
|
|
@@ -6259,14 +7313,14 @@ var createRun = (home, input, { mode, check: check2 } = {}) => {
|
|
|
6259
7313
|
var readContent = (home, id) => {
|
|
6260
7314
|
const dir = runDir(home, id);
|
|
6261
7315
|
assertRunExists(dir);
|
|
6262
|
-
return readJson(
|
|
7316
|
+
return readJson(path4.join(dir, "content.json"), null);
|
|
6263
7317
|
};
|
|
6264
7318
|
var readRun = (home, id) => {
|
|
6265
7319
|
const dir = runDir(home, id);
|
|
6266
7320
|
assertRunExists(dir);
|
|
6267
|
-
const content = readJson(
|
|
6268
|
-
const feedback2 = readJson(
|
|
6269
|
-
const state = readJson(
|
|
7321
|
+
const content = readJson(path4.join(dir, "content.json"), null);
|
|
7322
|
+
const feedback2 = readJson(path4.join(dir, "feedback.json"), []);
|
|
7323
|
+
const state = readJson(path4.join(dir, "state.json"), { processed_feedback_ids: [] });
|
|
6270
7324
|
const processed2 = new Set(state.processed_feedback_ids ?? []);
|
|
6271
7325
|
return {
|
|
6272
7326
|
content,
|
|
@@ -6318,30 +7372,30 @@ var pendingFeedbackByDraft = (home, id, run) => {
|
|
|
6318
7372
|
};
|
|
6319
7373
|
var readDraftVersion = (home, id, version2, content = readContent(home, id)) => {
|
|
6320
7374
|
if (version2 === (content.draft_version ?? 0)) return content.draft_text;
|
|
6321
|
-
const file =
|
|
6322
|
-
if (!Number.isInteger(version2) || version2 < 0 || !
|
|
7375
|
+
const file = path4.join(runDir(home, id), `draft-${version2}.txt`);
|
|
7376
|
+
if (!Number.isInteger(version2) || version2 < 0 || !existsSync4(file)) {
|
|
6323
7377
|
throw problem(409, "storage_integrity", "\uD53C\uB4DC\uBC31\uC744 \uB0A8\uAE34 \uCD08\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", "\uAC80\uC218 \uAE30\uB85D\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
|
|
6324
7378
|
}
|
|
6325
|
-
return
|
|
7379
|
+
return readFileSync3(file, "utf8");
|
|
6326
7380
|
};
|
|
6327
7381
|
var markProcessed = (home, id, feedbackIds) => {
|
|
6328
7382
|
const dir = runDir(home, id);
|
|
6329
7383
|
assertRunExists(dir);
|
|
6330
7384
|
return withRunLock(dir, () => {
|
|
6331
|
-
const feedback2 = readJson(
|
|
7385
|
+
const feedback2 = readJson(path4.join(dir, "feedback.json"), []);
|
|
6332
7386
|
const known = new Set(feedback2.map((item) => item.id));
|
|
6333
7387
|
const unknown2 = feedbackIds.filter((fid) => !known.has(fid));
|
|
6334
7388
|
if (unknown2.length) {
|
|
6335
7389
|
throw problem(400, "unknown_feedback", `\uC774 \uAC80\uC218\uC5D0 \uC5C6\uB294 \uD53C\uB4DC\uBC31 ID\uC785\uB2C8\uB2E4: ${unknown2.join(", ")}`, "\uD53C\uB4DC\uBC31 \uBAA9\uB85D\uC744 \uB2E4\uC2DC \uC77D\uC5B4 \uC8FC\uC138\uC694.");
|
|
6336
7390
|
}
|
|
6337
|
-
const state = readJson(
|
|
7391
|
+
const state = readJson(path4.join(dir, "state.json"), { processed_feedback_ids: [] });
|
|
6338
7392
|
const processed2 = [.../* @__PURE__ */ new Set([...state.processed_feedback_ids ?? [], ...feedbackIds])];
|
|
6339
|
-
writeAtomic(
|
|
7393
|
+
writeAtomic(path4.join(dir, "state.json"), JSON.stringify({ ...state, processed_feedback_ids: processed2 }));
|
|
6340
7394
|
const done = new Set(processed2);
|
|
6341
7395
|
const needed = new Set(feedback2.filter((item) => !done.has(item.id)).map((item) => item.draft_version));
|
|
6342
7396
|
for (const file of readdirSync(dir)) {
|
|
6343
7397
|
const match = /^draft-(\d+)\.txt$/u.exec(file);
|
|
6344
|
-
if (match && !needed.has(Number(match[1])))
|
|
7398
|
+
if (match && !needed.has(Number(match[1]))) rmSync2(path4.join(dir, file), { force: true });
|
|
6345
7399
|
}
|
|
6346
7400
|
return { processed: processed2.length, pending: feedback2.filter((item) => !done.has(item.id)).length };
|
|
6347
7401
|
});
|
|
@@ -6350,7 +7404,7 @@ var reviseDraft = (home, id, draftText, { check: check2 } = {}) => {
|
|
|
6350
7404
|
const dir = runDir(home, id);
|
|
6351
7405
|
assertRunExists(dir);
|
|
6352
7406
|
return withRunLock(dir, () => {
|
|
6353
|
-
const content = readJson(
|
|
7407
|
+
const content = readJson(path4.join(dir, "content.json"), null);
|
|
6354
7408
|
if (content.draft_text === draftText) {
|
|
6355
7409
|
throw problem(409, "unchanged_draft", "\uC218\uC815\uBCF8\uC774 \uAE30\uC874 \uCD08\uC548\uACFC \uAC19\uC2B5\uB2C8\uB2E4.", "\uC218\uC815\uD55C \uD30C\uC77C\uC744 \uC9C0\uC815\uD588\uB294\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
|
|
6356
7410
|
}
|
|
@@ -6367,18 +7421,18 @@ var reviseDraft = (home, id, draftText, { check: check2 } = {}) => {
|
|
|
6367
7421
|
revised_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6368
7422
|
};
|
|
6369
7423
|
const serialized = serializeReviewContent(next);
|
|
6370
|
-
const feedback2 = readJson(
|
|
7424
|
+
const feedback2 = readJson(path4.join(dir, "feedback.json"), []);
|
|
6371
7425
|
const unversioned = feedback2.filter((item) => item.draft_version === void 0);
|
|
6372
7426
|
if (unversioned.length) {
|
|
6373
7427
|
const fitsCurrent = selectionChecker(content.draft_text);
|
|
6374
7428
|
for (const item of unversioned) item.draft_version = feedbackDraftVersion(item, content, fitsCurrent);
|
|
6375
|
-
writeAtomic(
|
|
7429
|
+
writeAtomic(path4.join(dir, "feedback.json"), JSON.stringify(feedback2, null, 2));
|
|
6376
7430
|
}
|
|
6377
|
-
const processed2 = new Set(readJson(
|
|
7431
|
+
const processed2 = new Set(readJson(path4.join(dir, "state.json"), { processed_feedback_ids: [] }).processed_feedback_ids ?? []);
|
|
6378
7432
|
if (feedback2.some((item) => !processed2.has(item.id) && item.draft_version === version2)) {
|
|
6379
|
-
writeAtomic(
|
|
7433
|
+
writeAtomic(path4.join(dir, `draft-${version2}.txt`), content.draft_text);
|
|
6380
7434
|
}
|
|
6381
|
-
writeAtomic(
|
|
7435
|
+
writeAtomic(path4.join(dir, "content.json"), serialized);
|
|
6382
7436
|
return { id, revised_at: next.revised_at };
|
|
6383
7437
|
});
|
|
6384
7438
|
};
|
|
@@ -6386,32 +7440,32 @@ var finishRun = (home, id, force = false) => {
|
|
|
6386
7440
|
const dir = runDir(home, id);
|
|
6387
7441
|
assertRunExists(dir);
|
|
6388
7442
|
return withRunLock(dir, () => {
|
|
6389
|
-
const feedback2 = readJson(
|
|
6390
|
-
const state = readJson(
|
|
7443
|
+
const feedback2 = readJson(path4.join(dir, "feedback.json"), []);
|
|
7444
|
+
const state = readJson(path4.join(dir, "state.json"), { processed_feedback_ids: [] });
|
|
6391
7445
|
const processed2 = new Set(state.processed_feedback_ids ?? []);
|
|
6392
7446
|
const pending = feedback2.filter((item) => !processed2.has(item.id)).length;
|
|
6393
7447
|
if (pending > 0 && force !== true) return { finished: false, pending };
|
|
6394
7448
|
const trash = `${dir}.${process.pid}.deleting`;
|
|
6395
7449
|
renameSync(dir, trash);
|
|
6396
|
-
|
|
7450
|
+
rmSync2(trash, { recursive: true, force: true });
|
|
6397
7451
|
return { finished: true, pending };
|
|
6398
7452
|
});
|
|
6399
7453
|
};
|
|
6400
7454
|
var discardRun = (home, id) => finishRun(home, id, true);
|
|
6401
7455
|
var listRuns = (home, now = Date.now()) => {
|
|
6402
|
-
if (!
|
|
7456
|
+
if (!existsSync4(runsDir(home))) return [];
|
|
6403
7457
|
return readdirSync(runsDir(home)).filter((name) => REVIEW_ID.test(name)).flatMap((id) => {
|
|
6404
7458
|
try {
|
|
6405
7459
|
const dir = runDir(home, id);
|
|
6406
7460
|
let meta2 = null;
|
|
6407
7461
|
try {
|
|
6408
|
-
meta2 = readJson(
|
|
7462
|
+
meta2 = readJson(path4.join(dir, "meta.json"), null);
|
|
6409
7463
|
} catch {
|
|
6410
7464
|
meta2 = null;
|
|
6411
7465
|
}
|
|
6412
|
-
const createdAt = (meta2?.created_at ? meta2 : readJson(
|
|
6413
|
-
const feedback2 = readJson(
|
|
6414
|
-
const processed2 = new Set(readJson(
|
|
7466
|
+
const createdAt = (meta2?.created_at ? meta2 : readJson(path4.join(dir, "content.json"), null)).created_at;
|
|
7467
|
+
const feedback2 = readJson(path4.join(dir, "feedback.json"), []);
|
|
7468
|
+
const processed2 = new Set(readJson(path4.join(dir, "state.json"), { processed_feedback_ids: [] }).processed_feedback_ids ?? []);
|
|
6415
7469
|
return [
|
|
6416
7470
|
{
|
|
6417
7471
|
review_id: id,
|
|
@@ -6428,8 +7482,8 @@ var listRuns = (home, now = Date.now()) => {
|
|
|
6428
7482
|
};
|
|
6429
7483
|
|
|
6430
7484
|
// ../../scripts/review-cli.mjs
|
|
6431
|
-
var SCRIPT_DIR =
|
|
6432
|
-
var SERVER_SCRIPT =
|
|
7485
|
+
var SCRIPT_DIR = path5.dirname(fileURLToPath(import.meta.url));
|
|
7486
|
+
var SERVER_SCRIPT = path5.join(SCRIPT_DIR, "review-server.mjs");
|
|
6433
7487
|
var MAX_TEXT_BYTES = 2 * 1024 * 1024;
|
|
6434
7488
|
var STALE_DAYS = 14;
|
|
6435
7489
|
var MEDIA_TYPES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp" };
|
|
@@ -6452,15 +7506,15 @@ var readRaw = (file, maxBytes = MAX_TEXT_BYTES) => {
|
|
|
6452
7506
|
const stat = statSync2(file, { throwIfNoEntry: false });
|
|
6453
7507
|
if (!stat?.isFile()) throw new UsageError(`not a file: ${file}`);
|
|
6454
7508
|
if (stat.size > maxBytes) throw new UsageError(`file larger than ${maxBytes / 1024 / 1024} MiB: ${file}`);
|
|
6455
|
-
return
|
|
7509
|
+
return readFileSync4(file, "utf8");
|
|
6456
7510
|
};
|
|
6457
7511
|
var readText = (file) => readRaw(file).normalize("NFC");
|
|
6458
7512
|
var requireOptions = (options, ...keys) => {
|
|
6459
7513
|
for (const key of keys) if (!options[key]) throw new UsageError(`--${key} is required`);
|
|
6460
7514
|
};
|
|
6461
7515
|
var loadStandards = () => {
|
|
6462
|
-
const file =
|
|
6463
|
-
return
|
|
7516
|
+
const file = path5.join(".gyojeong", "standards");
|
|
7517
|
+
return existsSync5(file) ? parseStandards(readFileSync4(file, "utf8")) : { terms: [] };
|
|
6464
7518
|
};
|
|
6465
7519
|
var processAlive2 = (pid) => {
|
|
6466
7520
|
try {
|
|
@@ -6485,7 +7539,7 @@ var health = async (port, home) => {
|
|
|
6485
7539
|
var runningServer = async (home) => {
|
|
6486
7540
|
let info;
|
|
6487
7541
|
try {
|
|
6488
|
-
info = JSON.parse(
|
|
7542
|
+
info = JSON.parse(readFileSync4(serverInfoPath(home), "utf8"));
|
|
6489
7543
|
} catch {
|
|
6490
7544
|
return null;
|
|
6491
7545
|
}
|
|
@@ -6493,7 +7547,7 @@ var runningServer = async (home) => {
|
|
|
6493
7547
|
return await health(info.port, home) ? info : null;
|
|
6494
7548
|
};
|
|
6495
7549
|
var spawnServer = async (home) => {
|
|
6496
|
-
const log = openSync(
|
|
7550
|
+
const log = openSync(path5.join(home, "server.log"), "a", 384);
|
|
6497
7551
|
const child = spawn(process.execPath, [SERVER_SCRIPT], {
|
|
6498
7552
|
detached: true,
|
|
6499
7553
|
stdio: ["ignore", log, log],
|
|
@@ -6507,13 +7561,13 @@ var spawnServer = async (home) => {
|
|
|
6507
7561
|
if (info) return { port: info.port, started: true };
|
|
6508
7562
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6509
7563
|
}
|
|
6510
|
-
throw new Error(`review server did not start; see ${
|
|
7564
|
+
throw new Error(`review server did not start; see ${path5.join(home, "server.log")}`);
|
|
6511
7565
|
};
|
|
6512
7566
|
var ensureServer = async (home) => {
|
|
6513
7567
|
const existing = await runningServer(home);
|
|
6514
7568
|
if (existing) return { port: existing.port, started: false };
|
|
6515
|
-
|
|
6516
|
-
const release = acquireLock(
|
|
7569
|
+
mkdirSync3(home, { recursive: true, mode: 448 });
|
|
7570
|
+
const release = acquireLock(path5.join(home, "server.lock"));
|
|
6517
7571
|
try {
|
|
6518
7572
|
const started = await runningServer(home);
|
|
6519
7573
|
return started ? { port: started.port, started: false } : await spawnServer(home);
|
|
@@ -6532,9 +7586,9 @@ var reviewUrls = (port, id, content, { publicOrigin } = loadIdleConfig()) => {
|
|
|
6532
7586
|
};
|
|
6533
7587
|
};
|
|
6534
7588
|
var attachmentInput = (file) => {
|
|
6535
|
-
const mediaType = MEDIA_TYPES[
|
|
7589
|
+
const mediaType = MEDIA_TYPES[path5.extname(file).toLowerCase()];
|
|
6536
7590
|
if (!mediaType) throw new UsageError(`unsupported attachment type: ${file} (png, jpeg, webp)`);
|
|
6537
|
-
return { filename:
|
|
7591
|
+
return { filename: path5.basename(file), media_type: mediaType, content_base64: readFileSync4(file).toString("base64") };
|
|
6538
7592
|
};
|
|
6539
7593
|
var publish = async (home, options) => {
|
|
6540
7594
|
requireOptions(options, "source", "draft");
|
|
@@ -6678,7 +7732,16 @@ var verifyFeedback = (home, options) => {
|
|
|
6678
7732
|
var processed = (home, options) => {
|
|
6679
7733
|
requireOptions(options, "review", "ids");
|
|
6680
7734
|
const ids = options.ids.split(",").map((id) => id.trim()).filter(Boolean);
|
|
6681
|
-
|
|
7735
|
+
const run = readRun(home, options.review);
|
|
7736
|
+
const selected = run.feedback.filter((item) => ids.includes(item.id));
|
|
7737
|
+
const unknown2 = ids.filter((id) => !selected.some((item) => item.id === id));
|
|
7738
|
+
if (unknown2.length) throw new UsageError(`unknown feedback ids: ${unknown2.join(",")}`);
|
|
7739
|
+
const durable = ruleCurationPreview(options.review, selected);
|
|
7740
|
+
return {
|
|
7741
|
+
review_id: options.review,
|
|
7742
|
+
...markProcessed(home, options.review, ids),
|
|
7743
|
+
rules: { pending_curation: durable.length }
|
|
7744
|
+
};
|
|
6682
7745
|
};
|
|
6683
7746
|
var url2 = async (home, options) => {
|
|
6684
7747
|
requireOptions(options, "review");
|
|
@@ -6695,7 +7758,55 @@ var discard = (home, options) => {
|
|
|
6695
7758
|
discardRun(home, options.review);
|
|
6696
7759
|
return { review_id: options.review, discarded: true };
|
|
6697
7760
|
};
|
|
6698
|
-
var
|
|
7761
|
+
var rules = async (home, options) => {
|
|
7762
|
+
if (options.compact === "true" && (options.attach.length > 0 || Object.keys(options).some((key) => key !== "compact" && key !== "attach"))) {
|
|
7763
|
+
throw new UsageError("compact rules cannot be combined with other options");
|
|
7764
|
+
}
|
|
7765
|
+
if (options.compact === "true") return readGistRules();
|
|
7766
|
+
if (options.history) {
|
|
7767
|
+
const history = await scanSessionHistory(options.history);
|
|
7768
|
+
if (!options.export) return history;
|
|
7769
|
+
const target = path5.resolve(options.export);
|
|
7770
|
+
if (existsSync5(target)) throw new UsageError(`history export already exists: ${target}`);
|
|
7771
|
+
const payload = {
|
|
7772
|
+
schema_version: 1,
|
|
7773
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7774
|
+
count: history.count,
|
|
7775
|
+
truncated: history.truncated,
|
|
7776
|
+
candidates: history.candidates
|
|
7777
|
+
};
|
|
7778
|
+
writeFileSync3(target, `${JSON.stringify(payload, null, 2)}
|
|
7779
|
+
`, { flag: "wx", mode: 384 });
|
|
7780
|
+
chmodSync3(target, 384);
|
|
7781
|
+
return { exported: target, count: history.count, truncated: history.truncated };
|
|
7782
|
+
}
|
|
7783
|
+
if (options["history-import"]) {
|
|
7784
|
+
const payload = JSON.parse(readRaw(options["history-import"]));
|
|
7785
|
+
if (payload?.schema_version !== 1 || !Array.isArray(payload?.candidates) || payload.candidates.length > 200 || payload.candidates.some((item) => typeof item?.text !== "string" || item.text.length > 500)) {
|
|
7786
|
+
throw new UsageError("invalid history export");
|
|
7787
|
+
}
|
|
7788
|
+
return { count: payload.candidates.length, truncated: payload.truncated === true, candidates: payload.candidates };
|
|
7789
|
+
}
|
|
7790
|
+
const run = options.review ? readRun(home, options.review) : null;
|
|
7791
|
+
const promoteIds = options.ids ? options.ids.split(",").map((id) => id.trim()).filter(Boolean) : [];
|
|
7792
|
+
const candidateIds = options["candidate-ids"] ? options["candidate-ids"].split(",").map((id) => id.trim()).filter(Boolean) : [];
|
|
7793
|
+
const sessionCorrections = options.session ? JSON.parse(readRaw(options.session)) : [];
|
|
7794
|
+
const curations = options.curate ? JSON.parse(readRaw(options.curate)) : [];
|
|
7795
|
+
const processedFeedback = run?.feedback.filter((item) => run.processed_feedback_ids.includes(item.id)) ?? [];
|
|
7796
|
+
if (run && !options.curate) return { review_id: options.review, candidates: ruleCurationPreview(options.review, processedFeedback) };
|
|
7797
|
+
const result = updateGistRules({
|
|
7798
|
+
...run ? { reviewId: options.review, feedback: processedFeedback } : {},
|
|
7799
|
+
promoteIds,
|
|
7800
|
+
candidateIds,
|
|
7801
|
+
sessionCorrections: Array.isArray(sessionCorrections) ? sessionCorrections : [sessionCorrections],
|
|
7802
|
+
curations: Array.isArray(curations) ? curations : [curations],
|
|
7803
|
+
completeOnboarding: options["complete-onboarding"] === "true",
|
|
7804
|
+
includeCandidates: options.candidates === "true",
|
|
7805
|
+
distill: options.distill === "true"
|
|
7806
|
+
});
|
|
7807
|
+
return result;
|
|
7808
|
+
};
|
|
7809
|
+
var COMMANDS = { publish, revise, feedback, "verify-feedback": verifyFeedback, processed, url: url2, list, discard, rules };
|
|
6699
7810
|
var main = async (argv = process.argv.slice(2)) => {
|
|
6700
7811
|
const [command, ...rest] = argv;
|
|
6701
7812
|
const run = COMMANDS[command];
|
|
@@ -6705,8 +7816,9 @@ var main = async (argv = process.argv.slice(2)) => {
|
|
|
6705
7816
|
return 64;
|
|
6706
7817
|
}
|
|
6707
7818
|
try {
|
|
6708
|
-
const
|
|
6709
|
-
|
|
7819
|
+
const options = parseArgs(rest);
|
|
7820
|
+
const result = await run(storeHome(), options);
|
|
7821
|
+
process.stdout.write(`${JSON.stringify(result, null, command === "rules" && options.compact === "true" ? 0 : 2)}
|
|
6710
7822
|
`);
|
|
6711
7823
|
return 0;
|
|
6712
7824
|
} catch (error2) {
|
|
@@ -6721,7 +7833,7 @@ var main = async (argv = process.argv.slice(2)) => {
|
|
|
6721
7833
|
return 1;
|
|
6722
7834
|
}
|
|
6723
7835
|
};
|
|
6724
|
-
if (process.argv[1] &&
|
|
7836
|
+
if (process.argv[1] && path5.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
6725
7837
|
process.exitCode = await main();
|
|
6726
7838
|
}
|
|
6727
7839
|
export {
|