@litfamily/litopencode 1.0.24 → 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 +8 -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-docx/SKILL.md +1 -1
- package/skills/lit-docx/references/tonalities/manual.md +1 -1
- package/skills/lit-humanizer/SKILL.md +1 -1
- package/skills/lit-humanizer/fixtures/rule-cases.json +8 -2
- package/skills/lit-humanizer/references/taxonomy.md +4 -1
- package/skills/lit-humanizer/rules.json +55 -1
- package/skills/lit-humanizer/scripts/core.mjs +13 -2
- package/skills/lit-pptx/SKILL.md +2 -2
- package/skills/lit-pptx/specs/markdown-slide-spec-v1.md +1 -1
- package/skills/managed-skill-manifest.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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
|
+
|
|
12
|
+
## 1.0.25 - 2026-10-15
|
|
13
|
+
|
|
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.
|
|
15
|
+
|
|
8
16
|
## 1.0.24 - 2026-10-14
|
|
9
17
|
|
|
10
18
|
- The LIT card, the chip beside the prompt, the sidebar panel and the home line now wear the LIT colours in every OpenCode theme: the `🔥 LIT` pill runs from pink to flame, the workflow map ramps from flame through pink to the LIT accent, the running stage and the current skills are accent pills, and warnings and the finished state use LIT amber and green. Text, panels and borders still follow your theme, and on a light theme the LIT colours deepen so they stay readable. The README pictures of the card show the new look.
|
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",
|
package/skills/lit-docx/SKILL.md
CHANGED
|
@@ -116,7 +116,7 @@ State material limitations, including a blocked export or visual check, in the u
|
|
|
116
116
|
|
|
117
117
|
## #contract.evidence
|
|
118
118
|
|
|
119
|
-
Retain the direction card, conversion output, the gate JSON, and rendered PNG pages in the task's evidence location. Open the rendered pages; file existence alone cannot establish typography or pagination.
|
|
119
|
+
Retain the direction card, conversion output, the gate JSON, and rendered PNG pages in the task's evidence location. Open the rendered pages; file existence alone cannot establish typography or pagination. Write a real uncertainty once, as a fact about the data or method beside the number it affects, and keep source attribution beside the figure; what the document is not for goes in the reply.
|
|
120
120
|
|
|
121
121
|
## #contract.hard_stops
|
|
122
122
|
|
|
@@ -53,7 +53,7 @@ in a hanging indent wide enough for the widest number, so the heading texts of a
|
|
|
53
53
|
|
|
54
54
|
- Do not use a `::: cover` directive unless you want a kicker line above the title; the control block
|
|
55
55
|
comes from the frontmatter.
|
|
56
|
-
- Open with scope and responsibilities: what the manual covers, what it
|
|
56
|
+
- Open with scope and responsibilities: what the manual covers and who does what, stated as facts (which line, which equipment, which site); a list of what it is not for belongs in the reply.
|
|
57
57
|
- Write each procedure as a Markdown numbered list (`1.`, `2.`, …), one action per step, in the
|
|
58
58
|
imperative. Each new list restarts at 1.
|
|
59
59
|
- Put conditions and values in tables (inspection items with their limits and the action when a limit is
|
|
@@ -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
|
|
|
@@ -50,7 +50,10 @@
|
|
|
50
50
|
"en-chatbot-opener": "Certainly! Here is the overview.",
|
|
51
51
|
"en-testament": "The project stands as a testament to careful planning.",
|
|
52
52
|
"en-left-in-narration": "I've updated the file and saved it.",
|
|
53
|
-
"en-chatbot-closer": "Let me know if you would like another version."
|
|
53
|
+
"en-chatbot-closer": "Let me know if you would like another version.",
|
|
54
|
+
"ko-negative-pledge": "동일 조건의 독립 성능 비교로 쓰지 않음.",
|
|
55
|
+
"ko-negative-pledge-repeat": "측정값은 인과관계를 주장하지 않음.\n두 공정의 차이는 최종 결론이 아님.",
|
|
56
|
+
"en-negative-pledge": "This is not intended as a head-to-head benchmark."
|
|
54
57
|
},
|
|
55
58
|
"negative": {
|
|
56
59
|
"ko-bold-deliverable-label": "표 7의 출처는 각주 2에 적었다.",
|
|
@@ -103,7 +106,10 @@
|
|
|
103
106
|
"en-chatbot-opener": "The overview starts with the sample period.",
|
|
104
107
|
"en-testament": "The project used two review rounds.",
|
|
105
108
|
"en-left-in-narration": "The file contains the updated overview.",
|
|
106
|
-
"en-chatbot-closer": "The requested revision is attached."
|
|
109
|
+
"en-chatbot-closer": "The requested revision is attached.",
|
|
110
|
+
"ko-negative-pledge": "이 장비는 방폭 구역에서 사용하지 않습니다. 냉각수로 쓰지 않음.",
|
|
111
|
+
"ko-negative-pledge-repeat": "첨가제 B는 수율을 개선하지 않았다. 이 결과는 우연이 아니다.",
|
|
112
|
+
"en-negative-pledge": "This is not intended as a substitute for professional advice."
|
|
107
113
|
},
|
|
108
114
|
"contextCases": {
|
|
109
115
|
"plainSourceAttached": [
|
|
@@ -16,7 +16,10 @@ This original category comes from the 26 real lines in fixtures/pos-real.txt and
|
|
|
16
16
|
- en-demo-self-disclaimer — en, block. A demonstration label attached to a synthesis, assumption or recommendation is process framing. Name what is proposed and what was measured.
|
|
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
|
-
- ko-status-negation, ko-mi-status, en-epistemic-negation and en-not-performed — warnings. 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.
|
|
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; 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
|
+
- 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. 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.
|
|
20
23
|
- ko-plain-meta-label-warn and en-plain-caveat-label — warnings for boundary headings. They may be useful in internal or technical packets.
|
|
21
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.
|
|
22
25
|
- ko-chatbot-opener and en-chatbot-opener — block stock greetings and praise at the start of a deliverable.
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
"id": "ko-status-negation",
|
|
186
186
|
"lang": "ko",
|
|
187
187
|
"severity": "warn",
|
|
188
|
-
"pattern": "(?:검증|실측|측정|실행|실시|시작|확인|보장|평가|판단|조언|의미|증명|재현|사용|제공|적용|쓰)(?:[된한]
|
|
188
|
+
"pattern": "(?:검증|실측|측정|실행|실시|시작|확인|보장|평가|판단|조언|의미|증명|재현|사용|제공|적용|쓰)(?:[된한](?:\\s*(?:적이|값이))?|하지(?:\\s*않(?:음|는다|았다))?|되지\\s*않(?:음|는다|았다)|(?<=쓰)지|이|은|는)?\\s*(?:(?:\\*\\*|__)\\s*)?(?:아님|않음|않는다|않았다|않습니다|않았습니다|없음)|보류",
|
|
189
189
|
"flags": "im",
|
|
190
190
|
"scope": "line",
|
|
191
191
|
"context": [
|
|
@@ -879,6 +879,60 @@
|
|
|
879
879
|
"en-chatbot-closer:positive",
|
|
880
880
|
"en-chatbot-closer:negative"
|
|
881
881
|
]
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
"id": "ko-negative-pledge",
|
|
885
|
+
"lang": "ko",
|
|
886
|
+
"severity": "block",
|
|
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
|
+
"flags": "m",
|
|
889
|
+
"scope": "line",
|
|
890
|
+
"context": [
|
|
891
|
+
"fenced_code",
|
|
892
|
+
"quoted_user_text",
|
|
893
|
+
"blockquote_callout"
|
|
894
|
+
],
|
|
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.",
|
|
896
|
+
"fixture_ids": [
|
|
897
|
+
"ko-negative-pledge:positive",
|
|
898
|
+
"ko-negative-pledge:negative"
|
|
899
|
+
]
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
"id": "ko-negative-pledge-repeat",
|
|
903
|
+
"lang": "ko",
|
|
904
|
+
"severity": "block",
|
|
905
|
+
"pattern": "(?:(?:해석하|주장하|대표하)지\\s*(?:[*_]{1,2}\\s*)?않(?:음|습니다)|(?:비교|결론|벤치마크|순위|대표값|일반화)(?:이|가)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아닙니다)(?![가-힣]))(?:[*_]{1,2})?(?=\\s*(?:[.!?。]|$))[\\s\\S]*?(?:(?:해석하|주장하|대표하)지\\s*(?:[*_]{1,2}\\s*)?않(?:음|습니다)|(?:비교|결론|벤치마크|순위|대표값|일반화)(?:이|가)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아닙니다)(?![가-힣]))(?:[*_]{1,2})?(?=\\s*(?:[.!?。]|$))",
|
|
906
|
+
"flags": "m",
|
|
907
|
+
"scope": "document",
|
|
908
|
+
"context": [
|
|
909
|
+
"fenced_code",
|
|
910
|
+
"quoted_user_text",
|
|
911
|
+
"blockquote_callout"
|
|
912
|
+
],
|
|
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.",
|
|
914
|
+
"fixture_ids": [
|
|
915
|
+
"ko-negative-pledge-repeat:positive",
|
|
916
|
+
"ko-negative-pledge-repeat:negative"
|
|
917
|
+
]
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
"id": "en-negative-pledge",
|
|
921
|
+
"lang": "en",
|
|
922
|
+
"severity": "block",
|
|
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)",
|
|
924
|
+
"flags": "i",
|
|
925
|
+
"scope": "sentence",
|
|
926
|
+
"context": [
|
|
927
|
+
"fenced_code",
|
|
928
|
+
"quoted_user_text",
|
|
929
|
+
"blockquote_callout"
|
|
930
|
+
],
|
|
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\".",
|
|
932
|
+
"fixture_ids": [
|
|
933
|
+
"en-negative-pledge:positive",
|
|
934
|
+
"en-negative-pledge:negative"
|
|
935
|
+
]
|
|
882
936
|
}
|
|
883
937
|
]
|
|
884
938
|
}
|
|
@@ -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;
|
package/skills/lit-pptx/SKILL.md
CHANGED
|
@@ -92,7 +92,7 @@ Under `lit`, create a `.pptx` by default. Do not ask a theme, colour or font que
|
|
|
92
92
|
|
|
93
93
|
4. **Give each message the visual that fits it, sized to the slide.** A numeric series is a native editable chart (`::: chart type=column|bar|line|area|stacked|pie|doughnut`, a pipe table of categories and series, optional `unit=` and a `>` caption; one series carries its values as labels, while lines of two or more series are read off a shown value axis, so keep a line chart to the series that matter); headline numbers are a `::: kpi-table` row of four to six figures with a second row giving each figure's basis or comparison; a sequence is a process or timeline family; exact lookups are a table of five to eight rows with right-aligned numbers; options are a comparison with three or four labelled criteria rows. Numbers never become the display element: no figure is set above the title size, and nothing is inflated or rounded up for effect. A title and a small table floating over half an empty slide is unfinished.
|
|
94
94
|
|
|
95
|
-
5. **Write dense, sourced content.** Every pack defaults to density 10, the compact step: 24 pt margins, body 13 pt, table cells 11 pt, sources 9 pt, never below those floors. Each content slide carries its basis, a comparison, the period, and an implication or next action; data slides end with a `출처:` / `Source:` line (and a `주:` / `Note:` line where a definition matters), which the engine sets as a strip on the body floor. References run eight to ten entries. `references/density-and-fill.md` lists what each family needs to fill its body and what the fill policies do when it does not.
|
|
95
|
+
5. **Write dense, sourced content.** Every pack defaults to density 10, the compact step: 24 pt margins, body 13 pt, table cells 11 pt, sources 9 pt, never below those floors. Each content slide carries its basis, a comparison, the period, and an implication or next action; data slides end with a `출처:` / `Source:` line (and a `주:` / `Note:` line where a definition matters), which the engine sets as a strip on the body floor. Write a real uncertainty once, as a fact about the data or method beside the number it affects; what the deck is not for goes in the reply. References run eight to ten entries. `references/density-and-fill.md` lists what each family needs to fill its body and what the fill policies do when it does not.
|
|
96
96
|
|
|
97
97
|
6. **Write the Markdown source** as `<name>.md` beside the output, with `tonality:`, `title:` and, for sample content, `notice:` in its frontmatter. `specs/markdown-slide-spec-v2.md` is the dialect contract; open the worked example in `references/examples/` closest to the job before writing. Twelve examples cover all eight tonalities in Korean and English and each compiles and passes the gate as shipped. Pictures are referenced by path from the source folder; the skill ships no images. For a chart or diagram picture on a dark tonality, put its dark variant beside it (`flow.png` and `flow.dark.png`); a dark ground draws the variant. Korean text runs and charts with Korean labels are written as ko-KR, so a renderer applies Korean spacing rules.
|
|
98
98
|
|
|
@@ -102,7 +102,7 @@ Under `lit`, create a `.pptx` by default. Do not ask a theme, colour or font que
|
|
|
102
102
|
|
|
103
103
|
9. **Fonts for travel.** When the deck must open on machines without Pretendard, run `node <skill>/run.mjs embed deck.pptx` and rerun the gate. The post-embedding integrity check reopens the deck and verifies ZIP data, content types and relationship targets.
|
|
104
104
|
|
|
105
|
-
10. **Look at the pages.** Render with LibreOffice when available: `soffice -env:UserInstallation=file:///tmp/<unique-profile> --headless --convert-to pdf --outdir <output-dir> deck.pptx`, convert the PDF to PNG, and open every slide at presentation size. Check hierarchy, Hangul line breaks, numbers against the source, cramped or hollow areas, and whether each treatment suits its slide. Check the card's "wrong if" line: if it came true, rebuild under the alternative and update the card and reply. Three full compile → gate → look rounds is the budget; a defect that survives is
|
|
105
|
+
10. **Look at the pages.** Render with LibreOffice when available: `soffice -env:UserInstallation=file:///tmp/<unique-profile> --headless --convert-to pdf --outdir <output-dir> deck.pptx`, convert the PDF to PNG, and open every slide at presentation size. Check hierarchy, Hangul line breaks, numbers against the source, cramped or hollow areas, and whether each treatment suits its slide. Check the card's "wrong if" line: if it came true, rebuild under the alternative and update the card and reply. Three full compile → gate → look rounds is the budget; a defect that survives is named in the reply, not written onto a slide. Without soffice, report that structural QA ran and visual preview was unavailable; never claim render verification.
|
|
106
106
|
|
|
107
107
|
11. **Deliver.** The `.pptx`, its Markdown source, the build log with the direction card and, when useful, the rendered PNGs sit in the user's project. The reply names the tonality and the two alternatives. Never leave generated files inside the skill.
|
|
108
108
|
|
|
@@ -236,7 +236,7 @@ Enrolled templates may advertise these semantic compositions of existing blocks:
|
|
|
236
236
|
|
|
237
237
|
- **figure plus interpretation:** `image` + `figure-caption` + body interpretation;
|
|
238
238
|
- **table plus decision takeaway:** table/`kpi-table` + `table-caption` + body takeaway;
|
|
239
|
-
- **source capture:** `image` + `figure-caption` containing locator/source + body
|
|
239
|
+
- **source capture:** `image` + `figure-caption` containing locator/source + body stating what the source shows and why it is on the slide;
|
|
240
240
|
- **two-record appendix evidence:** title/body or `summary-group`, with no more than two records and DOI/canonical links when available.
|
|
241
241
|
|
|
242
242
|
For a Markdown image used as evidence, alt text is the visible figure caption and should contain a figure number plus `Source:`/`출처:`. A blockquote immediately after a table is its visible table caption/source. Appendix DOI/canonical/raw URL fields use `[label](https://...)` in table cells so the PPTX contains an external hyperlink relationship. These requirements reuse existing parser and renderer behavior.
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
},
|
|
246
246
|
{
|
|
247
247
|
"path": "fixtures/rule-cases.json",
|
|
248
|
-
"sha256": "
|
|
248
|
+
"sha256": "498dae0a7aa89ec1b8b6b5bcd929a099c2fc988fbf4d18598f638cee16eeace4"
|
|
249
249
|
},
|
|
250
250
|
{
|
|
251
251
|
"path": "references/code-patterns.md",
|
|
@@ -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",
|
|
@@ -3636,7 +3636,7 @@
|
|
|
3636
3636
|
},
|
|
3637
3637
|
{
|
|
3638
3638
|
"path": "specs/markdown-slide-spec-v1.md",
|
|
3639
|
-
"sha256": "
|
|
3639
|
+
"sha256": "90ab86cef7ac7d7b8fa2cbe5b309c455638948272d84508fdf35aaab635d31bc"
|
|
3640
3640
|
},
|
|
3641
3641
|
{
|
|
3642
3642
|
"path": "specs/markdown-slide-spec-v2.md",
|
|
@@ -3831,7 +3831,7 @@
|
|
|
3831
3831
|
},
|
|
3832
3832
|
{
|
|
3833
3833
|
"path": "references/tonalities/manual.md",
|
|
3834
|
-
"sha256": "
|
|
3834
|
+
"sha256": "db39dcc2f02cf2931286bb71ee20cc8c6ff551f6b618e845c024114527f1d5ee"
|
|
3835
3835
|
},
|
|
3836
3836
|
{
|
|
3837
3837
|
"path": "references/tonalities/memo.md",
|