From 8287bf37fc9850f582087865b48b11e0652a2e3d Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:50:39 +0800 Subject: [PATCH] =?UTF-8?q?docs(config):=20=E6=B7=BB=E5=8A=A0AI=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E7=BC=96=E7=A0=81=E8=A1=8C=E4=B8=BA=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E5=92=8C=E9=85=8D=E7=BD=AE=E9=AA=8C=E8=AF=81=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 定义了代码风格、注释规则和文档要求 - 规定了测试覆盖范围和安全编码准则 - 实现了配置模式解析和验证功能 - 添加了常量值比较和枚举校验逻辑 - 集成了数字范围和字符串长度验证 - 支持对象数组和嵌套结构验证 --- AGENTS.md | 4 + .../test/configValidation.test.js | 284 +++++++++++++++++- 2 files changed, 278 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cb19f63c..8e2d97b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,10 @@ All AI agents and contributors must follow these rules when writing, reviewing, `git.exe`) instead of the Linux `git` binary. - If a Git command in WSL fails with a worktree-style “not a git repository” path translation error, rerun it with the Windows Git executable and treat that as the repository-default Git path for the rest of the task. +- If the shell does not currently resolve `git.exe` to the host Windows Git installation, prepend that installation's + command directory to `PATH` and reset shell command hashing for the current session before continuing. +- After resolving the host Windows Git path, prefer an explicit session-local binding for subsequent commands so the + shell does not fall back to Linux `/usr/bin/git` later in the same WSL session. ## Commenting Rules (MUST) diff --git a/tools/gframework-config-tool/test/configValidation.test.js b/tools/gframework-config-tool/test/configValidation.test.js index fac60f1d..e21abf8a 100644 --- a/tools/gframework-config-tool/test/configValidation.test.js +++ b/tools/gframework-config-tool/test/configValidation.test.js @@ -65,7 +65,7 @@ test("parseSchemaContent should capture nested objects and object-array metadata assert.equal(schema.properties.phases.items.properties.wave.type, "integer"); }); -test("parseSchemaContent should capture const metadata for scalar, object, and array nodes", () => { +test("parseSchemaContent should capture const metadata for scalar, object, array, integer, and boolean nodes", () => { const schema = parseSchemaContent(` { "type": "object", @@ -78,19 +78,61 @@ test("parseSchemaContent should capture const metadata for scalar, object, and a "type": "object", "properties": { "gold": { "type": "integer" }, - "currency": { "type": "string" } + "items": { + "type": "array", + "items": { + "type": "string" + } + } }, "const": { - "gold": 10, - "currency": "coin" + "gold": 100, + "items": [ + "potion", + "sword" + ] } }, - "dropItemIds": { + "tags": { "type": "array", - "const": ["potion", "gem"], + "const": ["daily", "quest"], "items": { "type": "string" } + }, + "maxAttempts": { + "type": "integer", + "const": 3 + }, + "allowRetry": { + "type": "boolean", + "const": true + } + } + } + `); + const reorderedSchema = parseSchemaContent(` + { + "type": "object", + "properties": { + "reward": { + "type": "object", + "properties": { + "gold": { "type": "integer" }, + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "const": { + "items": [ + "potion", + "sword" + ], + "gold": 100 + } } } } @@ -98,10 +140,25 @@ test("parseSchemaContent should capture const metadata for scalar, object, and a assert.equal(schema.properties.rarity.constValue, "common"); assert.equal(schema.properties.rarity.constDisplayValue, "\"common\""); - assert.match(schema.properties.reward.constValue, /"currency":"coin"/u); - assert.match(schema.properties.reward.constDisplayValue, /"currency":"coin"/u); - assert.equal(schema.properties.dropItemIds.constValue, "[\"potion\",\"gem\"]"); - assert.equal(schema.properties.dropItemIds.constDisplayValue, "[\"potion\",\"gem\"]"); + assert.ok(schema.properties.rarity.constComparableValue); + + assert.equal(schema.properties.reward.constValue, "{\"gold\":100,\"items\":[\"potion\",\"sword\"]}"); + assert.equal(schema.properties.reward.constDisplayValue, "{\"gold\":100,\"items\":[\"potion\",\"sword\"]}"); + assert.equal( + schema.properties.reward.constComparableValue, + reorderedSchema.properties.reward.constComparableValue); + + assert.equal(schema.properties.tags.constValue, "[\"daily\",\"quest\"]"); + assert.equal(schema.properties.tags.constDisplayValue, "[\"daily\",\"quest\"]"); + assert.ok(schema.properties.tags.constComparableValue); + + assert.equal(schema.properties.maxAttempts.constValue, "3"); + assert.equal(schema.properties.maxAttempts.constDisplayValue, "3"); + assert.equal(schema.properties.maxAttempts.constComparableValue, "integer:1:3"); + + assert.equal(schema.properties.allowRetry.constValue, "true"); + assert.equal(schema.properties.allowRetry.constDisplayValue, "true"); + assert.equal(schema.properties.allowRetry.constComparableValue, "boolean:4:true"); }); test("parseSchemaContent should preserve empty-string const raw and display metadata", () => { @@ -291,6 +348,139 @@ rarity: rare assert.match(diagnostics[0].message, /constant value "common"|固定值 "common"/u); }); +test("validateParsedConfig should accept scalar, object, array, integer, and boolean const matches", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "properties": { + "rarity": { + "type": "string", + "const": "common" + }, + "reward": { + "type": "object", + "properties": { + "gold": { "type": "integer" }, + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "const": { + "gold": 100, + "items": [ + "potion", + "sword" + ] + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "const": [ + "daily", + "quest" + ] + }, + "maxAttempts": { + "type": "integer", + "const": 3 + }, + "allowRetry": { + "type": "boolean", + "const": true + } + } + } + `); + const yaml = parseTopLevelYaml(` +rarity: common +reward: + gold: 100 + items: + - potion + - sword +tags: + - daily + - quest +maxAttempts: 3 +allowRetry: true +`); + + assert.deepEqual(validateParsedConfig(schema, yaml), []); +}); + +test("validateParsedConfig should normalize object const comparisons but keep array const order", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "properties": { + "reward": { + "type": "object", + "properties": { + "gold": { "type": "integer" }, + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "const": { + "gold": 100, + "items": [ + "potion", + "sword" + ] + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "const": [ + "daily", + "quest" + ] + } + } + } + `); + const normalizedYaml = parseTopLevelYaml(` +reward: + items: + - potion + - sword + gold: 100 +tags: + - daily + - quest +`); + const arrayOrderMismatchYaml = parseTopLevelYaml(` +reward: + items: + - potion + - sword + gold: 100 +tags: + - quest + - daily +`); + + assert.deepEqual(validateParsedConfig(schema, normalizedYaml), []); + + const diagnostics = validateParsedConfig(schema, arrayOrderMismatchYaml); + + assert.equal(diagnostics.length, 1); + assert.match(diagnostics[0].message, /tags/u); + assert.match(diagnostics[0].message, /constant value \["daily","quest"\]|固定值 \["daily","quest"\]/u); +}); + test("validateParsedConfig should report object and array const mismatches", () => { const schema = parseSchemaContent(` { @@ -333,6 +523,60 @@ dropItemIds: assert.match(diagnostics[1].message, /dropItemIds/u); }); +test("validateParsedConfig should cover integer and boolean const scalar normalization and mismatches", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "properties": { + "maxAttempts": { + "type": "integer", + "const": 3 + }, + "allowRetry": { + "type": "boolean", + "const": true + } + } + } + `); + const normalizedYaml = parseTopLevelYaml(` +maxAttempts: "3" +allowRetry: "true" +`); + const integerMismatchYaml = parseTopLevelYaml(` +maxAttempts: 3.5 +allowRetry: true +`); + const booleanConstMismatchYaml = parseTopLevelYaml(` +maxAttempts: 3 +allowRetry: false +`); + const booleanTypeMismatchYaml = parseTopLevelYaml(` +maxAttempts: 3 +allowRetry: 0 +`); + + assert.deepEqual(validateParsedConfig(schema, normalizedYaml), []); + + const integerDiagnostics = validateParsedConfig(schema, integerMismatchYaml); + + assert.equal(integerDiagnostics.length, 1); + assert.match(integerDiagnostics[0].message, /maxAttempts/u); + assert.match(integerDiagnostics[0].message, /integer|整数/u); + + const booleanConstDiagnostics = validateParsedConfig(schema, booleanConstMismatchYaml); + + assert.equal(booleanConstDiagnostics.length, 1); + assert.match(booleanConstDiagnostics[0].message, /allowRetry/u); + assert.match(booleanConstDiagnostics[0].message, /constant value true|固定值 true/u); + + const booleanTypeDiagnostics = validateParsedConfig(schema, booleanTypeMismatchYaml); + + assert.equal(booleanTypeDiagnostics.length, 1); + assert.match(booleanTypeDiagnostics[0].message, /allowRetry/u); + assert.match(booleanTypeDiagnostics[0].message, /boolean|布尔/u); +}); + test("validateParsedConfig should report numeric range and string length mismatches", () => { const schema = parseSchemaContent(` { @@ -1261,6 +1505,26 @@ test("createSampleConfigYaml should preserve empty-string scalar const values", assert.match(sample, /^name: ""$/mu); }); +test("createSampleConfigYaml should prefer scalar const values over defaults", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "properties": { + "rarity": { + "type": "string", + "const": "common", + "default": "rare" + } + } + } + `); + + const sample = createSampleConfigYaml(schema); + + assert.match(sample, /^rarity: common$/mu); + assert.ok(!/^rarity: rare$/mu.test(sample)); +}); + test("parseBatchArrayValue should keep comma-separated batch editing behavior", () => { assert.deepEqual(parseBatchArrayValue(" potion, bomb , ,elixir "), ["potion", "bomb", "elixir"]); });