claude-code-jsonl-generator 0.0.0 → 0.1.0-beta.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tomachi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # claude-code-jsonl-generator
2
+
3
+ 実際の Claude Code JSONL コーパスをスキャンし、`claude-code-jsonl-parser` の型定義・型ガードを生成する CLI / ライブラリ。
4
+
5
+ ## ✨ Features
6
+
7
+ - **実コーパス駆動** — 手書きのスキーマではなく、実際の `.jsonl` ファイル群をスキャンしてフィールド形状を推論する
8
+ - **型定義 + 型ガードの同時生成** — TypeScript の `interface` と、対応する実行時ガード関数を 1 回のスキャンで生成
9
+ - **CLI 1 本で完結** — `--corpus` と `--out` を指定するだけ
10
+ - **プログラムからの利用も可能** — スキャナ・推論エンジン・emitter を個別に import できる
11
+
12
+ ## 📦 インストール
13
+
14
+ ```bash
15
+ npm install --save-dev claude-code-jsonl-generator
16
+ ```
17
+
18
+ ## 🚀 CLI の使い方
19
+
20
+ ```bash
21
+ npx claude-code-jsonl-generator --corpus /path/to/jsonl-corpus --out packages/parser/src/generated
22
+ ```
23
+
24
+ - `--corpus <dir>`: `.jsonl` ファイルを再帰的にスキャンする対象ディレクトリ
25
+ - `--out <dir>`: 生成物 (`types.generated.ts` / `guards.generated.ts`) の出力先
26
+
27
+ 実行すると標準エラー出力にスキャン件数のサマリー (総行数・検出した type 数・構文エラー行数) が表示される。
28
+ content ブロックのガードなど、手動でキュレーションしている部分は上書きされない。
29
+
30
+ 生成物は必ずレビューしてからコミットすること。本リポジトリでは
31
+ `scripts/check-generated-sync.mjs` で `claude-code-jsonl-parser` にコミット済みの
32
+ 生成物と再生成結果が一致しているかを CI で検証している。
33
+
34
+ ## 📖 プログラムからの利用 (上級者向け)
35
+
36
+ ```ts
37
+ import { scanCorpus, inferShape, emitInterface, emitGuardFunction } from 'claude-code-jsonl-generator'
38
+
39
+ const scan = await scanCorpus('/path/to/jsonl-corpus')
40
+ for (const [type, samples] of scan.samplesByType) {
41
+ const shape = inferShape(samples)
42
+ console.log(emitInterface(`${type}Entry`, type, shape))
43
+ console.log(emitGuardFunction(`is${type}`, type, shape))
44
+ }
45
+ ```
46
+
47
+ | エクスポート | 用途 |
48
+ | --- | --- |
49
+ | `scanCorpus(dir)` | ディレクトリを再帰的にスキャンし、`type` ごとのサンプルを収集 |
50
+ | `inferShape(samples)` | サンプル群からフィールド形状 (`Shape`) を推論 |
51
+ | `entrySchemas` / `knownTypeSchema` / `KNOWN_TYPES` | 既知 type の zod スキーマ (ビルド時検証用) |
52
+ | `emitInterface(name, type, shape)` | 推論した形状から TypeScript `interface` のソースを生成 |
53
+ | `emitGuardFunction(name, type, shape)` | 推論した形状から実行時ガード関数のソースを生成 |
54
+
55
+ ## 📑 ライセンス
56
+
57
+ このプロジェクトは [MIT License](./LICENSE) の下で公開されている。
package/dist/cli.cjs ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ const require_emit_guards = require("./emit-guards-DWi5YDuA.cjs");
3
+ let node_fs_promises = require("node:fs/promises");
4
+ let node_path = require("node:path");
5
+ node_path = require_emit_guards.__toESM(node_path, 1);
6
+ //#region src/cli.ts
7
+ /**
8
+ * 引数を解釈する。`--corpus <dir> --out <parser/src/generated>`。
9
+ *
10
+ * @param argv - process.argv.slice(2)
11
+ */
12
+ function parseArgs(argv) {
13
+ let corpus = "";
14
+ let out = "";
15
+ for (let i = 0; i < argv.length; i += 1) if (argv[i] === "--corpus") corpus = argv[i + 1] ?? "";
16
+ else if (argv[i] === "--out") out = argv[i + 1] ?? "";
17
+ if (!corpus || !out) {
18
+ console.error("Usage: claude-code-jsonl-generator --corpus <dir> --out <parser/src/generated>");
19
+ process.exit(2);
20
+ }
21
+ return {
22
+ corpus,
23
+ out
24
+ };
25
+ }
26
+ async function main() {
27
+ const { corpus, out } = parseArgs(process.argv.slice(2));
28
+ const scan = await require_emit_guards.scanCorpus(corpus);
29
+ console.error(`scanned: ${scan.totalLines} lines, ${scan.samplesByType.size} types, ${scan.malformedLines} malformed`);
30
+ const typeBlocks = [];
31
+ const guardBlocks = [];
32
+ const registry = [];
33
+ for (const [type, samples] of [...scan.samplesByType].toSorted()) {
34
+ const shape = require_emit_guards.inferShape(samples);
35
+ const name = `${require_emit_guards.pascalCase(type)}Entry`;
36
+ const fn = `is${require_emit_guards.pascalCase(type)}`;
37
+ typeBlocks.push(require_emit_guards.emitInterface(name, type, shape));
38
+ guardBlocks.push(require_emit_guards.emitGuardFunction(fn, type, shape));
39
+ registry.push(` '${type}': ${fn},`);
40
+ }
41
+ const typesOut = `/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\n\n` + typeBlocks.join("\n\n") + "\n";
42
+ const guardsHeader = "/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v)\n}\nfunction isString(v: unknown): v is string { return typeof v === 'string' }\nfunction isNumber(v: unknown): v is number { return typeof v === 'number' }\nfunction isBoolean(v: unknown): v is boolean { return typeof v === 'boolean' }\n\n";
43
+ const guardsRegistry = `\n\nexport const guards = {\n${registry.join("\n")}\n} as const satisfies Record<string, (v: unknown) => boolean>\nexport const KNOWN_TYPES = Object.keys(guards) as (keyof typeof guards)[]\nexport function guardByType(type: string, v: unknown): boolean | undefined {\n const g = (guards as Record<string, (x: unknown) => boolean>)[type]\n return g === undefined ? undefined : g(v)\n}\n`;
44
+ const guardsOut = guardsHeader + guardBlocks.join("\n\n") + guardsRegistry;
45
+ await (0, node_fs_promises.mkdir)(out, { recursive: true });
46
+ await (0, node_fs_promises.writeFile)(node_path.default.join(out, "types.generated.ts"), typesOut, "utf8");
47
+ await (0, node_fs_promises.writeFile)(node_path.default.join(out, "guards.generated.ts"), guardsOut, "utf8");
48
+ console.error(`written: ${node_path.default.join(out, "types.generated.ts")}, ${node_path.default.join(out, "guards.generated.ts")}`);
49
+ console.error("NOTE: review the diff before committing. content-block guards / message shapes are curated manually and are not overwritten by this generator.");
50
+ }
51
+ main().catch((error) => {
52
+ console.error(error);
53
+ process.exit(1);
54
+ });
55
+ //#endregion
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { a as inferShape, n as emitInterface, r as pascalCase, s as scanCorpus, t as emitGuardFunction } from "./emit-guards-CSDZc-iy.js";
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ //#region src/cli.ts
6
+ /**
7
+ * 引数を解釈する。`--corpus <dir> --out <parser/src/generated>`。
8
+ *
9
+ * @param argv - process.argv.slice(2)
10
+ */
11
+ function parseArgs(argv) {
12
+ let corpus = "";
13
+ let out = "";
14
+ for (let i = 0; i < argv.length; i += 1) if (argv[i] === "--corpus") corpus = argv[i + 1] ?? "";
15
+ else if (argv[i] === "--out") out = argv[i + 1] ?? "";
16
+ if (!corpus || !out) {
17
+ console.error("Usage: claude-code-jsonl-generator --corpus <dir> --out <parser/src/generated>");
18
+ process.exit(2);
19
+ }
20
+ return {
21
+ corpus,
22
+ out
23
+ };
24
+ }
25
+ async function main() {
26
+ const { corpus, out } = parseArgs(process.argv.slice(2));
27
+ const scan = await scanCorpus(corpus);
28
+ console.error(`scanned: ${scan.totalLines} lines, ${scan.samplesByType.size} types, ${scan.malformedLines} malformed`);
29
+ const typeBlocks = [];
30
+ const guardBlocks = [];
31
+ const registry = [];
32
+ for (const [type, samples] of [...scan.samplesByType].toSorted()) {
33
+ const shape = inferShape(samples);
34
+ const name = `${pascalCase(type)}Entry`;
35
+ const fn = `is${pascalCase(type)}`;
36
+ typeBlocks.push(emitInterface(name, type, shape));
37
+ guardBlocks.push(emitGuardFunction(fn, type, shape));
38
+ registry.push(` '${type}': ${fn},`);
39
+ }
40
+ const typesOut = `/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\n\n` + typeBlocks.join("\n\n") + "\n";
41
+ const guardsHeader = "/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v)\n}\nfunction isString(v: unknown): v is string { return typeof v === 'string' }\nfunction isNumber(v: unknown): v is number { return typeof v === 'number' }\nfunction isBoolean(v: unknown): v is boolean { return typeof v === 'boolean' }\n\n";
42
+ const guardsRegistry = `\n\nexport const guards = {\n${registry.join("\n")}\n} as const satisfies Record<string, (v: unknown) => boolean>\nexport const KNOWN_TYPES = Object.keys(guards) as (keyof typeof guards)[]\nexport function guardByType(type: string, v: unknown): boolean | undefined {\n const g = (guards as Record<string, (x: unknown) => boolean>)[type]\n return g === undefined ? undefined : g(v)\n}\n`;
43
+ const guardsOut = guardsHeader + guardBlocks.join("\n\n") + guardsRegistry;
44
+ await mkdir(out, { recursive: true });
45
+ await writeFile(path.join(out, "types.generated.ts"), typesOut, "utf8");
46
+ await writeFile(path.join(out, "guards.generated.ts"), guardsOut, "utf8");
47
+ console.error(`written: ${path.join(out, "types.generated.ts")}, ${path.join(out, "guards.generated.ts")}`);
48
+ console.error("NOTE: review the diff before committing. content-block guards / message shapes are curated manually and are not overwritten by this generator.");
49
+ }
50
+ main().catch((error) => {
51
+ console.error(error);
52
+ process.exit(1);
53
+ });
54
+ //#endregion
55
+ export {};
56
+
57
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { writeFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { scanCorpus } from './scanner'\nimport { inferShape } from './infer'\nimport { emitInterface, pascalCase } from './emit-types'\nimport { emitGuardFunction } from './emit-guards'\n\n/**\n * 引数を解釈する。`--corpus <dir> --out <parser/src/generated>`。\n *\n * @param argv - process.argv.slice(2)\n */\nfunction parseArgs(argv: string[]): { corpus: string; out: string } {\n let corpus = ''\n let out = ''\n for (let i = 0; i < argv.length; i += 1) {\n if (argv[i] === '--corpus') corpus = argv[i + 1] ?? ''\n else if (argv[i] === '--out') out = argv[i + 1] ?? ''\n }\n if (!corpus || !out) {\n console.error('Usage: claude-code-jsonl-generator --corpus <dir> --out <parser/src/generated>')\n process.exit(2)\n }\n return { corpus, out }\n}\n\nasync function main(): Promise<void> {\n const { corpus, out } = parseArgs(process.argv.slice(2))\n const scan = await scanCorpus(corpus)\n console.error(`scanned: ${scan.totalLines} lines, ${scan.samplesByType.size} types, ${scan.malformedLines} malformed`)\n\n const typeBlocks: string[] = []\n const guardBlocks: string[] = []\n const registry: string[] = []\n for (const [type, samples] of [...scan.samplesByType].toSorted()) {\n const shape = inferShape(samples)\n const name = `${pascalCase(type)}Entry`\n const fn = `is${pascalCase(type)}`\n typeBlocks.push(emitInterface(name, type, shape))\n guardBlocks.push(emitGuardFunction(fn, type, shape))\n registry.push(` '${type}': ${fn},`)\n }\n\n const typesHeader = `/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\\n\\n`\n const typesOut = typesHeader + typeBlocks.join('\\n\\n') + '\\n'\n\n const guardsHeader =\n `/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\\n\\n` +\n `function isObject(v: unknown): v is Record<string, unknown> {\\n` +\n ` return typeof v === 'object' && v !== null && !Array.isArray(v)\\n}\\n` +\n `function isString(v: unknown): v is string { return typeof v === 'string' }\\n` +\n `function isNumber(v: unknown): v is number { return typeof v === 'number' }\\n` +\n `function isBoolean(v: unknown): v is boolean { return typeof v === 'boolean' }\\n\\n`\n const guardsRegistry =\n `\\n\\nexport const guards = {\\n${registry.join('\\n')}\\n} as const satisfies Record<string, (v: unknown) => boolean>\\n` +\n `export const KNOWN_TYPES = Object.keys(guards) as (keyof typeof guards)[]\\n` +\n `export function guardByType(type: string, v: unknown): boolean | undefined {\\n` +\n ` const g = (guards as Record<string, (x: unknown) => boolean>)[type]\\n` +\n ` return g === undefined ? undefined : g(v)\\n}\\n`\n const guardsOut = guardsHeader + guardBlocks.join('\\n\\n') + guardsRegistry\n\n await mkdir(out, { recursive: true })\n await writeFile(path.join(out,'types.generated.ts'), typesOut, 'utf8')\n await writeFile(path.join(out,'guards.generated.ts'), guardsOut, 'utf8')\n console.error(`written: ${path.join(out,'types.generated.ts')}, ${path.join(out,'guards.generated.ts')}`)\n console.error('NOTE: review the diff before committing. content-block guards / message shapes are curated manually and are not overwritten by this generator.')\n}\n\nmain().catch((error: unknown) => {\n console.error(error)\n process.exit(1)\n})\n"],"mappings":";;;;;;;;;;AAaA,SAAS,UAAU,MAAiD;CAClE,IAAI,SAAS;CACb,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GACpC,IAAI,KAAK,OAAO,YAAY,SAAS,KAAK,IAAI,MAAM;MAC/C,IAAI,KAAK,OAAO,SAAS,MAAM,KAAK,IAAI,MAAM;CAErD,IAAI,CAAC,UAAU,CAAC,KAAK;EACnB,QAAQ,MAAM,gFAAgF;EAC9F,QAAQ,KAAK,CAAC;CAChB;CACA,OAAO;EAAE;EAAQ;CAAI;AACvB;AAEA,eAAe,OAAsB;CACnC,MAAM,EAAE,QAAQ,QAAQ,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;CACvD,MAAM,OAAO,MAAM,WAAW,MAAM;CACpC,QAAQ,MAAM,YAAY,KAAK,WAAW,UAAU,KAAK,cAAc,KAAK,UAAU,KAAK,eAAe,WAAW;CAErH,MAAM,aAAuB,CAAC;CAC9B,MAAM,cAAwB,CAAC;CAC/B,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,CAAC,MAAM,YAAY,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC,SAAS,GAAG;EAChE,MAAM,QAAQ,WAAW,OAAO;EAChC,MAAM,OAAO,GAAG,WAAW,IAAI,EAAE;EACjC,MAAM,KAAK,KAAK,WAAW,IAAI;EAC/B,WAAW,KAAK,cAAc,MAAM,MAAM,KAAK,CAAC;EAChD,YAAY,KAAK,kBAAkB,IAAI,MAAM,KAAK,CAAC;EACnD,SAAS,KAAK,MAAM,KAAK,KAAK,GAAG,EAAE;CACrC;CAGA,MAAM,WAAW,8EAAc,WAAW,KAAK,MAAM,IAAI;CAEzD,MAAM,eACJ;CAMF,MAAM,iBACJ,gCAAgC,SAAS,KAAK,IAAI,EAAE;CAKtD,MAAM,YAAY,eAAe,YAAY,KAAK,MAAM,IAAI;CAE5D,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;CACpC,MAAM,UAAU,KAAK,KAAK,KAAI,oBAAoB,GAAG,UAAU,MAAM;CACrE,MAAM,UAAU,KAAK,KAAK,KAAI,qBAAqB,GAAG,WAAW,MAAM;CACvE,QAAQ,MAAM,YAAY,KAAK,KAAK,KAAI,oBAAoB,EAAE,IAAI,KAAK,KAAK,KAAI,qBAAqB,GAAG;CACxG,QAAQ,MAAM,gJAAgJ;AAChK;AAEA,KAAK,CAAC,CAAC,OAAO,UAAmB;CAC/B,QAAQ,MAAM,KAAK;CACnB,QAAQ,KAAK,CAAC;AAChB,CAAC"}
@@ -0,0 +1,244 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { createReadStream } from "node:fs";
3
+ import { createInterface } from "node:readline";
4
+ import path from "node:path";
5
+ //#region src/scanner.ts
6
+ /**
7
+ * `.jsonl` ファイルを再帰収集する。
8
+ *
9
+ * @param dir - 起点ディレクトリ
10
+ * @returns 絶対パス配列
11
+ */
12
+ async function collectJsonlFiles(dir) {
13
+ const out = [];
14
+ for (const entry of await readdir(dir)) {
15
+ const full = path.join(dir, entry);
16
+ if ((await stat(full)).isDirectory()) out.push(...await collectJsonlFiles(full));
17
+ else if (entry.endsWith(".jsonl")) out.push(full);
18
+ }
19
+ return out;
20
+ }
21
+ /**
22
+ * コーパスを走査し type ごとに生サンプルを集約する。
23
+ *
24
+ * @param corpusDir - コーパスのルートディレクトリ
25
+ * @param maxSamplesPerType - type ごとの保持上限 (メモリ保護、既定 5000)
26
+ */
27
+ async function scanCorpus(corpusDir, maxSamplesPerType = 5e3) {
28
+ const samplesByType = /* @__PURE__ */ new Map();
29
+ let totalLines = 0;
30
+ let malformedLines = 0;
31
+ let typelessLines = 0;
32
+ for (const file of await collectJsonlFiles(corpusDir)) {
33
+ const rl = createInterface({
34
+ input: createReadStream(file, { encoding: "utf8" }),
35
+ crlfDelay: Infinity
36
+ });
37
+ for await (const raw of rl) {
38
+ if (raw.trim() === "") continue;
39
+ let obj;
40
+ try {
41
+ obj = JSON.parse(raw);
42
+ } catch {
43
+ malformedLines += 1;
44
+ continue;
45
+ }
46
+ totalLines += 1;
47
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj) || typeof obj.type !== "string") {
48
+ typelessLines += 1;
49
+ continue;
50
+ }
51
+ const record = obj;
52
+ const type = record.type;
53
+ const bucket = samplesByType.get(type) ?? [];
54
+ if (bucket.length < maxSamplesPerType) {
55
+ bucket.push(record);
56
+ samplesByType.set(type, bucket);
57
+ }
58
+ }
59
+ }
60
+ return {
61
+ samplesByType,
62
+ totalLines,
63
+ malformedLines,
64
+ typelessLines
65
+ };
66
+ }
67
+ //#endregion
68
+ //#region src/infer.ts
69
+ /**
70
+ * JSON 値の種別を返す。
71
+ *
72
+ * @param v - 対象値
73
+ */
74
+ function jsonKind(v) {
75
+ if (v === null) return "null";
76
+ if (Array.isArray(v)) return "array";
77
+ const t = typeof v;
78
+ if ([
79
+ "string",
80
+ "number",
81
+ "boolean"
82
+ ].includes(t)) return t;
83
+ return "object";
84
+ }
85
+ /** `InferOptions.maxLiterals` 省略時の既定値。 */
86
+ const DEFAULT_MAX_LITERALS = 20;
87
+ /**
88
+ * 同一 type のサンプル群からフィールド形状を推論する。
89
+ *
90
+ * @param samples - 生 JSON オブジェクト配列
91
+ * @param options - 推論オプション
92
+ */
93
+ function inferShape(samples, options = {}) {
94
+ const maxLiterals = options.maxLiterals ?? DEFAULT_MAX_LITERALS;
95
+ const count = samples.length;
96
+ const presence = /* @__PURE__ */ new Map();
97
+ const kinds = /* @__PURE__ */ new Map();
98
+ const literals = /* @__PURE__ */ new Map();
99
+ for (const sample of samples) for (const [key, value] of Object.entries(sample)) {
100
+ presence.set(key, (presence.get(key) ?? 0) + 1);
101
+ const kset = kinds.get(key) ?? /* @__PURE__ */ new Set();
102
+ const kind = jsonKind(value);
103
+ kset.add(kind);
104
+ kinds.set(key, kset);
105
+ const cur = literals.get(key);
106
+ if (cur === null) {} else if (kind === "string") {
107
+ const set = cur ?? /* @__PURE__ */ new Set();
108
+ set.add(value);
109
+ if (set.size > maxLiterals) literals.set(key, null);
110
+ else literals.set(key, set);
111
+ }
112
+ }
113
+ const fields = /* @__PURE__ */ new Map();
114
+ for (const [key, seen] of presence) {
115
+ const lit = literals.get(key);
116
+ const kindSet = kinds.get(key) ?? /* @__PURE__ */ new Set();
117
+ const allString = kindSet.size === 1 && kindSet.has("string");
118
+ fields.set(key, {
119
+ required: seen === count,
120
+ kinds: kindSet,
121
+ literals: allString && lit ? lit : void 0
122
+ });
123
+ }
124
+ return {
125
+ count,
126
+ fields
127
+ };
128
+ }
129
+ //#endregion
130
+ //#region src/emit-types.ts
131
+ /** JS 識別子として安全な名前かどうかを判定する正規表現。 */
132
+ const IDENTIFIER_RE$1 = /^[A-Za-z_$][\w$]*$/;
133
+ /** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */
134
+ function escapeSingleQuote$1(s) {
135
+ return s.replaceAll("'", String.raw`\'`);
136
+ }
137
+ /**
138
+ * 観測種別集合 (と任意のリテラル集合) を TypeScript 型文字列に写像する。
139
+ *
140
+ * @param kinds - 観測された JSON 種別
141
+ * @param literals - 文字列リテラル集合 (あれば)
142
+ */
143
+ function tsTypeOf(kinds, literals) {
144
+ const parts = [];
145
+ const mapped = {
146
+ string: "string",
147
+ number: "number",
148
+ boolean: "boolean",
149
+ null: "null",
150
+ array: "unknown[]",
151
+ object: "Record<string, unknown>"
152
+ };
153
+ if (literals && literals.size > 0 && kinds.has("string")) {
154
+ const lits = [...literals].toSorted().map((s) => `'${escapeSingleQuote$1(s)}'`);
155
+ parts.push(`${lits.join(" | ")} | (string & {})`);
156
+ } else if (kinds.has("string")) parts.push("string");
157
+ for (const k of [
158
+ "number",
159
+ "boolean",
160
+ "array",
161
+ "object",
162
+ "null"
163
+ ]) if (kinds.has(k)) parts.push(mapped[k]);
164
+ return [...new Set(parts)].join(" | ");
165
+ }
166
+ /** JS 識別子として安全ならそのまま、そうでなければクォートする。 */
167
+ function propKey(name) {
168
+ return IDENTIFIER_RE$1.test(name) ? name : `'${escapeSingleQuote$1(name)}'`;
169
+ }
170
+ /** interface 名として使うため type 文字列を PascalCase 化する (エミッタ内部用)。 */
171
+ function pascalCase(type) {
172
+ return type.split(/[-_]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
173
+ }
174
+ /**
175
+ * 1 type の interface 文字列を生成する。
176
+ *
177
+ * @param interfaceName - 出力する interface 名 (例 LastPromptEntry)
178
+ * @param type - 判別子文字列 (例 last-prompt)
179
+ * @param shape - 推論結果
180
+ */
181
+ function emitInterface(interfaceName, type, shape) {
182
+ const lines = [
183
+ `export interface ${interfaceName} {`,
184
+ ` _kind: 'known'`,
185
+ ` type: '${escapeSingleQuote$1(type)}'`
186
+ ];
187
+ for (const [name, field] of shape.fields) {
188
+ if (name === "type") continue;
189
+ const opt = field.required ? "" : "?";
190
+ const tsType = tsTypeOf(field.kinds, field.literals);
191
+ lines.push(` ${propKey(name)}${opt}: ${tsType}`);
192
+ }
193
+ lines.push("}");
194
+ return lines.join("\n");
195
+ }
196
+ //#endregion
197
+ //#region src/emit-guards.ts
198
+ /** JS 識別子として安全な名前かどうかを判定する正規表現。 */
199
+ const IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/;
200
+ /** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */
201
+ function escapeSingleQuote(s) {
202
+ return s.replaceAll("'", String.raw`\'`);
203
+ }
204
+ /** 単一種別の required フィールドに対する検査式を返す。 */
205
+ function checkExpr(name, kinds) {
206
+ const access = `v.${IDENTIFIER_RE.test(name) ? name : `['${escapeSingleQuote(name)}']`}`;
207
+ const hasNull = kinds.has("null");
208
+ const nonNull = [...kinds].filter((k) => k !== "null");
209
+ if (nonNull.length !== 1) return `'${escapeSingleQuote(name)}' in v`;
210
+ const kind = nonNull[0];
211
+ const expr = {
212
+ string: `isString(${access})`,
213
+ number: `isNumber(${access})`,
214
+ boolean: `isBoolean(${access})`,
215
+ object: `isObject(${access})`,
216
+ array: `Array.isArray(${access})`
217
+ }[kind];
218
+ if (expr === void 0) return `'${escapeSingleQuote(name)}' in v`;
219
+ return hasNull ? `(${access} === null || ${expr})` : expr;
220
+ }
221
+ /**
222
+ * 1 type のガード関数文字列を生成する。required フィールドのみ検査する。
223
+ *
224
+ * @param fnName - 関数名 (例 isMode)
225
+ * @param type - 判別子
226
+ * @param shape - 推論結果
227
+ */
228
+ function emitGuardFunction(fnName, type, shape) {
229
+ const checks = [`isObject(v)`, `v.type === '${escapeSingleQuote(type)}'`];
230
+ for (const [name, field] of shape.fields) {
231
+ if (name === "type" || !field.required) continue;
232
+ const expr = checkExpr(name, field.kinds);
233
+ if (expr) checks.push(expr);
234
+ }
235
+ return [
236
+ `function ${fnName}(v: unknown): boolean {`,
237
+ ` return ${checks.join(" &&\n ")}`,
238
+ `}`
239
+ ].join("\n");
240
+ }
241
+ //#endregion
242
+ export { inferShape as a, tsTypeOf as i, emitInterface as n, jsonKind as o, pascalCase as r, scanCorpus as s, emitGuardFunction as t };
243
+
244
+ //# sourceMappingURL=emit-guards-CSDZc-iy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit-guards-CSDZc-iy.js","names":["IDENTIFIER_RE","escapeSingleQuote"],"sources":["../src/scanner.ts","../src/infer.ts","../src/emit-types.ts","../src/emit-guards.ts"],"sourcesContent":["import { readdir, stat } from 'node:fs/promises'\nimport { createReadStream } from 'node:fs'\nimport { createInterface } from 'node:readline'\nimport path from 'node:path'\n\n/** スキャン結果。 */\nexport interface ScanResult {\n /** type 文字列 → その type の生 JSON オブジェクト配列。 */\n samplesByType: Map<string, Record<string, unknown>[]>\n /** 走査した有効行数 (パース成功)。 */\n totalLines: number\n /** JSON 構文エラー行数。 */\n malformedLines: number\n /** type フィールドを持たない/文字列でない行数。 */\n typelessLines: number\n}\n\n/**\n * `.jsonl` ファイルを再帰収集する。\n *\n * @param dir - 起点ディレクトリ\n * @returns 絶対パス配列\n */\nasync function collectJsonlFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n for (const entry of await readdir(dir)) {\n const full = path.join(dir, entry)\n const st = await stat(full)\n if (st.isDirectory()) {\n out.push(...(await collectJsonlFiles(full)))\n } else if (entry.endsWith('.jsonl')) {\n out.push(full)\n }\n }\n return out\n}\n\n/**\n * コーパスを走査し type ごとに生サンプルを集約する。\n *\n * @param corpusDir - コーパスのルートディレクトリ\n * @param maxSamplesPerType - type ごとの保持上限 (メモリ保護、既定 5000)\n */\nexport async function scanCorpus(\n corpusDir: string,\n maxSamplesPerType = 5000\n): Promise<ScanResult> {\n const samplesByType = new Map<string, Record<string, unknown>[]>()\n let totalLines = 0\n let malformedLines = 0\n let typelessLines = 0\n\n for (const file of await collectJsonlFiles(corpusDir)) {\n const rl = createInterface({\n input: createReadStream(file, { encoding: 'utf8' }),\n crlfDelay: Infinity,\n })\n for await (const raw of rl) {\n if (raw.trim() === '') continue\n let obj: unknown\n try {\n obj = JSON.parse(raw)\n } catch {\n malformedLines += 1\n continue\n }\n totalLines += 1\n if (\n typeof obj !== 'object' || obj === null || Array.isArray(obj) ||\n typeof (obj as Record<string, unknown>).type !== 'string'\n ) {\n typelessLines += 1\n continue\n }\n const record = obj as Record<string, unknown>\n const type = record.type as string\n const bucket = samplesByType.get(type) ?? []\n if (bucket.length < maxSamplesPerType) {\n bucket.push(record)\n samplesByType.set(type, bucket)\n }\n }\n }\n\n return { samplesByType, totalLines, malformedLines, typelessLines }\n}\n","/** JSON 値の種別。 */\nexport type JsonKind = 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object'\n\n/**\n * JSON 値の種別を返す。\n *\n * @param v - 対象値\n */\nexport function jsonKind(v: unknown): JsonKind {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n const t = typeof v\n if ((['string', 'number', 'boolean'] as string[]).includes(t)) {\n return t as 'string' | 'number' | 'boolean'\n }\n return 'object'\n}\n\n/** 1 フィールドの推論結果。 */\nexport interface FieldShape {\n /** 全サンプルに存在したか。 */\n required: boolean\n /** 観測された JSON 種別集合。 */\n kinds: Set<JsonKind>\n /** 観測された文字列リテラル集合 (閾値以下のときのみ)。 */\n literals?: Set<string>\n}\n\n/** 1 type (または content ブロック) の推論結果。 */\nexport interface Shape {\n /** サンプル総数。 */\n count: number\n /** フィールド名 → 推論結果。 */\n fields: Map<string, FieldShape>\n}\n\n/** `InferOptions.maxLiterals` 省略時の既定値。 */\nconst DEFAULT_MAX_LITERALS = 20\n\n/** 推論オプション。 */\nexport interface InferOptions {\n /** 文字列リテラルを列挙として保持する上限。超えたら literals を破棄。既定は `DEFAULT_MAX_LITERALS`。 */\n maxLiterals?: number\n}\n\n/**\n * 同一 type のサンプル群からフィールド形状を推論する。\n *\n * @param samples - 生 JSON オブジェクト配列\n * @param options - 推論オプション\n */\nexport function inferShape(\n samples: Record<string, unknown>[],\n options: InferOptions = {}\n): Shape {\n const maxLiterals = options.maxLiterals ?? DEFAULT_MAX_LITERALS\n const count = samples.length\n const presence = new Map<string, number>()\n const kinds = new Map<string, Set<JsonKind>>()\n const literals = new Map<string, Set<string> | null>()\n\n for (const sample of samples) {\n for (const [key, value] of Object.entries(sample)) {\n presence.set(key, (presence.get(key) ?? 0) + 1)\n\n const kset = kinds.get(key) ?? new Set<JsonKind>()\n const kind = jsonKind(value)\n kset.add(kind)\n kinds.set(key, kset)\n\n // 文字列リテラル収集 (null は「破棄済み」マーカー)\n const cur = literals.get(key)\n if (cur === null) {\n // すでに破棄済み\n } else if (kind === 'string') {\n const set = cur ?? new Set<string>()\n set.add(value as string)\n if (set.size > maxLiterals) {\n literals.set(key, null)\n } else {\n literals.set(key, set)\n }\n }\n }\n }\n\n const fields = new Map<string, FieldShape>()\n for (const [key, seen] of presence) {\n const lit = literals.get(key)\n const kindSet = kinds.get(key) ?? new Set<JsonKind>()\n // 全て文字列だった場合のみ literals を採用する\n const allString = kindSet.size === 1 && kindSet.has('string')\n fields.set(key, {\n required: seen === count,\n kinds: kindSet,\n literals: allString && lit ? lit : undefined,\n })\n }\n\n return { count, fields }\n}\n","import type { JsonKind, Shape } from './infer'\n\n/** JS 識別子として安全な名前かどうかを判定する正規表現。 */\nconst IDENTIFIER_RE = /^[A-Za-z_$][\\w$]*$/\n\n/** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */\nfunction escapeSingleQuote(s: string): string {\n return s.replaceAll('\\'', String.raw`\\'`)\n}\n\n/**\n * 観測種別集合 (と任意のリテラル集合) を TypeScript 型文字列に写像する。\n *\n * @param kinds - 観測された JSON 種別\n * @param literals - 文字列リテラル集合 (あれば)\n */\nexport function tsTypeOf(kinds: Set<JsonKind>, literals?: Set<string>): string {\n const parts: string[] = []\n const mapped: Record<JsonKind, string> = {\n string: 'string',\n number: 'number',\n boolean: 'boolean',\n null: 'null',\n array: 'unknown[]',\n object: 'Record<string, unknown>',\n }\n // 文字列リテラルが与えられていれば開いた union にする\n if (literals && literals.size > 0 && kinds.has('string')) {\n const lits = [...literals].toSorted().map((s) => `'${escapeSingleQuote(s)}'`)\n parts.push(`${lits.join(' | ')} | (string & {})`)\n } else if (kinds.has('string')) {\n parts.push('string')\n }\n for (const k of ['number', 'boolean', 'array', 'object', 'null'] as JsonKind[]) {\n if (kinds.has(k)) parts.push(mapped[k])\n }\n // 重複除去して結合\n return [...new Set(parts)].join(' | ')\n}\n\n/** JS 識別子として安全ならそのまま、そうでなければクォートする。 */\nfunction propKey(name: string): string {\n return IDENTIFIER_RE.test(name) ? name : `'${escapeSingleQuote(name)}'`\n}\n\n/** interface 名として使うため type 文字列を PascalCase 化する (エミッタ内部用)。 */\nexport function pascalCase(type: string): string {\n return type\n .split(/[-_]/)\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join('')\n}\n\n/**\n * 1 type の interface 文字列を生成する。\n *\n * @param interfaceName - 出力する interface 名 (例 LastPromptEntry)\n * @param type - 判別子文字列 (例 last-prompt)\n * @param shape - 推論結果\n */\nexport function emitInterface(\n interfaceName: string,\n type: string,\n shape: Shape\n): string {\n const lines: string[] = [\n `export interface ${interfaceName} {`,\n ` _kind: 'known'`,\n ` type: '${escapeSingleQuote(type)}'`,\n ]\n for (const [name, field] of shape.fields) {\n if (name === 'type') continue\n const opt = field.required ? '' : '?'\n const tsType = tsTypeOf(field.kinds, field.literals)\n lines.push(` ${propKey(name)}${opt}: ${tsType}`)\n }\n lines.push('}')\n return lines.join('\\n')\n}\n","import type { JsonKind, Shape } from './infer'\n\n/** JS 識別子として安全な名前かどうかを判定する正規表現。 */\nconst IDENTIFIER_RE = /^[A-Za-z_$][\\w$]*$/\n\n/** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */\nfunction escapeSingleQuote(s: string): string {\n return s.replaceAll('\\'', String.raw`\\'`)\n}\n\n/** 単一種別の required フィールドに対する検査式を返す。 */\nfunction checkExpr(name: string, kinds: Set<JsonKind>): string | undefined {\n const access = `v.${IDENTIFIER_RE.test(name) ? name : `['${escapeSingleQuote(name)}']`}`\n // object|null → isObject or null 許容\n const hasNull = kinds.has('null')\n const nonNull = [...kinds].filter((k) => k !== 'null')\n if (nonNull.length !== 1) {\n // 複数種別が混在する場合は存在確認のみ (過剰厳格を避ける)\n return `'${escapeSingleQuote(name)}' in v`\n }\n const kind = nonNull[0]\n const base: Partial<Record<JsonKind, string>> = {\n string: `isString(${access})`,\n number: `isNumber(${access})`,\n boolean: `isBoolean(${access})`,\n object: `isObject(${access})`,\n array: `Array.isArray(${access})`,\n }\n const expr = base[kind]\n if (expr === undefined) return `'${escapeSingleQuote(name)}' in v`\n return hasNull ? `(${access} === null || ${expr})` : expr\n}\n\n/**\n * 1 type のガード関数文字列を生成する。required フィールドのみ検査する。\n *\n * @param fnName - 関数名 (例 isMode)\n * @param type - 判別子\n * @param shape - 推論結果\n */\nexport function emitGuardFunction(\n fnName: string,\n type: string,\n shape: Shape\n): string {\n const checks: string[] = [\n `isObject(v)`,\n `v.type === '${escapeSingleQuote(type)}'`,\n ]\n for (const [name, field] of shape.fields) {\n if (name === 'type' || !field.required) continue\n const expr = checkExpr(name, field.kinds)\n if (expr) checks.push(expr)\n }\n return [\n `function ${fnName}(v: unknown): boolean {`,\n ` return ${checks.join(' &&\\n ')}`,\n `}`,\n ].join('\\n')\n}\n"],"mappings":"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"}