@kaydash9999/banker-plugins 0.19.0 → 0.19.2
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +63 -0
- package/bin/lib/telemetry-config.mjs +25 -0
- package/hooks/obsidize-hook.mjs +23 -4
- package/hooks/progress.mjs +24 -12
- package/hooks/register.mjs +1 -0
- package/hooks/telemetry-count-skill.mjs +4 -6
- package/hooks/telemetry-count.mjs +4 -6
- package/hooks/update-checkin.mjs +18 -17
- package/hooks/update-fetch.mjs +8 -7
- package/package.json +1 -1
- package/skills/anti-drm/SKILL.md +14 -7
- package/skills/anti-drm/scripts/anti-drm.mjs +92 -31
- package/skills/anti-drm/scripts/instruction-toggle.mjs +45 -14
- package/skills/lineage/SKILL.md +4 -1
- package/skills/lineage/lineage.py +230 -82
- package/skills/omc-patch/SKILL.md +30 -16
- package/skills/omc-patch/scripts/omc-patch.mjs +291 -72
- package/skills/tone-compact/SKILL.md +7 -2
- package/skills/tone-compact/scripts/instruction-toggle.mjs +45 -14
- package/skills/tone-compact/scripts/tone-compact.mjs +2 -1
|
@@ -47,7 +47,6 @@ import {
|
|
|
47
47
|
readFileSync,
|
|
48
48
|
readSync,
|
|
49
49
|
realpathSync,
|
|
50
|
-
statSync,
|
|
51
50
|
} from "node:fs";
|
|
52
51
|
import { homedir } from "node:os";
|
|
53
52
|
import { dirname, join } from "node:path";
|
|
@@ -61,6 +60,7 @@ import {
|
|
|
61
60
|
done,
|
|
62
61
|
hasMarker,
|
|
63
62
|
lf,
|
|
63
|
+
LINK_REPLACED,
|
|
64
64
|
noBom,
|
|
65
65
|
present,
|
|
66
66
|
refuse,
|
|
@@ -204,9 +204,9 @@ export function readConfig(file) {
|
|
|
204
204
|
return checkConfig({ deny: raw.deny, alt: raw.alt, tsvDelimiter: raw.tsvDelimiter ?? "tab" });
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// What sits at the settings path: "none", "file" (a regular file,
|
|
208
|
-
//
|
|
209
|
-
//
|
|
207
|
+
// What sits at the settings path: "none", "file" (a regular file with this one name), "link" (a link, wherever it
|
|
208
|
+
// leads), "hardlink" (a regular file with another name too) or "other" (a folder, a FIFO). The save replaces a link
|
|
209
|
+
// or a hard link by this name only: what it led to stays, so nothing of it is read or kept.
|
|
210
210
|
function pathKind(file) {
|
|
211
211
|
let st;
|
|
212
212
|
try {
|
|
@@ -215,31 +215,64 @@ function pathKind(file) {
|
|
|
215
215
|
if (e.code === "ENOENT" || e.code === "ENOTDIR") return "none";
|
|
216
216
|
throw e;
|
|
217
217
|
}
|
|
218
|
-
if (st.
|
|
219
|
-
if (!st.
|
|
218
|
+
if (st.isSymbolicLink()) return "link";
|
|
219
|
+
if (!st.isFile()) return "other";
|
|
220
|
+
return st.nlink > 1 ? "hardlink" : "file";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Opened without blocking. O_NOFOLLOW, where the system has it, keeps the open from following a link put at the name
|
|
224
|
+
// after the first look; Windows has none, so there the name is looked at again after the open.
|
|
225
|
+
const PRIOR_OPEN = constants.O_RDONLY | (constants.O_NONBLOCK ?? 0);
|
|
226
|
+
const NO_FOLLOW = constants.O_NOFOLLOW ?? 0;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* What the settings name holds after the open, against the opened file, from their bigint stats: "same", "link" (a
|
|
230
|
+
* link now: the open followed it), "hardlink" (the opened file has another name now) or "changed" (gone, not a regular
|
|
231
|
+
* file, or another file: the file IDs differ; an ID of 0, which some file systems give, proves nothing).
|
|
232
|
+
*/
|
|
233
|
+
export function nameNow(opened, now) {
|
|
234
|
+
if (now?.isSymbolicLink()) return "link";
|
|
235
|
+
if (!now?.isFile()) return "changed";
|
|
236
|
+
if (opened.ino !== 0n && (now.ino !== opened.ino || now.dev !== opened.dev)) return "changed";
|
|
237
|
+
return now.nlink > 1n ? "hardlink" : "same";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The name's own stats in bigint, or null when it cannot be looked at.
|
|
241
|
+
function lstatOrNull(file) {
|
|
220
242
|
try {
|
|
221
|
-
return
|
|
243
|
+
return lstatSync(file, { bigint: true });
|
|
222
244
|
} catch {
|
|
223
|
-
return
|
|
245
|
+
return null;
|
|
224
246
|
}
|
|
225
247
|
}
|
|
226
248
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
249
|
+
// What the file opened at the settings path gives, as priorBytes says. Without O_NOFOLLOW the name must still hold it.
|
|
250
|
+
function openedBytes(file, fd, noFollow) {
|
|
251
|
+
const st = fstatSync(fd);
|
|
252
|
+
if (!st.isFile() || st.nlink > 1) return { bytes: null };
|
|
253
|
+
const now = noFollow ? "same" : nameNow(fstatSync(fd, { bigint: true }), lstatOrNull(file));
|
|
254
|
+
if (now === "changed") return { refusal: "확인하는 사이에 바뀐 자리라 설정을 저장하지 않았습니다" };
|
|
255
|
+
if (now !== "same") return { bytes: null };
|
|
256
|
+
if (st.size > MAX_BYTES) return { refusal: "64KiB 보다 큰 파일이라 설정을 저장하지 않았습니다" };
|
|
257
|
+
return { bytes: readFileSync(fd) };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The bytes of the regular file with this one name at the settings path: { bytes } (null when it turned out to be
|
|
262
|
+
* something else: gone, a link, not a regular file, or a file with another name too, which the save loses nothing
|
|
263
|
+
* of) or { refusal }. noFollow is O_NOFOLLOW where the system has it; 0 takes the Windows way.
|
|
264
|
+
*/
|
|
265
|
+
export function priorBytes(file, noFollow = NO_FOLLOW) {
|
|
230
266
|
let fd;
|
|
231
267
|
try {
|
|
232
|
-
fd = openSync(file,
|
|
268
|
+
fd = openSync(file, PRIOR_OPEN | noFollow);
|
|
233
269
|
} catch (e) {
|
|
234
|
-
if (e.code === "ENOENT" || e.code === "ENOTDIR") return { bytes: null };
|
|
270
|
+
if (e.code === "ENOENT" || e.code === "ENOTDIR" || e.code === "ELOOP") return { bytes: null };
|
|
235
271
|
if (denied(e)) return { refusal: "읽을 수 없는 파일이라 설정을 저장하지 않았습니다" };
|
|
236
272
|
throw e;
|
|
237
273
|
}
|
|
238
274
|
try {
|
|
239
|
-
|
|
240
|
-
if (!st.isFile()) return { bytes: null };
|
|
241
|
-
if (st.size > MAX_BYTES) return { refusal: "64KiB 보다 큰 파일이라 설정을 저장하지 않았습니다" };
|
|
242
|
-
return { bytes: readFileSync(fd) };
|
|
275
|
+
return openedBytes(file, fd, noFollow);
|
|
243
276
|
} finally {
|
|
244
277
|
closeSync(fd);
|
|
245
278
|
}
|
|
@@ -260,22 +293,27 @@ function ownSettings(bytes) {
|
|
|
260
293
|
}
|
|
261
294
|
}
|
|
262
295
|
|
|
263
|
-
//
|
|
264
|
-
|
|
296
|
+
// Keeps a file anti-drm did not write at bak, once, and says what it did: "kept", "earlier" (a regular file is there
|
|
297
|
+
// already and stays) or "other" (something else is there: a link, a dangling one too, or a folder; nothing is written).
|
|
298
|
+
// The note comes from the same look as the write.
|
|
299
|
+
function keepOnce(bak, bytes) {
|
|
300
|
+
let st;
|
|
265
301
|
try {
|
|
266
|
-
lstatSync(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
302
|
+
st = lstatSync(bak);
|
|
303
|
+
} catch (e) {
|
|
304
|
+
if (e.code !== "ENOENT") throw e;
|
|
305
|
+
writeFresh(bak, bytes, 0o600);
|
|
306
|
+
return "kept";
|
|
270
307
|
}
|
|
308
|
+
return st.isFile() ? "earlier" : "other";
|
|
271
309
|
}
|
|
272
310
|
|
|
273
|
-
// What a save finds at the settings path: { keep } (the bytes to put in .bak,
|
|
274
|
-
//
|
|
311
|
+
// What a save finds at the settings path: { keep } (the bytes to put in .bak, null for none: nothing there, a link
|
|
312
|
+
// or a hard link, whose file stays, or anti-drm's own settings) or { refusal }.
|
|
275
313
|
function priorSettings(file) {
|
|
276
314
|
const kind = pathKind(file);
|
|
277
|
-
if (kind === "none") return { keep: null };
|
|
278
315
|
if (kind === "other") return { refusal: "일반 파일이 아니라 설정을 저장하지 않았습니다" };
|
|
316
|
+
if (kind !== "file") return { keep: null };
|
|
279
317
|
const got = priorBytes(file);
|
|
280
318
|
if (got.refusal) return got;
|
|
281
319
|
return { keep: got.bytes === null || ownSettings(got.bytes) ? null : got.bytes };
|
|
@@ -285,12 +323,14 @@ function priorSettings(file) {
|
|
|
285
323
|
* Saves the settings: the folder 0755, the file 0644 through a temp file and a
|
|
286
324
|
* rename. keep, when given, is a file anti-drm did not write; its bytes go to
|
|
287
325
|
* <file>.bak, readable by this account only, once: a .bak already there is the
|
|
288
|
-
* first such file and stays.
|
|
326
|
+
* first such file and stays. Gives back what became of keep: null when none was
|
|
327
|
+
* given, else "kept", "earlier" or "other" (see keepOnce).
|
|
289
328
|
*/
|
|
290
329
|
export function writeConfig(file, config, keep = null) {
|
|
291
330
|
mkdirSync(dirname(file), { recursive: true, mode: 0o755 });
|
|
292
|
-
|
|
331
|
+
const kept = keep === null ? null : keepOnce(`${file}.bak`, keep);
|
|
293
332
|
writeFresh(file, `${JSON.stringify(config, null, 2)}\n`, 0o644);
|
|
333
|
+
return kept;
|
|
294
334
|
}
|
|
295
335
|
|
|
296
336
|
// ---- rules ----
|
|
@@ -396,17 +436,38 @@ export function status(opts = {}) {
|
|
|
396
436
|
return done(lines.join("\n"));
|
|
397
437
|
}
|
|
398
438
|
|
|
399
|
-
//
|
|
439
|
+
// What a save does beyond writing the settings, as lines to report: a link or a hard link at the settings path becomes
|
|
440
|
+
// a regular file (what it led to stays, and nothing of it goes to .bak), and a file anti-drm did not write goes to
|
|
441
|
+
// .bak, or is not kept when an earlier one, or something that is not a regular file, is there already. kept is what
|
|
442
|
+
// writeConfig gave back.
|
|
443
|
+
const KEPT_NOTES = {
|
|
444
|
+
kept: (bak) => `참고: anti-drm 이 쓰지 않은 원래 파일은 ${bak} 에 남겼습니다.`,
|
|
445
|
+
earlier: (bak) => `참고: anti-drm 이 쓰지 않은 원래 파일은 남기지 않았습니다. 먼저 남긴 파일이 있습니다: ${bak}`,
|
|
446
|
+
other: (bak) => `참고: anti-drm 이 쓰지 않은 원래 파일은 남기지 않았습니다. 그 자리에 일반 파일이 아닌 것이 있습니다: ${bak}`,
|
|
447
|
+
};
|
|
448
|
+
const REPLACED_NOTES = {
|
|
449
|
+
link: LINK_REPLACED,
|
|
450
|
+
hardlink: "참고: 하드 링크였던 자리를 일반 파일로 바꿨습니다. 같은 파일의 다른 이름은 건드리지 않았습니다.",
|
|
451
|
+
};
|
|
452
|
+
function saveNotes(file, replaced, kept) {
|
|
453
|
+
const notes = REPLACED_NOTES[replaced] ? [REPLACED_NOTES[replaced]] : [];
|
|
454
|
+
if (kept) notes.push(KEPT_NOTES[kept](where(`${file}.bak`)));
|
|
455
|
+
return notes;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Saves the settings, as a result to report. The look just before the write names what the rename replaces.
|
|
400
459
|
function save(o, config) {
|
|
460
|
+
let notes;
|
|
401
461
|
try {
|
|
402
462
|
const prior = priorSettings(o.configFile);
|
|
403
463
|
if (prior.refusal) return refuse(`${prior.refusal}: ${where(o.configFile)}`);
|
|
404
|
-
|
|
464
|
+
const replaced = pathKind(o.configFile);
|
|
465
|
+
notes = saveNotes(o.configFile, replaced, writeConfig(o.configFile, config, prior.keep));
|
|
405
466
|
} catch (e) {
|
|
406
467
|
if (denied(e)) return refuse(`쓸 권한이 없어 설정을 저장하지 못했습니다 (${e.code}): ${where(o.configFile)}`);
|
|
407
468
|
throw e;
|
|
408
469
|
}
|
|
409
|
-
return done(`설정을 저장했습니다: ${where(o.configFile)}
|
|
470
|
+
return done([`설정을 저장했습니다: ${where(o.configFile)}`, ...notes].join("\n"));
|
|
410
471
|
}
|
|
411
472
|
|
|
412
473
|
// New rules in each runtime that holds the rules, but `skip`. A runtime that
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
* the Codex folder), so both skills turned on at once keep both blocks; a lock
|
|
43
43
|
* older than a minute is taken over.
|
|
44
44
|
*
|
|
45
|
-
* Results are { ok, message }; ok false means refused with nothing changed
|
|
45
|
+
* Results are { ok, message }; ok false means refused with nothing changed, or,
|
|
46
|
+
* when a change of two files was refused at the second, with the first changed
|
|
47
|
+
* and named in the message ("앞서 바꾼 파일").
|
|
46
48
|
* Anything else that goes wrong throws.
|
|
47
49
|
*/
|
|
48
50
|
import {
|
|
@@ -95,7 +97,7 @@ const isLink = (path) => {
|
|
|
95
97
|
|
|
96
98
|
const UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
97
99
|
|
|
98
|
-
// What sits at a name that is not a regular file: a folder, a FIFO or a
|
|
100
|
+
// What sits at a name that is not a regular file: a folder, a FIFO, a socket, a device or a link loop.
|
|
99
101
|
// It is never read; reading a FIFO waits for a writer.
|
|
100
102
|
const SPECIAL = Object.freeze({ bytes: null, text: null, special: true });
|
|
101
103
|
// What sits at a name that is a link to nothing: Codex finds no file there, and writing would follow the link.
|
|
@@ -148,7 +150,7 @@ function readFile(path) {
|
|
|
148
150
|
|
|
149
151
|
/** A result that did, or found already done, what was asked. */
|
|
150
152
|
export const done = (message) => ({ ok: true, message });
|
|
151
|
-
/** A result that refused and changed nothing. */
|
|
153
|
+
/** A result that refused and changed nothing, unless its message names a file changed before (applied). */
|
|
152
154
|
export const refuse = (message) => ({ ok: false, message });
|
|
153
155
|
|
|
154
156
|
/** A skill's marks: the first line of its rules file, and the lines around its Codex block. */
|
|
@@ -226,6 +228,7 @@ function claudeOn(t, o, getRules) {
|
|
|
226
228
|
const state = claudeState(t, readFile(file), rules);
|
|
227
229
|
if (state === "foreign") return refuse(`같은 이름의 다른 파일이 있어 그대로 두었습니다: ${shown}`);
|
|
228
230
|
if (state === "on") return done(`Claude Code: 이미 켜져 있습니다 (변경 없음). ${shown}`);
|
|
231
|
+
const wasLink = isLink(file);
|
|
229
232
|
try {
|
|
230
233
|
mkdirSync(dirname(file), { recursive: true, mode: 0o755 });
|
|
231
234
|
writeFresh(file, claudeText(t, rules));
|
|
@@ -233,13 +236,13 @@ function claudeOn(t, o, getRules) {
|
|
|
233
236
|
if (denied(e)) return refuse(`쓸 권한이 없어 켜지 못했습니다 (${e.code}): ${shown}`);
|
|
234
237
|
throw e;
|
|
235
238
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
? `Claude Code: ${t.updatedText}. ${shown}`
|
|
239
|
-
: `Claude Code: 켰습니다. 다음 세션부터 자동 적용됩니다. ${shown}`,
|
|
240
|
-
);
|
|
239
|
+
const words = state === "stale" ? `Claude Code: ${t.updatedText}. ${shown}` : `Claude Code: 켰습니다. 다음 세션부터 자동 적용됩니다. ${shown}`;
|
|
240
|
+
return done(wasLink ? `${words}\n${LINK_REPLACED}` : words);
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/** The note for a name that was a link (to nothing, or to an older copy) and is now a regular file. */
|
|
244
|
+
export const LINK_REPLACED = "참고: 링크였던 자리를 일반 파일로 바꿨습니다. 링크가 가리키던 곳은 건드리지 않았습니다.";
|
|
245
|
+
|
|
243
246
|
function claudeOff(t, o) {
|
|
244
247
|
const file = claudeFile(t, o);
|
|
245
248
|
const state = claudeState(t, readFile(file));
|
|
@@ -435,10 +438,14 @@ function withBlock(t, text, rules) {
|
|
|
435
438
|
}
|
|
436
439
|
|
|
437
440
|
// The state when the block is not in its place: "hidden" (only in an override with no text of its own),
|
|
438
|
-
// "
|
|
439
|
-
// nothing that on must not write
|
|
441
|
+
// "hiddenbroken" (the same, with AGENTS.md a link to nothing, so on cannot move it), "misplaced" (only in AGENTS.md
|
|
442
|
+
// while Codex reads the override), "dangling" (nowhere, and AGENTS.md is a link to nothing that on must not write
|
|
443
|
+
// through) or "off".
|
|
440
444
|
function placeState(files) {
|
|
441
|
-
if (hasBlock(files.other))
|
|
445
|
+
if (hasBlock(files.other)) {
|
|
446
|
+
if (files.other !== files.all[0]) return "misplaced";
|
|
447
|
+
return files.target.dangling ? "hiddenbroken" : "hidden";
|
|
448
|
+
}
|
|
442
449
|
return files.target.dangling ? "dangling" : "off";
|
|
443
450
|
}
|
|
444
451
|
|
|
@@ -477,12 +484,32 @@ function writeBlocked(changes) {
|
|
|
477
484
|
// whatever the umask allows more. A file left empty is removed, unless it is a
|
|
478
485
|
// link or has other names (a hard link), whose other names would keep the old
|
|
479
486
|
// text: it is emptied instead. Codex skips an empty file anyway.
|
|
487
|
+
// A file that was not there when it was read is created only if it still is not ("wx"): a name that appeared in
|
|
488
|
+
// between, a link planted there among them, is not written through.
|
|
480
489
|
function apply(t, changes) {
|
|
481
490
|
for (const { file, after } of changes) {
|
|
482
491
|
if (file.bytes !== null) writeFresh(`${file.path}.${t.slug}.bak`, file.bytes, 0o600);
|
|
483
492
|
if (after === "" && !isLink(file.path) && !hasOtherNames(file.path)) rmSync(file.path, { force: true });
|
|
484
|
-
else writeFileSync(file.path, after, { mode: 0o644 });
|
|
493
|
+
else writeFileSync(file.path, after, { mode: 0o644, flag: file.bytes === null ? "wx" : "w" });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Applies the changes, or the refusal for a write that failed with nothing of the file changed: a name that
|
|
498
|
+
// appeared in between (EEXIST), or a write a sandbox refused after access() allowed it. The refusal names the place
|
|
499
|
+
// the system refused (a rename's target, as the backup, or the file it was about), and a file changed before the
|
|
500
|
+
// failing one. Other errors propagate.
|
|
501
|
+
function applied(t, changes) {
|
|
502
|
+
let i = 0;
|
|
503
|
+
try {
|
|
504
|
+
for (; i < changes.length; i++) apply(t, [changes[i]]);
|
|
505
|
+
} catch (e) {
|
|
506
|
+
if (e.code !== "EEXIST" && !denied(e)) throw e;
|
|
507
|
+
const why = e.code === "EEXIST" ? "쓰는 사이에 같은 이름이 생겨 그대로 두었습니다" : `쓸 권한이 없어 그대로 두었습니다 (${where(e.code)})`;
|
|
508
|
+
const at = [e.dest, e.path].find((p) => typeof p === "string") ?? changes[i].file.path;
|
|
509
|
+
const before = i === 0 ? "" : `\n앞서 바꾼 파일: ${changes.slice(0, i).map((c) => where(c.file.path)).join(", ")}`;
|
|
510
|
+
return refuse(`${why}: ${where(at)}${before}`);
|
|
485
511
|
}
|
|
512
|
+
return null;
|
|
486
513
|
}
|
|
487
514
|
|
|
488
515
|
// Whether the file at path is also reachable under another name: a hard link.
|
|
@@ -523,6 +550,7 @@ function dropStale(path) {
|
|
|
523
550
|
st = lstatSync(path);
|
|
524
551
|
} catch (e) {
|
|
525
552
|
if (e.code === "ENOENT") return true; /* gone in between */
|
|
553
|
+
if (e.code === "ESTALE") return false; /* a network share's stale handle: rest and look again, not spin */
|
|
526
554
|
throw e;
|
|
527
555
|
}
|
|
528
556
|
if (st.isDirectory() || Math.abs(Date.now() - st.mtimeMs) <= LOCK_STALE_MS) return false;
|
|
@@ -628,7 +656,8 @@ function codexOnHeld(t, o, getRules) {
|
|
|
628
656
|
if (hasBlock(files.other)) changes.push({ file: files.other, after: stripped(files.other) });
|
|
629
657
|
const why = writeBlocked(changes);
|
|
630
658
|
if (why) return refuse(why);
|
|
631
|
-
|
|
659
|
+
const failed = applied(t, changes);
|
|
660
|
+
if (failed) return failed;
|
|
632
661
|
const note = files.target === files.all[1] ? `\n${OMX_NOTE}` : "";
|
|
633
662
|
return done(`Codex: ${words}. ${tail}${note}`);
|
|
634
663
|
}
|
|
@@ -646,7 +675,8 @@ function codexOffHeld(t, o) {
|
|
|
646
675
|
if (changes.length === 0) return done("Codex: 이미 꺼져 있습니다 (변경 없음).");
|
|
647
676
|
const why = writeBlocked(changes);
|
|
648
677
|
if (why) return refuse(why);
|
|
649
|
-
|
|
678
|
+
const failed = applied(t, changes);
|
|
679
|
+
if (failed) return failed;
|
|
650
680
|
const paths = changes.map((c) => where(c.file.path)).join(", ");
|
|
651
681
|
const baks = changes.map((c) => where(`${c.file.path}.${t.slug}.bak`)).join(", ");
|
|
652
682
|
return done(`Codex: 껐습니다. 다음 세션부터 적용되지 않습니다. ${paths}\n지우기 전 파일: ${baks}`);
|
|
@@ -661,6 +691,7 @@ const STATE_TEXT = {
|
|
|
661
691
|
foreign: "꺼짐, 같은 이름의 다른 파일이 있음 (손대지 않음)",
|
|
662
692
|
misplaced: "꺼짐, 단 블록이 Codex 가 읽지 않는 AGENTS.md 에 있음 (on 으로 옮김)",
|
|
663
693
|
hidden: "켜짐, 단 AGENTS.override.md 에 블록만 있어 AGENTS.md 가 가려짐 (on 으로 옮김)",
|
|
694
|
+
hiddenbroken: "켜짐, 단 AGENTS.override.md 에 블록만 있고 AGENTS.md 가 끊어진 링크라 on 이 블록을 옮기지 못함 (링크 확인 필요)",
|
|
664
695
|
notutf8: "UTF-8 이 아닌 지침 파일이 있어 손대지 않음 (on, off 모두 거부)",
|
|
665
696
|
notregular: "일반 파일이 아닌 지침 파일이 있어 손대지 않음 (on, off 모두 거부)",
|
|
666
697
|
unpaired: "표시의 짝이 맞지 않음 (on, off 모두 거부. 남은 표시 줄 정리 필요)",
|
package/skills/lineage/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: lineage
|
|
3
3
|
description: "현재 세션 대화를 카카오톡 스타일의 단일 HTML 로 내보냄. Claude 답변은 한 줄 요약 아래에 원문을 접어 두고, 펼치면 마크다운으로 렌더해 보여 줌. 기본은 세션 모델이 요약을 쓰고 노이즈 턴을 걸러 내며, `--rulebase` 는 모델 없이 규칙만 씀. 여러 세션을 합칠 수 있음. 'lineage'/'대화 export'/'카톡 스타일 html' 시 사용."
|
|
4
4
|
invocation: /lineage
|
|
5
|
-
version: 3.0.
|
|
5
|
+
version: 3.0.7
|
|
6
6
|
schema_version: 1
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -17,6 +17,8 @@ Claude 본문은 **마크다운으로 렌더**(헤딩·리스트·표·코드·
|
|
|
17
17
|
노이즈(스킬 본문·compaction 요약·조작 명령·에코 교환)는 **기본으로 걸러진다**.
|
|
18
18
|
Secret 자동 redaction. 규칙 단계는 결정론(turn uuid·요약기버전 캐시)이고, 세션 모델의 검토 결정은 캐시해 다음 실행에 다시 쓴다.
|
|
19
19
|
|
|
20
|
+
> **3.0.6** — 글자 방향을 바꾸는 제어 문자 9자(U+202A~202E, U+2066~2069)는 `--no-markdown` 이어도 `[U+202E]` 처럼 보이는 표지로 넣는다. 화면의 글자 순서가 실제와 달라 보이는 속임수를 막기 위해서다. 방향 표시(LRM, RLM, ALM)는 그대로 둔다.
|
|
21
|
+
|
|
20
22
|
> **3.0.0 — 기본 흐름이 세션 모델 검토로 바뀌었습니다.**
|
|
21
23
|
> - 규칙이 1차로 정리한 턴을 세션 모델이 다시 검토한다. 요약 줄을 직접 쓰고, 남길 턴과 뺄 턴을 고른다.
|
|
22
24
|
> - 2.x 의 한 번 실행(규칙만, 모델 호출 없음)은 `--rulebase` 다.
|
|
@@ -214,6 +216,7 @@ Schema-tolerant 파서: 미지 record type → stderr WARN + 다음 line. `v1`/`
|
|
|
214
216
|
|
|
215
217
|
- 캐시 hit(`~/.cache/lineage/<schema>/<sid>/<uuid>-<digest>.md`, 3.0.2 까지 쓴 `.txt` 도 읽음) → 그대로. digest 는 본문+요약기버전
|
|
216
218
|
해시라 **요약기를 고치면 자동 무효화**된다.
|
|
219
|
+
- 캐시는 64KiB 이하의 일반 파일만 읽고 링크는 따르지 않는다. 그 자리의 FIFO, 링크, 큰 파일, 깊게 중첩된 결정 캐시는 읽지 못한 것으로 보고 다시 요약한다(3.0.6).
|
|
217
220
|
- miss → head+tail 추출(도입이 순수 의도문이면 결론절을 끝에서 읽어 승격) + 0600 캐시 쓰기.
|
|
218
221
|
- 요약 저장은 항상 **redact 적용 후**(평문 secret 이 디스크에 남지 않음).
|
|
219
222
|
|