@kaydash9999/banker-plugins 0.16.3 → 0.17.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +57 -0
- package/README.md +14 -11
- package/bin/banker.js +2 -2
- package/codex/manifest.json +2 -1
- package/codex/transform-matrix.md +2 -1
- package/commands/setup.md +2 -2
- package/hooks/progress-plan.mjs +408 -20
- package/hooks/progress.mjs +202 -50
- package/hooks/register.mjs +7 -1
- package/package.json +1 -1
- package/skills/3d-intro-build/references/assemble.mjs +10 -2
- package/skills/3d-intro-build/references/curate.mjs +10 -2
- package/skills/3d-intro-build/references/preview-lib.mjs +2 -0
- package/skills/3d-intro-build/references/serve.mjs +10 -2
- package/skills/3d-intro-build/references/verify-intro.mjs +10 -2
- package/skills/motion-graphic-make/references/curate.mjs +10 -2
- package/skills/motion-graphic-make/references/preview-lib.mjs +2 -0
- package/skills/motion-graphic-make/references/serve.mjs +10 -2
- package/skills/obsidizer/obsidize.mjs +10 -2
- package/skills/omc-patch/SKILL.md +105 -22
- package/skills/omc-patch/scripts/omc-patch.mjs +609 -255
- package/skills/omc-patch/scripts/probe-pm.mjs +192 -0
- package/skills/ultra-chain/SKILL.md +475 -0
- package/skills/ultra-chain/references/capture.mjs +232 -0
- package/skills/ultra-chain/references/chain-ledger.mjs +491 -0
- package/skills/ultra-chain/references/chain-report.mjs +257 -0
- package/skills/ultra-chain/references/preview-lib.mjs +258 -0
- package/skills/ultra-chain/references/serve.mjs +139 -0
- package/skills/update-banker/SKILL.md +2 -1
- package/skills/vertical-pptx/verify_a4p.mjs +10 -2
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// project-memory probe for /banker:omc-patch. Runs one OMC plugin folder's project-memory-session.mjs the
|
|
3
|
+
// way Claude Code runs it at SessionStart, in a scratch project with its own home and state folder, then
|
|
4
|
+
// checks that the hook really wrote project-memory.json. A hook whose patched import died inside a block
|
|
5
|
+
// still exits 0 and prints {"continue":true}; only the file shows whether it did its work.
|
|
6
|
+
// The user's ~/.claude and the current project's .omc are not touched. Pure Node, no dependencies.
|
|
7
|
+
//
|
|
8
|
+
// node probe-pm.mjs --plugin <OMC plugin folder> [--scratch <new folder>] [--keep] [--timeout <seconds>]
|
|
9
|
+
//
|
|
10
|
+
// Without --scratch the probe works in a new temp folder and removes it at the end (--keep leaves it).
|
|
11
|
+
// A --scratch folder is never reused and is left for inspection.
|
|
12
|
+
// Prints PROBE GREEN or PROBE RED with the reason. Exit 0 green, 1 red, 2 usage (no hook script, the
|
|
13
|
+
// scratch folder already exists, a bad option).
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
15
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { dirname, join, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const HOOK = join("scripts", "project-memory-session.mjs");
|
|
21
|
+
const PACKAGE = { name: "omc-pm-probe", version: "0.0.0", scripts: { build: "node build.js", test: "node test.js" } };
|
|
22
|
+
const MAX_OUTPUT = 16 * 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
export class UsageError extends Error {}
|
|
25
|
+
|
|
26
|
+
export function parseArgs(argv) {
|
|
27
|
+
const o = { plugin: null, scratch: null, keep: false, timeout: 30 };
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
const a = argv[i];
|
|
30
|
+
if (a === "--keep") o.keep = true;
|
|
31
|
+
else if (["--plugin", "--scratch", "--timeout"].includes(a)) {
|
|
32
|
+
const v = argv[++i];
|
|
33
|
+
if (v === undefined || v.startsWith("--")) throw new UsageError(`${a} 뒤에 값이 필요합니다`);
|
|
34
|
+
o[a.slice(2)] = a === "--timeout" ? Number(v) : v;
|
|
35
|
+
} else throw new UsageError(`모르는 인자 ${a}`);
|
|
36
|
+
}
|
|
37
|
+
if (!o.plugin) throw new UsageError("--plugin <OMC 플러그인 폴더> 가 필요합니다");
|
|
38
|
+
if (!(o.timeout > 0)) throw new UsageError("--timeout 은 0 보다 큰 초입니다");
|
|
39
|
+
return o;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Variables of this session the hook must not see. GIT_* goes too: GIT_DIR and its kin would point git
|
|
43
|
+
// past the ceiling below.
|
|
44
|
+
const dropped = (u) => u.startsWith("CLAUDE_") || u.startsWith("OMC_") || u.startsWith("GIT_") || u === "DISABLE_OMC";
|
|
45
|
+
|
|
46
|
+
// The hook sees only what Claude Code would give it, pointed at the scratch tree: no CLAUDE_*, OMC_* or GIT_*
|
|
47
|
+
// variable of this session leaks in, and git stops at the scratch folder. Keys compare in upper case
|
|
48
|
+
// because Windows treats them so.
|
|
49
|
+
export function probeEnv(base, scratch, plugin) {
|
|
50
|
+
const env = {};
|
|
51
|
+
for (const [k, v] of Object.entries(base)) if (!dropped(k.toUpperCase())) env[k] = v;
|
|
52
|
+
const home = join(scratch, "home");
|
|
53
|
+
Object.assign(env, {
|
|
54
|
+
HOME: home, USERPROFILE: home, CLAUDE_CONFIG_DIR: join(home, ".claude"), CLAUDE_PLUGIN_ROOT: plugin,
|
|
55
|
+
OMC_STATE_DIR: join(scratch, "state"), OMC_NOTIFY: "0", GIT_CEILING_DIRECTORIES: scratch,
|
|
56
|
+
});
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeScratch(scratch) {
|
|
61
|
+
const project = join(scratch, "project");
|
|
62
|
+
for (const d of [project, join(scratch, "home", ".claude"), join(scratch, "state")]) mkdirSync(d, { recursive: true });
|
|
63
|
+
writeFileSync(join(project, "package.json"), JSON.stringify(PACKAGE));
|
|
64
|
+
return project;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Every project-memory.json under the scratch folder, wherever the hook chose to put it.
|
|
68
|
+
export function findMemoryFiles(dir, depth = 0) {
|
|
69
|
+
if (depth > 8) return [];
|
|
70
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
71
|
+
const p = join(dir, e.name);
|
|
72
|
+
if (e.isDirectory()) return findMemoryFiles(p, depth + 1);
|
|
73
|
+
return e.isFile() && e.name === "project-memory.json" ? [p] : [];
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const samePath = (a, b) => {
|
|
78
|
+
const norm = (p) => resolve(String(p)).replace(/[\\/]+$/, "");
|
|
79
|
+
return process.platform === "win32" ? norm(a).toLowerCase() === norm(b).toLowerCase() : norm(a) === norm(b);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const languagesOf = (mem) => (Array.isArray(mem.techStack?.languages) ? mem.techStack.languages : []);
|
|
83
|
+
|
|
84
|
+
// Each check: [passes(mem, project), what failed]. Checked in order; the first failure is the reason.
|
|
85
|
+
const MEMORY_CHECKS = [
|
|
86
|
+
[(mem, project) => samePath(mem.projectRoot, project), (mem) => `projectRoot 가 스크래치 프로젝트가 아님(${mem.projectRoot})`],
|
|
87
|
+
[(mem) => languagesOf(mem).length > 0, () => "techStack.languages 가 비어 있음"],
|
|
88
|
+
[(mem) => mem.build?.scripts?.build === PACKAGE.scripts.build, () => "build.scripts.build 가 package.json 과 다름"],
|
|
89
|
+
[(mem) => mem.lastScanned !== undefined && mem.lastScanned !== null, () => "lastScanned 가 없음"],
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// The first check that fails, or null when the memory describes the scratch project.
|
|
93
|
+
export function memoryProblem(mem, project) {
|
|
94
|
+
if (!mem || typeof mem !== "object") return "project-memory.json 을 JSON 으로 읽지 못함";
|
|
95
|
+
const failed = MEMORY_CHECKS.find(([passes]) => !passes(mem, project));
|
|
96
|
+
return failed ? failed[1](mem) : null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readJson(file) {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function verdict(run, files, project) {
|
|
108
|
+
if (run.error?.code === "ENOBUFS") return "PROBE RED: 훅 출력이 16MB 를 넘음";
|
|
109
|
+
if (run.error?.code === "ETIMEDOUT" || run.signal) return "PROBE RED: 훅이 시간 안에 끝나지 않음";
|
|
110
|
+
if (run.status !== 0) return `PROBE RED: 훅 종료 코드 ${run.status}`;
|
|
111
|
+
if (!files.length) return "PROBE RED: no project-memory.json (훅이 돌았지만 아무 일도 하지 않음)";
|
|
112
|
+
if (files.length > 1) return `PROBE RED: project-memory.json 이 ${files.length}개`;
|
|
113
|
+
const problem = memoryProblem(readJson(files[0]), project);
|
|
114
|
+
return problem ? `PROBE RED: ${problem}` : "PROBE GREEN: 스크래치 프로젝트의 project-memory.json 이 만들어짐";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function probe({ plugin, scratch, timeout }) {
|
|
118
|
+
const script = join(plugin, HOOK);
|
|
119
|
+
const project = makeScratch(scratch);
|
|
120
|
+
const payload = JSON.stringify({ session_id: "omc-patch-probe", transcript_path: join(scratch, "transcript.jsonl"),
|
|
121
|
+
cwd: project, hook_event_name: "SessionStart", source: "startup" });
|
|
122
|
+
const run = spawnSync(process.execPath, [script], { cwd: project, input: payload, env: probeEnv(process.env, scratch, plugin),
|
|
123
|
+
encoding: "utf8", timeout: Math.max(1, Math.ceil(timeout * 1000)), maxBuffer: MAX_OUTPUT, windowsHide: true });
|
|
124
|
+
const files = findMemoryFiles(scratch);
|
|
125
|
+
return { script, run, files, line: verdict(run, files, project) };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const one = (s) => String(s || "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
129
|
+
|
|
130
|
+
// The parsed options with the plugin folder resolved, or a UsageError for what cannot run.
|
|
131
|
+
function prepare(argv) {
|
|
132
|
+
const o = parseArgs(argv);
|
|
133
|
+
const plugin = resolve(o.plugin);
|
|
134
|
+
if (!existsSync(join(plugin, HOOK))) throw new UsageError(`훅 스크립트가 없습니다: ${join(plugin, HOOK)}`);
|
|
135
|
+
return { ...o, plugin };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// A --scratch folder is made here and must not exist yet. A plain mkdir fails on anything already at that
|
|
139
|
+
// path, a symlink included, so the probe never writes through a folder it did not create.
|
|
140
|
+
function claimScratch(dir) {
|
|
141
|
+
mkdirSync(dirname(dir), { recursive: true });
|
|
142
|
+
try {
|
|
143
|
+
mkdirSync(dir, { mode: 0o700 });
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (e.code === "EEXIST") throw new UsageError(`스크래치 폴더가 이미 있습니다(다시 쓰지 않음): ${dir}`);
|
|
146
|
+
throw e;
|
|
147
|
+
}
|
|
148
|
+
return dir;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function removeTemp(scratch, out) {
|
|
152
|
+
try {
|
|
153
|
+
rmSync(scratch, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 });
|
|
154
|
+
} catch (e) {
|
|
155
|
+
out.write(`probe-pm: 임시 폴더를 지우지 못함(${e.code || e.message}): ${scratch}\n`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const reportLines = (r, scratch) => [`node ${process.execPath}`, `script ${r.script}`, `scratch ${scratch}`,
|
|
160
|
+
`exit ${r.run.status}${r.run.signal ? ` (signal ${r.run.signal})` : ""}`, `stdout ${one(r.run.stdout)}`,
|
|
161
|
+
`stderr ${one(r.run.stderr)}`, `memfiles ${r.files.length}`, r.line].join("\n");
|
|
162
|
+
|
|
163
|
+
export function main(argv, out = process.stdout) {
|
|
164
|
+
let o;
|
|
165
|
+
let scratch;
|
|
166
|
+
try {
|
|
167
|
+
o = prepare(argv);
|
|
168
|
+
scratch = o.scratch ? claimScratch(resolve(o.scratch)) : mkdtempSync(join(tmpdir(), "omc-pm-probe-"));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
out.write(`probe-pm: ${e.message}\n`);
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const r = probe({ plugin: o.plugin, scratch, timeout: o.timeout });
|
|
175
|
+
out.write(`${reportLines(r, scratch)}\n`);
|
|
176
|
+
return r.line.startsWith("PROBE GREEN") ? 0 : 1;
|
|
177
|
+
} finally {
|
|
178
|
+
if (!o.scratch && !o.keep) removeTemp(scratch, out);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Run as a command, also when reached through a symlinked path: Node resolves
|
|
183
|
+
// symlinks for import.meta.url but not for process.argv[1].
|
|
184
|
+
const invokedDirectly = () => {
|
|
185
|
+
try {
|
|
186
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (invokedDirectly()) process.exitCode = main(process.argv.slice(2));
|