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.
@@ -4,9 +4,9 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>원본 문서 피드백</title>
7
- <script type="module" crossorigin src="./assets/feedback-wrapper-Bhgs7aQS.js"></script>
8
- <link rel="modulepreload" crossorigin href="./assets/feedback-contract-CMrinGBH.js">
9
- <link rel="stylesheet" crossorigin href="./assets/feedback-wrapper-DWKFHwo5.css">
7
+ <script type="module" crossorigin src="./assets/feedback-wrapper-BPcSoKgo.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/feedback-contract-_524rCEj.js">
9
+ <link rel="stylesheet" crossorigin href="./assets/feedback-wrapper-gbapxzQa.css">
10
10
  </head>
11
11
  <body>
12
12
  <iframe id="source" title="검수할 원본 페이지"></iframe>
@@ -4,9 +4,9 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>번역 검토</title>
7
- <script type="module" crossorigin src="./assets/index-CY49dpth.js"></script>
8
- <link rel="modulepreload" crossorigin href="./assets/feedback-contract-CMrinGBH.js">
9
- <link rel="stylesheet" crossorigin href="./assets/index-DOaH7bry.css">
7
+ <script type="module" crossorigin src="./assets/index-DlzIyqhR.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/feedback-contract-_524rCEj.js">
9
+ <link rel="stylesheet" crossorigin href="./assets/index-CxnczdwI.css">
10
10
  </head>
11
11
  <body>
12
12
  <main id="app" aria-live="polite"></main>
package/bin/gyojeong.mjs CHANGED
@@ -16,6 +16,7 @@ const COMMANDS = {
16
16
  url: ['review-cli.mjs', 'Print a review link, starting the server if it stopped', true],
17
17
  list: ['review-cli.mjs', 'List open reviews (older than 14 days are marked stale)', true],
18
18
  discard: ['review-cli.mjs', 'Delete a review without finishing it in the browser', true],
19
+ rules: ['review-cli.mjs', 'Sync/list rules; --compact true returns the writing rulebook only', true],
19
20
  serve: ['review-server.mjs', 'Run the review server in the foreground (default http://127.0.0.1:8377)'],
20
21
  };
21
22
 
@@ -32,7 +33,7 @@ if (!command || command === '--help' || command === '-h' || !COMMANDS[command])
32
33
  const out = [`gyojeong ${packageJson.version}`, '', 'Usage: gyojeong <command> [options]', '', ...lines].join('\n');
33
34
  if (command && !COMMANDS[command] && command !== '--help' && command !== '-h') {
34
35
  console.error(`Unknown command: ${command}\n\n${out}`);
35
- process.exit(2);
36
+ process.exit(64);
36
37
  }
37
38
  console.log(out);
38
39
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gyojeong",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Korean review and verification layer: deterministic checks plus a local sentence-level review server.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/scripts/check.mjs CHANGED
@@ -79,10 +79,107 @@ function trimUrl(url) {
79
79
  function maskCode(text) {
80
80
  return text.replace(CODE_SPAN_RE, " ");
81
81
  }
82
- function stripComments(text) {
82
+ var HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([
83
+ "bash",
84
+ "dockerfile",
85
+ "make",
86
+ "makefile",
87
+ "perl",
88
+ "php",
89
+ "pl",
90
+ "powershell",
91
+ "ps1",
92
+ "py",
93
+ "python",
94
+ "r",
95
+ "rb",
96
+ "ruby",
97
+ "sh",
98
+ "shell",
99
+ "toml",
100
+ "yaml",
101
+ "yml",
102
+ "zsh"
103
+ ]);
104
+ var SHELL_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["bash", "sh", "shell", "zsh"]);
105
+ var WHITESPACE_HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["yaml", "yml"]);
106
+ var ANYWHERE_HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([
107
+ "perl",
108
+ "php",
109
+ "pl",
110
+ "powershell",
111
+ "ps1",
112
+ "rb",
113
+ "ruby"
114
+ ]);
115
+ var MAKE_LANGUAGES = /* @__PURE__ */ new Set(["make", "makefile"]);
116
+ var PHP_ATTRIBUTE_LANGUAGES = /* @__PURE__ */ new Set(["php"]);
117
+ var LINE_HASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["dockerfile"]);
118
+ var SLASH_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([
119
+ "c",
120
+ "c++",
121
+ "cc",
122
+ "cpp",
123
+ "cs",
124
+ "csharp",
125
+ "cxx",
126
+ "go",
127
+ "h",
128
+ "hpp",
129
+ "java",
130
+ "javascript",
131
+ "js",
132
+ "jsonc",
133
+ "jsx",
134
+ "kotlin",
135
+ "kt",
136
+ "less",
137
+ "php",
138
+ "rs",
139
+ "rust",
140
+ "scala",
141
+ "scss",
142
+ "swift",
143
+ "ts",
144
+ "tsx",
145
+ "typescript"
146
+ ]);
147
+ var C_STYLE_BLOCK_COMMENT_LANGUAGES = /* @__PURE__ */ new Set([...SLASH_COMMENT_LANGUAGES, "css"]);
148
+ var HTML_COMMENT_LANGUAGES = /* @__PURE__ */ new Set(["html", "markdown", "md", "svelte", "svg", "vue", "xml"]);
149
+ var PAIRED_DELIMITERS = /* @__PURE__ */ new Map([["(", ")"], ["{", "}"], ["[", "]"], ["<", ">"]]);
150
+ var delimitedLiteralStart = (rest, previous, language) => {
151
+ if (language === "ruby" || language === "rb") {
152
+ return rest.match(/^%(?:[qQrwWixI])?([^\w\s])/u);
153
+ }
154
+ if ((language === "perl" || language === "pl") && !/[\w$@%]/u.test(previous)) {
155
+ return rest.match(/^(?:qq|qw|qx|qr|q)([^\w\s])/u);
156
+ }
157
+ return null;
158
+ };
159
+ var slashLiteralStart = (text, index, language) => {
160
+ if (!["perl", "pl", "rb", "ruby"].includes(language) || text[index] !== "/") return false;
161
+ const beforeText = text.slice(0, index);
162
+ const before = beforeText.match(/\S\s*$/u)?.[0].trim() ?? "";
163
+ if (before === "" || /[=(:,!\[\{;~]/u.test(before.at(-1) ?? "")) return true;
164
+ const word = beforeText.match(/([A-Za-z]+)\s*$/u)?.[1]?.toLowerCase();
165
+ if (["rb", "ruby"].includes(language)) {
166
+ return ["and", "if", "not", "or", "return", "then", "unless", "when", "while"].includes(word ?? "");
167
+ }
168
+ return ["m", "qr", "s"].includes(word ?? "");
169
+ };
170
+ function stripComments(text, language = "") {
171
+ const normalizedLanguage = language.toLowerCase();
172
+ const hashComments = HASH_COMMENT_LANGUAGES.has(normalizedLanguage);
173
+ const slashComments = SLASH_COMMENT_LANGUAGES.has(normalizedLanguage);
174
+ const cStyleBlockComments = C_STYLE_BLOCK_COMMENT_LANGUAGES.has(normalizedLanguage);
175
+ const htmlComments = HTML_COMMENT_LANGUAGES.has(normalizedLanguage);
176
+ const knownLanguage = hashComments || slashComments || cStyleBlockComments || htmlComments;
83
177
  let out = "";
84
178
  let quote = null;
179
+ let quoteOpen = null;
180
+ let quoteDepth = 0;
85
181
  let block = null;
182
+ let lineStart = 0;
86
183
  for (let i = 0; i < text.length; i += 1) {
87
184
  const rest = text.slice(i);
88
185
  if (block) {
@@ -90,48 +187,92 @@ function stripComments(text) {
90
187
  i += block.length - 1;
91
188
  block = null;
92
189
  }
190
+ if (text[i] === "\n") {
191
+ lineStart = i + 1;
192
+ }
93
193
  continue;
94
194
  }
95
195
  const char = text[i];
196
+ const atLineStart = /^\s*$/.test(text.slice(lineStart, i));
197
+ const genericSlashComment = !knownLanguage && atLineStart && rest.startsWith("//");
198
+ const genericHashComment = !knownLanguage && atLineStart && char === "#" && !rest.startsWith("#!") && !/^#[\w-]+\s*\{/u.test(rest);
199
+ const genericCStyleBlockComment = !knownLanguage && atLineStart && rest.startsWith("/*");
200
+ const genericHtmlComment = !knownLanguage && atLineStart && rest.startsWith("<!--");
201
+ const previous = text[i - 1] ?? "";
202
+ const makeRecipe = MAKE_LANGUAGES.has(normalizedLanguage) && text[lineStart] === " ";
203
+ const shellHashComment = (SHELL_COMMENT_LANGUAGES.has(normalizedLanguage) || makeRecipe) && char === "#" && (i === 0 || /[\s;&|()<>]/u.test(previous));
204
+ const whitespaceHashComment = WHITESPACE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) && char === "#" && (i === 0 || /\s/u.test(previous)) && !(PHP_ATTRIBUTE_LANGUAGES.has(normalizedLanguage) && rest.startsWith("#["));
205
+ const lineHashComment = LINE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) && char === "#" && atLineStart;
206
+ const anywhereHashComment = ANYWHERE_HASH_COMMENT_LANGUAGES.has(normalizedLanguage) && char === "#" && !(PHP_ATTRIBUTE_LANGUAGES.has(normalizedLanguage) && rest.startsWith("#["));
207
+ const makeHashComment = MAKE_LANGUAGES.has(normalizedLanguage) && char === "#" && (makeRecipe ? shellHashComment : true);
208
+ 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);
96
209
  if (quote) {
97
210
  out += char;
98
211
  if (char === "\\") {
99
212
  out += text[i + 1] ?? "";
100
213
  i += 1;
214
+ } else if (quoteOpen && char === quoteOpen) {
215
+ quoteDepth += 1;
101
216
  } else if (char === quote) {
102
- quote = null;
217
+ if (quoteDepth > 0) {
218
+ quoteDepth -= 1;
219
+ } else {
220
+ quote = null;
221
+ quoteOpen = null;
222
+ }
103
223
  }
104
224
  continue;
105
225
  }
226
+ const delimitedLiteral = delimitedLiteralStart(rest, previous, normalizedLanguage);
227
+ if (delimitedLiteral) {
228
+ const opener = delimitedLiteral[1];
229
+ const token = delimitedLiteral[0];
230
+ quoteOpen = PAIRED_DELIMITERS.has(opener) ? opener : null;
231
+ quote = PAIRED_DELIMITERS.get(opener) ?? opener;
232
+ quoteDepth = 0;
233
+ out += token;
234
+ i += token.length - 1;
235
+ continue;
236
+ }
106
237
  if (char === '"' || char === "'" || char === "`") {
107
238
  quote = char;
108
239
  out += char;
109
240
  continue;
110
241
  }
111
- if (rest.startsWith("/*")) {
242
+ if (slashLiteralStart(text, i, normalizedLanguage)) {
243
+ quote = "/";
244
+ out += char;
245
+ continue;
246
+ }
247
+ if (cStyleBlockComments && rest.startsWith("/*") || genericCStyleBlockComment) {
112
248
  block = "*/";
113
249
  continue;
114
250
  }
115
- if (rest.startsWith("<!--")) {
251
+ if (htmlComments && rest.startsWith("<!--") || genericHtmlComment) {
116
252
  block = "-->";
117
253
  continue;
118
254
  }
119
- if (rest.startsWith("//") || char === "#") {
255
+ if (slashComments && rest.startsWith("//") || languageHashComment || genericSlashComment || genericHashComment) {
120
256
  const end = text.indexOf("\n", i);
121
257
  if (end === -1) break;
122
258
  i = end - 1;
259
+ lineStart = end;
123
260
  continue;
124
261
  }
125
262
  out += char;
263
+ if (char === "\n") {
264
+ lineStart = i + 1;
265
+ }
126
266
  }
127
267
  return out;
128
268
  }
129
269
  function normalizeCodeBlock(text) {
130
270
  const raw = text.split("\n");
131
271
  const open = raw[0] ?? "";
272
+ const language = open.match(/^\s*(?:`{3,}|~{3,})\s*([^\s{]+)/)?.[1] ?? "";
132
273
  const closes = raw.length > 1 && /^\s*(`{3,}|~{3,})\s*$/.test(raw[raw.length - 1] ?? "");
133
274
  const body = raw.slice(1, closes ? -1 : void 0).join("\n");
134
- const lines = [open, ...stripComments(body).split("\n")];
275
+ const lines = [open, ...stripComments(body, language).split("\n")];
135
276
  const indent = lines[0].match(/^\s*/)[0].length;
136
277
  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");
137
278
  }
@@ -246,25 +387,18 @@ var ENGLISH_NUMBER_WORDS = {
246
387
  triple: 3,
247
388
  both: 2,
248
389
  dozen: 12,
249
- half: 50,
250
- hex: 16,
251
- hexadecimal: 16,
252
- octal: 8,
253
- binary: 2,
254
- decimal: 10,
255
- january: 1,
256
- february: 2,
257
- march: 3,
258
- april: 4,
259
- may: 5,
260
- june: 6,
261
- july: 7,
262
- august: 8,
263
- september: 9,
264
- october: 10,
265
- november: 11,
266
- december: 12
390
+ half: 50
267
391
  };
392
+ 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 };
393
+ var MONTH_DATE_RE = new RegExp(`\\b(${Object.keys(MONTHS).join("|")})\\s+\\d|\\d\\s+(${Object.keys(MONTHS).join("|")})\\b`, "g");
394
+ var SECOND_UNIT_RE = /\b(?:\d+(?:\.\d+)?|a|an|one|per|each|every)\s+second\b|\bseconds\b/g;
395
+ var KOREAN_BASE_RE = /(\d+)\s*진/g;
396
+ var ENGLISH_BASES = { hex: 16, hexadecimal: 16, octal: 8, binary: 2, decimal: 10 };
397
+ var ENGLISH_BASE_RE = new RegExp(`\\b(${Object.keys(ENGLISH_BASES).join("|")})\\b`, "g");
398
+ function sourceBases(prose, language) {
399
+ if (language === "en") return new Set([...prose.toLowerCase().matchAll(ENGLISH_BASE_RE)].map((m) => String(ENGLISH_BASES[m[1]])));
400
+ return new Set([...prose.matchAll(KOREAN_BASE_RE)].map((m) => normalizeNumber(m[1])));
401
+ }
268
402
  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 };
269
403
  var KOREAN_NUMERAL = "\uD55C|\uD558\uB098|\uB450|\uB458|\uC138|\uC14B|\uB124|\uB137|\uB2E4\uC12F|\uC5EC\uC12F|\uC77C\uACF1|\uC5EC\uB35F|\uC544\uD649|\uC5F4";
270
404
  var KOREAN_COUNTER = "\uAC1C|\uBC88|\uAC00\uC9C0|\uBA85|\uB300|\uC904|\uB2E8\uACC4|\uBC30|\uC2DC\uAC04|\uBD84|\uCD08|\uC77C|\uC8FC|\uB2EC|\uACF3|\uCABD|\uC7A5|\uCE78";
@@ -289,7 +423,9 @@ function digitNumbers(prose) {
289
423
  function justifiedNumbers(prose, language) {
290
424
  const out = /* @__PURE__ */ new Set();
291
425
  if (language === "en") {
292
- for (const m of prose.toLowerCase().matchAll(/\b[a-z]+\b/g)) {
426
+ const lower = prose.toLowerCase();
427
+ for (const m of lower.matchAll(MONTH_DATE_RE)) out.add(String(MONTHS[m[1] ?? m[2]]));
428
+ for (const m of lower.replace(SECOND_UNIT_RE, " ").matchAll(/\b[a-z]+\b/g)) {
293
429
  if (Object.hasOwn(ENGLISH_NUMBER_WORDS, m[0])) out.add(String(ENGLISH_NUMBER_WORDS[m[0]]));
294
430
  }
295
431
  if (/\bnon-?negative\b|\bzero-based\b/i.test(prose)) out.add("0");
@@ -304,14 +440,17 @@ function justifiedNumbers(prose, language) {
304
440
  }
305
441
  function checkNumbers(sourceBlocks, draftBlocks, sourceLanguage, add) {
306
442
  const sourceProse = numberProse(sourceBlocks);
307
- const draftProse = numberProse(draftBlocks);
443
+ const draftText = numberProse(draftBlocks);
444
+ const bases = sourceBases(sourceProse, sourceLanguage);
445
+ const draftBases = new Set([...draftText.matchAll(KOREAN_BASE_RE)].map((m) => normalizeNumber(m[1])));
446
+ const draftProse = draftText.replace(KOREAN_BASE_RE, " ");
308
447
  const sourceDigits = digitNumbers(sourceProse);
309
448
  const draftDigits = digitNumbers(draftProse);
310
449
  const justified = /* @__PURE__ */ new Set([...sourceDigits, ...justifiedNumbers(sourceProse, sourceLanguage)]);
311
450
  const draftWords = justifiedNumbers(draftProse, "ko");
312
- const draftAll = /* @__PURE__ */ new Set([...draftDigits, ...draftWords]);
451
+ const draftAll = /* @__PURE__ */ new Set([...draftDigits, ...draftWords, ...draftBases]);
313
452
  const addedWords = [...draftWords].filter((n) => Number(n) >= 3);
314
- for (const n of [...draftDigits, ...addedWords]) {
453
+ for (const n of [...draftDigits, ...addedWords, ...[...draftBases].filter((b) => !bases.has(b))]) {
315
454
  if (!justified.has(n)) add("fail", "number", `\uC6D0\uBB38\uC5D0 \uC5C6\uB294 \uC22B\uC790\uAC00 \uC0DD\uACBC\uC2B5\uB2C8\uB2E4: ${n}`, { token: n });
316
455
  }
317
456
  for (const n of sourceDigits) {
@@ -322,7 +461,7 @@ var EN_WEAK_PROHIBITION = [/\bavoid(?:s|ing)?\b/i, /\b(?:forbid|forbids|disallow
322
461
  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;
323
462
  var EN = {
324
463
  prohibition: [
325
- /(?:^|[.!?:]\s+|\n\s*(?:[-*+]|\d+[.)])?\s*|\*\*|>\s*)(?:do not|don't|never|avoid)\b(?!\s+(?:need|have to|require))/i,
464
+ /(?:^|[.!?:;]\s+|\n\s*(?:[-*+]|\d+[.)])?\s*|\*\*|>\s*)(?:do not|don't|never|avoid)\b(?!\s+(?:need|have to|require))/i,
326
465
  // "should not", and "should, however, not"
327
466
  /\b(?:must|shall|should)(?:\s+not|n't|,[^,.;!?\n]{1,25},\s+not)\b/i,
328
467
  /\b(?:is|are|isn't|aren't)\s+not\s+(?:allowed|permitted)\b/i,