diff --git a/README.md b/README.md index 65c5919..3949cd4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ frameforge --plugins frameforge /path/to/your/game-project ``` -首次检查只会发现技术栈和报告规范缺口,不会修改项目或自动启用插件。要启用专业检查,在**项目自身**建立 `.frameforge/profile.json`,明确填写插件 ID、版本和项目正式规范路径,格式参见 [Profile 示例](docs/profile-example.json)。检查命令仍为 `frameforge /path/to/your/game-project`。 +首次检查只会发现技术栈和报告规范缺口,不会修改项目或自动启用插件。要启用专业检查,在**项目自身**建立 `.frameforge/profile.json`,明确填写插件 ID、版本和项目正式规范路径,格式参见 [Profile 示例](docs/profile-example.json)。检查命令仍为 `frameforge /path/to/your/game-project`。 Profile 中的显式选择独立于自动发现;即使发现标记缺失,也会执行已选择插件的专业指导,并在没有证据时报告 `not_evaluated`。 ### 贡献者开发环境 @@ -104,7 +104,7 @@ frameforge --plugins frameforge /path/to/your/game-project ``` -Inspection is read-only and does not activate plugins. For opt-in checks, create a project-owned `.frameforge/profile.json` with explicit plugin IDs/versions and canonical standard references; see the [profile example](docs/profile-example.json). Ordinary users do not need development dependencies or the test suite. +Inspection is read-only and does not activate plugins. For opt-in checks, create a project-owned `.frameforge/profile.json` with explicit plugin IDs/versions and canonical standard references; see the [profile example](docs/profile-example.json). Ordinary users do not need development dependencies or the test suite. Explicit Profile selection is independent of automatic discovery markers: selected plugin guidance still runs when a marker is absent, and reports `not_evaluated` when no source evidence is available. ### Contributor setup and tests diff --git a/docs/best-practices.md b/docs/best-practices.md index 7dde7e6..b9c8112 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -16,7 +16,7 @@ frameforge /path/to/your/game-project 当前从 GitHub 源码安装,尚未发布 PyPI 包。不要使用 `pip install frameforge`:PyPI 上已有同名的其他项目。 -首次检查先识别技术栈、候选插件和规范缺口。发现插件不代表启用插件;首次扫描不会修改项目。 +首次检查先识别技术栈、候选插件和规范缺口。发现插件不代表启用插件;首次扫描不会修改项目。 项目 Profile 中显式选择的插件会独立启用对应专业指导,不要求自动发现标记同时命中;未找到源码证据时返回 `not_evaluated`。 ### 2. 建立项目自己的规范 @@ -105,7 +105,7 @@ frameforge /path/to/your/game-project Install from GitHub source for now; no PyPI release is available. Do not run `pip install frameforge`: that name is already used by an unrelated PyPI project. -The first inspection discovers technology markers, candidate plugins and possible standards gaps. Discovery does not activate plugins or modify the project. +The first inspection discovers technology markers, candidate plugins and possible standards gaps. Discovery does not activate plugins or modify the project. Professional guidance is activated independently by explicit Profile selection, even when automatic discovery markers are absent; without source evidence the result is `not_evaluated`. ### 2. Define project-owned standards diff --git a/src/frameforge/core.py b/src/frameforge/core.py index a334652..7dd350d 100644 --- a/src/frameforge/core.py +++ b/src/frameforge/core.py @@ -359,8 +359,9 @@ def inspect(project, plugin_dir=None, profile_data=None, unity_external_evidence if len(lines) == 1: binding['clause_line'] = lines[0] binding['clause_excerpt'] = clause['text'] - professional = [entry for suggestion in found if suggestion['plugin_id'] in chosen - for entry in professional_preview(root, catalog[suggestion['plugin_id']])] + # Discovery only recommends plugins; explicit Profile selection controls guidance activation. + professional = [entry for plugin_id in sorted(chosen) + for entry in professional_preview(root, catalog[plugin_id])] art_ui_gaps, art_ui_implementation, art_ui_unity_binding_evidence, structural = run_plugin_checks( root, chosen, found, standards, art_ui_checks, art_ui_implementation_checks, art_ui_unity_binding, structural_paths, project_file, unity_external_evidence) diff --git a/tests/test_professional_guidance.py b/tests/test_professional_guidance.py index d09e542..9de9f48 100644 --- a/tests/test_professional_guidance.py +++ b/tests/test_professional_guidance.py @@ -37,6 +37,24 @@ def test_skynet_no_project_specific_path(self): self.assertEqual(len(result),2) self.assertEqual(result[0]['evidence'][0]['path'],'backend/custom_actor.lua') + def test_explicit_selection_runs_guidance_without_discovery_marker(self): + with tempfile.TemporaryDirectory() as d: + root=Path(d) + source=root/'Assets/Engine/Controller.cs' + source.parent.mkdir(parents=True) + source.write_text('public class Controller : MonoBehaviour {}') + (root/'.frameforge').mkdir() + (root/'.frameforge/profile.json').write_text(json.dumps( + {'schema_version':1,'selected_plugins':[{'id':'unity','version':'1.0.0'}]})) + result=inspect(root) + self.assertEqual(result['suggested_plugins'], []) + self.assertEqual(len(result['professional_guidance']), 2) + self.assertEqual(result['professional_guidance'][0]['status'],'review_recommended') + self.assertEqual(result['professional_guidance'][0]['evidence'][0]['path'], + 'Assets/Engine/Controller.cs') + self.assertEqual(result['professional_guidance'][1]['status'],'not_evaluated') + self.assertFalse(result['blocking']) + def test_unmatched_signal_is_not_a_gap(self): with tempfile.TemporaryDirectory() as d: root=Path(d)