@kaydash9999/banker-plugins 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +52 -0
- package/README.md +18 -7
- package/codex/manifest.json +1 -0
- package/codex/transform-matrix.md +1 -0
- package/hooks/graceful-pause.mjs +4 -2
- package/hooks/obsidize-hook.mjs +38 -28
- package/hooks/progress-plan.mjs +14 -8
- package/hooks/progress.mjs +174 -47
- package/hooks/register.mjs +14 -7
- package/hooks/update-checkin.mjs +29 -27
- package/package.json +1 -1
- package/skills/anti-drm/SKILL.md +36 -12
- package/skills/anti-drm/scripts/anti-drm.mjs +121 -30
- package/skills/anti-drm/scripts/instruction-toggle.mjs +241 -35
- package/skills/lineage/SKILL.md +1 -1
- package/skills/lineage/lineage.py +29 -2
- package/skills/omc-patch/SKILL.md +18 -6
- package/skills/omc-patch/scripts/omc-patch.mjs +400 -87
- package/skills/theme-selection/SKILL.md +165 -0
- package/skills/theme-selection/assets/LICENSE +21 -0
- package/skills/theme-selection/assets/NOTICE.md +30 -0
- package/skills/theme-selection/assets/palettes.json +81 -0
- package/skills/theme-selection/references/contrast.md +138 -0
- package/skills/theme-selection/references/integrate.md +161 -0
- package/skills/theme-selection/scripts/themes.mjs +1067 -0
- package/skills/tone-compact/SKILL.md +8 -4
- package/skills/tone-compact/scripts/instruction-toggle.mjs +241 -35
- package/skills/tone-compact/scripts/tone-compact.mjs +2 -3
- package/skills/update-banker/SKILL.md +1 -1
package/hooks/progress.mjs
CHANGED
|
@@ -7,23 +7,29 @@
|
|
|
7
7
|
* step for the work at hand. Under each step are the tool calls made while it was the step in
|
|
8
8
|
* progress (a subagent's or a background agent's included), one level deep. A press on a step or
|
|
9
9
|
* a call shows a short description under the list; a press on a step's arrow folds or unfolds its
|
|
10
|
-
* calls.
|
|
10
|
+
* calls. Of two calls on the task list that overlap, the one begun later stands, and a list or a
|
|
11
|
+
* read begun before a change keeps what changed after it began; the engine's own order of
|
|
12
|
+
* overlapping calls is not something this module can see.
|
|
11
13
|
*
|
|
12
|
-
* Claude 5 models get no task list tool in Claude Code 2.1.296
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Claude 5 models get no task list tool in Claude Code 2.1.296 and 2.1.300 unless
|
|
15
|
+
* CLAUDE_CODE_ENABLE_TODO_TOOLS is set; older models and a background job get one. (Seen in a
|
|
16
|
+
* live 2.1.300 session with Haiku 5.5: with the variable TaskCreate, TaskUpdate, TaskList and
|
|
17
|
+
* TaskGet came and the pane showed their list; without it, none.) Opening the pane offers the
|
|
18
|
+
* model one of its own (`mcp__banker__todo`, TodoWrite's shape) where it has none, and the list it
|
|
19
|
+
* sends is the steps.
|
|
15
20
|
* Above them the pane shows the plan file the session touched last (progress-plan.mjs reads it):
|
|
16
21
|
* the whole plan as a tree that folds like a toggle list, its sections folded at first.
|
|
17
22
|
* A prompt's harness tags leave its title, and a background task's notice (or a teammate's message)
|
|
18
23
|
* goes on with the last prompt's step instead of becoming one: the step runs again while it does,
|
|
19
24
|
* and keeps its own end and time.
|
|
20
25
|
*
|
|
21
|
-
* `/progress` and `/progress show` toggle the pane, `on` and `off` set it.
|
|
22
|
-
* registered `immediate`, so it answers while a turn runs
|
|
23
|
-
* this file: commands/progress.md and
|
|
26
|
+
* `/progress` and `/progress show` toggle the pane, `on` and `off` set it. From Claude Code 2.1.289
|
|
27
|
+
* on the command is registered `immediate`, so it answers while a turn runs; an older engine gets it
|
|
28
|
+
* as a plain command. An engine without mods never loads this file: commands/progress.md and
|
|
29
|
+
* hooks/progress-fallback.mjs answer there instead.
|
|
24
30
|
*/
|
|
25
31
|
|
|
26
|
-
import { cells, clip, fit,
|
|
32
|
+
import { cells, clip, fit, parsePlan, pick, planKind, readPrompt } from './progress-plan.mjs';
|
|
27
33
|
|
|
28
34
|
const COMMAND = 'progress';
|
|
29
35
|
const PANE = 'banker-progress';
|
|
@@ -91,6 +97,8 @@ const NOTICE = {
|
|
|
91
97
|
completed: '백그라운드 작업 끝남', failed: '백그라운드 작업 실패', stopped: '백그라운드 작업 중지됨',
|
|
92
98
|
killed: '백그라운드 작업 중지됨', message: '팀원 메시지',
|
|
93
99
|
};
|
|
100
|
+
// How a notice's turn ended other than with an answer, as the row under its step says it.
|
|
101
|
+
const NOTICE_END = { aborted: '알림 처리 중단됨', refusal: '알림 처리 거절됨', error: '알림 처리 오류로 끝남' };
|
|
94
102
|
// How a prompt's turn ended (turn.complete's reason), as its step's state.
|
|
95
103
|
const ENDED = { answer: 'done', aborted: 'stopped', refusal: 'refused', error: 'failed' };
|
|
96
104
|
// Why a hook failed (the kind of the engine's HookFailure), for a failure that brings no message.
|
|
@@ -108,12 +116,17 @@ const tally = () => ({ calls: [], count: 0, failed: 0 });
|
|
|
108
116
|
// The calls made while the task list has no task in progress.
|
|
109
117
|
const looseStep = () => ({ key: 'loose', title: '작업 목록 밖의 작업', ...tally() });
|
|
110
118
|
|
|
111
|
-
// `rev` counts the task changes (TaskCreate, TaskUpdate) taken in,
|
|
119
|
+
// `rev` counts the task changes (TaskCreate, TaskUpdate) taken in, and `order` the calls begun: a TaskList or
|
|
120
|
+
// TaskGet begun before a change keeps what changed after it began, and of two calls that overlap the one begun later
|
|
121
|
+
// stands. `marks` holds, per task id, the orders that decide it (markOf). `listAt` is the order of the TaskList the
|
|
122
|
+
// list last came from.
|
|
112
123
|
// `plan` is the plan last read from `planPath`, `planShut` whether the whole plan is folded and
|
|
113
124
|
// `planOpen` the ids of its nodes unfolded. `touch` counts the calls that touched a plan file and
|
|
114
|
-
// `taken` is the touch the plan came from, to tell a read that a newer one overtook.
|
|
125
|
+
// `taken` is the touch the plan came from, to tell a read that a newer one overtook. `followed` is the step in
|
|
126
|
+
// progress the pane last asked to bring into view.
|
|
115
127
|
export const freshState = () => ({ turns: [], tasks: new Map(), todos: null, loose: looseStep(), open: new Set(), selected: null, seq: 0, rev: 0,
|
|
116
|
-
plan: null, planPath: null, planShut: false, planOpen: new Set(), touch: 0, taken: 0
|
|
128
|
+
order: 0, marks: new Map(), listAt: 0, plan: null, planPath: null, planShut: false, planOpen: new Set(), touch: 0, taken: 0,
|
|
129
|
+
followed: null });
|
|
117
130
|
|
|
118
131
|
const tail = (path) => String(path).split(/[\\/]/).filter(Boolean).slice(-2).join('/');
|
|
119
132
|
const seconds = (ms) => {
|
|
@@ -179,7 +192,10 @@ function dropFinished(s) {
|
|
|
179
192
|
s.loose = looseStep();
|
|
180
193
|
}
|
|
181
194
|
|
|
195
|
+
// A main turn's start. A subagent's, a fork's or a teammate's run (it carries an agentId; Claude Code 2.1.300
|
|
196
|
+
// raises turn.start for them too) makes no step: its calls go under the step in progress.
|
|
182
197
|
export function startTurn(s, e, now) {
|
|
198
|
+
if (e.agentId) return;
|
|
183
199
|
const said = readPrompt(e.text);
|
|
184
200
|
if (said.kind === 'notice') return noticeTurn(s, e, said, now);
|
|
185
201
|
dropFinished(s);
|
|
@@ -187,13 +203,16 @@ export function startTurn(s, e, now) {
|
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
// A main turn's end; a subagent's (it carries an agentId) ends no step, and a notice's leaves its
|
|
190
|
-
// step's own end as it was. A turn
|
|
191
|
-
// the
|
|
206
|
+
// step's own end as it was. A notice's turn that did not end with an answer (stopped, refused, an
|
|
207
|
+
// error) adds a row saying so under the step. A turn the module never saw start (it loaded mid-turn)
|
|
208
|
+
// ends the step for the work at hand.
|
|
192
209
|
export function endTurn(s, e, now) {
|
|
193
210
|
if (e.agentId) return;
|
|
194
211
|
const host = s.turns.find((x) => x.notices.has(e.turnId));
|
|
195
212
|
if (host) {
|
|
196
213
|
host.notices.delete(e.turnId);
|
|
214
|
+
const how = pick(NOTICE_END, e.reason);
|
|
215
|
+
if (how) note(s, host, how, false, now);
|
|
197
216
|
return;
|
|
198
217
|
}
|
|
199
218
|
const t = s.turns.find((x) => x.id === e.turnId) ?? s.turns.find((x) => x.id === AT_HAND && x.status === 'running');
|
|
@@ -275,17 +294,46 @@ function putTask(s, t, now) {
|
|
|
275
294
|
while (s.tasks.size > MAX_STEPS) s.tasks.delete(overflow([...s.tasks.values()]).id);
|
|
276
295
|
}
|
|
277
296
|
|
|
278
|
-
|
|
297
|
+
// The marks of a task id, made on first use. They live in the state, not on the task, so a task deleted, or not
|
|
298
|
+
// created yet, keeps them: `changedRev` the change count after its last TaskCreate or TaskUpdate (a delete
|
|
299
|
+
// included), `statusAt` and `status` the order and the status of the last status update begun, `seenAt` the order
|
|
300
|
+
// of the last read taken in.
|
|
301
|
+
function markOf(s, id) {
|
|
302
|
+
if (!s.marks.has(id)) s.marks.set(id, {});
|
|
303
|
+
return s.marks.get(id);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Whether a list or a read begun at (since, order) is older than what the pane holds of a task: the task changed
|
|
307
|
+
// after it began (a delete too), or a later-begun read already brought it.
|
|
308
|
+
function stale(s, id, since, order) {
|
|
309
|
+
const m = s.marks.get(id);
|
|
310
|
+
return Boolean(m) && ((m.changedRev ?? 0) > since || order < (m.seenAt ?? 0));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// An update begun after the create may end first (the ids are predictable): its status stands, and a delete wins.
|
|
314
|
+
function createTask(s, e, r, now, { order }) {
|
|
279
315
|
const id = r?.result?.task?.id;
|
|
280
316
|
if (id === undefined) return;
|
|
281
|
-
|
|
317
|
+
const m = s.marks.get(String(id));
|
|
318
|
+
const status = m && order < (m.statusAt ?? 0) ? m.status : 'pending';
|
|
319
|
+
if (status === 'deleted') return;
|
|
320
|
+
putTask(s, { id: String(id), subject: e.subject, description: e.description, activeForm: e.activeForm, status }, now);
|
|
282
321
|
}
|
|
283
322
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
323
|
+
// The status a TaskUpdate left: the change it answers with, else the one it asked for.
|
|
324
|
+
const updatedStatus = (e, r) => r?.result?.statusChange?.to ?? e.status;
|
|
325
|
+
|
|
326
|
+
// A task this module never saw (a list from before it loaded, or one another session shares) joins the list while
|
|
327
|
+
// it is not done. Of two status updates to one task, the one begun later stands; an update without a status (an
|
|
328
|
+
// owner, a description) brings its words only.
|
|
329
|
+
function updateTask(s, e, r, now, { order }) {
|
|
287
330
|
const id = String(e.taskId);
|
|
288
|
-
const status = r
|
|
331
|
+
const status = updatedStatus(e, r);
|
|
332
|
+
if (status !== undefined) {
|
|
333
|
+
const m = markOf(s, id);
|
|
334
|
+
if (order < (m.statusAt ?? 0)) return;
|
|
335
|
+
Object.assign(m, { statusAt: order, status });
|
|
336
|
+
}
|
|
289
337
|
if (status === 'deleted') {
|
|
290
338
|
s.tasks.delete(id);
|
|
291
339
|
return;
|
|
@@ -294,28 +342,55 @@ function updateTask(s, e, r, now) {
|
|
|
294
342
|
putTask(s, { id, subject: e.subject, description: e.description, activeForm: e.activeForm, status }, now);
|
|
295
343
|
}
|
|
296
344
|
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
345
|
+
// Whether the pane changed a task (a delete too) after a list of it began.
|
|
346
|
+
const changedAfter = (s, id, since) => (s.marks.get(id)?.changedRev ?? 0) > since;
|
|
347
|
+
// The status a list or a read begun at order shows for a task: a status update begun after it stands, also for a
|
|
348
|
+
// task the pane did not hold then.
|
|
349
|
+
const readStatus = (s, id, order, status) => {
|
|
350
|
+
const m = s.marks.get(id);
|
|
351
|
+
return m && order < (m.statusAt ?? 0) ? m.status : status;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// One task a TaskList names: as the pane holds it where it changed since the list began or a later-begun read
|
|
355
|
+
// brought it, else as listed.
|
|
356
|
+
function takeListed(s, before, t, { over, since, order, now }) {
|
|
357
|
+
const id = String(t.id);
|
|
358
|
+
const held = before.get(id);
|
|
359
|
+
if (held) s.tasks.set(id, held);
|
|
360
|
+
if (stale(s, id, since, order)) return;
|
|
361
|
+
const status = readStatus(s, id, order, t.status);
|
|
362
|
+
if (status === 'deleted') return;
|
|
363
|
+
if (held || !over) putTask(s, { id, subject: t.subject, status }, now);
|
|
364
|
+
markOf(s, id).seenAt = order;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// TaskList names the whole list, in its order: a task it leaves out is gone, unless it changed after the list
|
|
368
|
+
// began. A list whose every task is completed is over, and only brings up to date the tasks still on the pane. A
|
|
369
|
+
// list begun before one already taken in changes nothing.
|
|
370
|
+
function listTasks(s, _e, r, now, { since, order }) {
|
|
300
371
|
const listed = r?.result?.tasks;
|
|
301
|
-
if (
|
|
372
|
+
if (!Array.isArray(listed) || order < s.listAt) return;
|
|
373
|
+
s.listAt = order;
|
|
302
374
|
const over = listed.every((t) => t.status === 'completed');
|
|
303
375
|
const before = s.tasks;
|
|
376
|
+
const named = new Set(listed.map((t) => String(t.id)));
|
|
304
377
|
s.tasks = new Map();
|
|
305
|
-
for (const t of listed) {
|
|
306
|
-
|
|
307
|
-
if (before.has(id)) s.tasks.set(id, before.get(id));
|
|
308
|
-
if (before.has(id) || !over) putTask(s, { id, subject: t.subject, status: t.status }, now);
|
|
309
|
-
}
|
|
378
|
+
for (const t of listed) takeListed(s, before, t, { over, since, order, now });
|
|
379
|
+
for (const [id, held] of before) if (!named.has(id) && changedAfter(s, id, since)) s.tasks.set(id, held);
|
|
310
380
|
}
|
|
311
381
|
|
|
312
|
-
// TaskGet reads one task: one it cannot find is gone, and a completed one off the pane stays off.
|
|
313
|
-
|
|
314
|
-
|
|
382
|
+
// TaskGet reads one task: one it cannot find is gone, and a completed one off the pane stays off. A read older than
|
|
383
|
+
// what the pane holds of the task changes nothing.
|
|
384
|
+
function getTask(s, e, r, now, { since, order }) {
|
|
385
|
+
if (stale(s, String(e.taskId), since, order)) return;
|
|
315
386
|
const t = r?.result?.task;
|
|
316
387
|
if (t === null) s.tasks.delete(String(e.taskId));
|
|
317
|
-
if (!t
|
|
318
|
-
|
|
388
|
+
if (!t) return;
|
|
389
|
+
const id = String(t.id);
|
|
390
|
+
const status = readStatus(s, id, order, t.status);
|
|
391
|
+
if (status === 'deleted' || (status === 'completed' && !s.tasks.has(id))) return;
|
|
392
|
+
putTask(s, { id, subject: t.subject, description: t.description, status }, now);
|
|
393
|
+
markOf(s, id).seenAt = order;
|
|
319
394
|
}
|
|
320
395
|
|
|
321
396
|
const TASK_NOTES = {
|
|
@@ -329,15 +404,22 @@ const TASK_NOTES = {
|
|
|
329
404
|
// A refused or failed call changes nothing: the task list stays as Claude Code holds it. A TaskUpdate
|
|
330
405
|
// that changed nothing (an id it cannot find, a TaskCompleted hook that blocks) answers
|
|
331
406
|
// `success: false` without isError.
|
|
332
|
-
// TaskList and TaskGet run beside other calls
|
|
333
|
-
// the
|
|
407
|
+
// TaskList and TaskGet run beside other calls (`since` is the count of changes when one began, `order` its place
|
|
408
|
+
// among the calls begun): what changed after it began, or what a later-begun call already brought, stays.
|
|
334
409
|
const refused = (r) => Boolean(r?.deny || r?.isError || r?.result?.success === false);
|
|
335
410
|
const CHANGES = new Set(['TaskCreate', 'TaskUpdate']);
|
|
411
|
+
// The task a TaskCreate or TaskUpdate changed, or undefined where the answer names none.
|
|
412
|
+
const changedId = (e, r) => (e.tool === 'TaskCreate' ? r?.result?.task?.id : e.taskId);
|
|
336
413
|
|
|
337
|
-
export function noteTaskTool(s, e, r, now, since) {
|
|
414
|
+
export function noteTaskTool(s, e, r, now, since = s.rev, order = ++s.order) {
|
|
338
415
|
if (refused(r)) return;
|
|
339
|
-
TASK_NOTES[e.tool]?.(s, e, r, now, since
|
|
340
|
-
if (CHANGES.has(e.tool))
|
|
416
|
+
TASK_NOTES[e.tool]?.(s, e, r, now, { since, order });
|
|
417
|
+
if (!CHANGES.has(e.tool)) return;
|
|
418
|
+
s.rev += 1;
|
|
419
|
+
// Only a change the pane took in marks the task: one it holds after the change, or a delete. An update of a task the
|
|
420
|
+
// pane does not hold leaves the lists alone (its status still counts, through readStatus).
|
|
421
|
+
const id = changedId(e, r);
|
|
422
|
+
if (id !== undefined && (s.tasks.has(String(id)) || updatedStatus(e, r) === 'deleted')) markOf(s, String(id)).changedRev = s.rev;
|
|
341
423
|
}
|
|
342
424
|
|
|
343
425
|
// The list a to-do call sent, each item's words cleaned, or what is wrong with it.
|
|
@@ -369,12 +451,13 @@ export function answerTodo(s, e, now, paneOpen = true) {
|
|
|
369
451
|
|
|
370
452
|
// The plan file a call touched, with the touch's count, when it is one and the call went through:
|
|
371
453
|
// a write by anyone, a read by the session itself (an agent reviewing a plan does not make it the plan).
|
|
372
|
-
// `own` is a write by the session itself.
|
|
454
|
+
// `own` is a write by the session itself; `weak` a review round or open questions beside a plan.
|
|
373
455
|
export function planTouched(s, e, call) {
|
|
374
456
|
const path = e.file_path ?? e.notebook_path;
|
|
375
|
-
|
|
457
|
+
const kind = planKind(path);
|
|
458
|
+
if (!call?.ok || !PLAN_TOUCH.has(e.tool) || !kind || (e.tool === 'Read' && e.agentId)) return null;
|
|
376
459
|
s.touch += 1;
|
|
377
|
-
return { path, touch: s.touch, own: !e.agentId && e.tool !== 'Read' };
|
|
460
|
+
return { path, touch: s.touch, own: !e.agentId && e.tool !== 'Read', weak: kind === 'weak' };
|
|
378
461
|
}
|
|
379
462
|
|
|
380
463
|
function parsed(path, text) {
|
|
@@ -393,10 +476,13 @@ function current(s, path, { touch, since }) {
|
|
|
393
476
|
return path === s.planPath && s.taken <= since;
|
|
394
477
|
}
|
|
395
478
|
|
|
396
|
-
// Whether what was read stays off the pane: no plan
|
|
397
|
-
// has some, unless the session wrote it itself (an agent's
|
|
398
|
-
// session only read, does not push the plan away; a plan the session
|
|
399
|
-
|
|
479
|
+
// Whether what was read stays off the pane: no plan; a review round or open questions while the pane shows another
|
|
480
|
+
// plan; or a file with no steps in place of a plan that has some, unless the session wrote it itself (an agent's
|
|
481
|
+
// review written beside a plan, or a note the session only read, does not push the plan away; a plan the session
|
|
482
|
+
// writes is the plan, steps or not).
|
|
483
|
+
const keepsOff = (s, path, plan, read) => !plan
|
|
484
|
+
|| (path !== s.planPath && Boolean(s.plan) && read.weak === true)
|
|
485
|
+
|| (path !== s.planPath && !plan.total && Boolean(s.plan?.total) && !read.own);
|
|
400
486
|
|
|
401
487
|
// The ids in a tree, to keep the unfolded ones that are still there when a plan is read again.
|
|
402
488
|
function treeIds(nodes, out = new Set()) {
|
|
@@ -442,6 +528,9 @@ export function steps(s) {
|
|
|
442
528
|
return s.loose.calls.length ? [...items, { ...s.loose, status: looseStatus(s, items) }] : items;
|
|
443
529
|
}
|
|
444
530
|
|
|
531
|
+
// The key of the step in progress (a task or a to-do item in progress, or the prompt that runs), or null.
|
|
532
|
+
const runningKey = (s) => [...steps(s)].reverse().find((t) => ['in_progress', 'running'].includes(statusOf(t)))?.key ?? null;
|
|
533
|
+
|
|
445
534
|
function callCount(step) {
|
|
446
535
|
return `도구 호출 ${step.count}회${step.failed ? `(실패 ${step.failed}회)` : ''}`;
|
|
447
536
|
}
|
|
@@ -655,6 +744,7 @@ export async function runProgress($, args) {
|
|
|
655
744
|
if (pane) await $.ui.close({ id: PANE });
|
|
656
745
|
return {};
|
|
657
746
|
}
|
|
747
|
+
ref.s.followed = null; // before the open: the engine may draw the pane before the open answers
|
|
658
748
|
await showPane($, pane);
|
|
659
749
|
await offerTodo($);
|
|
660
750
|
return {};
|
|
@@ -676,6 +766,7 @@ export const SPEC = { name: COMMAND, description: '진행 상황 패널을 켜
|
|
|
676
766
|
async function watchCall($, e, next) {
|
|
677
767
|
const s = ref.s;
|
|
678
768
|
const since = s.rev;
|
|
769
|
+
const order = ++s.order;
|
|
679
770
|
const call = startCall(s, e, Date.now());
|
|
680
771
|
let r = { isError: true };
|
|
681
772
|
try {
|
|
@@ -683,9 +774,9 @@ async function watchCall($, e, next) {
|
|
|
683
774
|
return r;
|
|
684
775
|
} finally {
|
|
685
776
|
endCall(call, r, Date.now());
|
|
686
|
-
if (TASK_TOOLS.has(e.tool)) noteTaskTool(s, e, r, Date.now(), since);
|
|
777
|
+
if (TASK_TOOLS.has(e.tool)) noteTaskTool(s, e, r, Date.now(), since, order);
|
|
687
778
|
const touched = planTouched(s, e, call);
|
|
688
|
-
if (touched) takePlan(s, touched.path, await readText($, touched.path), { touch: touched.touch, own: touched.own });
|
|
779
|
+
if (touched) takePlan(s, touched.path, await readText($, touched.path), { touch: touched.touch, own: touched.own, weak: touched.weak });
|
|
689
780
|
$.ui.invalidate('ui.render');
|
|
690
781
|
}
|
|
691
782
|
}
|
|
@@ -708,6 +799,41 @@ async function readText($, path) {
|
|
|
708
799
|
// The session's progress, one per loaded module.
|
|
709
800
|
const ref = { s: freshState() };
|
|
710
801
|
|
|
802
|
+
// How long a move waits, and how many times it asks. The row a change brings is drawn after the invalidate that came
|
|
803
|
+
// with it; a move asked before that is refused ("no element of its own is drawn under that key" in 2.1.300).
|
|
804
|
+
const FOLLOW_WAIT_MS = 100;
|
|
805
|
+
const FOLLOW_TRIES = 4;
|
|
806
|
+
|
|
807
|
+
// Brings the step in progress into the pane's window as the pane draws, when that step changed (a task started, a
|
|
808
|
+
// new prompt) or the pane was opened again: in a long list it can sit below the window. Only a drawn pane asks, so a
|
|
809
|
+
// closed one or a tab behind another asks nothing. `nearest` moves nothing while its row shows, and the person's own
|
|
810
|
+
// scrolling stands until the next change. An engine without the move changes nothing.
|
|
811
|
+
function follow($) {
|
|
812
|
+
const key = runningKey(ref.s);
|
|
813
|
+
if (!key || key === ref.s.followed) return;
|
|
814
|
+
ref.s.followed = key;
|
|
815
|
+
followSoon($, key, FOLLOW_TRIES);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Asks for the move after a wait, and again while the row is not drawn yet. A later change of the step in progress,
|
|
819
|
+
// or any other refusal (no surface, the person moved the window meanwhile), ends it.
|
|
820
|
+
function followSoon($, key, tries) {
|
|
821
|
+
try {
|
|
822
|
+
$.clock.after(FOLLOW_WAIT_MS, async () => {
|
|
823
|
+
if (ref.s.followed !== key) return;
|
|
824
|
+
let answer;
|
|
825
|
+
try {
|
|
826
|
+
answer = await $.ui.scroll({ to: { key: `row-${key}` }, in: PANE, block: 'nearest' });
|
|
827
|
+
} catch {
|
|
828
|
+
return; /* an engine without $.ui.scroll */
|
|
829
|
+
}
|
|
830
|
+
if (tries > 1 && /\bdrawn\b/.test(answer?.deny ?? '')) followSoon($, key, tries - 1);
|
|
831
|
+
});
|
|
832
|
+
} catch {
|
|
833
|
+
/* an engine without $.clock */
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
711
837
|
// The command and the events only this feature hooks. register.mjs hooks the events every banker
|
|
712
838
|
// feature shares (the engine takes one hook per event from the module) and calls the functions
|
|
713
839
|
// below for them. The fallback's name is hooked too: where mods run, `/banker:progress` toggles
|
|
@@ -735,6 +861,7 @@ export function registerProgress(on) {
|
|
|
735
861
|
on('ui.render', { component: 'Pane' }, async ($, e, next) => {
|
|
736
862
|
if (e.requestId !== PANE) return next(e);
|
|
737
863
|
const redraw = () => $.ui.invalidate('ui.render');
|
|
864
|
+
follow($);
|
|
738
865
|
const cols = Number.isFinite(e.props?.bodyColumns) ? e.props.bodyColumns : Infinity;
|
|
739
866
|
const act = {
|
|
740
867
|
cols,
|
package/hooks/register.mjs
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The engine reads the module's source before it runs it: each `on("<event>", hook)` names its
|
|
7
7
|
* event as a string literal, an event without a matcher is hooked once in the whole module (its
|
|
8
|
-
* imported files included), and `$` goes only to functions declared in the file that holds it
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* imported files included), and `$` goes only to functions declared in the file that holds it
|
|
9
|
+
* (2.1.300 also takes a function imported from the module's own files; an older engine refuses
|
|
10
|
+
* the whole module, so this file keeps to the older rule, and a test holds it). So each feature
|
|
11
|
+
* (graceful-pause.mjs, progress.mjs) hooks the events only it needs, and this file hooks the
|
|
12
|
+
* four they share: it calls the features' plain functions for them and makes the
|
|
11
13
|
* engine calls itself, the module's command registrations among them. The engine shows a
|
|
12
14
|
* `$.ui.log` line under the plugin's name, so the lines here do not repeat it.
|
|
13
15
|
*/
|
|
@@ -37,13 +39,18 @@ async function registerOne($, spec) {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
+
// A command spec without `immediate`, for an engine where answering at once was not seen to work.
|
|
43
|
+
const withoutImmediate = ({ immediate: _immediate, ...spec }) => spec;
|
|
44
|
+
|
|
45
|
+
// /graceful-pause from 2.1.289 on (an older engine gets one line saying why). /progress wherever mods run, as a
|
|
46
|
+
// command that answers at once (immediate) only from 2.1.289 on, where that was seen to work; below it, or with no
|
|
47
|
+
// version, it answers when the turn ends. A name the engine refuses is logged and the other still registers.
|
|
42
48
|
async function registerCommands($) {
|
|
43
49
|
const version = await engineVersion($);
|
|
44
|
-
|
|
50
|
+
const recent = atLeast(version, MIN_ENGINE);
|
|
51
|
+
if (recent) await registerOne($, PAUSE_SPEC);
|
|
45
52
|
else $.ui.log(`/${PAUSE} 는 Claude Code ${MIN_ENGINE.join('.')} 이상에서만 켭니다 (이 엔진: ${version || '버전 미상'})`);
|
|
46
|
-
await registerOne($, PROGRESS_SPEC);
|
|
53
|
+
await registerOne($, recent ? PROGRESS_SPEC : withoutImmediate(PROGRESS_SPEC));
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
// /progress's plan file, read again at a turn's start and end: a ralph PRD or a plan's checkboxes
|
package/hooks/update-checkin.mjs
CHANGED
|
@@ -158,42 +158,44 @@ function fetchSkillChangesMap(url) {
|
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (!url) return; // 엔드포인트 없음 - 무동작(로그 미변경).
|
|
165
|
-
|
|
166
|
-
// 로그 없음도 유효한 체크인(설치 펄스 + 알림 fetch 겸함): 빈 counts 로 진행한다.
|
|
161
|
+
// 보낼 본문. PII/IP/지속ID 없음: version + os(플랫폼 계열) + counts(스킬별 x 시간대)뿐.
|
|
162
|
+
// 로그 없음도 유효한 체크인(설치 펄스 + 알림 fetch 겸함): 빈 counts 로 진행한다.
|
|
163
|
+
function checkinBody() {
|
|
167
164
|
let raw = '';
|
|
168
165
|
try { raw = readFileSync(USAGE_LOG_PATH, 'utf8'); } catch { raw = ''; }
|
|
166
|
+
return JSON.stringify({ version: installedVersion() || 'unknown', os: process.platform, counts: aggregate(raw) });
|
|
167
|
+
}
|
|
169
168
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
176
|
-
const responseBody = await post(url, payload);
|
|
177
|
-
|
|
178
|
-
// 응답의 latest 로 공유 update-check 캐시를 갱신(알림 겸함). 없으면 캐시 미변경.
|
|
179
|
-
// update-fetch 처럼 notified:null 재무장을 하지 않아도 등가다: dedupe 판정이 notified===latest
|
|
180
|
-
// (update-notify)라 전진 버전에서 구 notified 는 항상 신 latest 와 달라 재고지가 정상 발화하고,
|
|
181
|
-
// count-default-on 은 이 훅만·npm 배포는 update-fetch 만 써 같은 캐시를 겹쳐 쓰지 않는다.
|
|
169
|
+
// 응답의 latest 로 공유 update-check 캐시를 갱신(알림 겸함). 없으면 캐시 미변경.
|
|
170
|
+
// update-fetch 처럼 notified:null 재무장을 하지 않아도 등가다: dedupe 판정이 notified===latest
|
|
171
|
+
// (update-notify)라 전진 버전에서 구 notified 는 항상 신 latest 와 달라 재고지가 정상 발화하고,
|
|
172
|
+
// count-default-on 은 이 훅만·npm 배포는 update-fetch 만 써 같은 캐시를 겹쳐 쓰지 않는다.
|
|
173
|
+
async function refreshUpdateCache(responseBody) {
|
|
182
174
|
const latest = extractLatest(responseBody);
|
|
183
|
-
if (latest)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
175
|
+
if (!latest) return;
|
|
176
|
+
// 개인화용: 변경-스킬 매니페스트를 best-effort GET(실패 시 changedSkills 생략, 기존 값 보존).
|
|
177
|
+
const changesUrl = process.env.BANKER_SKILL_CHANGES_URL || SKILL_CHANGES_URL; // 테스트 주입용 override.
|
|
178
|
+
const map = await fetchSkillChangesMap(changesUrl);
|
|
179
|
+
const patch = { latest, checkedAt: Date.now() };
|
|
180
|
+
if (map) patch.changedSkills = changedSkillsBetween(map, installedVersion(), latest);
|
|
181
|
+
try { writeUpdateCache(patch); } catch { /* best-effort */ }
|
|
182
|
+
}
|
|
191
183
|
|
|
192
|
-
|
|
184
|
+
// 전송 시도 후(성공/실패 무관) 로그를 비워 24h 윈도우로 상한(배치 유실 허용).
|
|
185
|
+
function resetLog() {
|
|
193
186
|
try { writeFileSync(USAGE_LOG_PATH, ''); } catch { /* best-effort */ }
|
|
194
187
|
try { writeFileSync(LAST_FLUSH_PATH, new Date().toISOString()); } catch { /* best-effort */ }
|
|
195
188
|
}
|
|
196
189
|
|
|
190
|
+
async function main() {
|
|
191
|
+
if (!countingActive()) return; // 미활성 - 무동작(로그 미변경).
|
|
192
|
+
const url = endpoint();
|
|
193
|
+
if (!url) return; // 엔드포인트 없음 - 무동작(로그 미변경).
|
|
194
|
+
const responseBody = await post(url, checkinBody());
|
|
195
|
+
await refreshUpdateCache(responseBody);
|
|
196
|
+
resetLog();
|
|
197
|
+
}
|
|
198
|
+
|
|
197
199
|
main()
|
|
198
200
|
.catch(() => {}) // 어떤 예외도 삼킨다.
|
|
199
201
|
.finally(() => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaydash9999/banker-plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "banker — Claude Code + Codex CLI plugin (QA, audit, docs, architecture, wiki skills). Install via Claude Code marketplace, or `npm i -g @kaydash9999/banker-plugins` then `banker setup`.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"banker": "bin/banker.js"
|
package/skills/anti-drm/SKILL.md
CHANGED
|
@@ -43,9 +43,9 @@ argument-hint: "on | off | status | config"
|
|
|
43
43
|
|
|
44
44
|
| `- 설정:` 줄 | 다음 |
|
|
45
45
|
|---|---|
|
|
46
|
-
| `없음` | 아래 "질문" 을 한 뒤 `on --runtime <런타임> --
|
|
46
|
+
| `없음` | 아래 "질문" 을 한 뒤 "설정 넘기기" 대로 `on --runtime <런타임> --stdin` 실행 |
|
|
47
47
|
| 파일 경로 | `on --runtime <런타임>` |
|
|
48
|
-
| `올바르지 않음` | 이유를 전하고
|
|
48
|
+
| `올바르지 않음` | 이유를 전하고 "질문" 으로 설정을 다시 정한 뒤 `없음` 행과 같은 명령 실행 |
|
|
49
49
|
|
|
50
50
|
3. 성공하면 출력의 `적용할 규칙:` 아래 규칙을 이 세션에 바로 적용. 다음 세션부터는 자동 적용
|
|
51
51
|
|
|
@@ -63,7 +63,7 @@ argument-hint: "on | off | status | config"
|
|
|
63
63
|
|
|
64
64
|
1. `status` 로 지금 설정을 봄
|
|
65
65
|
2. 지금 값을 함께 보이며 아래 "질문" 을 함
|
|
66
|
-
3.
|
|
66
|
+
3. "설정 넘기기" 대로 `config --stdin` 실행
|
|
67
67
|
4. 이 런타임이 켜져 있으면 출력의 `적용할 규칙:` 아래 규칙을 이 세션에 바로 적용
|
|
68
68
|
|
|
69
69
|
## 질문
|
|
@@ -100,12 +100,31 @@ argument-hint: "on | off | status | config"
|
|
|
100
100
|
2. 직접 입력은 쉼표나 공백으로 나눔
|
|
101
101
|
3. 낱말마다 `^\.?[A-Za-z0-9]+$` 를 만족해야 함. 아니면 그 값을 보이고 다시 물음
|
|
102
102
|
4. 금지와 대체에 같은 확장자가 있으면 다시 물음
|
|
103
|
-
5.
|
|
103
|
+
5. 정규화한 확장자만 넘김
|
|
104
104
|
- 소문자로 바꿈
|
|
105
105
|
- 앞에 점이 없으면 붙임
|
|
106
|
-
- 쉼표로 이어
|
|
106
|
+
- 쉼표로 이어 "설정 넘기기" 의 `deny=`, `alt=` 줄에 씀
|
|
107
107
|
6. 스크립트가 거부하면 그 메시지를 전하고 다시 물음
|
|
108
108
|
|
|
109
|
+
### 설정 넘기기
|
|
110
|
+
|
|
111
|
+
- 설정 값은 명령줄 인자가 아니라 표준 입력으로 넘김. 따옴표 친 heredoc 안의 글은 셸이 해석하지 않음
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
node "<이 스킬 디렉터리 절대경로>/scripts/anti-drm.mjs" config --stdin <<'ANTI_DRM_SETTINGS'
|
|
115
|
+
deny=.txt,.pdf
|
|
116
|
+
alt=.md,.html,.tsv
|
|
117
|
+
tsv=tab
|
|
118
|
+
ANTI_DRM_SETTINGS
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- `on` 은 `config` 자리에 `on --runtime <런타임>` 을 씀
|
|
122
|
+
- PowerShell 은 `@'` 와 `'@` 사이에 같은 세 줄을 두고 `| node "<경로>/scripts/anti-drm.mjs" config --stdin` 으로 넘김
|
|
123
|
+
- 넘기기 전에 heredoc 안의 줄마다 `^(deny|alt|tsv)=[a-z0-9., ]*$` 에 맞는지 확인. 하나라도 맞지 않으면 실행하지 않고 "질문" 으로 돌아감
|
|
124
|
+
- "답 처리" 의 단어 검사와 겹치는 두 번째 방벽. 끝 표시(`ANTI_DRM_SETTINGS`, PowerShell 의 `'@`)는 이 꼴에 맞을 수 없어 값이 heredoc 을 일찍 닫지 못함
|
|
125
|
+
- 줄은 `deny=`, `alt=`, `tsv=` 를 하나씩. `tsv=` 는 대체 확장자에 `.tsv` 가 있을 때만 씀
|
|
126
|
+
- `--stdin` 은 입력이 끝날 때까지 기다림. 늘 heredoc 이나 파이프로 넘김
|
|
127
|
+
|
|
109
128
|
## 종료 코드
|
|
110
129
|
|
|
111
130
|
| 코드 | 뜻 | 처리 |
|
|
@@ -206,7 +225,7 @@ argument-hint: "on | off | status | config"
|
|
|
206
225
|
- Codex 블록은 `USER:OMX:POLICY` 표시로 감쌈
|
|
207
226
|
- 블록이 남음: `omx setup --merge-agents`, 기본 `omx setup`(team 모드 켜짐)
|
|
208
227
|
- 블록이 사라짐: `omx setup --force`, 덮어쓰기 확인, team 모드를 끈 `omx setup`. 그 뒤 `on` 다시 실행
|
|
209
|
-
- Codex 파일 변경 전 내용: `<파일>.anti-drm.bak` (이 계정만 읽기 가능)
|
|
228
|
+
- Codex 파일 변경 전 내용: `<파일>.anti-drm.bak` (이 계정만 읽기 가능). `off` 결과에 그 경로를 알림
|
|
210
229
|
- `tone-compact` 블록과 같은 파일에 있어도 서로 건드리지 않음
|
|
211
230
|
- `on`, `off` 는 그 런타임만 바꿈
|
|
212
231
|
- 설정 값을 준 `on` 과 `config` 는 규칙이 들어 있는 다른 런타임도 새 규칙으로 바꿈
|
|
@@ -217,14 +236,19 @@ argument-hint: "on | off | status | config"
|
|
|
217
236
|
| 메시지 | 조치 |
|
|
218
237
|
|---|---|
|
|
219
238
|
| 설정이 없습니다 | "질문" 으로 설정을 정해 `on` 에 넘김 |
|
|
220
|
-
| 설정
|
|
221
|
-
|
|
|
222
|
-
|
|
|
239
|
+
| 표준 입력에 설정 줄이 없습니다 | heredoc 이 비었음. "설정 넘기기" 대로 줄을 넣어 다시 실행 |
|
|
240
|
+
| 표준 입력이 64KiB 보다 큽니다 | "답 처리" 대로 정규화한 값만 넘김 |
|
|
241
|
+
| 설정 파일이 올바르지 않습니다 | 이유를 전하고 `config` 로 다시 만들지 물은 뒤 진행. 다른 사용자 소유 파일은 지우거나 옮기지 않음. 무엇인지 보이고 사용자가 정하게 함 |
|
|
242
|
+
| 일반 파일이 아니라 설정을 저장하지 않았습니다 | 설정 경로의 폴더나 특수 파일은 지우지 않음. 무엇인지 보이고, 옮길지 다른 경로(`BANKER_ANTI_DRM_CONFIG`)를 쓸지 물은 뒤 다시 실행 |
|
|
243
|
+
| 64KiB 보다 큰 파일이라, 읽을 수 없는 파일이라 설정을 저장하지 않았습니다 | 그 파일이 무엇인지 보이고, 권한을 고칠지 옮길지 물은 뒤 다시 실행 |
|
|
223
244
|
| anti-drm 이 쓰는 형식이라 금지할 수 없습니다 | 금지 목록에서 `.md`, `.json` 을 뺌 |
|
|
224
|
-
| 같은 이름의 다른 파일이 있음 | `rules/banker-anti-drm.md` 가 banker 파일이 아님.
|
|
225
|
-
| 표시의 짝이 맞지 않음 | 지침 파일에 anti-drm 표시 줄이 한쪽만 있거나 문장 속에 있음. 또는 블록 안에 다른 표시 줄이 있음. 짝을 새로 맞추지
|
|
245
|
+
| 같은 이름의 다른 파일이 있음 | `rules/banker-anti-drm.md` 가 banker 파일이 아님. 지우거나 이름을 바꾸지 않음. 내용을 보이고 어떻게 할지 물은 뒤 다시 실행 |
|
|
246
|
+
| 표시의 짝이 맞지 않음 | 지침 파일에 anti-drm 표시 줄이 한쪽만 있거나 문장 속에 있음. 또는 블록 안에 다른 표시 줄이 있음. 짝을 새로 맞추지 않음. 남은 표시 줄을 보이고 지울지 물은 뒤 다시 실행 |
|
|
226
247
|
| 다른 banker 블록만 있어 Codex 가 AGENTS.md 를 읽지 않음 | 메시지가 말한 스킬에서 `on` 실행 |
|
|
227
248
|
| 설정 폴더가 없음 | 해당 런타임 미설치. 또는 설정 폴더 환경 변수 값 확인 |
|
|
228
249
|
| 규칙이 지금 설정이나 이 버전과 다름 | `on` 한 번 실행 |
|
|
229
|
-
| UTF-8 이 아닌 파일 | 지침 파일을 UTF-8 로
|
|
250
|
+
| UTF-8 이 아닌 파일 | 지침 파일을 바꾸지 않음. 인코딩을 보이고, 백업을 만든 뒤 UTF-8 로 바꿀지 물은 뒤 다시 실행 |
|
|
251
|
+
| 끊어진 링크라 그대로 두었습니다 | AGENTS.md 가 가리키는 파일이 없음. 링크를 지우거나 바꾸지 않음. 링크와 대상을 보이고 어떻게 할지 물은 뒤 다시 실행 |
|
|
252
|
+
| 일반 파일이 아님 | 지침 파일 자리(AGENTS.md, AGENTS.override.md)의 폴더나 특수 파일은 지우지 않음. 무엇인지 사용자에게 보이고, 옮기거나 이름을 바꿀지 물은 뒤 다시 실행 |
|
|
253
|
+
| 다른 banker 지침 작업이 끝나지 않아 그대로 두었습니다 | 다른 banker 스킬의 `on`, `off` 가 도는 중. 잠시 뒤 다시 실행. 끝나지 않은 작업이 남긴 잠금은 1분 뒤 저절로 넘어감. 1분이 지나도 같거나 잠금 자리가 폴더면 잠금 경로를 보이고 어떻게 할지 물은 뒤 다시 실행. 잠금을 임의로 지우지 않음 |
|
|
230
254
|
| 쓸 권한이 없음 | 해당 파일과 폴더 권한 확인 |
|