@cartanova/qgrid-cli 2.5.4 → 2.5.5

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.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * JSON Schema 하위 스키마 위치 목록 — qgrid 의 스키마 순회 3곳이 공유하는 단일 정본.
3
+ *
4
+ * - `tool-emulation-schema.rewriteSchemaNode`: 사용자 스키마의 local `$ref` 를 namespace 로 rebase
5
+ * - `strictifier.assertNormalizationCompatibility`: provider 표현 가능성 검사
6
+ * - `strictifier.strictifyNode`: strict 재작성
7
+ *
8
+ * 세 소비자가 각자 테이블을 들고 있으면 새 keyword 를 한쪽에만 추가하는 순간 조용히 어긋난다.
9
+ * 재작성 패스가 훑지 않는 위치의 `$ref` 는 rebase 되지 않은 채 남고, 검사 패스가 놓친 위치의
10
+ * tuple 은 정규화 없이 provider 로 샌다. 실제로 통합 전 `additionalItems` 가 그 상태였다.
11
+ * 위치 목록은 여기서만 관리한다.
12
+ *
13
+ * 왜 이런 순회가 필요한지는 스킬 문서 참고:
14
+ * packages/cli/skills/qgrid/references/tool-calling-and-multiturn.md
15
+ *
16
+ * 소비자별로 다른 것은 위치 목록이 아니라 **각 위치를 어떻게 다루는가** 이므로(strictifier 의
17
+ * `normalizable` 플래그 등) 그 판단은 각 소비자가 이 목록 위에 얹는다.
18
+ */
19
+
20
+ /** 이름 → 하위 스키마 맵. 예: `properties`, `$defs`. */
21
+ export const SCHEMA_MAP_KEYWORDS = [
22
+ "$defs",
23
+ "definitions",
24
+ "dependentSchemas",
25
+ "patternProperties",
26
+ "properties",
27
+ ] as const;
28
+
29
+ /** 하위 스키마 배열. 예: `anyOf`, `prefixItems`. */
30
+ export const SCHEMA_ARRAY_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems"] as const;
31
+
32
+ /**
33
+ * 하위 스키마 하나를 직접 갖는 위치. `items` 와 `additionalItems` 는 Draft-7 tuple 형식에서
34
+ * 배열이 올 수 있으므로 소비자가 배열 분기를 따로 처리한다.
35
+ */
36
+ export const SCHEMA_SINGLE_KEYWORDS = [
37
+ "additionalItems",
38
+ "additionalProperties",
39
+ "contains",
40
+ "contentSchema",
41
+ "else",
42
+ "if",
43
+ "items",
44
+ "not",
45
+ "propertyNames",
46
+ "then",
47
+ "unevaluatedItems",
48
+ "unevaluatedProperties",
49
+ ] as const;
50
+
51
+ /**
52
+ * Draft-7 `dependencies` 는 값이 스키마이거나 property 이름 배열이라 위 분류에 들어가지 않는다.
53
+ * 배열 형태는 스키마가 아니므로 순회 대상이 아니다.
54
+ */
55
+ export const SCHEMA_DEPENDENCIES_KEYWORD = "dependencies";
56
+
57
+ /**
58
+ * qgrid 가 거부하는 참조/식별 keyword.
59
+ *
60
+ * 사용자 스키마는 envelope 의 `$defs` 아래로 옮겨지고 정의는 전역적으로 정규화되므로, base URI 를
61
+ * 새로 선언하거나(`$id`) 런타임 스코프로 대상이 정해지는 참조(`$dynamicRef` 등)는 옮긴 뒤의 의미를
62
+ * 보장할 수 없다. 조용히 잘못 rebase 하는 대신 명시적으로 거부한다.
63
+ */
64
+ export const UNSUPPORTED_REFERENCE_KEYWORDS = [
65
+ "$id",
66
+ "id",
67
+ "$anchor",
68
+ "$dynamicAnchor",
69
+ "$recursiveAnchor",
70
+ "$dynamicRef",
71
+ "$recursiveRef",
72
+ ] as const;
@@ -8,6 +8,13 @@
8
8
  * - recursive 처리 (nested objects, arrays, allOf/anyOf/oneOf)
9
9
  */
10
10
 
11
+ import {
12
+ SCHEMA_ARRAY_KEYWORDS,
13
+ SCHEMA_MAP_KEYWORDS,
14
+ SCHEMA_SINGLE_KEYWORDS,
15
+ UNSUPPORTED_REFERENCE_KEYWORDS,
16
+ } from "./json-schema-keywords";
17
+
11
18
  export type JsonSchema = Record<string, unknown>;
12
19
 
13
20
  export interface StrictifierOptions {
@@ -35,31 +42,38 @@ export function strictify(schema: JsonSchema, options: StrictifierOptions = {}):
35
42
  return result;
36
43
  }
37
44
 
38
- const SCHEMA_SCAN_MAP_KEYWORDS = [
39
- ["$defs", true],
40
- ["definitions", true],
41
- ["properties", true],
42
- ["dependentSchemas", false],
43
- ["patternProperties", false],
44
- ] as const;
45
- const SCHEMA_SCAN_ARRAY_KEYWORDS = [
46
- ["allOf", true],
47
- ["anyOf", true],
48
- ["oneOf", true],
49
- ["prefixItems", true],
50
- ] as const;
51
- const SCHEMA_SCAN_SINGLE_KEYWORDS = [
52
- ["additionalProperties", false],
53
- ["contains", false],
54
- ["contentSchema", false],
55
- ["else", false],
56
- ["if", false],
57
- ["not", false],
58
- ["propertyNames", false],
59
- ["then", false],
60
- ["unevaluatedItems", false],
61
- ["unevaluatedProperties", false],
62
- ] as const;
45
+ /**
46
+ * 위치 목록은 json-schema-keywords 가 소유하고, 여기서는 각 위치의 `normalizable` 여부만 얹는다.
47
+ * normalizable=true 는 strictifyNode 가 그 위치를 실제로 재작성한다는 뜻이다. `not`/`if` 처럼
48
+ * 재작성하면 caller 의미가 바뀌는 위치는 false 이고, 그 아래 tuple/`$ref` 는 정규화 대신 거부된다.
49
+ *
50
+ * `items`/`additionalItems` 는 tuple 판정과 함께 다뤄야 해서 아래 순회에서 개별 처리한다.
51
+ */
52
+ const NON_NORMALIZABLE_POSITIONS = new Set<string>([
53
+ "dependentSchemas",
54
+ "patternProperties",
55
+ "additionalProperties",
56
+ "contains",
57
+ "contentSchema",
58
+ "else",
59
+ "if",
60
+ "not",
61
+ "propertyNames",
62
+ "then",
63
+ "unevaluatedItems",
64
+ "unevaluatedProperties",
65
+ ]);
66
+
67
+ function positionEntries(keywords: readonly string[]): ReadonlyArray<readonly [string, boolean]> {
68
+ return keywords.map((keyword) => [keyword, !NON_NORMALIZABLE_POSITIONS.has(keyword)] as const);
69
+ }
70
+
71
+ const SCHEMA_SCAN_MAP_KEYWORDS = positionEntries(SCHEMA_MAP_KEYWORDS);
72
+ const SCHEMA_SCAN_ARRAY_KEYWORDS = positionEntries(SCHEMA_ARRAY_KEYWORDS);
73
+ // items/additionalItems 는 tuple 분기에서 직접 다루므로 단일 위치 스캔에서 제외한다.
74
+ const SCHEMA_SCAN_SINGLE_KEYWORDS = positionEntries(
75
+ SCHEMA_SINGLE_KEYWORDS.filter((keyword) => keyword !== "items" && keyword !== "additionalItems"),
76
+ );
63
77
 
64
78
  /**
65
79
  * Inspect every JSON Schema-bearing position without rewriting it.
@@ -191,16 +205,6 @@ function assertNormalizationCompatibility(
191
205
  assertReferenceTargets(schema, references);
192
206
  }
193
207
 
194
- const UNSUPPORTED_REFERENCE_KEYWORDS = [
195
- "$id",
196
- "id",
197
- "$anchor",
198
- "$dynamicAnchor",
199
- "$recursiveAnchor",
200
- "$dynamicRef",
201
- "$recursiveRef",
202
- ] as const;
203
-
204
208
  function assertUnsupportedReferenceKeywords(schema: JsonSchema, path: string): void {
205
209
  for (const keyword of UNSUPPORTED_REFERENCE_KEYWORDS) {
206
210
  if (Object.hasOwn(schema, keyword)) {
@@ -340,12 +344,14 @@ function strictifyNode(node: JsonSchema, path: string, provider?: string): JsonS
340
344
  return strictifySchemaArrays(result, path, provider);
341
345
  }
342
346
 
343
- const SCHEMA_MAP_KEYWORDS = ["$defs", "definitions"] as const;
347
+ // 재작성 패스는 정의 맵만 재귀한다. properties 는 strictifyObject 가 별도로 처리하므로
348
+ // 공유 위치 목록(SCHEMA_MAP_KEYWORDS)과 의도적으로 범위가 다르다.
349
+ const DEFINITION_MAP_KEYWORDS = ["$defs", "definitions"] as const;
344
350
 
345
351
  function strictifySchemaMaps(node: JsonSchema, path: string, provider?: string): JsonSchema {
346
352
  const result = { ...node };
347
353
 
348
- for (const keyword of SCHEMA_MAP_KEYWORDS) {
354
+ for (const keyword of DEFINITION_MAP_KEYWORDS) {
349
355
  const schemas = ownValue(node, keyword);
350
356
  if (!isSchemaObject(schemas)) continue;
351
357
 
@@ -362,12 +368,13 @@ function strictifySchemaMaps(node: JsonSchema, path: string, provider?: string):
362
368
  return result;
363
369
  }
364
370
 
365
- const SCHEMA_ARRAY_KEYWORDS = ["anyOf", "oneOf", "allOf"] as const;
371
+ // prefixItems 는 strictifyArray 의 tuple 경로가 다루므로 여기서는 조합 keyword 만 재귀한다.
372
+ const COMBINATOR_KEYWORDS = ["anyOf", "oneOf", "allOf"] as const;
366
373
 
367
374
  function strictifySchemaArrays(node: JsonSchema, path: string, provider?: string): JsonSchema {
368
375
  const result = { ...node };
369
376
 
370
- for (const keyword of SCHEMA_ARRAY_KEYWORDS) {
377
+ for (const keyword of COMBINATOR_KEYWORDS) {
371
378
  const schemas = ownValue(node, keyword);
372
379
  if (!schemas) continue;
373
380
  result[keyword] = (schemas as JsonSchema[]).map((schema, index) =>