From 5fa4a8da1b3b78366444644592044f353bf7068d Mon Sep 17 00:00:00 2001 From: bewaterhere-coder Date: Sun, 1 Nov 2026 18:43:29 +0800 Subject: [PATCH 1/2] docs: replace obsolete Profile example with canonical contract --- docs/best-practices.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/docs/best-practices.md b/docs/best-practices.md index b9c8112..e8fe0be 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -33,19 +33,7 @@ frameforge /path/to/your/game-project 在项目根目录创建 `.frameforge/profile.json`,只选择当前项目需要的插件,并绑定项目正式规范路径。不要为了消除 Warning 而创建空白规范或启用无关插件。 -```json -{ - "schema_version": 1, - "plugins": [ - {"id": "unity", "version": "1.0.0"} - ], - "standards": [ - {"domain": "architecture", "path": "docs/architecture.md", "version": "1.0"} - ] -} -``` - -以上仅为格式示例;插件 ID、版本和规范路径必须以实际项目为准。 +请直接复制并按项目实际情况修改[标准 Profile 示例](profile-example.json)。该示例与当前 Core 的 `schema_version: 1` 契约一致:使用 `selected_plugins` 显式选择插件,使用 `standards` 对象映射领域到项目相对路径。不要使用旧版 `plugins` 数组或 `standards` 对象数组。 若配置 `semantic_rules`,每条规则的 `plugin_id` 必须对应仓库中可用的插件,并且该插件必须在当前 Profile 中显式选择,版本也必须与插件清单一致。未知插件、未选择插件或版本不匹配会产生非阻塞 Warning;插件未被自动发现时,显式选择的规则仍会保留为 `not_evaluated`,不会静默消失。 `check_clauses` 的键是检查 ID:要绑定 Semantic Rule 时必须与其 `id` 完全一致;也允许独立条款覆盖记录,此时没有对应规则的条款保持 `not_evaluated`。检查 ID 和条款 ID 不得为空或带首尾空白。 From f095eff903e11b4037611b394fc6be2466b23f47 Mon Sep 17 00:00:00 2001 From: bewaterhere-coder Date: Sun, 1 Nov 2026 18:43:32 +0800 Subject: [PATCH 2/2] test: validate published Profile example against Core --- tests/test_core.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 442e79d..a0e720b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -11,6 +11,19 @@ def tearDown(self): self.tmp.cleanup() def profile(self, plugins, standards=None): p=self.root/'.frameforge/profile.json';p.parent.mkdir(exist_ok=True) p.write_text(json.dumps({'schema_version':1,'selected_plugins':plugins,'standards':standards or {}})) + def test_documented_profile_example_matches_core_contract(self): + example = Path(__file__).resolve().parents[1] / 'docs/profile-example.json' + profile_data = example.read_text(encoding='utf-8') + result = inspect(self.root, profile_data=profile_data) + self.assertEqual( + {item['id'] for item in result['selected_plugins']}, + {'unity', 'art-ui'}, + ) + self.assertNotIn('PROFILE_INVALID', [item['code'] for item in result['warnings']]) + self.assertEqual(result['standards']['architecture'], 'docs/architecture.md') + self.assertEqual(result['standards']['art-ui'], 'docs/art-direction.md') + self.assertFalse(result['blocking']) + def test_empty_nonblocking(self): r=inspect(self.root);self.assertFalse(r['blocking']);self.assertEqual(r['warnings'][0]['code'],'PROFILE_MISSING') def test_discovery_does_not_activate(self):