scene-capability-engine 3.6.46 → 3.6.48

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/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.6.48] - 2026-03-14
11
+
12
+ ### Changed
13
+ - Added `governance.duplicate_detection_scope` to studio spec governance policy with supported values `all`, `non_completed`, and `active_only`.
14
+ - Changed the default duplicate governance scope to `non_completed`, so duplicate detection compares active and stale specs but no longer treats completed historical specs as duplicate noise.
15
+ - Updated studio intake normalization and scene governance reporting to filter duplicate candidate sets by the configured scope before generating duplicate alerts.
16
+ - Synced the new duplicate governance scope into takeover baseline defaults so adopted and upgraded projects inherit the same non-completed duplicate detection behavior by default.
17
+ - Added regression coverage proving completed history specs do not trigger duplicate governance alerts under the new default scope.
18
+
19
+ ## [3.6.47] - 2026-03-14
20
+
21
+ ### Changed
22
+ - Promoted "clarification before disable" to a global SCE baseline rule: missing business scene/module/page/entity context now defaults to clarification in builtin interactive governance, is written into steering/template core principles, and is documented as mandatory for all SCE-integrated projects with no exceptions.
23
+ - Added `clarification-first-audit` as a release/audit guard so core scripts, policy/docs, starter/template baselines, and legacy fallback phrases are continuously checked for regression.
24
+ - Wired `clarification-first-audit` into `test.yml`, `release.yml`, and `steering-hygiene.yml` so CI, tag releases, and scheduled hygiene runs all enforce the clarification-first baseline.
25
+ - Extended takeover baseline auto-alignment so older adopted projects also repair missing clarification-first `CORE_PRINCIPLES` content during best-effort startup/default alignment.
26
+ - Added CLI integration coverage for `sce adopt`, `sce upgrade`, and startup takeover auto-alignment so clarification-first baseline propagation is verified end-to-end on real project fixtures.
27
+ - Extended takeover baseline to auto-create `.sce/config/errorbook-registry.json` and to inventory project-defined mistake-book/postmortem style artifacts so SCE takeover converges them into the canonical `errorbook` flow instead of letting parallel mechanisms coexist.
28
+ - Added three more global core principles to the steering baseline: forbid blind fixes without problem evidence, require evaluation before any steering entry is added/removed/rewritten, and default frontend/backend mismatch fixes to align the frontend to the existing backend API contract unless an interface change is explicitly requested.
29
+
10
30
  ## [3.6.46] - 2026-03-13
11
31
 
12
32
  ### Added
package/README.md CHANGED
@@ -139,6 +139,7 @@ sce auth status --json
139
139
  SCE is opinionated by default.
140
140
 
141
141
  - `studio plan` runs intake and scene/spec governance unless policy explicitly allows bypass.
142
+ - When business scene/module/page/entity context is missing, SCE must route to clarification first; unknown business scope must not be turned into blanket disable.
142
143
  - `verify` and `release` enforce problem-closure and related gates when a spec is bound.
143
144
  - Autonomous program execution applies gate evaluation, fallback-chain logic, governance replay, and auto-remediation.
144
145
  - State persistence prefers SQLite, not ad hoc local caches.
package/README.zh.md CHANGED
@@ -144,6 +144,7 @@ sce auth status --json
144
144
  SCE 默认是强治理的。
145
145
 
146
146
  - `studio plan` 默认执行 intake 与 scene/spec 治理,除非策略显式允许绕过
147
+ - 缺少业务场景/模块/页面/实体上下文时,SCE 必须先进入澄清,而不是把未知业务范围直接变成一刀切禁用
147
148
  - 当 spec 绑定时,`verify` 和 `release` 默认执行 problem-closure 等相关门禁
148
149
  - `close-loop-program` 默认带 gate 评估、fallback-chain、governance replay、auto-remediation
149
150
  - 状态持久化默认优先走 SQLite,而不是零散本地缓存
@@ -86,7 +86,7 @@
86
86
  "type": "string",
87
87
  "enum": [
88
88
  "allow_write",
89
- "block_high_risk_write"
89
+ "clarify_business_scope"
90
90
  ]
91
91
  },
92
92
  "advisory": {
@@ -366,6 +366,8 @@ sce workspace takeover-apply --json
366
366
  # until manual migration is completed.
367
367
  # For adopted projects, startup auto-runs takeover baseline alignment
368
368
  # before command execution (best effort, non-blocking).
369
+ # Takeover alignment now also repairs missing clarification-first
370
+ # CORE_PRINCIPLES baseline when older adopted projects drift.
369
371
 
370
372
  # Legacy commands (still supported)
371
373
  sce workspace sync
@@ -1806,8 +1808,14 @@ Interactive dialogue governance helper (script-level communication-rule gate):
1806
1808
  - Default policy: `docs/interactive-customization/dialogue-governance-policy-baseline.json` (fallback builtin policy when missing)
1807
1809
  - Default authorization dialogue policy: `docs/interactive-customization/authorization-dialogue-policy-baseline.json`
1808
1810
  - Default profile: `business-user` (use `system-maintainer` for maintenance/operator conversations)
1811
+ - Missing business scene/module/page/entity context defaults to `clarify`; unknown scope must not be converted into blanket disable fallback.
1809
1812
  - `--fail-on-deny` exits with code `2` to block unsafe requests in CI/automation.
1810
1813
 
1814
+ Clarification-first baseline audit helper:
1815
+ - `node scripts/clarification-first-audit.js [--project-path <path>] [--out <path>] [--fail-on-violation] [--json]`: verify that SCE global clarification-first baselines are present across core scripts, policy/docs, starter/template assets, and that legacy blanket-disable phrases do not reappear in tracked source/docs.
1816
+ - Default behavior: audit current project root.
1817
+ - `--fail-on-violation` exits with code `2` when required clarification-first baselines drift or prohibited legacy phrases are detected.
1818
+
1811
1819
  Interactive change-plan generator helper (script-level stage-B planning bridge):
1812
1820
  - `node scripts/interactive-plan-build.js --intent <path> [--context <path>] [--execution-mode <suggestion|apply>] [--out-plan <path>] [--out-markdown <path>] [--json]`: generate structured `Change_Plan` from `Change_Intent`, including action candidates, risk level, verification checks, rollback plan, approval status, and gate hint command.
1813
1821
  - Default outputs:
@@ -41,11 +41,13 @@
41
41
  "Always restate objective, scope, and expected impact before recommendations.",
42
42
  "When risk or permission is involved, explicitly list required approvals and authorization.",
43
43
  "If requirement is ambiguous, ask at most two focused clarification questions.",
44
+ "If business scene, module, page, entity, or constraints are missing, clarify and narrow scope before using any fallback restriction; do not replace understanding with blanket disable.",
44
45
  "Never propose credential export, approval bypass, or secret leakage."
45
46
  ],
46
47
  "clarification_templates": [
47
48
  "What business metric should improve first (speed, accuracy, cost, compliance)?",
48
- "Which module/page should be changed first, and what must remain unchanged?"
49
+ "Which module/page should be changed first, and what must remain unchanged?",
50
+ "Which entity or business rule is affected, and what constraint must stay intact?"
49
51
  ],
50
52
  "profiles": {
51
53
  "business-user": {
@@ -75,6 +77,7 @@
75
77
  ],
76
78
  "response_rules": [
77
79
  "For maintenance requests, require change ticket, rollback plan, and approval role before execution.",
80
+ "When business context is incomplete, ask for the affected module/page/entity before considering deny or write restrictions.",
78
81
  "If request targets production, require staged validation evidence first."
79
82
  ],
80
83
  "clarification_templates": [
@@ -31,6 +31,7 @@ This guide defines mandatory conversation and authorization behavior for an embe
31
31
  3. Confirmation before mutation:
32
32
  - For `apply`, assistant must ask a final explicit confirmation.
33
33
  - Confirmation text must include impact summary and rollback availability.
34
+ - Missing business scene/context must trigger clarification first; it must not be treated as a reason to blanket-disable the request.
34
35
 
35
36
  ## 4. Step-Up Authorization Rules
36
37
 
@@ -49,6 +50,10 @@ This guide defines mandatory conversation and authorization behavior for an embe
49
50
  - reject execution,
50
51
  - explain the blocked policy reason in plain language,
51
52
  - provide at least one safe alternative (`suggestion`, ticket, or scope reduction).
53
+ - If business context or symbol evidence is incomplete, assistant must:
54
+ - ask for missing `module/page/entity/business constraint` details,
55
+ - reduce scope to a verifiable change candidate,
56
+ - avoid using fallback as a blanket disable substitute before context is understood.
52
57
  - If environment is rate-limited or unstable (`429`/timeouts), assistant must:
53
58
  - avoid aggressive retries,
54
59
  - switch to phased queue execution guidance,
@@ -9,6 +9,8 @@ This directory stores release-facing documents:
9
9
  ## Archived Versions
10
10
 
11
11
  - [Release checklist](../release-checklist.md)
12
+ - [v3.6.48 release notes](./v3.6.48.md)
13
+ - [v3.6.47 release notes](./v3.6.47.md)
12
14
  - [v3.6.46 release notes](./v3.6.46.md)
13
15
  - [v3.6.45 release notes](./v3.6.45.md)
14
16
  - [v3.6.44 release notes](./v3.6.44.md)
@@ -0,0 +1,23 @@
1
+ # v3.6.47 Release Notes
2
+
3
+ Release date: 2026-03-14
4
+
5
+ ## Highlights
6
+
7
+ - Promoted clarification-first into an SCE-wide baseline: when business scene, module, page, entity, or constraints are unclear, the assistant must narrow scope first instead of converting uncertainty into blanket disable.
8
+ - Extended takeover baseline alignment so adopted and upgraded projects automatically repair missing core principles, auto-seed `.sce/config/errorbook-registry.json`, and inventory project-defined postmortem or mistake-book style mechanisms for convergence into `.sce/errorbook`.
9
+ - Added three more global steering baselines: no blind fixes without problem evidence, no arbitrary steering entry add/remove without evaluation, and frontend/backend mismatch fixes must default to the existing backend API contract unless an interface change is explicitly requested.
10
+
11
+ ## Validation
12
+
13
+ - `npx jest tests/unit/workspace/takeover-baseline.test.js --runInBand`
14
+ - `npx jest tests/integration/adopt-upgrade-clarification-first.integration.test.js --runInBand`
15
+ - `npx jest tests/integration/takeover-baseline-cli.integration.test.js --runInBand`
16
+ - `npm run audit:steering`
17
+ - `npm run gate:errorbook-registry-health`
18
+ - `npm run prepublishOnly`
19
+
20
+ ## Release Notes
21
+
22
+ - This patch release formalizes the steering and takeover rules discussed during recent SCE hardening work, so they are no longer implicit operator expectations but enforced project baselines.
23
+ - Existing SCE-integrated projects now inherit these rules automatically through startup takeover alignment, `sce adopt`, and `sce upgrade`, which prevents drift between newly adopted projects and older onboarded repositories.
@@ -0,0 +1,19 @@
1
+ # v3.6.48 Release Notes
2
+
3
+ Release date: 2026-03-14
4
+
5
+ ## Highlights
6
+
7
+ - Added `governance.duplicate_detection_scope` to studio spec governance with three supported values: `all`, `non_completed`, and `active_only`.
8
+ - Changed the default duplicate governance scope to `non_completed`, so duplicate checks now compare active and stale specs while excluding completed historical specs from duplicate pair detection.
9
+ - Synced the same default into takeover baseline, so adopted and upgraded projects inherit the lower-noise duplicate governance behavior automatically.
10
+
11
+ ## Validation
12
+
13
+ - `npx jest tests/unit/studio/spec-intake-governor.test.js --runInBand`
14
+ - `npm run prepublishOnly`
15
+
16
+ ## Release Notes
17
+
18
+ - This patch reduces governance noise in repositories with many completed template-style specs, where historical completed work previously flooded duplicate alerts and hid active governance problems.
19
+ - The default keeps duplicate sensitivity on unclosed work (`active` + `stale`) without requiring every upgraded project to hand-tune its own studio intake policy.
@@ -34,6 +34,8 @@ After SCE integration is enabled:
34
34
  5. `gated execution`: runtime policy + authorization tier + approval gate.
35
35
  6. `execution + audit`: execute or block, then emit summary and evidence.
36
36
 
37
+ If business scene or symbol evidence is incomplete, route to scope clarification first instead of using a blanket fallback disable.
38
+
37
39
  ## 5. Mode Playbooks
38
40
 
39
41
  ### 5.1 user-mode (business usage UI)
@@ -100,4 +102,3 @@ See also: `docs/security-governance-default-baseline.md`
100
102
  3. Enable required gate artifacts and audit logs.
101
103
  4. Run weekly governance and release gates.
102
104
  5. Keep capability matrix and ontology mapping updated per release.
103
-
@@ -27,7 +27,8 @@ node scripts/symbol-evidence-locate.js \
27
27
 
28
28
  Expected:
29
29
  - reliable evidence => `fallback_action=allow_write`
30
- - no reliable evidence => `fallback_action=block_high_risk_write` and exit code `2`
30
+ - no reliable evidence => `fallback_action=clarify_business_scope` and exit code `2`
31
+ - assistant must narrow module/page/entity and business constraints before deciding whether writes should proceed
31
32
 
32
33
  ## 3) Failure Attribution and Bounded Repair
33
34
 
@@ -5,6 +5,8 @@ This baseline is the default operating policy for SCE-driven delivery, including
5
5
  ## 1. Context and Data Safety
6
6
 
7
7
  - Enforce strict context contract validation (`--context-contract`, strict mode on).
8
+ - Missing business scene/module/page/entity context must route to clarification first; unknown scope is never a valid reason for blanket disable.
9
+ - This clarification-first rule applies to every SCE-integrated project and surface with no project-specific exception.
8
10
  - Block forbidden keys (for example secrets/private keys) from UI/provider payloads.
9
11
  - Keep payload masking enabled for business data and identity fields.
10
12
  - Reject context payloads that exceed size budget or schema bounds.
@@ -2,6 +2,8 @@
2
2
 
3
3
  This starter kit is the default baseline for onboarding an external project (including Moqui-based solutions) into SCE without project-specific flags.
4
4
 
5
+ It also inherits SCE's clarification-first rule: if business scene/module/page/entity context is missing, the assistant must narrow scope before any deny/disable fallback. This baseline applies to every onboarded project with no project-specific exception.
6
+
5
7
  ## Included Assets
6
8
 
7
9
  - `handoff-manifest.starter.json`: minimal manifest contract that works with `sce auto handoff` and `sce scene package-publish-batch`.
@@ -33,6 +35,7 @@ node scripts/release-ops-weekly-summary.js --json
33
35
 
34
36
  - `scene package publish-batch` gate passes.
35
37
  - capability lexicon unknown count is zero.
38
+ - missing business scope is handled through clarification, not blanket disable fallback.
36
39
  - release preflight is not blocked for hard-gate profiles.
37
40
  - weekly ops summary risk is not `high` unless explicitly approved.
38
41
 
@@ -9,6 +9,8 @@
9
9
  ## 历史版本归档
10
10
 
11
11
  - [发布检查清单](../release-checklist.md)
12
+ - [v3.6.48 发布说明](./v3.6.48.md)
13
+ - [v3.6.47 发布说明](./v3.6.47.md)
12
14
  - [v3.6.46 发布说明](./v3.6.46.md)
13
15
  - [v3.6.45 发布说明](./v3.6.45.md)
14
16
  - [v3.6.44 发布说明](./v3.6.44.md)
@@ -0,0 +1,23 @@
1
+ # v3.6.47 发布说明
2
+
3
+ 发布日期:2026-03-14
4
+
5
+ ## 重点变化
6
+
7
+ - 将 clarification-first 提升为 SCE 全局基线:业务场景、模块、页面、实体或约束不清楚时,必须先澄清范围,禁止把“不理解业务”直接落成一刀切禁用。
8
+ - 扩展 takeover baseline 对齐能力:已接管和升级项目现在会自动补齐缺失的核心原则、自动落 `.sce/config/errorbook-registry.json`,并把项目内自定义复盘册/问题账本类机制盘点后统一收敛到 `.sce/errorbook`。
9
+ - 新增三条全局 steering 基线:禁止盲改问题、禁止未经评估随意增删 steering 条目、以及问题修复时前后端接口不一致默认以后端现有契约为准。
10
+
11
+ ## 验证
12
+
13
+ - `npx jest tests/unit/workspace/takeover-baseline.test.js --runInBand`
14
+ - `npx jest tests/integration/adopt-upgrade-clarification-first.integration.test.js --runInBand`
15
+ - `npx jest tests/integration/takeover-baseline-cli.integration.test.js --runInBand`
16
+ - `npm run audit:steering`
17
+ - `npm run gate:errorbook-registry-health`
18
+ - `npm run prepublishOnly`
19
+
20
+ ## 发布说明
21
+
22
+ - 这个补丁版把最近一轮 SCE 治理收敛中的关键规则正式固化为了可执行基线,不再依赖操作者“记得遵守”。
23
+ - 之后无论是 startup auto-takeover、`sce adopt` 还是 `sce upgrade`,都会自动把这些规则补齐到项目里,避免新老接入项目在治理要求上继续分叉。
@@ -0,0 +1,19 @@
1
+ # v3.6.48 发布说明
2
+
3
+ 发布日期:2026-03-14
4
+
5
+ ## 重点变化
6
+
7
+ - 为 studio spec governance 增加 `governance.duplicate_detection_scope`,支持 `all`、`non_completed`、`active_only` 三种取值。
8
+ - 将默认 duplicate 检测策略调整为 `non_completed`:只比较 `active` 与 `stale` spec,不再把 `completed` 历史 spec 纳入 duplicate 对比。
9
+ - 将同样的默认值同步到 takeover baseline,确保 adopt / upgrade 后的项目自动继承这套降噪后的治理行为。
10
+
11
+ ## 验证
12
+
13
+ - `npx jest tests/unit/studio/spec-intake-governor.test.js --runInBand`
14
+ - `npm run prepublishOnly`
15
+
16
+ ## 发布说明
17
+
18
+ - 这个补丁版主要解决“历史 completed spec 太多时 duplicate 告警淹没真实 active 治理问题”的噪音问题。
19
+ - 新默认值仍保留对未收口 spec 的治理敏感度,但不再让历史归档 spec 持续制造无效 duplicate 告警。
@@ -82,7 +82,8 @@ const DEFAULT_STUDIO_INTAKE_POLICY = Object.freeze({
82
82
  require_auto_on_plan: true,
83
83
  max_active_specs_per_scene: 3,
84
84
  stale_days: 14,
85
- duplicate_similarity_threshold: 0.66
85
+ duplicate_similarity_threshold: 0.66,
86
+ duplicate_detection_scope: 'non_completed'
86
87
  },
87
88
  backfill: {
88
89
  enabled: true,
@@ -132,6 +133,14 @@ function normalizeBoolean(value, fallback = false) {
132
133
  return fallback;
133
134
  }
134
135
 
136
+ function normalizeDuplicateDetectionScope(value, fallback = 'non_completed') {
137
+ const normalized = normalizeText(value).toLowerCase();
138
+ if (['all', 'non_completed', 'active_only'].includes(normalized)) {
139
+ return normalized;
140
+ }
141
+ return fallback;
142
+ }
143
+
135
144
  function normalizeTextList(value = []) {
136
145
  if (!Array.isArray(value)) {
137
146
  return [];
@@ -330,6 +339,10 @@ function normalizeStudioIntakePolicy(raw = {}) {
330
339
  governance.duplicate_similarity_threshold,
331
340
  DEFAULT_STUDIO_INTAKE_POLICY.governance.duplicate_similarity_threshold
332
341
  ))
342
+ ),
343
+ duplicate_detection_scope: normalizeDuplicateDetectionScope(
344
+ governance.duplicate_detection_scope,
345
+ DEFAULT_STUDIO_INTAKE_POLICY.governance.duplicate_detection_scope
333
346
  )
334
347
  },
335
348
  backfill: {
@@ -898,6 +911,10 @@ function buildSceneGovernanceReport(records = [], policy = DEFAULT_STUDIO_INTAKE
898
911
  const governance = policy.governance || DEFAULT_STUDIO_INTAKE_POLICY.governance;
899
912
  const threshold = normalizeNumber(governance.duplicate_similarity_threshold, 0.66);
900
913
  const maxActive = normalizeInteger(governance.max_active_specs_per_scene, 3, 1, 200);
914
+ const duplicateScope = normalizeDuplicateDetectionScope(
915
+ governance.duplicate_detection_scope,
916
+ DEFAULT_STUDIO_INTAKE_POLICY.governance.duplicate_detection_scope
917
+ );
901
918
 
902
919
  const sceneMap = new Map();
903
920
  for (const record of records) {
@@ -918,12 +935,17 @@ function buildSceneGovernanceReport(records = [], policy = DEFAULT_STUDIO_INTAKE
918
935
  const activeSpecs = sortedSpecs.filter((item) => item.lifecycle_state === 'active');
919
936
  const staleSpecs = sortedSpecs.filter((item) => item.lifecycle_state === 'stale');
920
937
  const completedSpecs = sortedSpecs.filter((item) => item.lifecycle_state === 'completed');
938
+ const duplicateCandidateSpecs = duplicateScope === 'active_only'
939
+ ? activeSpecs
940
+ : (duplicateScope === 'non_completed'
941
+ ? sortedSpecs.filter((item) => item.lifecycle_state !== 'completed')
942
+ : sortedSpecs);
921
943
 
922
944
  const duplicates = [];
923
- for (let i = 0; i < sortedSpecs.length; i += 1) {
924
- for (let j = i + 1; j < sortedSpecs.length; j += 1) {
925
- const left = sortedSpecs[i];
926
- const right = sortedSpecs[j];
945
+ for (let i = 0; i < duplicateCandidateSpecs.length; i += 1) {
946
+ for (let j = i + 1; j < duplicateCandidateSpecs.length; j += 1) {
947
+ const left = duplicateCandidateSpecs[i];
948
+ const right = duplicateCandidateSpecs[j];
927
949
  const similarity = computeJaccard(left.tokens, right.tokens);
928
950
  if (similarity >= threshold) {
929
951
  duplicatePairs += 1;
@@ -12,6 +12,122 @@ const {
12
12
  } = require('../state/state-storage-policy');
13
13
 
14
14
  const TAKEOVER_BASELINE_SCHEMA_VERSION = '1.0';
15
+ const CLARIFICATION_FIRST_CORE_PRINCIPLE_HEADING = '## 11. 业务场景未知时必须先澄清,禁止直接彻底禁用';
16
+ const CLARIFICATION_FIRST_CORE_PRINCIPLE_SECTION = [
17
+ CLARIFICATION_FIRST_CORE_PRINCIPLE_HEADING,
18
+ '',
19
+ '- 不了解业务场景、模块、页面、实体或业务约束时,默认动作是先补上下文、缩小范围、生成澄清问题。',
20
+ '- 禁止把“未知业务场景”直接等同于 `deny`、`disable`、answer-only 或其他一刀切兜底禁用。',
21
+ '- 只有在明确命中安全/权限/合规/破坏性规则后,才允许进入阻断;否则必须先完成业务范围澄清。',
22
+ '- 这条规则适用于所有接入 SCE 的项目、模式和交互面,不允许按项目例外绕过。'
23
+ ].join('\n');
24
+ const NO_BLIND_FIX_CORE_PRINCIPLE_HEADING = '## 12. 禁止盲改问题,必须先建立问题契约和证据';
25
+ const NO_BLIND_FIX_CORE_PRINCIPLE_SECTION = [
26
+ NO_BLIND_FIX_CORE_PRINCIPLE_HEADING,
27
+ '',
28
+ '- 修改问题前,必须先明确现象、复现条件、影响范围、预期行为和验证方式。',
29
+ '- 缺少日志、数据、接口样本、最小复现或问题契约时,应先补证据,不得靠猜测连续改代码碰运气。',
30
+ '- 若两轮修改仍未收敛,必须回到调试、定位和根因分析,禁止在未理解问题前盲目扩大改动面。'
31
+ ].join('\n');
32
+ const STEERING_CHANGE_EVALUATION_CORE_PRINCIPLE_HEADING = '## 13. Steering 条目变更必须先评估,禁止随意增删';
33
+ const STEERING_CHANGE_EVALUATION_CORE_PRINCIPLE_SECTION = [
34
+ STEERING_CHANGE_EVALUATION_CORE_PRINCIPLE_HEADING,
35
+ '',
36
+ '- 新增、删除或重写 steering 条目前,必须先评估它是否真属于长期原则,是否与现有条目重复,是否应迁移到 `CURRENT_CONTEXT.md`、Spec 或项目文档。',
37
+ '- steering 变更必须说明触发原因、适用范围以及与现有规则的关系;未经评估,不得把临时偏好、短期任务或偶发结论直接固化进去。',
38
+ '- 接管、升级和治理脚本只能补齐基线、修复漂移,不能把未经评估的项目习惯直接塞进 steering。'
39
+ ].join('\n');
40
+ const BACKEND_API_PRECEDENCE_CORE_PRINCIPLE_HEADING = '## 14. 问题修复时前后端接口不一致默认以后端契约为准';
41
+ const BACKEND_API_PRECEDENCE_CORE_PRINCIPLE_SECTION = [
42
+ BACKEND_API_PRECEDENCE_CORE_PRINCIPLE_HEADING,
43
+ '',
44
+ '- 在修改问题的场景下,若前端调用后端 API 出现路径、方法、字段、状态码或响应结构不匹配,默认以后端现有接口契约为准。',
45
+ '- 除非明确要求新建接口或修改后端接口,否则禁止为了迁就前端错误调用去随意改后端实现或契约。',
46
+ '- 默认优先修正前端请求、映射、类型和兼容处理,使其与后端接口保持一致;若怀疑后端契约错误,应先确认再改。'
47
+ ].join('\n');
48
+ const REQUIRED_CORE_PRINCIPLE_SECTIONS = Object.freeze([
49
+ {
50
+ heading: CLARIFICATION_FIRST_CORE_PRINCIPLE_HEADING,
51
+ section: CLARIFICATION_FIRST_CORE_PRINCIPLE_SECTION
52
+ },
53
+ {
54
+ heading: NO_BLIND_FIX_CORE_PRINCIPLE_HEADING,
55
+ section: NO_BLIND_FIX_CORE_PRINCIPLE_SECTION
56
+ },
57
+ {
58
+ heading: STEERING_CHANGE_EVALUATION_CORE_PRINCIPLE_HEADING,
59
+ section: STEERING_CHANGE_EVALUATION_CORE_PRINCIPLE_SECTION
60
+ },
61
+ {
62
+ heading: BACKEND_API_PRECEDENCE_CORE_PRINCIPLE_HEADING,
63
+ section: BACKEND_API_PRECEDENCE_CORE_PRINCIPLE_SECTION
64
+ }
65
+ ]);
66
+
67
+ const ERRORBOOK_REGISTRY_DEFAULTS = Object.freeze({
68
+ enabled: true,
69
+ search_mode: 'remote',
70
+ cache_file: '.sce/errorbook/registry-cache.json',
71
+ sources: [
72
+ {
73
+ name: 'central',
74
+ enabled: true,
75
+ url: 'https://raw.githubusercontent.com/heguangyong/sce-errorbook-registry/main/registry/errorbook-registry.json',
76
+ index_url: 'https://raw.githubusercontent.com/heguangyong/sce-errorbook-registry/main/registry/errorbook-registry.index.json'
77
+ }
78
+ ]
79
+ });
80
+
81
+ const ERRORBOOK_CONVERGENCE_DEFAULTS = Object.freeze({
82
+ enabled: true,
83
+ canonical_mechanism: 'errorbook',
84
+ absorb_project_defined_mechanisms: true,
85
+ disallow_parallel_mechanisms: true,
86
+ intake_inventory_path: '.sce/errorbook/project-intake/custom-mechanism-inventory.json',
87
+ strategy: 'absorb_into_sce_errorbook'
88
+ });
89
+
90
+ const ERRORBOOK_PARALLEL_MECHANISM_KEYWORDS = Object.freeze([
91
+ '错题本',
92
+ '错题',
93
+ '故障复盘',
94
+ '事故复盘',
95
+ '问题复盘',
96
+ '复盘册',
97
+ 'mistake-book',
98
+ 'mistake_book',
99
+ 'mistakebook',
100
+ 'fault-book',
101
+ 'fault_book',
102
+ 'postmortem',
103
+ 'post-mortem',
104
+ 'lessons-learned',
105
+ 'lessons_learned',
106
+ 'defect-ledger',
107
+ 'defect_ledger',
108
+ 'issue-ledger',
109
+ 'issue_ledger',
110
+ 'incident-review',
111
+ 'incident_review'
112
+ ]);
113
+
114
+ const ERRORBOOK_SCAN_IGNORED_DIRS = new Set([
115
+ '.git',
116
+ '.hg',
117
+ '.svn',
118
+ '.sce',
119
+ 'node_modules',
120
+ 'dist',
121
+ 'build',
122
+ 'coverage',
123
+ '.next',
124
+ '.turbo',
125
+ '.idea',
126
+ '.vscode',
127
+ 'tmp',
128
+ 'temp',
129
+ '.tmp'
130
+ ]);
15
131
 
16
132
  const SESSION_GOVERNANCE_DEFAULTS = Object.freeze({
17
133
  schema_version: '1.0',
@@ -119,7 +235,8 @@ const STUDIO_INTAKE_POLICY_DEFAULTS = Object.freeze({
119
235
  require_auto_on_plan: true,
120
236
  max_active_specs_per_scene: 3,
121
237
  stale_days: 14,
122
- duplicate_similarity_threshold: 0.66
238
+ duplicate_similarity_threshold: 0.66,
239
+ duplicate_detection_scope: 'non_completed'
123
240
  },
124
241
  backfill: {
125
242
  enabled: true,
@@ -203,7 +320,8 @@ const TAKEOVER_DEFAULTS = Object.freeze({
203
320
  require_auto_on_plan: true,
204
321
  max_active_specs_per_scene: 3,
205
322
  stale_days: 14,
206
- duplicate_similarity_threshold: 0.66
323
+ duplicate_similarity_threshold: 0.66,
324
+ duplicate_detection_scope: 'non_completed'
207
325
  },
208
326
  backfill: {
209
327
  enabled: true,
@@ -217,6 +335,7 @@ const TAKEOVER_DEFAULTS = Object.freeze({
217
335
  max_direct_fix_rounds_before_debug: 2,
218
336
  forbid_bypass_workarounds: true
219
337
  },
338
+ errorbook_convergence: _clone(ERRORBOOK_CONVERGENCE_DEFAULTS),
220
339
  migration_policy: {
221
340
  legacy_kiro_supported: false,
222
341
  require_manual_legacy_migration_confirmation: true
@@ -335,6 +454,113 @@ function _buildTakeoverBaselineConfig(existing, sceVersion) {
335
454
  };
336
455
  }
337
456
 
457
+ function _buildErrorbookRegistryConfig(existing) {
458
+ const base = _isObject(existing) ? _clone(existing) : {};
459
+ return {
460
+ ..._clone(ERRORBOOK_REGISTRY_DEFAULTS),
461
+ ...base,
462
+ sources: Array.isArray(base.sources) && base.sources.length > 0
463
+ ? _clone(base.sources)
464
+ : _clone(ERRORBOOK_REGISTRY_DEFAULTS.sources)
465
+ };
466
+ }
467
+
468
+ function _normalizeRelativePath(value) {
469
+ return `${value || ''}`.replace(/\\/g, '/');
470
+ }
471
+
472
+ function _findParallelErrorbookKeyword(relativePath) {
473
+ const normalized = _normalizeRelativePath(relativePath).toLowerCase();
474
+ return ERRORBOOK_PARALLEL_MECHANISM_KEYWORDS.find((keyword) => normalized.includes(keyword.toLowerCase())) || null;
475
+ }
476
+
477
+ function _shouldSkipErrorbookScanDir(dirName) {
478
+ return ERRORBOOK_SCAN_IGNORED_DIRS.has(`${dirName || ''}`.trim());
479
+ }
480
+
481
+ async function _scanProjectDefinedErrorbookMechanisms(projectPath, fileSystem, currentDir = projectPath, depth = 0, findings = []) {
482
+ let entries = [];
483
+ try {
484
+ entries = await fileSystem.readdir(currentDir, { withFileTypes: true });
485
+ } catch (_error) {
486
+ return findings;
487
+ }
488
+
489
+ for (const entry of entries) {
490
+ const absolutePath = path.join(currentDir, entry.name);
491
+ const relativePath = _toRelativePosix(projectPath, absolutePath);
492
+
493
+ if (entry.isDirectory()) {
494
+ if (_shouldSkipErrorbookScanDir(entry.name)) {
495
+ continue;
496
+ }
497
+
498
+ const keyword = _findParallelErrorbookKeyword(relativePath);
499
+ if (keyword) {
500
+ findings.push({
501
+ path: relativePath,
502
+ kind: 'directory',
503
+ keyword,
504
+ action: 'absorb_into_sce_errorbook'
505
+ });
506
+ }
507
+
508
+ if (depth < 5) {
509
+ await _scanProjectDefinedErrorbookMechanisms(projectPath, fileSystem, absolutePath, depth + 1, findings);
510
+ }
511
+ continue;
512
+ }
513
+
514
+ if (!entry.isFile()) {
515
+ continue;
516
+ }
517
+
518
+ const keyword = _findParallelErrorbookKeyword(relativePath);
519
+ if (!keyword) {
520
+ continue;
521
+ }
522
+
523
+ findings.push({
524
+ path: relativePath,
525
+ kind: 'file',
526
+ keyword,
527
+ action: 'absorb_into_sce_errorbook'
528
+ });
529
+ }
530
+
531
+ return findings;
532
+ }
533
+
534
+ function _dedupeFindings(findings = []) {
535
+ const seen = new Set();
536
+ return findings.filter((item) => {
537
+ const key = `${item.kind}:${item.path}`;
538
+ if (seen.has(key)) {
539
+ return false;
540
+ }
541
+ seen.add(key);
542
+ return true;
543
+ }).sort((left, right) => `${left.path}`.localeCompare(`${right.path}`));
544
+ }
545
+
546
+ function _buildErrorbookConvergenceInventory(sceVersion, findings = []) {
547
+ const dedupedFindings = _dedupeFindings(findings);
548
+ return {
549
+ schema_version: TAKEOVER_BASELINE_SCHEMA_VERSION,
550
+ canonical_mechanism: 'errorbook',
551
+ strategy: 'absorb_into_sce_errorbook',
552
+ last_aligned_sce_version: sceVersion,
553
+ summary: {
554
+ detected_custom_mechanisms: dedupedFindings.length
555
+ },
556
+ guidance: [
557
+ 'Absorb reusable failure-remediation knowledge into .sce/errorbook instead of keeping project-defined parallel mistake-book flows.',
558
+ 'Use `sce errorbook record` for curated entries and `sce errorbook incident *` for short trial-and-error loops before curation.'
559
+ ],
560
+ findings: dedupedFindings
561
+ };
562
+ }
563
+
338
564
  async function _reconcileJsonFile(filePath, desired, options = {}) {
339
565
  const {
340
566
  projectPath,
@@ -403,6 +629,37 @@ async function _reconcileSteeringContract(projectPath, options = {}) {
403
629
  };
404
630
  }
405
631
 
632
+ async function _reconcileCorePrinciplesBaseline(projectPath, options = {}) {
633
+ const { apply, fileSystem } = options;
634
+ const corePrinciplesPath = path.join(projectPath, SCE_STEERING_DIR, DEFAULT_LAYER_FILES.core_principles);
635
+ const exists = await fileSystem.pathExists(corePrinciplesPath);
636
+ const existingContent = exists ? await fileSystem.readFile(corePrinciplesPath, 'utf8') : '';
637
+ const missingSections = REQUIRED_CORE_PRINCIPLE_SECTIONS.filter(({ heading }) => !existingContent.includes(heading));
638
+ const changed = missingSections.length > 0;
639
+
640
+ if (apply && changed) {
641
+ const normalized = `${existingContent || ''}`.trimEnd();
642
+ const appendedSections = missingSections.map((item) => item.section).join('\n\n');
643
+ const nextContent = normalized
644
+ ? `${normalized}\n\n${appendedSections}\n`
645
+ : `${appendedSections}\n`;
646
+ await fileSystem.ensureDir(path.dirname(corePrinciplesPath));
647
+ await fileSystem.writeFile(corePrinciplesPath, nextContent, 'utf8');
648
+ }
649
+
650
+ return {
651
+ path: _toRelativePosix(projectPath, corePrinciplesPath),
652
+ existed: exists,
653
+ changed,
654
+ status: !exists ? (changed ? 'created' : 'unchanged') : (changed ? 'updated' : 'unchanged'),
655
+ managed_by: 'takeover-baseline',
656
+ details: {
657
+ missing_required_headings_before: missingSections.map((item) => item.heading),
658
+ required_headings: REQUIRED_CORE_PRINCIPLE_SECTIONS.map((item) => item.heading)
659
+ }
660
+ };
661
+ }
662
+
406
663
  function _summarize(items) {
407
664
  const summary = {
408
665
  created: 0,
@@ -477,33 +734,38 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
477
734
  const adoptionPath = path.join(sceRoot, 'adoption-config.json');
478
735
  const autoConfigPath = path.join(sceRoot, 'auto', 'config.json');
479
736
  const takeoverConfigPath = path.join(sceRoot, 'config', 'takeover-baseline.json');
737
+ const errorbookRegistryPath = path.join(sceRoot, 'config', 'errorbook-registry.json');
480
738
  const sessionGovernancePath = path.join(sceRoot, 'config', 'session-governance.json');
481
739
  const specDomainPolicyPath = path.join(sceRoot, 'config', 'spec-domain-policy.json');
482
740
  const problemEvalPolicyPath = path.join(sceRoot, 'config', 'problem-eval-policy.json');
483
741
  const problemClosurePolicyPath = path.join(sceRoot, 'config', 'problem-closure-policy.json');
484
742
  const studioIntakePolicyPath = path.join(sceRoot, 'config', 'studio-intake-policy.json');
485
743
  const stateStoragePolicyPath = path.join(sceRoot, 'config', 'state-storage-policy.json');
744
+ const errorbookInventoryPath = path.join(sceRoot, 'errorbook', 'project-intake', 'custom-mechanism-inventory.json');
486
745
  const reportPath = path.join(sceRoot, 'reports', 'takeover-baseline-latest.json');
487
746
 
488
747
  const existingAdoption = await _readJsonSafe(adoptionPath, fileSystem);
489
748
  const existingAuto = await _readJsonSafe(autoConfigPath, fileSystem);
490
749
  const existingTakeover = await _readJsonSafe(takeoverConfigPath, fileSystem);
750
+ const existingErrorbookRegistry = await _readJsonSafe(errorbookRegistryPath, fileSystem);
491
751
  const existingSessionGovernance = await _readJsonSafe(sessionGovernancePath, fileSystem);
492
752
  const existingSpecDomainPolicy = await _readJsonSafe(specDomainPolicyPath, fileSystem);
493
753
  const existingProblemEvalPolicy = await _readJsonSafe(problemEvalPolicyPath, fileSystem);
494
754
  const existingProblemClosurePolicy = await _readJsonSafe(problemClosurePolicyPath, fileSystem);
495
755
  const existingStudioIntakePolicy = await _readJsonSafe(studioIntakePolicyPath, fileSystem);
496
756
  const existingStateStoragePolicy = await _readJsonSafe(stateStoragePolicyPath, fileSystem);
497
-
498
757
  const desiredAdoption = _buildAdoptionConfig(existingAdoption, nowIso, sceVersion);
499
758
  const desiredAutoConfig = _buildAutoConfig(existingAuto);
500
759
  const desiredTakeover = _buildTakeoverBaselineConfig(existingTakeover, sceVersion);
760
+ const desiredErrorbookRegistry = _buildErrorbookRegistryConfig(existingErrorbookRegistry);
501
761
  const desiredSessionGovernance = _deepMerge(existingSessionGovernance || {}, SESSION_GOVERNANCE_DEFAULTS);
502
762
  const desiredSpecDomainPolicy = _deepMerge(existingSpecDomainPolicy || {}, SPEC_DOMAIN_POLICY_DEFAULTS);
503
763
  const desiredProblemEvalPolicy = _deepMerge(existingProblemEvalPolicy || {}, PROBLEM_EVAL_POLICY_DEFAULTS);
504
764
  const desiredProblemClosurePolicy = _deepMerge(existingProblemClosurePolicy || {}, PROBLEM_CLOSURE_POLICY_DEFAULTS);
505
765
  const desiredStudioIntakePolicy = _deepMerge(existingStudioIntakePolicy || {}, STUDIO_INTAKE_POLICY_DEFAULTS);
506
766
  const desiredStateStoragePolicy = _deepMerge(existingStateStoragePolicy || {}, cloneStateStoragePolicyDefaults());
767
+ const customErrorbookFindings = await _scanProjectDefinedErrorbookMechanisms(projectPath, fileSystem);
768
+ const desiredErrorbookInventory = _buildErrorbookConvergenceInventory(sceVersion, customErrorbookFindings);
507
769
 
508
770
  const fileResults = [];
509
771
  fileResults.push(await _reconcileJsonFile(adoptionPath, desiredAdoption, {
@@ -521,6 +783,11 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
521
783
  apply,
522
784
  fileSystem
523
785
  }));
786
+ fileResults.push(await _reconcileJsonFile(errorbookRegistryPath, desiredErrorbookRegistry, {
787
+ projectPath,
788
+ apply,
789
+ fileSystem
790
+ }));
524
791
  fileResults.push(await _reconcileJsonFile(sessionGovernancePath, desiredSessionGovernance, {
525
792
  projectPath,
526
793
  apply,
@@ -551,10 +818,20 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
551
818
  apply,
552
819
  fileSystem
553
820
  }));
821
+ fileResults.push(await _reconcileJsonFile(errorbookInventoryPath, desiredErrorbookInventory, {
822
+ projectPath,
823
+ apply,
824
+ fileSystem,
825
+ managedBy: 'errorbook-convergence'
826
+ }));
554
827
  fileResults.push(await _reconcileSteeringContract(projectPath, {
555
828
  apply,
556
829
  fileSystem
557
830
  }));
831
+ fileResults.push(await _reconcileCorePrinciplesBaseline(projectPath, {
832
+ apply,
833
+ fileSystem
834
+ }));
558
835
 
559
836
  const auditFiles = _toAuditStatus(fileResults, apply);
560
837
  const summary = _summarize(auditFiles);
@@ -571,6 +848,12 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
571
848
  sce_version: sceVersion,
572
849
  drift_count: driftCount,
573
850
  enforced_defaults: _clone(TAKEOVER_DEFAULTS),
851
+ errorbook_convergence: {
852
+ canonical_mechanism: 'errorbook',
853
+ strategy: 'absorb_into_sce_errorbook',
854
+ detected_custom_mechanism_count: desiredErrorbookInventory.summary.detected_custom_mechanisms,
855
+ inventory_file: _toRelativePosix(projectPath, errorbookInventoryPath)
856
+ },
574
857
  files: auditFiles,
575
858
  summary
576
859
  };
@@ -595,6 +878,17 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
595
878
  }
596
879
 
597
880
  module.exports = {
881
+ CLARIFICATION_FIRST_CORE_PRINCIPLE_HEADING,
882
+ CLARIFICATION_FIRST_CORE_PRINCIPLE_SECTION,
883
+ NO_BLIND_FIX_CORE_PRINCIPLE_HEADING,
884
+ NO_BLIND_FIX_CORE_PRINCIPLE_SECTION,
885
+ STEERING_CHANGE_EVALUATION_CORE_PRINCIPLE_HEADING,
886
+ STEERING_CHANGE_EVALUATION_CORE_PRINCIPLE_SECTION,
887
+ BACKEND_API_PRECEDENCE_CORE_PRINCIPLE_HEADING,
888
+ BACKEND_API_PRECEDENCE_CORE_PRINCIPLE_SECTION,
889
+ REQUIRED_CORE_PRINCIPLE_SECTIONS,
890
+ ERRORBOOK_REGISTRY_DEFAULTS,
891
+ ERRORBOOK_CONVERGENCE_DEFAULTS,
598
892
  TAKEOVER_BASELINE_SCHEMA_VERSION,
599
893
  TAKEOVER_DEFAULTS,
600
894
  SESSION_GOVERNANCE_DEFAULTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.46",
3
+ "version": "3.6.48",
4
4
  "description": "SCE (Scene Capability Engine) - A CLI tool and npm package for spec-driven development with AI coding assistants.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -41,8 +41,10 @@
41
41
  "gate:npm-runtime-assets": "node scripts/npm-package-runtime-asset-check.js --fail-on-violation",
42
42
  "test:brand-consistency": "node scripts/check-branding-consistency.js",
43
43
  "audit:steering": "node scripts/steering-content-audit.js --fail-on-error",
44
+ "audit:clarification-first": "node scripts/clarification-first-audit.js --fail-on-violation",
44
45
  "audit:state-storage": "node scripts/state-storage-tiering-audit.js",
45
46
  "report:steering-audit": "node scripts/steering-content-audit.js --json",
47
+ "report:clarification-first-audit": "node scripts/clarification-first-audit.js --json",
46
48
  "report:state-storage": "node scripts/state-storage-tiering-audit.js --json",
47
49
  "report:interactive-approval-projection": "node scripts/interactive-approval-event-projection.js --action doctor --json",
48
50
  "audit:interactive-approval-projection": "node scripts/interactive-approval-event-projection.js --action doctor --fail-on-drift --fail-on-parse-error",
@@ -84,7 +86,7 @@
84
86
  "gate:release-asset-integrity": "node scripts/release-asset-integrity-check.js",
85
87
  "report:release-risk-remediation": "node scripts/release-risk-remediation-bundle.js --json",
86
88
  "report:moqui-core-regression": "node scripts/moqui-core-regression-suite.js --json",
87
- "prepublishOnly": "npm run test:release && npm run test:skip-audit && npm run test:sce-tracking && npm run gate:npm-runtime-assets && npm run test:brand-consistency && npm run audit:steering && npm run gate:git-managed && npm run gate:errorbook-registry-health && npm run gate:errorbook-release && npm run report:interactive-governance -- --fail-on-alert",
89
+ "prepublishOnly": "npm run test:release && npm run test:skip-audit && npm run test:sce-tracking && npm run gate:npm-runtime-assets && npm run test:brand-consistency && npm run audit:steering && npm run audit:clarification-first && npm run gate:git-managed && npm run gate:errorbook-registry-health && npm run gate:errorbook-release && npm run report:interactive-governance -- --fail-on-alert",
88
90
  "publish:manual": "npm publish --access public",
89
91
  "install-global": "npm install -g .",
90
92
  "uninstall-global": "npm uninstall -g scene-capability-engine"
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const REQUIRED_CHECKS = [
8
+ {
9
+ path: 'scripts/interactive-dialogue-governance.js',
10
+ requiredSnippets: [
11
+ 'contextHasBusinessScope',
12
+ 'goalMentionsBusinessScope',
13
+ 'business scene/module/page/entity context is missing; clarify scope before fallback or execution',
14
+ 'Which module/page/entity is affected first?'
15
+ ]
16
+ },
17
+ {
18
+ path: 'scripts/symbol-evidence-locate.js',
19
+ requiredSnippets: [
20
+ 'clarify_business_scope',
21
+ 'Clarify target module/page/entity and business constraints before deciding whether scoped writes are safe.'
22
+ ]
23
+ },
24
+ {
25
+ path: 'lib/workspace/takeover-baseline.js',
26
+ requiredSnippets: [
27
+ 'CLARIFICATION_FIRST_CORE_PRINCIPLE_HEADING',
28
+ '_reconcileCorePrinciplesBaseline',
29
+ '这条规则适用于所有接入 SCE 的项目、模式和交互面,不允许按项目例外绕过。'
30
+ ]
31
+ },
32
+ {
33
+ path: 'docs/interactive-customization/dialogue-governance-policy-baseline.json',
34
+ requiredSnippets: [
35
+ 'clarify and narrow scope before using any fallback restriction; do not replace understanding with blanket disable',
36
+ 'Which entity or business rule is affected, and what constraint must stay intact?'
37
+ ]
38
+ },
39
+ {
40
+ path: '.sce/steering/CORE_PRINCIPLES.md',
41
+ requiredSnippets: [
42
+ '业务场景未知时必须先澄清,禁止直接彻底禁用',
43
+ '这条规则适用于所有接入 SCE 的项目、模式和交互面,不允许按项目例外绕过。'
44
+ ]
45
+ },
46
+ {
47
+ path: 'template/.sce/steering/CORE_PRINCIPLES.md',
48
+ requiredSnippets: [
49
+ '业务场景未知时先澄清,不得直接彻底禁用',
50
+ '这条规则适用于所有使用 SCE 的项目,不设项目级例外。'
51
+ ]
52
+ },
53
+ {
54
+ path: 'README.md',
55
+ requiredSnippets: [
56
+ 'When business scene/module/page/entity context is missing, SCE must route to clarification first; unknown business scope must not be turned into blanket disable.'
57
+ ]
58
+ },
59
+ {
60
+ path: 'README.zh.md',
61
+ requiredSnippets: [
62
+ '缺少业务场景/模块/页面/实体上下文时,SCE 必须先进入澄清,而不是把未知业务范围直接变成一刀切禁用'
63
+ ]
64
+ },
65
+ {
66
+ path: 'docs/security-governance-default-baseline.md',
67
+ requiredSnippets: [
68
+ 'Missing business scene/module/page/entity context must route to clarification first; unknown scope is never a valid reason for blanket disable.',
69
+ 'This clarification-first rule applies to every SCE-integrated project and surface with no project-specific exception.'
70
+ ]
71
+ },
72
+ {
73
+ path: 'docs/starter-kit/README.md',
74
+ requiredSnippets: [
75
+ 'This baseline applies to every onboarded project with no project-specific exception.',
76
+ 'missing business scope is handled through clarification, not blanket disable fallback.'
77
+ ]
78
+ },
79
+ {
80
+ path: 'docs/command-reference.md',
81
+ requiredSnippets: [
82
+ 'Missing business scene/module/page/entity context defaults to `clarify`; unknown scope must not be converted into blanket disable fallback.'
83
+ ]
84
+ }
85
+ ];
86
+
87
+ const PROHIBITED_SNIPPETS = [
88
+ {
89
+ value: 'block_high_risk_write',
90
+ allowedPaths: [
91
+ 'scripts/clarification-first-audit.js',
92
+ 'tests/unit/scripts/clarification-first-audit.test.js'
93
+ ]
94
+ },
95
+ {
96
+ value: 'Fallback to answer-only mode and block high-risk writes.',
97
+ allowedPaths: [
98
+ 'scripts/clarification-first-audit.js',
99
+ 'tests/unit/scripts/clarification-first-audit.test.js'
100
+ ]
101
+ }
102
+ ];
103
+
104
+ const SEARCH_DIRECTORIES = ['lib', 'scripts', 'docs', '.sce', 'template', 'tests'];
105
+ const SEARCH_EXTENSIONS = new Set(['.js', '.md', '.json', '.txt', '.yaml', '.yml']);
106
+
107
+ function parseArgs(argv = process.argv.slice(2)) {
108
+ const options = {
109
+ projectPath: process.cwd(),
110
+ json: false,
111
+ failOnViolation: false,
112
+ out: null
113
+ };
114
+
115
+ for (let index = 0; index < argv.length; index += 1) {
116
+ const token = argv[index];
117
+ const next = argv[index + 1];
118
+ if (token === '--project-path' && next) {
119
+ options.projectPath = path.resolve(next);
120
+ index += 1;
121
+ continue;
122
+ }
123
+ if (token === '--json') {
124
+ options.json = true;
125
+ continue;
126
+ }
127
+ if (token === '--fail-on-violation') {
128
+ options.failOnViolation = true;
129
+ continue;
130
+ }
131
+ if (token === '--out' && next) {
132
+ options.out = path.resolve(next);
133
+ index += 1;
134
+ continue;
135
+ }
136
+ if (token === '--help' || token === '-h') {
137
+ printHelpAndExit(0);
138
+ }
139
+ }
140
+
141
+ return options;
142
+ }
143
+
144
+ function printHelpAndExit(code) {
145
+ const lines = [
146
+ 'Usage: node scripts/clarification-first-audit.js [options]',
147
+ '',
148
+ 'Options:',
149
+ ' --project-path <path> Project root to audit (default: current directory)',
150
+ ' --json Print JSON payload',
151
+ ' --fail-on-violation Exit code 2 when any violation is found',
152
+ ' --out <path> Write JSON payload to file',
153
+ ' -h, --help Show this help'
154
+ ];
155
+ console.log(lines.join('\n'));
156
+ process.exit(code);
157
+ }
158
+
159
+ function normalizeSlashes(value) {
160
+ return `${value || ''}`.replace(/\\/g, '/');
161
+ }
162
+
163
+ function pushViolation(violations, severity, rule, file, message) {
164
+ violations.push({
165
+ severity,
166
+ rule,
167
+ file,
168
+ message
169
+ });
170
+ }
171
+
172
+ function readText(filePath) {
173
+ return fs.readFileSync(filePath, 'utf8');
174
+ }
175
+
176
+ function collectFilesRecursive(rootDir, relativeRoot = '') {
177
+ if (!fs.existsSync(rootDir)) {
178
+ return [];
179
+ }
180
+ const results = [];
181
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
182
+ for (const entry of entries) {
183
+ const absolutePath = path.join(rootDir, entry.name);
184
+ const relativePath = normalizeSlashes(path.join(relativeRoot, entry.name));
185
+ if (entry.isDirectory()) {
186
+ if (entry.name === 'node_modules' || entry.name === '.git') {
187
+ continue;
188
+ }
189
+ results.push(...collectFilesRecursive(absolutePath, relativePath));
190
+ continue;
191
+ }
192
+ if (!entry.isFile()) {
193
+ continue;
194
+ }
195
+ if (!SEARCH_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
196
+ continue;
197
+ }
198
+ results.push({
199
+ absolutePath,
200
+ relativePath
201
+ });
202
+ }
203
+ return results;
204
+ }
205
+
206
+ function auditClarificationFirst(options = {}) {
207
+ const projectPath = path.resolve(options.projectPath || process.cwd());
208
+ const violations = [];
209
+ const checkedFiles = [];
210
+
211
+ for (const check of REQUIRED_CHECKS) {
212
+ const relativePath = normalizeSlashes(check.path);
213
+ const absolutePath = path.join(projectPath, relativePath);
214
+ checkedFiles.push(relativePath);
215
+ if (!fs.existsSync(absolutePath)) {
216
+ pushViolation(
217
+ violations,
218
+ 'error',
219
+ 'missing_required_file',
220
+ relativePath,
221
+ `Required clarification-first baseline file is missing: ${relativePath}`
222
+ );
223
+ continue;
224
+ }
225
+
226
+ const content = readText(absolutePath);
227
+ for (const snippet of check.requiredSnippets) {
228
+ if (!content.includes(snippet)) {
229
+ pushViolation(
230
+ violations,
231
+ 'error',
232
+ 'missing_required_snippet',
233
+ relativePath,
234
+ `Missing required clarification-first snippet: ${snippet}`
235
+ );
236
+ }
237
+ }
238
+ }
239
+
240
+ const searchableFiles = SEARCH_DIRECTORIES.flatMap((dirName) => {
241
+ const absoluteDir = path.join(projectPath, dirName);
242
+ return collectFilesRecursive(absoluteDir, dirName);
243
+ });
244
+
245
+ for (const file of searchableFiles) {
246
+ const content = readText(file.absolutePath);
247
+ for (const rule of PROHIBITED_SNIPPETS) {
248
+ if (!content.includes(rule.value)) {
249
+ continue;
250
+ }
251
+ const allowedPaths = Array.isArray(rule.allowedPaths) ? rule.allowedPaths.map(normalizeSlashes) : [];
252
+ if (allowedPaths.includes(file.relativePath)) {
253
+ continue;
254
+ }
255
+ pushViolation(
256
+ violations,
257
+ 'error',
258
+ 'prohibited_legacy_disable_phrase',
259
+ file.relativePath,
260
+ `Prohibited legacy fallback phrase found: ${rule.value}`
261
+ );
262
+ }
263
+ }
264
+
265
+ const errorCount = violations.filter((item) => item.severity === 'error').length;
266
+ return {
267
+ mode: 'clarification-first-audit',
268
+ project_path: projectPath,
269
+ checked_files: checkedFiles,
270
+ searched_file_count: searchableFiles.length,
271
+ violation_count: violations.length,
272
+ error_count: errorCount,
273
+ passed: violations.length === 0,
274
+ violations
275
+ };
276
+ }
277
+
278
+ function writeReportIfNeeded(report, outPath) {
279
+ if (!outPath) {
280
+ return;
281
+ }
282
+ const resolved = path.resolve(outPath);
283
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
284
+ fs.writeFileSync(resolved, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
285
+ }
286
+
287
+ function main() {
288
+ const options = parseArgs(process.argv.slice(2));
289
+ const report = auditClarificationFirst(options);
290
+ writeReportIfNeeded(report, options.out);
291
+
292
+ if (options.json) {
293
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
294
+ } else if (report.passed) {
295
+ console.log('[clarification-first-audit] passed');
296
+ } else {
297
+ console.error(`[clarification-first-audit] failed with ${report.violation_count} violation(s)`);
298
+ for (const violation of report.violations) {
299
+ console.error(`[clarification-first-audit] ${violation.rule} ${violation.file}: ${violation.message}`);
300
+ }
301
+ }
302
+
303
+ if (options.failOnViolation && !report.passed) {
304
+ process.exitCode = 2;
305
+ }
306
+ }
307
+
308
+ if (require.main === module) {
309
+ try {
310
+ main();
311
+ } catch (error) {
312
+ console.error(`[clarification-first-audit] ${error.message}`);
313
+ process.exit(1);
314
+ }
315
+ }
316
+
317
+ module.exports = {
318
+ REQUIRED_CHECKS,
319
+ PROHIBITED_SNIPPETS,
320
+ parseArgs,
321
+ auditClarificationFirst
322
+ };
@@ -46,11 +46,13 @@ const BUILTIN_POLICY = {
46
46
  'Always restate objective, scope, and expected impact before any action recommendation.',
47
47
  'When risk or permission is involved, explicitly tell user what approval is required.',
48
48
  'If requirement is ambiguous, ask at most two focused clarification questions.',
49
+ 'If business scene, module, page, entity, or constraints are missing, clarify scope first; never replace missing understanding with blanket disable.',
49
50
  'Never propose credential export, approval bypass, or secret leakage.'
50
51
  ],
51
52
  clarification_templates: [
52
53
  'What business outcome should improve first (speed, accuracy, cost, compliance)?',
53
- 'Which page or module should be changed first, and what should stay unchanged?'
54
+ 'Which page or module should be changed first, and what should stay unchanged?',
55
+ 'Which entity or business rule is affected, and what constraint must remain unchanged?'
54
56
  ],
55
57
  profiles: {
56
58
  'business-user': {
@@ -80,6 +82,7 @@ const BUILTIN_POLICY = {
80
82
  ],
81
83
  response_rules: [
82
84
  'For maintenance requests, require change ticket, rollback plan, and approval role before execution.',
85
+ 'When business context is incomplete, ask for the affected module/page/entity before deny or write restriction decisions.',
83
86
  'If request targets production, require staged validation evidence first.'
84
87
  ],
85
88
  clarification_templates: [
@@ -686,11 +689,18 @@ function evaluatePatternRules(goal, rules) {
686
689
  function pickClarificationQuestions(policy, context = {}) {
687
690
  const questions = [];
688
691
  const templates = Array.isArray(policy.clarification_templates) ? policy.clarification_templates : [];
689
- if (!context.module) {
690
- questions.push('Which module should be changed first?');
691
- }
692
- if (!context.page) {
693
- questions.push('Which page or screen is currently problematic?');
692
+ if (!context.module && !context.page && !context.entity) {
693
+ questions.push('Which module/page/entity is affected first?');
694
+ } else {
695
+ if (!context.module) {
696
+ questions.push('Which module should be changed first?');
697
+ }
698
+ if (!context.page) {
699
+ questions.push('Which page or screen is currently problematic?');
700
+ }
701
+ if (!context.entity) {
702
+ questions.push('Which entity or business rule is affected?');
703
+ }
694
704
  }
695
705
  for (const template of templates) {
696
706
  if (questions.length >= 2) {
@@ -703,11 +713,26 @@ function pickClarificationQuestions(policy, context = {}) {
703
713
  return questions.slice(0, 2);
704
714
  }
705
715
 
716
+ function contextHasBusinessScope(context = {}) {
717
+ const payload = context && typeof context === 'object' ? context : {};
718
+ return ['module', 'page', 'entity', 'scene_id', 'workflow_node']
719
+ .some((key) => normalizeText(payload[key]));
720
+ }
721
+
722
+ function goalMentionsBusinessScope(goal = '') {
723
+ const normalizedGoal = normalizeText(goal).toLowerCase();
724
+ if (!normalizedGoal) {
725
+ return false;
726
+ }
727
+ return /\b(page|screen|module|entity|workflow|scene)\b/.test(normalizedGoal);
728
+ }
729
+
706
730
  function evaluateDialogue(goal, context = {}, policy) {
707
731
  const normalizedGoal = normalizeText(goal);
708
732
  const tokens = normalizedGoal.split(/\s+/).filter(Boolean);
709
733
  const denyHits = evaluatePatternRules(normalizedGoal, policy.deny_patterns);
710
734
  const clarifyHits = evaluatePatternRules(normalizedGoal, policy.clarify_patterns);
735
+ const missingBusinessScope = !contextHasBusinessScope(context) && !goalMentionsBusinessScope(normalizedGoal);
711
736
 
712
737
  const reasons = [];
713
738
  if (normalizedGoal.length < policy.length_policy.min_chars) {
@@ -719,6 +744,9 @@ function evaluateDialogue(goal, context = {}, policy) {
719
744
  if (tokens.length < policy.length_policy.min_significant_tokens) {
720
745
  reasons.push(`goal has too few significant tokens (< ${policy.length_policy.min_significant_tokens})`);
721
746
  }
747
+ if (missingBusinessScope) {
748
+ reasons.push('business scene/module/page/entity context is missing; clarify scope before fallback or execution');
749
+ }
722
750
  reasons.push(...denyHits.map(item => item.reason));
723
751
  reasons.push(...clarifyHits.map(item => item.reason));
724
752
 
@@ -726,6 +754,7 @@ function evaluateDialogue(goal, context = {}, policy) {
726
754
  if (denyHits.length > 0) {
727
755
  decision = 'deny';
728
756
  } else if (
757
+ missingBusinessScope ||
729
758
  clarifyHits.length > 0 ||
730
759
  normalizedGoal.length < policy.length_policy.min_chars ||
731
760
  tokens.length < policy.length_policy.min_significant_tokens
@@ -736,6 +765,7 @@ function evaluateDialogue(goal, context = {}, policy) {
736
765
  return {
737
766
  decision,
738
767
  reasons: Array.from(new Set(reasons)),
768
+ business_scope_clarification_required: missingBusinessScope,
739
769
  deny_hits: denyHits,
740
770
  clarify_hits: clarifyHits,
741
771
  response_rules: Array.isArray(policy.response_rules) ? policy.response_rules : [],
@@ -749,6 +779,7 @@ function toContextRef(context) {
749
779
  product: normalizeText(payload.product || payload.app || ''),
750
780
  module: normalizeText(payload.module || ''),
751
781
  page: normalizeText(payload.page || ''),
782
+ entity: normalizeText(payload.entity || ''),
752
783
  scene_id: normalizeText(payload.scene_id || '')
753
784
  };
754
785
  }
@@ -7,6 +7,8 @@ const path = require('path');
7
7
  const DEFAULT_MAX_HITS = 10;
8
8
  const DEFAULT_MIN_SCORE = 0.35;
9
9
  const DEFAULT_MIN_RELIABLE_SCORE = 0.6;
10
+ const FALLBACK_ACTION_ALLOW_WRITE = 'allow_write';
11
+ const FALLBACK_ACTION_CLARIFY_BUSINESS_SCOPE = 'clarify_business_scope';
10
12
  const DEFAULT_EXTENSIONS = [
11
13
  '.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx',
12
14
  '.py', '.java', '.go', '.rb', '.php', '.cs',
@@ -121,7 +123,7 @@ function printHelpAndExit(code) {
121
123
  ` --min-score <0-1> Minimum hit score (default: ${DEFAULT_MIN_SCORE})`,
122
124
  ` --min-reliable-score <0-1> Reliability threshold (default: ${DEFAULT_MIN_RELIABLE_SCORE})`,
123
125
  ` --extensions <csv> File extensions (default: ${DEFAULT_EXTENSIONS.join(',')})`,
124
- ' --strict Exit code 2 when no reliable evidence is found',
126
+ ' --strict Exit code 2 when no reliable evidence is found and scope clarification is required',
125
127
  ' --json Print JSON payload',
126
128
  ' -h, --help Show this help'
127
129
  ];
@@ -306,10 +308,12 @@ function locateSymbolEvidence({
306
308
  evidence: {
307
309
  confidence,
308
310
  reliable,
309
- fallback_action: reliable ? 'allow_write' : 'block_high_risk_write',
311
+ fallback_action: reliable
312
+ ? FALLBACK_ACTION_ALLOW_WRITE
313
+ : FALLBACK_ACTION_CLARIFY_BUSINESS_SCOPE,
310
314
  advisory: reliable
311
315
  ? 'Symbol evidence is reliable; scoped code change can proceed.'
312
- : 'No reliable symbol evidence found. Fallback to answer-only mode and block high-risk writes.'
316
+ : 'No reliable symbol evidence found yet. Clarify target module/page/entity and business constraints before deciding whether scoped writes are safe.'
313
317
  },
314
318
  summary: {
315
319
  searched_files: candidateFiles.length,
@@ -15,6 +15,7 @@ This project uses **Spec-driven development** - a structured approach where:
15
15
  - When user requests a feature → Check if Spec exists, if not, help create one
16
16
  - When implementing → Follow the Spec's requirements and design
17
17
  - When stuck → Read the Spec documents for context
18
+ - When business scene/module/page/entity is unclear → Clarify scope first; do not replace missing understanding with blanket disable
18
19
  - Track progress by updating task status
19
20
 
20
21
  ---
@@ -22,6 +22,7 @@
22
22
  ## 4. 复用已有机制,不要平行造轮子
23
23
 
24
24
  - 已有能力优先复用,例如缺陷经验与发布阻断统一走 `errorbook`。
25
+ - 若接管项目中已存在自定义缺陷复盘册、故障账本或问题经验库,必须统一盘点并吸收到 `.sce/errorbook`,不要长期并存混用。
25
26
  - 不要在 steering 中额外定义另一套错题、发布、会话或治理模式。
26
27
 
27
28
  ## 5. 质量问题必须追根
@@ -33,3 +34,27 @@
33
34
 
34
35
  - 每周、发布前、重大 Spec 收尾后运行 `npm run audit:steering`。
35
36
  - 发现问题时优先合并重复、迁移错层、归档历史、删除失效条目。
37
+
38
+ ## 7. 业务场景未知时先澄清,不得直接彻底禁用
39
+
40
+ - 不了解业务场景、模块、页面、实体或业务约束时,先补上下文并缩小范围。
41
+ - 禁止把“暂时没理解场景”直接变成 blanket disable、answer-only 或一刀切阻断。
42
+ - 只有明确命中安全、权限、合规或破坏性规则时,才允许阻断;否则先澄清业务范围。
43
+ - 这条规则适用于所有使用 SCE 的项目,不设项目级例外。
44
+
45
+ ## 8. 禁止盲改问题
46
+
47
+ - 修问题前先明确现象、复现条件、影响范围和验证方式。
48
+ - 缺少证据时先补日志、数据、接口样本或最小复现,不要靠猜测连续改代码。
49
+ - 两轮修改仍未收敛时,先回到调试和根因分析,不要盲目扩大改动面。
50
+
51
+ ## 9. Steering 变更先评估,不得随意增删
52
+
53
+ - 新增、删除或重写 steering 条目前,先判断它是否真属于长期原则,是否应迁到 `CURRENT_CONTEXT.md`、Spec 或项目文档。
54
+ - 未经评估,不要把临时偏好、短期任务或偶发结论直接固化进 steering。
55
+
56
+ ## 10. 问题修复时前后端接口不一致默认以后端契约为准
57
+
58
+ - 前端调用后端 API 不匹配时,默认以后端现有接口契约为准。
59
+ - 除非明确要求新建或修改后端接口,否则不要为了迁就前端错误调用去改后端。
60
+ - 优先调整前端请求、映射、类型和兼容处理,使其与后端接口一致。