@litfamily/litopencode 1.0.25 → 1.0.26
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/CHANGELOG.md +4 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/dist/deliverable-hedge-guard.js +176 -10
- package/docs/assets/readme/badge-version.svg +1 -1
- package/package.json +1 -1
- package/skills/lit-humanizer/SKILL.md +1 -1
- package/skills/lit-humanizer/references/taxonomy.md +2 -2
- package/skills/lit-humanizer/rules.json +10 -7
- package/skills/lit-humanizer/scripts/core.mjs +13 -2
- package/skills/managed-skill-manifest.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ are tracked separately from this product history.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 1.0.26 - 2026-10-16
|
|
9
|
+
|
|
10
|
+
- The write guard now refuses more of the lines that tell a reader what a document is not for, such as `This table is not a ranking.` or `이 문서는 제품을 비교하려는 것이 아닙니다.` These forms came from real sessions. Bold-label callouts such as `> **주의:** ...` are now read too, while ordinary quoted text is still skipped, and lines from manuals and lab reports that only look similar stay allowed. Files saved through `apply_patch` are now checked before they land, the same way the write and edit tools already were.
|
|
11
|
+
|
|
8
12
|
## 1.0.25 - 2026-10-15
|
|
9
13
|
|
|
10
14
|
- The write guard now refuses sentences that say what a document is not for, such as `동일 조건의 독립 성능 비교로 쓰지 않음.` or an English line saying a result is not a benchmark. When the model writes one with the write or edit tool, the call is refused and the model rewrites it. Three new rules cover Korean pledges, a Korean pledge repeated through a document, and the English forms, and the status-negation warning now also reads `쓰지 않음`, the polite endings and bold-wrapped endings. Legal and financial notices, safety warnings, operating rules, method lines and real negative findings stay untouched, and the rules still finish in milliseconds on very long runs of spaces. The lit-docx and lit-pptx skills now ask for a real limit once, as a fact beside the number it affects, and keep anything about what the document is not for in the reply.
|
package/README-Ko-KR.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitOpenCode 로봇의 눈과 테두리가 빛난 뒤 LITFAMILY와 KEEP THE WORK LIT. 문구가 밝아지는 영상" /></picture></p>
|
|
2
2
|
|
|
3
3
|
# LitOpenCode
|
|
4
4
|
|
|
5
5
|
**LitOpenCode는 OpenCode용 워크플로 플러그인입니다. 프롬프트에 `lit`만 붙이면 계획하고, 만들고, 확인하고, 기록까지 남겨서 다음 세션이 어디서 멈췄는지 바로 압니다.**
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/readme/badge-version.svg" alt="1.0.26" /></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://img.shields.io/npm/dm/@litfamily/litopencode?label=downloads&color=D7F75B" alt="월간 npm 다운로드 수" /></a>
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/readme/badge-license.svg" alt="MIT 라이선스" /></a>
|
|
11
11
|
<a href="https://github.com/wjgoarxiv/litopencode"><img src="https://img.shields.io/github/stars/wjgoarxiv/litopencode?style=social" alt="GitHub 스타" /></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -57,9 +57,9 @@ npm uninstall -g @litfamily/litopencode
|
|
|
57
57
|
## 더 보기
|
|
58
58
|
|
|
59
59
|
- [GitHub의 사용 안내](https://github.com/wjgoarxiv/litopencode/blob/main/docs/guide-Ko-KR.md): 설치 옵션, 자동 인수인계, 문제 해결, 제거
|
|
60
|
-
- [워크플로 상세 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
61
|
-
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
62
|
-
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
60
|
+
- [워크플로 상세 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/reference-Ko-KR.md) · [English reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/reference.md)
|
|
61
|
+
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/CHANGELOG.md) · [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/privacy.md)
|
|
62
|
+
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/LICENSE) · [ASCII 글꼴 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>ASCII 로고 복사</summary>
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitOpenCode robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
|
|
2
2
|
|
|
3
3
|
# LitOpenCode
|
|
4
4
|
|
|
5
5
|
**LitOpenCode is a workflow plugin for OpenCode. Add `lit` to a prompt and the work gets planned, built, checked and written down, so the next session knows where you stopped.**
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/readme/badge-version.svg" alt="1.0.26" /></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://img.shields.io/npm/dm/@litfamily/litopencode?label=downloads&color=D7F75B" alt="npm downloads per month" /></a>
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
|
|
11
11
|
<a href="https://github.com/wjgoarxiv/litopencode"><img src="https://img.shields.io/github/stars/wjgoarxiv/litopencode?style=social" alt="GitHub stars" /></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -57,9 +57,9 @@ npm uninstall -g @litfamily/litopencode
|
|
|
57
57
|
## More
|
|
58
58
|
|
|
59
59
|
- [Guide on GitHub](https://github.com/wjgoarxiv/litopencode/blob/main/docs/guide.md): install options, automatic handoff, troubleshooting and removal.
|
|
60
|
-
- [Workflow reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
61
|
-
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
62
|
-
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
60
|
+
- [Workflow reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/reference.md)
|
|
61
|
+
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/CHANGELOG.md) · [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/privacy.md)
|
|
62
|
+
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/LICENSE) · [ASCII font license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.26/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>Copy ASCII logo</summary>
|
|
@@ -57,6 +57,116 @@ function toolText(input) {
|
|
|
57
57
|
return { file: candidate, unavailable: "new text exceeds scan limit" };
|
|
58
58
|
return { file: candidate, value: values[0] };
|
|
59
59
|
}
|
|
60
|
+
const patchTools = new Set(["apply_patch", "patch"]);
|
|
61
|
+
function patchTextOf(input) {
|
|
62
|
+
if (!patchTools.has(input.tool) || !isRecord(input.args))
|
|
63
|
+
return undefined;
|
|
64
|
+
const value = input.args.patchText;
|
|
65
|
+
return typeof value === "string" ? value : undefined;
|
|
66
|
+
}
|
|
67
|
+
// Reads the apply_patch envelope: `*** Add File:` bodies are `+` lines, `*** Update File:` carries an optional
|
|
68
|
+
// `*** Move to:` and `@@` chunks of ` `, `-` and `+` lines. Delete hunks remove text and are not scanned.
|
|
69
|
+
function parsePatch(patchText) {
|
|
70
|
+
const lines = patchText.split(/\r?\n/u);
|
|
71
|
+
const begin = lines.findIndex((line) => line.trim() === "*** Begin Patch");
|
|
72
|
+
const end = lines.findIndex((line) => line.trim() === "*** End Patch");
|
|
73
|
+
if (begin < 0 || end < 0 || begin >= end)
|
|
74
|
+
throw new Error("patch text has no Begin/End markers");
|
|
75
|
+
const isHeader = (line) => /^\*\*\* (?:Add|Update|Delete) File:/u.test(line);
|
|
76
|
+
const files = [];
|
|
77
|
+
let index = begin + 1;
|
|
78
|
+
while (index < end) {
|
|
79
|
+
const line = lines[index];
|
|
80
|
+
if (line.startsWith("*** Add File:")) {
|
|
81
|
+
const body = [];
|
|
82
|
+
index += 1;
|
|
83
|
+
while (index < end && !isHeader(lines[index])) {
|
|
84
|
+
if (lines[index].startsWith("+"))
|
|
85
|
+
body.push(lines[index].slice(1));
|
|
86
|
+
index += 1;
|
|
87
|
+
}
|
|
88
|
+
files.push({ kind: "add", file: line.slice(13).trim(), contents: body.join("\n"), chunks: [], added: body });
|
|
89
|
+
}
|
|
90
|
+
else if (line.startsWith("*** Update File:")) {
|
|
91
|
+
const file = line.slice(16).trim();
|
|
92
|
+
let moveTo;
|
|
93
|
+
index += 1;
|
|
94
|
+
if (index < end && lines[index].startsWith("*** Move to:")) {
|
|
95
|
+
moveTo = lines[index].slice(12).trim();
|
|
96
|
+
index += 1;
|
|
97
|
+
}
|
|
98
|
+
const chunks = [];
|
|
99
|
+
const added = [];
|
|
100
|
+
let before = [];
|
|
101
|
+
let after = [];
|
|
102
|
+
let open = false;
|
|
103
|
+
const close = () => {
|
|
104
|
+
if (open)
|
|
105
|
+
chunks.push({ before, after });
|
|
106
|
+
before = [];
|
|
107
|
+
after = [];
|
|
108
|
+
open = false;
|
|
109
|
+
};
|
|
110
|
+
while (index < end && !isHeader(lines[index])) {
|
|
111
|
+
const row = lines[index];
|
|
112
|
+
if (row.startsWith("@@"))
|
|
113
|
+
close();
|
|
114
|
+
else if (row.startsWith("*** End of File")) { /* marker only */ }
|
|
115
|
+
else if (row.startsWith("+")) {
|
|
116
|
+
open = true;
|
|
117
|
+
after.push(row.slice(1));
|
|
118
|
+
added.push(row.slice(1));
|
|
119
|
+
}
|
|
120
|
+
else if (row.startsWith("-")) {
|
|
121
|
+
open = true;
|
|
122
|
+
before.push(row.slice(1));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
open = true;
|
|
126
|
+
const text = row.startsWith(" ") ? row.slice(1) : row;
|
|
127
|
+
before.push(text);
|
|
128
|
+
after.push(text);
|
|
129
|
+
}
|
|
130
|
+
index += 1;
|
|
131
|
+
}
|
|
132
|
+
close();
|
|
133
|
+
files.push({ kind: "update", file, moveTo: moveTo === "" ? undefined : moveTo, contents: "", chunks, added });
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
index += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return files;
|
|
140
|
+
}
|
|
141
|
+
function applyChunks(oldText, chunks) {
|
|
142
|
+
const lines = oldText.split("\n");
|
|
143
|
+
const trailing = lines.length > 1 && lines[lines.length - 1] === "";
|
|
144
|
+
if (trailing)
|
|
145
|
+
lines.pop();
|
|
146
|
+
let cursor = 0;
|
|
147
|
+
for (const chunk of chunks) {
|
|
148
|
+
if (chunk.before.length === 0) {
|
|
149
|
+
lines.push(...chunk.after);
|
|
150
|
+
cursor = lines.length;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const matches = (at, normalize) => chunk.before.every((text, offset) => at + offset < lines.length && normalize(lines[at + offset]) === normalize(text));
|
|
154
|
+
let found = -1;
|
|
155
|
+
for (const normalize of [(text) => text, (text) => text.trimEnd(), (text) => text.trim()]) {
|
|
156
|
+
for (let at = cursor; at + chunk.before.length <= lines.length && found < 0; at += 1) {
|
|
157
|
+
if (matches(at, normalize))
|
|
158
|
+
found = at;
|
|
159
|
+
}
|
|
160
|
+
if (found >= 0)
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (found < 0)
|
|
164
|
+
throw new Error("patch chunk was not found in the current file");
|
|
165
|
+
lines.splice(found, chunk.before.length, ...chunk.after);
|
|
166
|
+
cursor = found + chunk.after.length;
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n") + (trailing ? "\n" : "");
|
|
169
|
+
}
|
|
60
170
|
function runJsonProcess(command, args, input, timeoutMs) {
|
|
61
171
|
return new Promise((resolve, reject) => {
|
|
62
172
|
const child = spawn(command, [...args], { shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -186,8 +296,71 @@ export function createDeliverableHedgeGuard(options) {
|
|
|
186
296
|
const timeoutMs = options.timeoutMs ?? defaultTimeoutMs;
|
|
187
297
|
const scan = options.scan ?? ((request) => defaultScan(request, timeoutMs));
|
|
188
298
|
const pending = new Map();
|
|
299
|
+
async function scanTarget(file, oldText, newText) {
|
|
300
|
+
if (Buffer.byteLength(newText, "utf8") > maxTextBytes)
|
|
301
|
+
throw new Error("new text exceeds scan limit");
|
|
302
|
+
const findings = await scan({ file, oldText, newText });
|
|
303
|
+
const block = findings.filter((finding) => finding.severity === "block");
|
|
304
|
+
if (block.length > 0) {
|
|
305
|
+
const first = block[0];
|
|
306
|
+
throw new Error(`LIT_HUMANIZER_BLOCKED rule=${first.rule} line=${first.line} path=${displayPath(file)}. Revise the new text before saving.`);
|
|
307
|
+
}
|
|
308
|
+
return findings.length;
|
|
309
|
+
}
|
|
310
|
+
// An apply_patch call can add several files; each deliverable-path target gets the same per-file scan as write
|
|
311
|
+
// and edit. A chunk that cannot be located in the current file is scanned as appended text instead, so an added
|
|
312
|
+
// pledge is still refused (the host rejects a patch whose context does not match, so nothing is saved either way).
|
|
313
|
+
async function guardPatch(callID, patchText) {
|
|
314
|
+
try {
|
|
315
|
+
if (Buffer.byteLength(patchText, "utf8") > maxTextBytes)
|
|
316
|
+
throw new Error("patch text exceeds scan limit");
|
|
317
|
+
let warned = 0;
|
|
318
|
+
let warnedFile = "";
|
|
319
|
+
for (const entry of parsePatch(patchText)) {
|
|
320
|
+
const target = entry.moveTo ?? entry.file;
|
|
321
|
+
if (isInternalPath(target) || !readerFacingExtensions.has(path.extname(target).toLowerCase()))
|
|
322
|
+
continue;
|
|
323
|
+
const oldText = await existingText(projectRoot, entry.kind === "add" ? target : entry.file);
|
|
324
|
+
let newText;
|
|
325
|
+
if (entry.kind === "add")
|
|
326
|
+
newText = entry.contents;
|
|
327
|
+
else {
|
|
328
|
+
try {
|
|
329
|
+
newText = applyChunks(oldText, entry.chunks);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
newText = `${oldText}\n${entry.added.join("\n")}`;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const count = await scanTarget(target, oldText, newText);
|
|
336
|
+
if (count > 0 && warned === 0)
|
|
337
|
+
warnedFile = target;
|
|
338
|
+
warned += count;
|
|
339
|
+
}
|
|
340
|
+
if (warned > 0) {
|
|
341
|
+
remember(pending, callID, {
|
|
342
|
+
tier: "warn",
|
|
343
|
+
message: `LIT_HUMANIZER_WARN findings=${warned} path=${displayPath(warnedFile)}. Review the new wording; keep any accurate, useful warning.`
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
if (error instanceof Error && error.message.startsWith("LIT_HUMANIZER_BLOCKED"))
|
|
349
|
+
throw error;
|
|
350
|
+
remember(pending, callID, {
|
|
351
|
+
tier: "unavailable",
|
|
352
|
+
message: `LIT_HUMANIZER_GUARD_UNAVAILABLE: ${oneLine(error)}; write allowed without this check.`
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
189
356
|
return {
|
|
190
357
|
async before(input) {
|
|
358
|
+
const patchText = patchTextOf(input);
|
|
359
|
+
if (patchText !== undefined) {
|
|
360
|
+
pending.delete(input.callID);
|
|
361
|
+
await guardPatch(input.callID, patchText);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
191
364
|
const text = toolText(input);
|
|
192
365
|
if (text === undefined)
|
|
193
366
|
return;
|
|
@@ -204,18 +377,11 @@ export function createDeliverableHedgeGuard(options) {
|
|
|
204
377
|
const newText = input.tool === "write"
|
|
205
378
|
? text.value ?? ""
|
|
206
379
|
: applyEdit(oldText, input.args.oldString ?? input.args.old_string, text.value ?? "", input.args.replaceAll);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const findings = await scan({ file: text.file, oldText, newText });
|
|
210
|
-
const block = findings.filter((finding) => finding.severity === "block");
|
|
211
|
-
if (block.length > 0) {
|
|
212
|
-
const first = block[0];
|
|
213
|
-
throw new Error(`LIT_HUMANIZER_BLOCKED rule=${first.rule} line=${first.line} path=${displayPath(text.file)}. Revise the new text before saving.`);
|
|
214
|
-
}
|
|
215
|
-
if (findings.length > 0) {
|
|
380
|
+
const warned = await scanTarget(text.file, oldText, newText);
|
|
381
|
+
if (warned > 0) {
|
|
216
382
|
remember(pending, input.callID, {
|
|
217
383
|
tier: "warn",
|
|
218
|
-
message: `LIT_HUMANIZER_WARN findings=${
|
|
384
|
+
message: `LIT_HUMANIZER_WARN findings=${warned} path=${displayPath(text.file)}. Review the new wording; keep any accurate, useful warning.`
|
|
219
385
|
});
|
|
220
386
|
}
|
|
221
387
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.26"><title>release: 1.0.26</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.26</text></g></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@litfamily/litopencode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "Workflow plugin for OpenCode: add lit to a prompt and the work is planned, built, checked and written down so the next session can pick it up.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -115,7 +115,7 @@ DOCX and PPTX text extraction uses `scripts/extract_office_text.py`, which relie
|
|
|
115
115
|
|
|
116
116
|
## Guard scope and limits
|
|
117
117
|
|
|
118
|
-
Scan only text being added or changed. Preserve pre-existing file content and user quotations. Skip fenced and inline code, Markdown blockquotes, and internal paths such as `plans/`, `evidence/`, `HANDOFF*`, `.lit*/`, `.hermes/`, and ledgers. The native OpenCode guard applies to reader-facing text paths, not every source or configuration file. Office and PDF checks happen after creation: a hit asks the model to fix its source and rebuild; it cannot undo the tool that created the file.
|
|
118
|
+
Scan only text being added or changed. Preserve pre-existing file content and user quotations. Skip fenced and inline code, Markdown blockquotes (except a quote opening with a bold label, which the pledge rules read as the author's callout), and internal paths such as `plans/`, `evidence/`, `HANDOFF*`, `.lit*/`, `.hermes/`, and ledgers. The native OpenCode guard applies to reader-facing text paths, not every source or configuration file. Office and PDF checks happen after creation: a hit asks the model to fix its source and rebuild; it cannot undo the tool that created the file.
|
|
119
119
|
|
|
120
120
|
Block only high-confidence patterns such as label-only evidence lines, stacked disclaimers, and chatbot residue. Word choice, contrast, triads, and rhythm are warnings. A hit is an editing signal, not proof of model authorship. Stop when another change would alter meaning, hide a real limitation, or flatten the writer's voice.
|
|
121
121
|
|
|
@@ -17,9 +17,9 @@ This original category comes from the 26 real lines in fixtures/pos-real.txt and
|
|
|
17
17
|
- ko-bracket-status-tag and en-bracket-status-tag — ko/en, block. Replace bracket keys with a sentence describing the status.
|
|
18
18
|
- ko-standalone-scope-disclaimer and en-standalone-scope-disclaimer — ko/en, block for stock disclaimers. Preserve a user-requested legal, medical or safety notice and place it once where it applies.
|
|
19
19
|
- ko-status-negation, ko-mi-status, en-epistemic-negation and en-not-performed — warnings. ko-status-negation also reads 쓰지 않음, the polite 않습니다/않았습니다 endings and a bold-wrapped ending, so a method line such as “fitting parameter는 쓰지 않음” is advised on, never refused. A negative result or unperformed action may be the finding. Write the actor, scope and verb plainly; never turn unknown into false or an estimate into a fact.
|
|
20
|
-
- ko-negative-pledge — ko, block, one line at a time. The reader meets a sentence about what the document is not for: a self-reference (이 표, 본 자료, 해당 보고서) whose verb is negated 해석/의미/주장/대표 or whose noun is a claim noun with 아님; a bare “비교로 쓰지 않음” with no subject;
|
|
20
|
+
- ko-negative-pledge — ko, block, one line at a time. The reader meets a sentence about what the document is not for: a self-reference (이 표, 본 자료, 해당 보고서) whose verb is negated 해석/의미/주장/대표 or whose noun is a claim noun with 아님; a bare “비교로 쓰지 않음” with no subject; a purpose word such as 공개용 수치가 아님; a comparison refusal about the numbers (숫자는 서로 비교할 수 없습니다 when it stands alone, 순위처럼 읽지 말아 주세요); or a ranking refusal (…만으로 순위를 정할 수 없다, 표의 순서는 순위가 아님). Move the sentence to the reply, or turn it into a fact about the data beside the number it affects (“샘플은 한 공정 조건만 포함한다”). Operating rules (사용하지 않습니다, 냉각수로 쓰지 않음), conditional method facts (합계와 비교하면 안 됩니다), type rules (숫자와 문자열은 서로 비교할 수 없습니다), data-handling rules, and legal or financial notices (대체하지, 보장하지, 권유가 아닙니다) stay outside the pattern on purpose. A quote line that opens with a bold label (`> **주의:** …`) is the author's own callout, so this rule and the two below read it with the quote marker blanked; a plain quote line stays skipped.
|
|
21
21
|
- ko-negative-pledge-repeat — ko, block, whole document. Two or more sentences ending in 해석하지/주장하지/대표하지 않음, or in a claim noun + 아님, make a list of disclaimers. One such sentence stays with the warning above; the use verbs and 의미하/간주하/인용하 are left out because definitions and processing rules end that way.
|
|
22
|
-
- en-negative-pledge — en, block, one sentence. “This is not intended as a benchmark”, “these figures do not constitute a comparison”, “not to be read as a ranking”, and “the deck makes no claim about …” say what the document is not. A comparison, benchmark or ranking noun is required, and substitute-for-advice and investment, legal or medical advice notices stay clean. Say it in the reply, or state the data fact.
|
|
22
|
+
- en-negative-pledge — en, block, one sentence. “This is not intended as a benchmark”, “these figures do not constitute a comparison”, “not to be read as a ranking”, and “the deck makes no claim about …” say what the document is not. A comparison, benchmark or ranking noun is required, and substitute-for-advice and investment, legal or medical advice notices stay clean. A deliverable subject (this table, our deck) that is not a ranking or is not meant to rank also counts, and so do “they are not a ranking.” and “do not compare these numbers.” at the end of a sentence; a refusal that goes on to describe or instruct stays clean. It reads bold-label callouts too. Say it in the reply, or state the data fact.
|
|
23
23
|
- ko-plain-meta-label-warn and en-plain-caveat-label — warnings for boundary headings. They may be useful in internal or technical packets.
|
|
24
24
|
- en-knowledge-cutoff, en-ai-self-disclaimer, en-hope-helps and ko-chatbot-signoff — block. Remove the model wrapper or generic sign-off, then retain the answer.
|
|
25
25
|
- ko-chatbot-opener and en-chatbot-opener — block stock greetings and praise at the start of a deliverable.
|
|
@@ -884,14 +884,15 @@
|
|
|
884
884
|
"id": "ko-negative-pledge",
|
|
885
885
|
"lang": "ko",
|
|
886
886
|
"severity": "block",
|
|
887
|
-
"pattern": "(?<![가-힣])(?:이|본|해당|위|아래)\\s+(?:자료|문서|보고서|슬라이드|덱|표|그림|그래프|차트|수치|결과|비교|분석|내용|예시)(?:은|는)\\s[^.!?\\n]{0,40}?(?:(?:해석하|의미하|주장하|대표하)(?:지\\s*(?:[*_]{1,2}\\s*)?(?:않(?:음|는다|습니다)|말\\s
|
|
887
|
+
"pattern": "(?<![가-힣])(?:이|본|해당|위|아래)\\s+(?:자료|문서|보고서|슬라이드|덱|표|그림|그래프|차트|수치|결과|비교|분석|내용|예시)(?:은|는)\\s[^.!?\\n]{0,40}?(?:(?:해석하|의미하|주장하|대표하)(?:지\\s*(?:[*_]{1,2}\\s*)?(?:않(?:음|는다|습니다)|말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:(?:비교|결론|벤치마크|순위|대표값|일반화)(?:이|가)|목적이|(?:위한|려는)\\s*것이)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣])|순위표(?:이|가)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다|아니고)(?![가-힣]))|(?:비교|결론|벤치마크|순위|대표값|일반화)(?:처럼|으로|로)\\s*(?:[*_]{1,2}\\s*)?(?:쓰|사용하|해석하|읽|간주하|인용하)(?:지\\s*(?:[*_]{1,2}\\s*)?(?:않(?:음|는다|습니다)|말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:공개|배포|대외|외부|홍보|발표|보고|비교|인용|공식)용\\s*(?:자료|문서|수치|표|그래프)(?:이|가)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣])|(?<![가-힣])(?:숫자|수치)(?:들)?(?:은|는|이|가|을|를|끼리|도)(?![가-힣])(?:(?!면\\s)[^.!?\\n]){0,40}?(?<![와과]\\s)(?<![와과])비교할\\s*수\\s*(?:는\\s*)?없(?:다|습니다|음|나)(?![가-힣])|(?<![가-힣])(?:숫자|수치)(?:들)?끼리\\s*(?:서로\\s*|직접\\s*)?비교(?:해서|하여|해)\\s[^.!?\\n]{0,30}?[\"”’']\\s*(?:는|라는)\\s*결론[^.!?\\n]{0,8}?(?:수\\s*없(?:다|습니다)(?![가-힣])|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:(?<![가-힣])(?:숫자|수치)(?:들)?|(?<![가-힣])(?:이|위|아래|본)\\s+(?:표|값|자료|그래프|차트))[^.!?\\n]{0,40}?(?:빠르|느리)다고\\s*판단하(?:지\\s*(?:[*_]{1,2}\\s*)?(?:말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:빠르|느리)다[\"”’']?\\s*(?:는|라는)\\s*식으로\\s*읽(?:지\\s*(?:[*_]{1,2}\\s*)?(?:말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|만으로\\s[^.!?\\n]{0,40}?(?<![가-힣])순위를\\s*정하[^.!?\\n]{0,25}?(?:수\\s*없(?:다|습니다|음)(?![가-힣])|(?:해|하)서는\\s*안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?<![가-힣])순위[^.!?\\n]{0,20}?근거로\\s*(?:[*_]{1,2}\\s*)?사용할\\s*수\\s*(?:는\\s*)?없(?:다|습니다|음)(?![가-힣])|할\\s*수\\s*없는\\s*것\\s{0,3}:\\s{0,3}(?:[*_]{1,2}\\s{0,3})?[^.!?\\n]{0,30}?(?<![가-힣])순위를?\\s*매기거나|(?<![가-힣])표의\\s+(?:나열\\s+)?순서(?:는|가)\\s[^.!?\\n]{0,20}?(?<![가-힣])순위(?:(?:를|을)\\s*뜻하지\\s*(?:[*_]{1,2}\\s*)?않(?:음|는다|습니다)|(?:가|이)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣]))",
|
|
888
888
|
"flags": "m",
|
|
889
889
|
"scope": "line",
|
|
890
890
|
"context": [
|
|
891
891
|
"fenced_code",
|
|
892
|
-
"quoted_user_text"
|
|
892
|
+
"quoted_user_text",
|
|
893
|
+
"blockquote_callout"
|
|
893
894
|
],
|
|
894
|
-
"rationale": "A sentence that tells the reader what the document, table or number is not for belongs in the reply or becomes a data fact.
|
|
895
|
+
"rationale": "A sentence that tells the reader what the document, table or number is not for belongs in the reply or becomes a data fact. Shapes: a self-reference topic (이/본/해당/위/아래 + document noun + 은/는) with a negated claim verb, a claim noun + 아님 (순위표 also + 아니고) or 위한/-려는 것이 아님, within one bullet; a comparison or ranking noun + (으)로/처럼 + a negated use or reading verb (순위처럼 읽지 말아 주세요); a purpose-용 data noun (공개용 수치) + 아님; 숫자/수치 + particle said to be incomparable (서로 비교할 수 없다) or compared among themselves toward a quoted verdict (숫자끼리 비교해 \"…빠르다\"는 결론을 내리면 안 됩니다); a speed judgment forbidden for these numbers or a quoted verdict forbidden as a reading (이 숫자로 … 느리다고 판단하면 안 됩니다); ranking refused for this data (…만으로 순위를 정할 수 없다, 순위의 근거로 사용할 수 없다, 할 수 없는 것: 순위를 매기거나); and a table's row order said not to be a ranking (표의 순서는 순위가 아니다). Use verbs after a self-reference, 근거/평가/증거/추천, inference limits (결론 내릴 수 없다, 근거가 되지 않는다), operating rules (…근거로 사용하지 마십시오), conditional method facts (…면 비교할 수 없다, 합계와 비교하면 안 됩니다), type-system rules (숫자 문자열을 그대로 비교하지 마십시오), reported speech and explainers (…면 안 된다는 반론, …면 안 되는 이유), order notices without a table anchor, and legal or financial notices (대체하, 보장하, 권유) stay outside the pattern because operating rules, method lines, manuals, discussions and standard notices use them.",
|
|
895
896
|
"fixture_ids": [
|
|
896
897
|
"ko-negative-pledge:positive",
|
|
897
898
|
"ko-negative-pledge:negative"
|
|
@@ -906,7 +907,8 @@
|
|
|
906
907
|
"scope": "document",
|
|
907
908
|
"context": [
|
|
908
909
|
"fenced_code",
|
|
909
|
-
"quoted_user_text"
|
|
910
|
+
"quoted_user_text",
|
|
911
|
+
"blockquote_callout"
|
|
910
912
|
],
|
|
911
913
|
"rationale": "Two or more sentences that end in 해석하지/주장하지/대표하지 않음 or a claim noun + 이/가 + 아님 turn a document into a list of disclaimers. 의미하, 간주하, 인용하 and the use verbs are left out because repeated definitions, data-processing rules, method lines and operating rules end that way; a single such sentence stays with the warn-only ko-status-negation.",
|
|
912
914
|
"fixture_ids": [
|
|
@@ -918,14 +920,15 @@
|
|
|
918
920
|
"id": "en-negative-pledge",
|
|
919
921
|
"lang": "en",
|
|
920
922
|
"severity": "block",
|
|
921
|
-
"pattern": "\\b(?:this|these)\\s+(?:deck|report|document|analysis|table|chart|figure|figures|numbers|results|slide|comparison)\\s+(?:(?:is|are)\\s+not\\s+(?:intended|meant|to\\s+be\\s+(?:read|used|taken|interpreted))(?:\\s+to\\s+be\\s+(?:read|used|taken|interpreted))?\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|(?:should|must)\\s+not\\s+be\\s+(?:read|used|taken|interpreted)\\s+as\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b)|\\b(?:this|these)\\s+(?:is|are)\\s+not\\s+(?:intended|meant)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\bthe\\s+(?:deck|report|document|analysis)\\s+makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b|(?:^|\\s[-*]\\s)\\s*(?:[-*]\\s+|\\d+\\.\\s+)?(?:[*_]{1,2})?not\\s+(?:intended|meant|to\\s+be\\s+read|to\\s+be\\s+used)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\b(?:this|these)\\s+(?:table|chart|figure|figures|numbers|results|comparison|analysis)\\s+(?:does|do)\\s+not\\s+constitute\\s+(?:an?\\s+)?[^.!?]{0,60}?\\b(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)",
|
|
923
|
+
"pattern": "\\b(?:this|these)\\s+(?:deck|report|document|analysis|table|chart|figure|figures|numbers|results|slide|comparison)\\s+(?:(?:is|are)\\s+not\\s+(?:intended|meant|to\\s+be\\s+(?:read|used|taken|interpreted))(?:\\s+to\\s+be\\s+(?:read|used|taken|interpreted))?\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|(?:should|must)\\s+not\\s+be\\s+(?:read|used|taken|interpreted)\\s+as\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b)|\\b(?:this|these)\\s+(?:is|are)\\s+not\\s+(?:intended|meant)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\bthe\\s+(?:deck|report|document|analysis)\\s+makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b|(?:^|\\s[-*]\\s)\\s*(?:[-*]\\s+|\\d+\\.\\s+)?(?:[*_]{1,2})?not\\s+(?:intended|meant|to\\s+be\\s+read|to\\s+be\\s+used)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\b(?:this|these)\\s+(?:table|chart|figure|figures|numbers|results|comparison|analysis)\\s+(?:does|do)\\s+not\\s+constitute\\s+(?:an?\\s+)?[^.!?]{0,60}?\\b(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\bthey\\s+are\\s+not\\s+(?:an?\\s+)?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)(?=\\s*(?:[.!?*_]|$))|\\b(?:do\\s+not|don[’']t)\\s+compare\\s+(?:these|those)\\s+(?:numbers|figures|results|values|scores|latencies)(?:\\s+(?:with|to)\\s+(?:each\\s+other|one\\s+another)\\b|(?=\\s*(?:[.!?*_]|$)))|\\b(?:these|those|the)\\s+(?:numbers|figures)\\s+are\\s+not\\s+comparable(?:\\s+(?:with|to)\\s+(?:each\\s+other|one\\s+another)\\b|(?=\\s*(?:[.!?*_]|$)))|\\b(?:this|the|these|our)\\s+(?:table|chart|figure|graph|list|report|document|deck|slide|page|summary|overview|analysis)s?\\s+(?:is|are)\\s+not\\s+(?:(?:meant|intended)\\s+)?(?:(?:to\\s+be\\s+)?(?:an?\\s+)?(?:ranking|benchmark|comparison|leaderboard|endorsement)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)(?=\\s*(?:[.!?*_]|$))|to\\s+(?:rank|compare|benchmark)\\b)|\\b(?:do\\s+not|don[’']t)\\s+read\\s+(?:this|the|these|our)\\s+(?:table|chart|figure|graph|list|report|document|deck|slide|page|summary|overview|analysis)s?\\s+as\\s+(?:an?\\s+)?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)",
|
|
922
924
|
"flags": "i",
|
|
923
925
|
"scope": "sentence",
|
|
924
926
|
"context": [
|
|
925
927
|
"fenced_code",
|
|
926
|
-
"quoted_user_text"
|
|
928
|
+
"quoted_user_text",
|
|
929
|
+
"blockquote_callout"
|
|
927
930
|
],
|
|
928
|
-
"rationale": "A sentence that says the deck, report or numbers are not intended, meant or to be read as a comparison, benchmark or ranking, or make no claim about something belongs in the reply, and so does a table, chart or set of numbers that does not constitute one. Requiring one of those nouns keeps safety labels and advice, offer or diagnosis notices clean; \"does not constitute\" for a report or document stays a notice form, and \"the\" is not a subject except with \"makes no claim\".",
|
|
931
|
+
"rationale": "A sentence that says the deck, report or numbers are not intended, meant or to be read as a comparison, benchmark or ranking, or make no claim about something belongs in the reply, and so does a table, chart or set of numbers that does not constitute one; \"they are not a ranking.\", \"do not compare these numbers (with each other).\" and \"the numbers are not comparable.\" are the same refusal when they close the sentence. Requiring one of those nouns keeps safety labels and advice, offer or diagnosis notices clean; a refusal that continues (a ranking of schools, comparable with the previous release, compare these values directly; …, this is not a benchmark, it is …) describes or instructs and stays clean; a deliverable subject (this table, the report, our deck) that is not a ranking, benchmark, comparison, leaderboard or endorsement, is not meant to rank or compare, or is not to be read as a ranking is the same refusal, while a bare \"this\" is not a subject; \"does not constitute\" for a report or document stays a notice form, and \"the\" is not a subject except with \"makes no claim\" or \"numbers are not comparable\".",
|
|
929
932
|
"fixture_ids": [
|
|
930
933
|
"en-negative-pledge:positive",
|
|
931
934
|
"en-negative-pledge:negative"
|
|
@@ -82,7 +82,12 @@ function sourceRows(text) {
|
|
|
82
82
|
if (/^\s{0,3}>\s?/.test(line)) {
|
|
83
83
|
captionPending = 0;
|
|
84
84
|
figureActive = false;
|
|
85
|
-
|
|
85
|
+
// A quote that opens with a bold label (> **주의:** ...) is the author's own callout, not quoted user
|
|
86
|
+
// text. Only rules that list blockquote_callout read it, with the quote marker blanked to spaces.
|
|
87
|
+
const callout = /^\s{0,3}>\s?(?:\*\*|__)[^*_\n]{1,24}(?:\*\*|__)/.test(line)
|
|
88
|
+
? inlineCodeAsSpaces(line.replace(/^\s{0,3}>\s?/, (prefix) => ' '.repeat(prefix.length)))
|
|
89
|
+
: '';
|
|
90
|
+
return { line, number: index + 1, candidate: '', callout, skipped: true, caption: false, captionAttribution: false };
|
|
86
91
|
}
|
|
87
92
|
const candidate = inlineCodeAsSpaces(line);
|
|
88
93
|
const caption = isCaption(line);
|
|
@@ -149,7 +154,8 @@ function splitSentences(span) {
|
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
function ruleSegments(rows, rule, cache) {
|
|
152
|
-
const
|
|
157
|
+
const readCallouts = rule.context.includes('blockquote_callout');
|
|
158
|
+
const cacheKey = `${rule.scope}/${rule.context.includes('table_figure_caption')}/${readCallouts}`;
|
|
153
159
|
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
154
160
|
const skipCaptions = rule.context.includes('table_figure_caption');
|
|
155
161
|
const segments = [];
|
|
@@ -163,6 +169,11 @@ function ruleSegments(rows, rule, cache) {
|
|
|
163
169
|
|
|
164
170
|
for (const row of rows) {
|
|
165
171
|
const captionExempt = skipCaptions && (row.caption || row.captionAttribution);
|
|
172
|
+
if (readCallouts && row.callout) {
|
|
173
|
+
flush();
|
|
174
|
+
segments.push({ text: row.callout, line: row.number });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
166
177
|
if (row.skipped || captionExempt || (rule.scope === 'sentence' && !row.candidate.trim())) {
|
|
167
178
|
flush();
|
|
168
179
|
continue;
|
|
@@ -293,15 +293,15 @@
|
|
|
293
293
|
},
|
|
294
294
|
{
|
|
295
295
|
"path": "references/taxonomy.md",
|
|
296
|
-
"sha256": "
|
|
296
|
+
"sha256": "6836096bef306685749d8d0639026ff63e42f2e452a14b762f4d6241421080c8"
|
|
297
297
|
},
|
|
298
298
|
{
|
|
299
299
|
"path": "rules.json",
|
|
300
|
-
"sha256": "
|
|
300
|
+
"sha256": "d79e5619f673912e032bcd54fd24ea299b1b2e22643fb128cbe522ad020cc6eb"
|
|
301
301
|
},
|
|
302
302
|
{
|
|
303
303
|
"path": "scripts/core.mjs",
|
|
304
|
-
"sha256": "
|
|
304
|
+
"sha256": "abaff0390680e9c2602cde701f7224acd3586ebed0dcb2a807570359baac62fc"
|
|
305
305
|
},
|
|
306
306
|
{
|
|
307
307
|
"path": "scripts/detect.mjs",
|