@kaydash9999/banker-plugins 0.23.3 → 0.24.1

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.
Files changed (78) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +56 -0
  4. package/README.md +14 -6
  5. package/codex/manifest.json +6 -2
  6. package/codex/skills/teamwork/scripts/teamwork.mjs +3 -3
  7. package/codex/transform-matrix.md +6 -2
  8. package/package.json +1 -1
  9. package/skills/build-testbox/SKILL.md +213 -0
  10. package/skills/build-testbox/scripts/args.mjs +156 -0
  11. package/skills/build-testbox/scripts/browser-cell.mjs +181 -0
  12. package/skills/build-testbox/scripts/browsers.mjs +164 -0
  13. package/skills/build-testbox/scripts/containers.mjs +237 -0
  14. package/skills/build-testbox/scripts/detect.mjs +166 -0
  15. package/skills/build-testbox/scripts/proc.mjs +91 -0
  16. package/skills/build-testbox/scripts/report.mjs +109 -0
  17. package/skills/build-testbox/scripts/setup.mjs +144 -0
  18. package/skills/build-testbox/scripts/testbox.mjs +148 -0
  19. package/skills/git-init/SKILL.md +152 -0
  20. package/skills/git-init/scripts/git-init.mjs +440 -0
  21. package/skills/git-init/scripts/marker-block.mjs +321 -0
  22. package/skills/git-init/scripts/payload-scan.mjs +186 -0
  23. package/skills/init-dir/SKILL.md +172 -0
  24. package/skills/init-dir/scripts/init-dir.mjs +308 -0
  25. package/skills/init-dir/scripts/marker-block.mjs +321 -0
  26. package/skills/init-dir/scripts/templates.mjs +100 -0
  27. package/skills/init-dir/templates/KB/README.md +15 -0
  28. package/skills/init-dir/templates/docs/README.md +14 -0
  29. package/skills/init-dir/templates/logs/README.md +14 -0
  30. package/skills/init-dir/templates/logs/gitignore +3 -0
  31. package/skills/init-dir/templates/mockup/README.md +7 -0
  32. package/skills/init-dir/templates/scripts/README.md +39 -0
  33. package/skills/init-dir/templates/scripts/lib/service.mjs +596 -0
  34. package/skills/init-dir/templates/scripts/restart.cmd +13 -0
  35. package/skills/init-dir/templates/scripts/restart.sh +10 -0
  36. package/skills/init-dir/templates/scripts/service.conf +5 -0
  37. package/skills/init-dir/templates/scripts/start.cmd +13 -0
  38. package/skills/init-dir/templates/scripts/start.sh +10 -0
  39. package/skills/init-dir/templates/scripts/status.cmd +13 -0
  40. package/skills/init-dir/templates/scripts/status.sh +10 -0
  41. package/skills/init-dir/templates/scripts/stop.cmd +13 -0
  42. package/skills/init-dir/templates/scripts/stop.sh +10 -0
  43. package/skills/init-dir/templates/tests/README.md +15 -0
  44. package/skills/init-dir/templates/tests/run.cmd +13 -0
  45. package/skills/init-dir/templates/tests/run.mjs +160 -0
  46. package/skills/init-dir/templates/tests/run.sh +10 -0
  47. package/skills/init-dir/templates/tests/tests.conf +7 -0
  48. package/skills/init-dir/templates/work/README.md +8 -0
  49. package/skills/progress-mon/README.md +11 -8
  50. package/skills/progress-mon/SKILL.md +53 -16
  51. package/skills/progress-mon/assets/fixture/README.md +1 -1
  52. package/skills/progress-mon/assets/fixture/adr.json +35 -0
  53. package/skills/progress-mon/assets/fixture/arch.json +1 -1
  54. package/skills/progress-mon/assets/fixture/flow.json +2 -2
  55. package/skills/progress-mon/assets/fixture/variants/arch-auto/arch.json +1 -1
  56. package/skills/progress-mon/assets/fixture/variants/legacy-init/rules.json +6 -0
  57. package/skills/progress-mon/assets/fixture/variants/legacy-init/state.json +5 -0
  58. package/skills/progress-mon/references/adr.md +184 -0
  59. package/skills/progress-mon/references/authoring.md +13 -5
  60. package/skills/progress-mon/scripts/pm-adr-view.mjs +101 -0
  61. package/skills/progress-mon/scripts/pm-adr.mjs +515 -0
  62. package/skills/progress-mon/scripts/pm-check.mjs +59 -22
  63. package/skills/progress-mon/scripts/pm-data.mjs +1 -1
  64. package/skills/progress-mon/scripts/pm-html.mjs +1 -0
  65. package/skills/progress-mon/scripts/pm-memory.mjs +113 -28
  66. package/skills/progress-mon/scripts/pm-render.mjs +6 -4
  67. package/skills/progress-mon/scripts/pm-server.mjs +2 -1
  68. package/skills/progress-mon/scripts/pm-sources.mjs +10 -0
  69. package/skills/progress-mon/scripts/progress-mon.mjs +3 -2
  70. package/skills/tune-tmux/SKILL.md +235 -0
  71. package/skills/tune-tmux/scripts/app-notes.mjs +38 -0
  72. package/skills/tune-tmux/scripts/marker-block.mjs +321 -0
  73. package/skills/tune-tmux/scripts/psmux-caps.json +71 -0
  74. package/skills/tune-tmux/scripts/tmux-config.mjs +251 -0
  75. package/skills/tune-tmux/scripts/tune-tmux.mjs +284 -0
  76. package/skills/ultra-chain/SKILL.md +52 -22
  77. package/skills/ultra-chain/references/chain-ledger.mjs +93 -11
  78. package/skills/ultra-chain/references/chain-report.mjs +13 -2
@@ -3,7 +3,7 @@
3
3
  // run.json (when, where and from which commit the run started) and events.jsonl (one JSON line per stage
4
4
  // event). The time of every event is taken here, never written by the model. Pure Node, no dependencies.
5
5
  //
6
- // node chain-ledger.mjs init --base <folder> [--runtime claude|codex] [--exclude]
6
+ // node chain-ledger.mjs init --base <folder> [--runtime claude|codex] [--agents N] [--exclude]
7
7
  // node chain-ledger.mjs event <run> --stage <id> --status <status> [--file <event.json>] [--round <n>]
8
8
  // [--summary <text>] [--reason <text>]
9
9
  // node chain-ledger.mjs boxes <run> --file <boxes.mjs report> [--stage <id>] [--round <n>]
@@ -61,7 +61,13 @@ const bool = () => (v) => (typeof v === "boolean" ? v : undefined);
61
61
  const path = () => (v) => runPath(v) ?? undefined;
62
62
  const COUNT = int(0, 10_000_000);
63
63
 
64
- // Each field: [check, required, what the check wants]. A check returns the cleaned value or undefined.
64
+ // The judges of one visual view (stage 7): every judge's score and its verdict after the score rule.
65
+ const JUDGES = { max: 20, fields: {
66
+ id: [text(60), true, "60자 이하 글"], score: [num(0, 100), false, "0~100 수"],
67
+ verdict: [oneOf("pass", "revise", "fail", "unverified"), true, "pass, revise, fail, unverified"] } };
68
+
69
+ // Each field: [check, required, what the check wants]. A check returns the cleaned value or undefined. `lists` are
70
+ // arrays of records inside one record.
65
71
  const RECORDS = {
66
72
  artifacts: { max: 50, fields: { label: [text(200), false, "200자 이하 글"], path: [path(), true, "실행 폴더 기준 상대 경로"] } },
67
73
  tests: { max: 60, fields: {
@@ -73,7 +79,7 @@ const RECORDS = {
73
79
  name: [text(100), true, "100자 이하 글"], mock: [path(), false, "실행 폴더 기준 상대 경로"],
74
80
  actual: [path(), false, "실행 폴더 기준 상대 경로"], score: [num(0, 100), false, "0~100 수"],
75
81
  verdict: [oneOf("pass", "revise", "fail", "unverified"), false, "pass, revise, fail, unverified"], intended: [bool(), false, "true, false"],
76
- note: [text(500), false, "500자 이하 글"] } },
82
+ note: [text(500), false, "500자 이하 글"] }, lists: { judges: JUDGES } },
77
83
  checklist: { max: 60, fields: {
78
84
  item: [text(300), true, "300자 이하 글"], why: [text(500), false, "500자 이하 글"], ref: [path(), false, "실행 폴더 기준 상대 경로"] } },
79
85
  seats: { max: 20, fields: {
@@ -109,9 +115,16 @@ function checkFields(obj, fields, where, extra = []) {
109
115
  return out;
110
116
  }
111
117
 
118
+ function checkItem(item, spec, where) {
119
+ const lists = spec.lists ?? {};
120
+ const out = checkFields(item, spec.fields, where, Object.keys(lists));
121
+ for (const [key, sub] of Object.entries(lists)) if (item[key] !== undefined) out[key] = checkList(item[key], sub, `${where}.${key}`);
122
+ return out;
123
+ }
124
+
112
125
  function checkList(list, spec, where) {
113
126
  if (!Array.isArray(list) || list.length > spec.max) throw new UsageError(`${where}: ${spec.max}개 이하 배열이어야 합니다`);
114
- return list.map((item, i) => checkFields(item, spec.fields, `${where}[${i}]`));
127
+ return list.map((item, i) => checkItem(item, spec, `${where}[${i}]`));
115
128
  }
116
129
 
117
130
  const BLOCKERS = { max: 50, fields: { item: [text(1000), true, "1000자 이하 글"] } };
@@ -137,6 +150,73 @@ export function checkEvent(raw) {
137
150
  return event;
138
151
  }
139
152
 
153
+ // ── reviewer count (--agents) ────────────────────────────────────────────────
154
+
155
+ // One count for every independent verdict of the chain: the ralph-qa seats of stages 3 and 6, the critic seats of
156
+ // stage 5 and the judges of stage 7. Under 3 is raised to 3 as ralph-qa does; over 17 is refused (the seat record
157
+ // holds 20, less the three outside seats).
158
+ const AGENTS_DEFAULT = 3;
159
+ const AGENTS_MAX = 17;
160
+
161
+ function agentsOf(value) {
162
+ if (value === undefined || value === null) return { agents: AGENTS_DEFAULT, agentsSource: "default" };
163
+ const n = typeof value === "number" ? value : /^\d+$/.test(String(value)) ? Number(value) : Number.NaN;
164
+ if (!Number.isInteger(n) || n < 0 || n > AGENTS_MAX) throw new UsageError(`--agents 는 0~${AGENTS_MAX} 정수입니다(좌석 기록 20 에서 외부 좌석 3 을 뺀 상한)`);
165
+ if (n < AGENTS_DEFAULT) return { agents: AGENTS_DEFAULT, agentsSource: "raised", agentsAsked: n };
166
+ return { agents: n, agentsSource: "flag" };
167
+ }
168
+
169
+ const seatIds = (prefix, n) => Array.from({ length: n }, (_, i) => `${prefix}#${i + 1}`);
170
+ const sameIds = (ids, prefix, n) => ids.length === n && [...ids].sort().join("\n") === seatIds(prefix, n).sort().join("\n");
171
+
172
+ // Stage 5's critic seat loop: an APPROVE or ITERATE round has exactly <agents> seats critic#1..critic#N, and APPROVE
173
+ // only when every seat approves. INCONCLUSIVE (a lost seat) and ralph's own verdict (by "ralph-reviewer") are not counted.
174
+ // A critic record takes only the results the loop can end in, so PASS or REJECT cannot slip past the seat count.
175
+ const CRITIC_RESULTS = ["APPROVE", "ITERATE", "INCONCLUSIVE", "STOPPED"];
176
+ function checkCriticSeats(event, n) {
177
+ const v = event.verdict;
178
+ if (event.stage !== "build" || !v || v.by !== "critic") return;
179
+ if (!CRITIC_RESULTS.includes(v.result)) throw new UsageError(`verdict.result: build 단계 critic 기록은 ${CRITIC_RESULTS.join(", ")} 만 됩니다(받은 값 ${v.result})`);
180
+ if (!["APPROVE", "ITERATE"].includes(v.result)) return;
181
+ const seats = v.seats ?? [];
182
+ if (!sameIds(seats.map((s) => s.id), "critic", n)) {
183
+ throw new UsageError(`verdict.seats: critic ${v.result} 기록은 좌석 ${n}개(${seatIds("critic", n).join(", ")})여야 합니다`);
184
+ }
185
+ if (v.result === "APPROVE" && seats.some((s) => s.verdict !== "APPROVE")) {
186
+ throw new UsageError("verdict.result: 모든 critic 좌석이 APPROVE 일 때만 APPROVE 입니다");
187
+ }
188
+ }
189
+
190
+ // Stage 5 verdicts come from the critic seat loop or ralph's own reviewer only; any other `by` would slip past the
191
+ // seat count above.
192
+ const BUILD_JUDGES = ["critic", "ralph-reviewer"];
193
+ function checkBuildBy(event) {
194
+ if (event.stage !== "build" || !event.verdict || BUILD_JUDGES.includes(event.verdict.by)) return;
195
+ throw new UsageError(`verdict.by: build 단계 판정은 ${BUILD_JUDGES.join(", ")} 만 기록합니다(받은 값 ${event.verdict.by})`);
196
+ }
197
+
198
+ // Stage 7: a view passes only when all <agents> judges judge#1..judge#N pass with 90 or more, and its score is the
199
+ // lowest judge score.
200
+ function checkVisualJudges(event, n) {
201
+ (event.visual ?? []).forEach((v, i) => {
202
+ if (v.verdict !== "pass") return;
203
+ const where = `visual[${i}]`;
204
+ const judges = v.judges ?? [];
205
+ if (!sameIds(judges.map((j) => j.id), "judge", n)) throw new UsageError(`${where}.judges: pass 는 판정자 ${n}명(${seatIds("judge", n).join(", ")})이 있어야 합니다`);
206
+ if (judges.some((j) => j.verdict !== "pass" || !(j.score >= 90))) throw new UsageError(`${where}.judges: pass 는 모든 판정자가 pass 이고 90점 이상일 때만입니다`);
207
+ const low = Math.min(...judges.map((j) => j.score));
208
+ if (v.score !== low) throw new UsageError(`${where}.score: 판정자 점수의 최솟값 ${low} 이어야 합니다`);
209
+ });
210
+ }
211
+
212
+ // Run folders made before --agents (no `agents` in run.json) are not checked, so an old run can still be resumed.
213
+ function checkQuorum(event, meta) {
214
+ if (!isObject(meta) || !Number.isInteger(meta.agents)) return;
215
+ checkBuildBy(event);
216
+ checkCriticSeats(event, meta.agents);
217
+ checkVisualJudges(event, meta.agents);
218
+ }
219
+
140
220
  // ── run folder ───────────────────────────────────────────────────────────────
141
221
 
142
222
  function git(cwd, args) {
@@ -191,19 +271,20 @@ function ignoreState(root, dir, baseDir, exclude) {
191
271
  // Folders the stages write into, made up front so a shell redirect into them never fails.
192
272
  export const RUN_FOLDERS = ["mock/expected", "actual", "visual", "qa", "boxes"];
193
273
 
194
- // Creates <base>/<id>/ with run.json and an empty events.jsonl. opts: base, cwd, runtime, exclude, now, rand.
274
+ // Creates <base>/<id>/ with run.json and an empty events.jsonl. opts: base, cwd, runtime, agents, exclude, now, rand.
195
275
  export function initRun(opts) {
196
276
  const o = { cwd: process.cwd(), runtime: null, exclude: false, now: new Date(), rand: randomBytes(2).toString("hex"), ...opts };
197
277
  if (!o.base) throw new UsageError("--base 가 필요합니다");
198
278
  if (/[\u0000-\u001F]/.test(o.base)) throw new UsageError("--base 에 제어 문자가 있습니다");
199
279
  if (o.runtime !== null && !["claude", "codex"].includes(o.runtime)) throw new UsageError("--runtime 은 claude, codex 중 하나입니다");
280
+ const agents = agentsOf(o.agents);
200
281
  const info = repoInfo(o.cwd);
201
282
  const baseDir = resolve(o.cwd, o.base);
202
283
  const id = runId(o.now, o.rand);
203
284
  const dir = join(baseDir, id);
204
285
  if (existsSync(dir)) throw new Error(`이미 있는 실행 폴더입니다: ${dir}`);
205
286
  mkdirSync(dir, { recursive: true });
206
- const meta = { schema: 1, id, createdAt: o.now.toISOString(), runtime: o.runtime, ...info };
287
+ const meta = { schema: 1, id, createdAt: o.now.toISOString(), runtime: o.runtime, ...agents, ...info };
207
288
  writeFileSync(join(dir, "run.json"), `${JSON.stringify(meta, null, 1)}\n`);
208
289
  writeFileSync(join(dir, "events.jsonl"), "");
209
290
  for (const f of RUN_FOLDERS) mkdirSync(join(dir, f), { recursive: true });
@@ -224,8 +305,9 @@ function needsNewline(file) {
224
305
  }
225
306
 
226
307
  export function appendEvent(dir, raw, now = new Date()) {
227
- readRun(dir);
308
+ const meta = readRun(dir);
228
309
  const event = { t: now.toISOString(), ...checkEvent(raw) };
310
+ checkQuorum(event, meta);
229
311
  const file = join(dir, "events.jsonl");
230
312
  appendFileSync(file, `${needsNewline(file) ? "\n" : ""}${JSON.stringify(event)}\n`);
231
313
  return event;
@@ -446,7 +528,7 @@ export function gatesToTests(text, round, dir, gateDir) {
446
528
 
447
529
  // ── command line ─────────────────────────────────────────────────────────────
448
530
 
449
- const FLAGS = new Set(["--base", "--runtime", "--stage", "--status", "--file", "--round", "--summary", "--reason", "--unverified", "--why"]);
531
+ const FLAGS = new Set(["--base", "--runtime", "--agents", "--stage", "--status", "--file", "--round", "--summary", "--reason", "--unverified", "--why"]);
450
532
 
451
533
  export function parseArgs(argv) {
452
534
  const o = { _: [] };
@@ -533,7 +615,7 @@ export function progressLine(e) {
533
615
  const withProgress = (e) => ({ ...e, progress: progressLine(e) });
534
616
 
535
617
  const COMMANDS = {
536
- init: (o) => initRun({ base: o.base, runtime: o.runtime ?? null, exclude: Boolean(o.exclude) }),
618
+ init: (o) => initRun({ base: o.base, runtime: o.runtime ?? null, agents: o.agents, exclude: Boolean(o.exclude) }),
537
619
  event: (o) => withProgress(appendEvent(runDir(o), eventFrom(o))),
538
620
  boxes: (o) => {
539
621
  const dir = runDir(o);
@@ -566,10 +648,10 @@ const COMMANDS = {
566
648
  // show is the first command of a resume, so it also re-makes the stage folders an older run may lack.
567
649
  show: (o) => {
568
650
  const dir = runDir(o);
569
- const { id } = readRun(dir);
651
+ const { id, agents = null, agentsSource = null } = readRun(dir);
570
652
  for (const f of RUN_FOLDERS) mkdirSync(join(dir, f), { recursive: true });
571
653
  const { events, bad } = readEvents(dir);
572
- return { id, dir, ...summarize(events), bad };
654
+ return { id, dir, agents, agentsSource, ...summarize(events), bad };
573
655
  },
574
656
  };
575
657
 
@@ -66,12 +66,19 @@ export function taskText(task) {
66
66
  return lines.length > 1 && lines[1].trim() && lines[1].trim() === lines[0].trim() ? [lines[0], ...lines.slice(2)].join("\n") : String(task);
67
67
  }
68
68
 
69
+ // How many reviewers each independent verdict used (--agents), and where the number came from.
70
+ export function agentsText(run) {
71
+ if (!Number.isInteger(run.agents)) return "기록 없음(옛 실행)";
72
+ if (run.agentsSource === "raised") return `${run.agents}(${run.agentsAsked ?? "?"} 에서 올림)`;
73
+ return `${run.agents}(${run.agentsSource === "flag" ? "--agents" : "기본"})`;
74
+ }
75
+
69
76
  function renderHeader(run, task, sum, generatedAt) {
70
77
  const total = sum.stages.reduce((a, s) => a + (s.ms ?? 0), 0);
71
78
  const current = sum.current ? `${esc(LABEL[sum.current])} 단계` : "모든 단계 끝";
72
79
  const facts = [
73
80
  ["실행", esc(run.id)], ["상태", `${badge(sum.overall)} ${current}`], ["시작", esc(when(run.createdAt))],
74
- ["단계 시간 합", esc(duration(total))], ["런타임", esc(run.runtime ?? "미상")],
81
+ ["단계 시간 합", esc(duration(total))], ["런타임", esc(run.runtime ?? "미상")], ["검토자 수", esc(agentsText(run))],
75
82
  ["기준 커밋", esc((run.baseCommit ?? "").slice(0, 12))], ["브랜치", esc(run.branch ?? "")], ["보고서 생성", esc(when(generatedAt))],
76
83
  ];
77
84
  const dl = facts.map(([k, v]) => `<div><dt>${esc(k)}</dt><dd>${v}</dd></div>`).join("");
@@ -123,12 +130,16 @@ function renderShot(p, caption, ctx) {
123
130
  return `<figure class="shot"><a href="${esc(h)}"><img src="${esc(h)}" alt="${esc(caption)}" loading="lazy"></a><figcaption>${esc(caption)}</figcaption></figure>`;
124
131
  }
125
132
 
133
+ const judgesTable = (judges) => (judges?.length
134
+ ? table(["판정자", "점수", "판정"], judges.map((j) => [td(esc(j.id)), td(esc(j.score ?? "")), td(badge(j.verdict))])) : "");
135
+
126
136
  function renderVisual(events, ctx) {
127
137
  const cards = latestVisual(events).map((v) => {
128
138
  const score = v.score === undefined ? "" : ` ${v.score}점`;
129
139
  const intended = v.intended ? ' <span class="s s-note">의도한 차이</span>' : "";
130
140
  return `<article class="card"><h3>${esc(v.name)}${esc(score)} ${v.verdict ? badge(v.verdict) : ""}${intended}</h3>`
131
- + `<div class="pair">${renderShot(v.mock, "목업", ctx)}${renderShot(v.actual, "실제", ctx)}</div>${v.note ? `<p>${esc(v.note)}</p>` : ""}</article>`;
141
+ + `<div class="pair">${renderShot(v.mock, "목업", ctx)}${renderShot(v.actual, "실제", ctx)}</div>${judgesTable(v.judges)}`
142
+ + `${v.note ? `<p>${esc(v.note)}</p>` : ""}</article>`;
132
143
  });
133
144
  return `<section><h2>시각 검토</h2>${cards.length ? cards.join("\n") : '<p class="none">없음</p>'}</section>`;
134
145
  }