scene-capability-engine 3.0.0

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.
Files changed (336) hide show
  1. package/CHANGELOG.md +2513 -0
  2. package/LICENSE +21 -0
  3. package/README.md +765 -0
  4. package/README.zh.md +630 -0
  5. package/bin/kiro-spec-engine.js +796 -0
  6. package/bin/kse.js +3 -0
  7. package/bin/sce.js +3 -0
  8. package/bin/sco.js +3 -0
  9. package/docs/331-poc-adaptation-roadmap.md +156 -0
  10. package/docs/331-poc-dual-track-integration-guide.md +120 -0
  11. package/docs/331-poc-weekly-delivery-checklist.md +52 -0
  12. package/docs/OFFLINE_INSTALL.md +96 -0
  13. package/docs/README.md +279 -0
  14. package/docs/adopt-migration-guide.md +599 -0
  15. package/docs/adoption-guide.md +616 -0
  16. package/docs/agent-hooks-analysis.md +815 -0
  17. package/docs/architecture.md +733 -0
  18. package/docs/articles/ai-driven-development-philosophy-and-practice-review.md +208 -0
  19. package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +459 -0
  20. package/docs/articles/ai-driven-development-philosophy-and-practice.md +492 -0
  21. package/docs/autonomous-control-guide.md +851 -0
  22. package/docs/command-reference.md +1368 -0
  23. package/docs/community.md +115 -0
  24. package/docs/cross-tool-guide.md +555 -0
  25. package/docs/developer-guide.md +619 -0
  26. package/docs/document-governance.md +865 -0
  27. package/docs/environment-management-guide.md +526 -0
  28. package/docs/examples/add-export-command/design.md +194 -0
  29. package/docs/examples/add-export-command/requirements.md +110 -0
  30. package/docs/examples/add-export-command/tasks.md +88 -0
  31. package/docs/examples/add-rest-api/design.md +855 -0
  32. package/docs/examples/add-rest-api/requirements.md +323 -0
  33. package/docs/examples/add-rest-api/tasks.md +355 -0
  34. package/docs/examples/add-user-dashboard/design.md +192 -0
  35. package/docs/examples/add-user-dashboard/requirements.md +143 -0
  36. package/docs/examples/add-user-dashboard/tasks.md +91 -0
  37. package/docs/faq.md +697 -0
  38. package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +156 -0
  39. package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +24 -0
  40. package/docs/images/wechat-qr.png +0 -0
  41. package/docs/integration-modes.md +529 -0
  42. package/docs/integration-philosophy.md +313 -0
  43. package/docs/knowledge-management-guide.md +263 -0
  44. package/docs/manual-workflows-guide.md +418 -0
  45. package/docs/moqui-capability-matrix.md +73 -0
  46. package/docs/moqui-template-core-library-playbook.md +109 -0
  47. package/docs/multi-agent-coordination-guide.md +553 -0
  48. package/docs/multi-repo-management-guide.md +1344 -0
  49. package/docs/quick-start-with-ai-tools.md +375 -0
  50. package/docs/quick-start.md +146 -0
  51. package/docs/release-checklist.md +121 -0
  52. package/docs/releases/README.md +13 -0
  53. package/docs/releases/v1.46.2-validation.md +45 -0
  54. package/docs/releases/v1.46.2.md +50 -0
  55. package/docs/scene-runtime-guide.md +347 -0
  56. package/docs/spec-collaboration-guide.md +369 -0
  57. package/docs/spec-locking-guide.md +225 -0
  58. package/docs/spec-numbering-guide.md +348 -0
  59. package/docs/spec-workflow.md +519 -0
  60. package/docs/steering-strategy-guide.md +196 -0
  61. package/docs/team-collaboration-guide.md +465 -0
  62. package/docs/testing-strategy.md +272 -0
  63. package/docs/tools/claude-guide.md +654 -0
  64. package/docs/tools/cursor-guide.md +706 -0
  65. package/docs/tools/generic-guide.md +446 -0
  66. package/docs/tools/kiro-guide.md +308 -0
  67. package/docs/tools/vscode-guide.md +445 -0
  68. package/docs/tools/windsurf-guide.md +391 -0
  69. package/docs/troubleshooting.md +1135 -0
  70. package/docs/upgrade-guide.md +639 -0
  71. package/docs/value-observability-guide.md +127 -0
  72. package/docs/zh/README.md +341 -0
  73. package/docs/zh/quick-start.md +764 -0
  74. package/docs/zh/release-checklist.md +121 -0
  75. package/docs/zh/releases/README.md +13 -0
  76. package/docs/zh/releases/v1.46.2-validation.md +45 -0
  77. package/docs/zh/releases/v1.46.2.md +50 -0
  78. package/docs/zh/spec-numbering-guide.md +348 -0
  79. package/docs/zh/tools/claude-guide.md +349 -0
  80. package/docs/zh/tools/cursor-guide.md +281 -0
  81. package/docs/zh/tools/generic-guide.md +499 -0
  82. package/docs/zh/tools/kiro-guide.md +342 -0
  83. package/docs/zh/tools/vscode-guide.md +449 -0
  84. package/docs/zh/tools/windsurf-guide.md +378 -0
  85. package/docs/zh/value-observability-guide.md +127 -0
  86. package/docs//344/272/244/344/273/230/346/270/205/345/215/225.md +75 -0
  87. package/lib/adoption/adoption-logger.js +487 -0
  88. package/lib/adoption/adoption-strategy.js +538 -0
  89. package/lib/adoption/backup-manager.js +420 -0
  90. package/lib/adoption/conflict-resolver.js +410 -0
  91. package/lib/adoption/detection-engine.js +275 -0
  92. package/lib/adoption/diff-viewer.js +226 -0
  93. package/lib/adoption/error-formatter.js +509 -0
  94. package/lib/adoption/file-classifier.js +385 -0
  95. package/lib/adoption/progress-reporter.js +534 -0
  96. package/lib/adoption/smart-orchestrator.js +470 -0
  97. package/lib/adoption/strategy-selector.js +218 -0
  98. package/lib/adoption/summary-generator.js +493 -0
  99. package/lib/adoption/template-sync.js +605 -0
  100. package/lib/auto/autonomous-engine.js +485 -0
  101. package/lib/auto/checkpoint-manager.js +300 -0
  102. package/lib/auto/close-loop-runner.js +2476 -0
  103. package/lib/auto/config-schema.js +176 -0
  104. package/lib/auto/decision-engine.js +344 -0
  105. package/lib/auto/error-recovery-manager.js +580 -0
  106. package/lib/auto/goal-decomposer.js +278 -0
  107. package/lib/auto/progress-tracker.js +502 -0
  108. package/lib/auto/safety-manager.js +186 -0
  109. package/lib/auto/semantic-decomposer.js +137 -0
  110. package/lib/auto/state-manager.js +126 -0
  111. package/lib/auto/task-queue-manager.js +340 -0
  112. package/lib/backup/backup-system.js +372 -0
  113. package/lib/backup/selective-backup.js +207 -0
  114. package/lib/collab/agent-registry.js +240 -0
  115. package/lib/collab/collab-manager.js +285 -0
  116. package/lib/collab/contract-manager.js +320 -0
  117. package/lib/collab/coordinator.js +370 -0
  118. package/lib/collab/dependency-manager.js +280 -0
  119. package/lib/collab/index.js +20 -0
  120. package/lib/collab/integration-manager.js +202 -0
  121. package/lib/collab/merge-coordinator.js +252 -0
  122. package/lib/collab/metadata-manager.js +233 -0
  123. package/lib/collab/multi-agent-config.js +120 -0
  124. package/lib/collab/spec-lifecycle-manager.js +304 -0
  125. package/lib/collab/sync-barrier.js +88 -0
  126. package/lib/collab/visualizer.js +208 -0
  127. package/lib/commands/adopt.js +749 -0
  128. package/lib/commands/auto.js +19559 -0
  129. package/lib/commands/collab.js +275 -0
  130. package/lib/commands/context.js +99 -0
  131. package/lib/commands/docs.js +808 -0
  132. package/lib/commands/doctor.js +273 -0
  133. package/lib/commands/env.js +420 -0
  134. package/lib/commands/knowledge.js +309 -0
  135. package/lib/commands/lock.js +235 -0
  136. package/lib/commands/ops.js +409 -0
  137. package/lib/commands/orchestrate.js +446 -0
  138. package/lib/commands/prompt.js +105 -0
  139. package/lib/commands/repo.js +118 -0
  140. package/lib/commands/rollback.js +219 -0
  141. package/lib/commands/scene.js +15549 -0
  142. package/lib/commands/spec-bootstrap.js +147 -0
  143. package/lib/commands/spec-gate.js +157 -0
  144. package/lib/commands/spec-pipeline.js +205 -0
  145. package/lib/commands/status.js +321 -0
  146. package/lib/commands/task.js +199 -0
  147. package/lib/commands/templates.js +654 -0
  148. package/lib/commands/upgrade.js +231 -0
  149. package/lib/commands/value.js +569 -0
  150. package/lib/commands/watch.js +684 -0
  151. package/lib/commands/workflows.js +240 -0
  152. package/lib/commands/workspace-multi.js +325 -0
  153. package/lib/commands/workspace.js +189 -0
  154. package/lib/context/context-exporter.js +378 -0
  155. package/lib/context/prompt-generator.js +482 -0
  156. package/lib/data/moqui-capability-lexicon.json +45 -0
  157. package/lib/environment/backup-system.js +189 -0
  158. package/lib/environment/environment-manager.js +379 -0
  159. package/lib/environment/environment-registry.js +168 -0
  160. package/lib/gitignore/gitignore-backup.js +229 -0
  161. package/lib/gitignore/gitignore-detector.js +239 -0
  162. package/lib/gitignore/gitignore-integration.js +267 -0
  163. package/lib/gitignore/gitignore-transformer.js +193 -0
  164. package/lib/gitignore/layered-rules-template.js +42 -0
  165. package/lib/governance/archive-tool.js +284 -0
  166. package/lib/governance/cleanup-tool.js +237 -0
  167. package/lib/governance/config-manager.js +186 -0
  168. package/lib/governance/diagnostic-engine.js +271 -0
  169. package/lib/governance/doc-reference-checker.js +200 -0
  170. package/lib/governance/execution-logger.js +243 -0
  171. package/lib/governance/file-scanner.js +285 -0
  172. package/lib/governance/hooks-manager.js +333 -0
  173. package/lib/governance/reporter.js +337 -0
  174. package/lib/governance/validation-engine.js +181 -0
  175. package/lib/i18n.js +79 -0
  176. package/lib/knowledge/entry-manager.js +208 -0
  177. package/lib/knowledge/index-manager.js +261 -0
  178. package/lib/knowledge/knowledge-manager.js +273 -0
  179. package/lib/knowledge/template-manager.js +191 -0
  180. package/lib/lock/index.js +21 -0
  181. package/lib/lock/lock-file.js +192 -0
  182. package/lib/lock/lock-manager.js +321 -0
  183. package/lib/lock/machine-identifier.js +135 -0
  184. package/lib/lock/steering-file-lock.js +207 -0
  185. package/lib/lock/task-lock-manager.js +345 -0
  186. package/lib/operations/audit-logger.js +293 -0
  187. package/lib/operations/feedback-manager.js +1147 -0
  188. package/lib/operations/index.js +23 -0
  189. package/lib/operations/models/index.js +170 -0
  190. package/lib/operations/operations-manager.js +151 -0
  191. package/lib/operations/operations-validator.js +280 -0
  192. package/lib/operations/permission-manager.js +354 -0
  193. package/lib/operations/template-loader.js +143 -0
  194. package/lib/orchestrator/agent-spawner.js +629 -0
  195. package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
  196. package/lib/orchestrator/index.js +19 -0
  197. package/lib/orchestrator/orchestration-engine.js +1270 -0
  198. package/lib/orchestrator/orchestrator-config.js +173 -0
  199. package/lib/orchestrator/status-monitor.js +591 -0
  200. package/lib/python-checker.js +209 -0
  201. package/lib/repo/config-manager.js +580 -0
  202. package/lib/repo/errors/config-error.js +13 -0
  203. package/lib/repo/errors/git-error.js +15 -0
  204. package/lib/repo/errors/repo-error.js +14 -0
  205. package/lib/repo/git-operations.js +181 -0
  206. package/lib/repo/handlers/.gitkeep +1 -0
  207. package/lib/repo/handlers/exec-handler.js +155 -0
  208. package/lib/repo/handlers/health-handler.js +169 -0
  209. package/lib/repo/handlers/init-handler.js +197 -0
  210. package/lib/repo/handlers/status-handler.js +176 -0
  211. package/lib/repo/output-formatter.js +184 -0
  212. package/lib/repo/path-resolver.js +178 -0
  213. package/lib/repo/repo-manager.js +514 -0
  214. package/lib/scene-runtime/audit-emitter.js +59 -0
  215. package/lib/scene-runtime/binding-plugin-loader.js +351 -0
  216. package/lib/scene-runtime/binding-registry.js +349 -0
  217. package/lib/scene-runtime/eval-bridge.js +44 -0
  218. package/lib/scene-runtime/index.js +19 -0
  219. package/lib/scene-runtime/moqui-adapter.js +620 -0
  220. package/lib/scene-runtime/moqui-client.js +606 -0
  221. package/lib/scene-runtime/moqui-extractor.js +2029 -0
  222. package/lib/scene-runtime/plan-compiler.js +208 -0
  223. package/lib/scene-runtime/policy-gate.js +58 -0
  224. package/lib/scene-runtime/runtime-executor.js +358 -0
  225. package/lib/scene-runtime/scene-loader.js +96 -0
  226. package/lib/scene-runtime/scene-ontology.js +959 -0
  227. package/lib/scene-runtime/scene-template-linter.js +852 -0
  228. package/lib/scene-runtime/templates/scene-template-erp-query-v0.1.yaml +28 -0
  229. package/lib/scene-runtime/templates/scene-template-hybrid-shadow-v0.1.yaml +34 -0
  230. package/lib/spec/bootstrap/context-collector.js +48 -0
  231. package/lib/spec/bootstrap/draft-generator.js +158 -0
  232. package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
  233. package/lib/spec/bootstrap/trace-emitter.js +59 -0
  234. package/lib/spec/multi-spec-orchestrate.js +93 -0
  235. package/lib/spec/pipeline/constants.js +6 -0
  236. package/lib/spec/pipeline/stage-adapters.js +118 -0
  237. package/lib/spec/pipeline/stage-runner.js +146 -0
  238. package/lib/spec/pipeline/state-store.js +119 -0
  239. package/lib/spec-gate/engine/gate-engine.js +165 -0
  240. package/lib/spec-gate/policy/default-policy.js +22 -0
  241. package/lib/spec-gate/policy/policy-loader.js +103 -0
  242. package/lib/spec-gate/result-emitter.js +81 -0
  243. package/lib/spec-gate/rules/default-rules.js +156 -0
  244. package/lib/spec-gate/rules/rule-registry.js +51 -0
  245. package/lib/steering/adoption-config.js +164 -0
  246. package/lib/steering/compliance-auto-fixer.js +204 -0
  247. package/lib/steering/compliance-cache.js +99 -0
  248. package/lib/steering/compliance-error-reporter.js +70 -0
  249. package/lib/steering/context-sync-manager.js +273 -0
  250. package/lib/steering/index.js +92 -0
  251. package/lib/steering/spec-steering.js +230 -0
  252. package/lib/steering/steering-compliance-checker.js +73 -0
  253. package/lib/steering/steering-loader.js +144 -0
  254. package/lib/steering/steering-manager.js +289 -0
  255. package/lib/task/index.js +12 -0
  256. package/lib/task/task-claimer.js +489 -0
  257. package/lib/task/task-status-store.js +418 -0
  258. package/lib/templates/cache-manager.js +440 -0
  259. package/lib/templates/content-generalizer.js +247 -0
  260. package/lib/templates/frontmatter-generator.js +128 -0
  261. package/lib/templates/git-handler.js +471 -0
  262. package/lib/templates/metadata-collector.js +328 -0
  263. package/lib/templates/path-utils.js +144 -0
  264. package/lib/templates/registry-parser.js +505 -0
  265. package/lib/templates/spec-reader.js +216 -0
  266. package/lib/templates/template-applicator.js +249 -0
  267. package/lib/templates/template-creator.js +256 -0
  268. package/lib/templates/template-error.js +143 -0
  269. package/lib/templates/template-exporter.js +502 -0
  270. package/lib/templates/template-manager.js +782 -0
  271. package/lib/templates/template-validator.js +361 -0
  272. package/lib/upgrade/migration-engine.js +382 -0
  273. package/lib/upgrade/migrations/.gitkeep +52 -0
  274. package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
  275. package/lib/utils/file-diff.js +177 -0
  276. package/lib/utils/fs-utils.js +274 -0
  277. package/lib/utils/tool-detector.js +383 -0
  278. package/lib/utils/validation.js +324 -0
  279. package/lib/value/gate-summary-emitter.js +99 -0
  280. package/lib/value/metric-contract-loader.js +210 -0
  281. package/lib/value/risk-evaluator.js +117 -0
  282. package/lib/value/weekly-snapshot-builder.js +61 -0
  283. package/lib/version/version-checker.js +156 -0
  284. package/lib/version/version-manager.js +327 -0
  285. package/lib/watch/action-executor.js +458 -0
  286. package/lib/watch/event-debouncer.js +323 -0
  287. package/lib/watch/execution-logger.js +550 -0
  288. package/lib/watch/file-watcher.js +499 -0
  289. package/lib/watch/presets.js +266 -0
  290. package/lib/watch/watch-manager.js +533 -0
  291. package/lib/workspace/multi/global-config.js +150 -0
  292. package/lib/workspace/multi/index.js +22 -0
  293. package/lib/workspace/multi/path-utils.js +173 -0
  294. package/lib/workspace/multi/workspace-context-resolver.js +244 -0
  295. package/lib/workspace/multi/workspace-registry.js +196 -0
  296. package/lib/workspace/multi/workspace-state-manager.js +537 -0
  297. package/lib/workspace/multi/workspace.js +90 -0
  298. package/lib/workspace/workspace-manager.js +370 -0
  299. package/lib/workspace/workspace-sync.js +356 -0
  300. package/locales/en.json +114 -0
  301. package/locales/zh.json +114 -0
  302. package/package.json +102 -0
  303. package/template/.kiro/README.md +247 -0
  304. package/template/.kiro/hooks/check-spec-on-create.kiro.hook +17 -0
  305. package/template/.kiro/hooks/run-tests-on-save.kiro.hook +13 -0
  306. package/template/.kiro/hooks/sync-tasks-on-edit.kiro.hook +16 -0
  307. package/template/.kiro/specs/SPEC_WORKFLOW_GUIDE.md +134 -0
  308. package/template/.kiro/steering/CORE_PRINCIPLES.md +133 -0
  309. package/template/.kiro/steering/CURRENT_CONTEXT.md +30 -0
  310. package/template/.kiro/steering/ENVIRONMENT.md +35 -0
  311. package/template/.kiro/steering/RULES_GUIDE.md +46 -0
  312. package/template/.kiro/templates/operations/default/change-impact.md +112 -0
  313. package/template/.kiro/templates/operations/default/deployment.md +91 -0
  314. package/template/.kiro/templates/operations/default/feedback-response.md +269 -0
  315. package/template/.kiro/templates/operations/default/migration-plan.md +172 -0
  316. package/template/.kiro/templates/operations/default/monitoring.md +135 -0
  317. package/template/.kiro/templates/operations/default/operations.md +135 -0
  318. package/template/.kiro/templates/operations/default/rollback.md +143 -0
  319. package/template/.kiro/templates/operations/default/tools.yaml +364 -0
  320. package/template/.kiro/templates/operations/default/troubleshooting.md +123 -0
  321. package/template/.kiro/tools/backup_manager.py +295 -0
  322. package/template/.kiro/tools/configuration_manager.py +218 -0
  323. package/template/.kiro/tools/document_evaluator.py +550 -0
  324. package/template/.kiro/tools/enhancement_logger.py +168 -0
  325. package/template/.kiro/tools/error_handler.py +335 -0
  326. package/template/.kiro/tools/improvement_identifier.py +444 -0
  327. package/template/.kiro/tools/modification_applicator.py +737 -0
  328. package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
  329. package/template/.kiro/tools/quality_scorer.py +305 -0
  330. package/template/.kiro/tools/report_generator.py +154 -0
  331. package/template/.kiro/tools/ultrawork_enhancer.py +676 -0
  332. package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
  333. package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
  334. package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
  335. package/template/.kiro/tools/workflow_quality_gate.py +100 -0
  336. package/template/README.md +111 -0
@@ -0,0 +1,852 @@
1
+ 'use strict';
2
+
3
+ // ─── Constants ─────────────────────────────────────────────────────
4
+
5
+ /** Known binding ref prefixes for validation */
6
+ const KNOWN_BINDING_REF_PREFIXES = ['moqui.', 'spec.erp.', 'kse.scene.'];
7
+
8
+ /** Valid risk levels for governance checks */
9
+ const VALID_RISK_LEVELS = ['low', 'medium', 'high'];
10
+
11
+ /** Pattern for kebab-case names */
12
+ const KEBAB_CASE_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
13
+
14
+ /** Pattern for semver versions */
15
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
16
+
17
+ /** Required top-level fields in scene-package.json */
18
+ const REQUIRED_PACKAGE_FIELDS = ['apiVersion', 'kind', 'metadata', 'capabilities', 'artifacts', 'governance'];
19
+
20
+ /** Required top-level fields in scene.yaml */
21
+ const REQUIRED_MANIFEST_FIELDS = ['apiVersion', 'kind', 'metadata', 'spec'];
22
+
23
+ /** Score dimension weights (total = 100) */
24
+ const SCORE_WEIGHTS = {
25
+ contractValidity: 30,
26
+ lintPassRate: 30,
27
+ documentationQuality: 20,
28
+ governanceCompleteness: 20
29
+ };
30
+
31
+ // ─── Lint Engine Functions ─────────────────────────────────────────
32
+
33
+ /**
34
+ * Create a single LintItem.
35
+ * @param {'error'|'warning'|'info'} level - Severity level
36
+ * @param {string} code - Machine-readable error code
37
+ * @param {string} message - Human-readable description
38
+ * @returns {{ level: string, code: string, message: string }}
39
+ */
40
+ function createLintItem(level, code, message) {
41
+ return { level, code, message };
42
+ }
43
+
44
+ /**
45
+ * Check scene-package.json manifest completeness.
46
+ * Verifies that all required top-level fields are present.
47
+ * @param {Object} contract - Parsed scene-package.json object
48
+ * @returns {Array<{ level: string, code: string, message: string }>}
49
+ */
50
+ function checkManifestCompleteness(contract) {
51
+ const items = [];
52
+ for (const field of REQUIRED_PACKAGE_FIELDS) {
53
+ if (contract[field] === undefined || contract[field] === null) {
54
+ items.push(createLintItem('error', 'MISSING_PACKAGE_FIELD', `scene-package.json is missing required field: ${field}`));
55
+ }
56
+ }
57
+ return items;
58
+ }
59
+
60
+ /**
61
+ * Check scene.yaml manifest completeness.
62
+ * Verifies that all required top-level fields are present.
63
+ * @param {Object} manifest - Parsed scene.yaml object
64
+ * @returns {Array<{ level: string, code: string, message: string }>}
65
+ */
66
+ function checkSceneManifestCompleteness(manifest) {
67
+ const items = [];
68
+ for (const field of REQUIRED_MANIFEST_FIELDS) {
69
+ if (manifest[field] === undefined || manifest[field] === null) {
70
+ items.push(createLintItem('warning', 'MISSING_MANIFEST_FIELD', `scene.yaml is missing required field: ${field}`));
71
+ }
72
+ }
73
+ return items;
74
+ }
75
+
76
+ /**
77
+ * Validate binding ref format.
78
+ * Extracts refs from capability_contract.bindings (or spec.capability_contract.bindings)
79
+ * and verifies each ref starts with a known prefix.
80
+ * @param {Object} contract - scene-package.json or scene.yaml object
81
+ * @returns {Array<{ level: string, code: string, message: string }>}
82
+ */
83
+ function checkBindingRefFormat(contract) {
84
+ const items = [];
85
+ // Try both direct and nested paths for bindings
86
+ const bindings =
87
+ (contract.capability_contract && contract.capability_contract.bindings) ||
88
+ (contract.spec && contract.spec.capability_contract && contract.spec.capability_contract.bindings);
89
+
90
+ if (!bindings || typeof bindings !== 'object') {
91
+ return items;
92
+ }
93
+
94
+ const bindingEntries = Array.isArray(bindings) ? bindings : Object.values(bindings);
95
+
96
+ for (const binding of bindingEntries) {
97
+ const ref = typeof binding === 'string' ? binding : (binding && binding.ref);
98
+ if (!ref || typeof ref !== 'string') continue;
99
+
100
+ const matchesKnownPrefix = KNOWN_BINDING_REF_PREFIXES.some(prefix => ref.startsWith(prefix));
101
+ if (!matchesKnownPrefix) {
102
+ items.push(createLintItem('warning', 'INVALID_BINDING_REF', `Binding ref "${ref}" does not match any known prefix (${KNOWN_BINDING_REF_PREFIXES.join(', ')})`));
103
+ }
104
+ }
105
+
106
+ return items;
107
+ }
108
+
109
+ /**
110
+ * Check governance contract reasonableness.
111
+ * Validates risk_level, approval.required, and idempotency.required fields.
112
+ * @param {Object} governance - governance object
113
+ * @returns {Array<{ level: string, code: string, message: string }>}
114
+ */
115
+ function checkGovernanceReasonableness(governance) {
116
+ const items = [];
117
+
118
+ if (!governance || typeof governance !== 'object') {
119
+ return items;
120
+ }
121
+
122
+ // Check risk_level
123
+ if (governance.risk_level !== undefined && governance.risk_level !== null) {
124
+ if (!VALID_RISK_LEVELS.includes(governance.risk_level)) {
125
+ items.push(createLintItem('error', 'INVALID_RISK_LEVEL', `governance.risk_level must be one of: ${VALID_RISK_LEVELS.join(', ')} (got "${governance.risk_level}")`));
126
+ }
127
+ }
128
+
129
+ // Check approval.required
130
+ if (!governance.approval || typeof governance.approval.required !== 'boolean') {
131
+ items.push(createLintItem('warning', 'MISSING_APPROVAL', 'governance.approval.required is not set or is not a boolean'));
132
+ }
133
+
134
+ // Check idempotency.required
135
+ if (!governance.idempotency || typeof governance.idempotency.required !== 'boolean') {
136
+ items.push(createLintItem('warning', 'MISSING_IDEMPOTENCY', 'governance.idempotency.required is not set or is not a boolean'));
137
+ }
138
+
139
+ return items;
140
+ }
141
+
142
+ /**
143
+ * Check package contract consistency.
144
+ * Validates name is kebab-case, version is valid semver, and entry_scene file exists.
145
+ * @param {Object} contract - scene-package.json object
146
+ * @param {string} packageDir - Package directory path
147
+ * @param {Object} [fileSystem] - fs-extra compatible file system
148
+ * @returns {Promise<Array<{ level: string, code: string, message: string }>>}
149
+ */
150
+ async function checkPackageConsistency(contract, packageDir, fileSystem) {
151
+ const fs = fileSystem || require('fs-extra');
152
+ const path = require('path');
153
+ const semver = require('semver');
154
+ const items = [];
155
+
156
+ // Check metadata.name is kebab-case
157
+ const name = contract.metadata && contract.metadata.name;
158
+ if (name !== undefined && name !== null) {
159
+ if (typeof name !== 'string' || !KEBAB_CASE_PATTERN.test(name)) {
160
+ items.push(createLintItem('error', 'NAME_NOT_KEBAB', `metadata.name "${name}" is not valid kebab-case`));
161
+ }
162
+ }
163
+
164
+ // Check metadata.version is valid semver
165
+ const version = contract.metadata && contract.metadata.version;
166
+ if (version !== undefined && version !== null) {
167
+ if (typeof version !== 'string' || !semver.valid(version)) {
168
+ items.push(createLintItem('error', 'INVALID_VERSION', `metadata.version "${version}" is not valid semver`));
169
+ }
170
+ }
171
+
172
+ // Check artifacts.entry_scene file exists
173
+ const entryScene = contract.artifacts && contract.artifacts.entry_scene;
174
+ if (entryScene && typeof entryScene === 'string') {
175
+ const entryPath = path.resolve(packageDir, entryScene);
176
+ const exists = await fs.pathExists(entryPath);
177
+ if (!exists) {
178
+ items.push(createLintItem('error', 'ENTRY_SCENE_MISSING', `artifacts.entry_scene file "${entryScene}" does not exist in package directory`));
179
+ }
180
+ }
181
+
182
+ return items;
183
+ }
184
+
185
+ /**
186
+ * Check template variables schema completeness.
187
+ * Validates that each variable has a non-empty type and description field.
188
+ * Looks in contract.variables, contract.parameters, or contract.spec.variables/parameters.
189
+ * @param {Object} contract - scene-package.json object
190
+ * @returns {Array<{ level: string, code: string, message: string }>}
191
+ */
192
+ function checkTemplateVariables(contract) {
193
+ const items = [];
194
+
195
+ // Try multiple paths for variables
196
+ const variables =
197
+ contract.variables ||
198
+ contract.parameters ||
199
+ (contract.spec && (contract.spec.variables || contract.spec.parameters));
200
+
201
+ if (!Array.isArray(variables) || variables.length === 0) {
202
+ return items;
203
+ }
204
+
205
+ for (const variable of variables) {
206
+ if (!variable || typeof variable !== 'object') continue;
207
+
208
+ const varName = variable.name || variable.key || '(unnamed)';
209
+
210
+ if (!variable.type || (typeof variable.type === 'string' && variable.type.trim() === '')) {
211
+ items.push(createLintItem('warning', 'VARIABLE_MISSING_TYPE', `Template variable "${varName}" is missing a non-empty type field`));
212
+ }
213
+
214
+ if (!variable.description || (typeof variable.description === 'string' && variable.description.trim() === '')) {
215
+ items.push(createLintItem('warning', 'VARIABLE_MISSING_DESC', `Template variable "${varName}" is missing a non-empty description field`));
216
+ }
217
+ }
218
+
219
+ return items;
220
+ }
221
+
222
+ /**
223
+ * Check documentation presence.
224
+ * Verifies that README.md exists in packageDir or metadata.description is non-empty.
225
+ * @param {Object} contract - scene-package.json object
226
+ * @param {string} packageDir - Package directory path
227
+ * @param {Object} [fileSystem] - fs-extra compatible file system
228
+ * @returns {Promise<{ items: Array<{ level: string, code: string, message: string }>, hasReadme: boolean }>}
229
+ */
230
+ async function checkDocumentation(contract, packageDir, fileSystem) {
231
+ const fs = fileSystem || require('fs-extra');
232
+ const path = require('path');
233
+ const items = [];
234
+
235
+ // Check README.md existence
236
+ const readmePath = path.resolve(packageDir, 'README.md');
237
+ const hasReadme = await fs.pathExists(readmePath);
238
+
239
+ // Check metadata.description
240
+ const description = contract.metadata && contract.metadata.description;
241
+ const hasDescription = typeof description === 'string' && description.trim() !== '';
242
+
243
+ if (hasReadme) {
244
+ items.push(createLintItem('info', 'HAS_README', 'README.md is present'));
245
+ }
246
+
247
+ if (hasDescription) {
248
+ items.push(createLintItem('info', 'HAS_DESCRIPTION', 'metadata.description is present'));
249
+ }
250
+
251
+ if (!hasReadme && !hasDescription) {
252
+ items.push(createLintItem('warning', 'NO_DOCUMENTATION', 'No README.md found and metadata.description is empty; at least one form of documentation is recommended'));
253
+ }
254
+
255
+ return { items, hasReadme };
256
+ }
257
+
258
+ /**
259
+ * Check action abstraction fields on bindings.
260
+ * Validates intent, preconditions, and postconditions when present.
261
+ * @param {Object} contract - Parsed scene-package.json object
262
+ * @returns {Array<{ level: string, code: string, message: string }>}
263
+ */
264
+ function checkActionAbstraction(contract) {
265
+ const items = [];
266
+ const bindings = contract && contract.capability_contract && contract.capability_contract.bindings;
267
+ if (!Array.isArray(bindings)) return items;
268
+
269
+ for (const binding of bindings) {
270
+ if (!binding || typeof binding !== 'object') continue;
271
+ const ref = binding.ref || '(unknown)';
272
+
273
+ // Check EMPTY_INTENT: intent exists and is empty string
274
+ if ('intent' in binding && binding.intent === '') {
275
+ items.push(createLintItem('warning', 'EMPTY_INTENT', `Binding "${ref}" has an empty intent`));
276
+ }
277
+
278
+ // Check INVALID_PRECONDITIONS: preconditions exists but is not a string array
279
+ if ('preconditions' in binding) {
280
+ if (!Array.isArray(binding.preconditions) || !binding.preconditions.every(p => typeof p === 'string')) {
281
+ items.push(createLintItem('error', 'INVALID_PRECONDITIONS', `Binding "${ref}" has invalid preconditions (must be a string array)`));
282
+ }
283
+ }
284
+
285
+ // Check INVALID_POSTCONDITIONS: postconditions exists but is not a string array
286
+ if ('postconditions' in binding) {
287
+ if (!Array.isArray(binding.postconditions) || !binding.postconditions.every(p => typeof p === 'string')) {
288
+ items.push(createLintItem('error', 'INVALID_POSTCONDITIONS', `Binding "${ref}" has invalid postconditions (must be a string array)`));
289
+ }
290
+ }
291
+ }
292
+
293
+ return items;
294
+ }
295
+
296
+ /**
297
+ * Check data lineage consistency.
298
+ * Validates that lineage source/sink refs exist in bindings.
299
+ * @param {Object} contract - Parsed scene-package.json object
300
+ * @returns {Array<{ level: string, code: string, message: string }>}
301
+ */
302
+ function checkDataLineage(contract) {
303
+ const items = [];
304
+
305
+ // Collect binding refs
306
+ const bindings = contract && contract.capability_contract && contract.capability_contract.bindings;
307
+ const bindingRefs = new Set();
308
+ if (Array.isArray(bindings)) {
309
+ for (const b of bindings) {
310
+ if (b && typeof b.ref === 'string') bindingRefs.add(b.ref);
311
+ }
312
+ }
313
+
314
+ // Get data_lineage
315
+ const lineage = contract && contract.governance_contract && contract.governance_contract.data_lineage;
316
+ if (!lineage || typeof lineage !== 'object') return items;
317
+
318
+ // Check sources
319
+ if (Array.isArray(lineage.sources)) {
320
+ for (const source of lineage.sources) {
321
+ if (source && typeof source.ref === 'string' && !bindingRefs.has(source.ref)) {
322
+ items.push(createLintItem('warning', 'LINEAGE_SOURCE_NOT_IN_BINDINGS', `Data lineage source ref "${source.ref}" is not found in capability_contract.bindings`));
323
+ }
324
+ }
325
+ }
326
+
327
+ // Check sinks
328
+ if (Array.isArray(lineage.sinks)) {
329
+ for (const sink of lineage.sinks) {
330
+ if (sink && typeof sink.ref === 'string' && !bindingRefs.has(sink.ref)) {
331
+ items.push(createLintItem('warning', 'LINEAGE_SINK_NOT_IN_BINDINGS', `Data lineage sink ref "${sink.ref}" is not found in capability_contract.bindings`));
332
+ }
333
+ }
334
+ }
335
+
336
+ return items;
337
+ }
338
+
339
+ /**
340
+ * Check agent_hints field validity.
341
+ * Validates summary, complexity, and estimated_duration_ms when present.
342
+ * @param {Object} contract - Parsed scene-package.json object
343
+ * @returns {Array<{ level: string, code: string, message: string }>}
344
+ */
345
+ function checkAgentHints(contract) {
346
+ const items = [];
347
+ const hints = contract && contract.agent_hints;
348
+ if (!hints || typeof hints !== 'object') return items;
349
+
350
+ // Check EMPTY_AGENT_SUMMARY
351
+ if ('summary' in hints && hints.summary === '') {
352
+ items.push(createLintItem('warning', 'EMPTY_AGENT_SUMMARY', 'agent_hints.summary is empty'));
353
+ }
354
+
355
+ // Check INVALID_AGENT_COMPLEXITY
356
+ if ('complexity' in hints) {
357
+ const validComplexity = ['low', 'medium', 'high'];
358
+ if (!validComplexity.includes(hints.complexity)) {
359
+ items.push(createLintItem('error', 'INVALID_AGENT_COMPLEXITY', `agent_hints.complexity "${hints.complexity}" is not valid (must be low, medium, or high)`));
360
+ }
361
+ }
362
+
363
+ // Check INVALID_AGENT_DURATION
364
+ if ('estimated_duration_ms' in hints) {
365
+ const val = hints.estimated_duration_ms;
366
+ if (typeof val !== 'number' || !Number.isInteger(val) || val <= 0) {
367
+ items.push(createLintItem('error', 'INVALID_AGENT_DURATION', `agent_hints.estimated_duration_ms must be a positive integer`));
368
+ }
369
+ }
370
+
371
+ return items;
372
+ }
373
+
374
+ function normalizeSemanticItems(raw) {
375
+ if (Array.isArray(raw)) {
376
+ return raw.filter(item => item && typeof item === 'object');
377
+ }
378
+ if (!raw || typeof raw !== 'object') {
379
+ return [];
380
+ }
381
+ const candidates = raw.items || raw.values || raw.list || raw.nodes;
382
+ if (Array.isArray(candidates)) {
383
+ return candidates.filter(item => item && typeof item === 'object');
384
+ }
385
+ return Object.values(raw).filter(item => item && typeof item === 'object');
386
+ }
387
+
388
+ /**
389
+ * Check ontology semantic coverage and scene governance bridge.
390
+ * Applies to scene-domain-profile packages to ensure ontology entities/relations,
391
+ * business rules, and decision logic are present and propagated to scene manifest.
392
+ * @param {Object} contract - Parsed scene-package.json object
393
+ * @param {Object|null} manifest - Parsed scene.yaml object
394
+ * @returns {Array<{ level: string, code: string, message: string }>}
395
+ */
396
+ function checkOntologySemanticCoverage(contract, manifest = null) {
397
+ const items = [];
398
+ if (!contract || typeof contract !== 'object') {
399
+ return items;
400
+ }
401
+
402
+ const kind = typeof contract.kind === 'string' ? contract.kind.trim() : '';
403
+ if (kind !== 'scene-domain-profile') {
404
+ return items;
405
+ }
406
+
407
+ const ontologyModel = contract.ontology_model && typeof contract.ontology_model === 'object'
408
+ ? contract.ontology_model
409
+ : {};
410
+ const governanceContract = contract.governance_contract && typeof contract.governance_contract === 'object'
411
+ ? contract.governance_contract
412
+ : {};
413
+
414
+ const entities = normalizeSemanticItems(ontologyModel.entities);
415
+ const relations = normalizeSemanticItems(ontologyModel.relations);
416
+ const businessRules = normalizeSemanticItems(governanceContract.business_rules);
417
+ const decisionLogic = normalizeSemanticItems(governanceContract.decision_logic);
418
+
419
+ if (entities.length === 0) {
420
+ items.push(createLintItem('warning', 'ONTOLOGY_ENTITIES_MISSING', 'ontology_model.entities is empty or missing'));
421
+ }
422
+ if (relations.length === 0) {
423
+ items.push(createLintItem('warning', 'ONTOLOGY_RELATIONS_MISSING', 'ontology_model.relations is empty or missing'));
424
+ }
425
+ if (businessRules.length === 0) {
426
+ items.push(createLintItem('warning', 'ONTOLOGY_BUSINESS_RULES_MISSING', 'governance_contract.business_rules is empty or missing'));
427
+ }
428
+ if (decisionLogic.length === 0) {
429
+ items.push(createLintItem('warning', 'ONTOLOGY_DECISION_LOGIC_MISSING', 'governance_contract.decision_logic is empty or missing'));
430
+ }
431
+
432
+ const sceneGovernance = manifest
433
+ && manifest.spec
434
+ && typeof manifest.spec === 'object'
435
+ && manifest.spec.governance_contract
436
+ && typeof manifest.spec.governance_contract === 'object'
437
+ ? manifest.spec.governance_contract
438
+ : null;
439
+ if (!sceneGovernance) {
440
+ items.push(createLintItem('warning', 'SCENE_GOVERNANCE_CONTRACT_MISSING', 'scene.yaml spec.governance_contract is missing for ontology bridge'));
441
+ return items;
442
+ }
443
+
444
+ const sceneBusinessRules = normalizeSemanticItems(sceneGovernance.business_rules);
445
+ const sceneDecisionLogic = normalizeSemanticItems(sceneGovernance.decision_logic);
446
+ if (sceneBusinessRules.length === 0) {
447
+ items.push(createLintItem('warning', 'SCENE_GOVERNANCE_RULES_MISSING', 'scene.yaml spec.governance_contract.business_rules is empty or missing'));
448
+ }
449
+ if (sceneDecisionLogic.length === 0) {
450
+ items.push(createLintItem('warning', 'SCENE_GOVERNANCE_DECISIONS_MISSING', 'scene.yaml spec.governance_contract.decision_logic is empty or missing'));
451
+ }
452
+
453
+ const packageRuleIds = new Set(
454
+ businessRules
455
+ .map(rule => (typeof rule.id === 'string' ? rule.id.trim() : ''))
456
+ .filter(Boolean)
457
+ );
458
+ const sceneRuleIds = new Set(
459
+ sceneBusinessRules
460
+ .map(rule => (typeof rule.id === 'string' ? rule.id.trim() : ''))
461
+ .filter(Boolean)
462
+ );
463
+ const missingSceneRules = Array.from(packageRuleIds).filter(ruleId => !sceneRuleIds.has(ruleId));
464
+ if (missingSceneRules.length > 0) {
465
+ items.push(
466
+ createLintItem(
467
+ 'warning',
468
+ 'SCENE_GOVERNANCE_RULES_UNALIGNED',
469
+ `scene.yaml governance_contract is missing ${missingSceneRules.length} business rule id(s) from scene-package.json`
470
+ )
471
+ );
472
+ }
473
+
474
+ const packageDecisionIds = new Set(
475
+ decisionLogic
476
+ .map(item => (typeof item.id === 'string' ? item.id.trim() : ''))
477
+ .filter(Boolean)
478
+ );
479
+ const sceneDecisionIds = new Set(
480
+ sceneDecisionLogic
481
+ .map(item => (typeof item.id === 'string' ? item.id.trim() : ''))
482
+ .filter(Boolean)
483
+ );
484
+ const missingSceneDecisions = Array.from(packageDecisionIds).filter(decisionId => !sceneDecisionIds.has(decisionId));
485
+ if (missingSceneDecisions.length > 0) {
486
+ items.push(
487
+ createLintItem(
488
+ 'warning',
489
+ 'SCENE_GOVERNANCE_DECISIONS_UNALIGNED',
490
+ `scene.yaml governance_contract is missing ${missingSceneDecisions.length} decision_logic id(s) from scene-package.json`
491
+ )
492
+ );
493
+ }
494
+
495
+ return items;
496
+ }
497
+
498
+ /**
499
+ * Execute full template lint checks.
500
+ * Orchestrates all individual checks, reads scene-package.json and scene.yaml,
501
+ * and returns a structured LintResult.
502
+ * @param {string} packageDir - Scene package directory path
503
+ * @param {Object} [options] - { fileSystem }
504
+ * @returns {Promise<LintResult>}
505
+ */
506
+ async function lintScenePackage(packageDir, options = {}) {
507
+ const fs = options.fileSystem || require('fs-extra');
508
+ const path = require('path');
509
+ const yaml = require('js-yaml');
510
+
511
+ const allItems = [];
512
+ let contract = null;
513
+ let manifest = null;
514
+ let hasReadme = false;
515
+ let contractErrors = [];
516
+ let manifestErrors = [];
517
+
518
+ // 1. Read scene-package.json
519
+ try {
520
+ const contractPath = path.resolve(packageDir, 'scene-package.json');
521
+ contract = await fs.readJson(contractPath);
522
+ } catch (err) {
523
+ const errorItem = createLintItem('error', 'MANIFEST_READ_FAILED', `Failed to read scene-package.json: ${err.message}`);
524
+ return {
525
+ valid: false,
526
+ errors: [errorItem],
527
+ warnings: [],
528
+ info: [],
529
+ summary: { error_count: 1, warning_count: 0, info_count: 0, checks_run: 1 },
530
+ _context: {
531
+ contract: null,
532
+ manifest: null,
533
+ hasReadme: false,
534
+ contractErrors: [errorItem],
535
+ manifestErrors: []
536
+ }
537
+ };
538
+ }
539
+
540
+ // 2. Read scene.yaml
541
+ try {
542
+ const yamlPath = path.resolve(packageDir, 'scene.yaml');
543
+ const yamlContent = await fs.readFile(yamlPath, 'utf8');
544
+ manifest = yaml.load(yamlContent);
545
+ } catch (err) {
546
+ allItems.push(createLintItem('warning', 'SCENE_YAML_READ_FAILED', `Failed to read scene.yaml: ${err.message}`));
547
+ }
548
+
549
+ // 3. Check manifest completeness (scene-package.json)
550
+ contractErrors = checkManifestCompleteness(contract);
551
+ allItems.push(...contractErrors);
552
+
553
+ // 4. Check scene.yaml manifest completeness (if loaded)
554
+ if (manifest) {
555
+ manifestErrors = checkSceneManifestCompleteness(manifest);
556
+ allItems.push(...manifestErrors);
557
+ }
558
+
559
+ // 5. Check binding ref format on contract (and manifest if available)
560
+ allItems.push(...checkBindingRefFormat(contract));
561
+ if (manifest) {
562
+ allItems.push(...checkBindingRefFormat(manifest));
563
+ }
564
+
565
+ // 6. Extract governance and check reasonableness
566
+ const governance =
567
+ (contract.governance) ||
568
+ (contract.spec && contract.spec.governance_contract);
569
+ allItems.push(...checkGovernanceReasonableness(governance));
570
+
571
+ // 7. Check package consistency (async)
572
+ allItems.push(...await checkPackageConsistency(contract, packageDir, fs));
573
+
574
+ // 8. Check template variables
575
+ allItems.push(...checkTemplateVariables(contract));
576
+
577
+ // 9. Check documentation (async)
578
+ const docResult = await checkDocumentation(contract, packageDir, fs);
579
+ allItems.push(...docResult.items);
580
+ hasReadme = docResult.hasReadme;
581
+
582
+ // 10. Check action abstraction
583
+ allItems.push(...checkActionAbstraction(contract));
584
+
585
+ // 11. Check data lineage
586
+ allItems.push(...checkDataLineage(contract));
587
+
588
+ // 12. Check ontology semantic coverage and scene bridge
589
+ allItems.push(...checkOntologySemanticCoverage(contract, manifest));
590
+
591
+ // 13. Check agent hints
592
+ allItems.push(...checkAgentHints(contract));
593
+
594
+ // Separate items by level
595
+ const errors = allItems.filter(item => item.level === 'error');
596
+ const warnings = allItems.filter(item => item.level === 'warning');
597
+ const info = allItems.filter(item => item.level === 'info');
598
+
599
+ return {
600
+ valid: errors.length === 0,
601
+ errors,
602
+ warnings,
603
+ info,
604
+ summary: {
605
+ error_count: errors.length,
606
+ warning_count: warnings.length,
607
+ info_count: info.length,
608
+ checks_run: 10
609
+ },
610
+ _context: {
611
+ contract,
612
+ manifest,
613
+ hasReadme,
614
+ contractErrors,
615
+ manifestErrors
616
+ }
617
+ };
618
+ }
619
+
620
+ // ─── Score Calculator Functions ────────────────────────────────────
621
+
622
+ /**
623
+ * Calculate contract validity dimension score (max 30 points).
624
+ * Awards 15 points for zero package contract errors and 15 points for zero manifest errors.
625
+ * @param {Object} lintResult - LintResult from lintScenePackage
626
+ * @returns {{ score: number, details: Object }}
627
+ */
628
+ function scoreContractValidity(lintResult) {
629
+ const ctx = lintResult._context || {};
630
+ const contractErrors = ctx.contractErrors || [];
631
+ const manifestErrors = ctx.manifestErrors || [];
632
+
633
+ const packageContract = contractErrors.length === 0 ? 15 : 0;
634
+ const sceneManifest = manifestErrors.length === 0 ? 15 : 0;
635
+
636
+ return {
637
+ score: packageContract + sceneManifest,
638
+ details: {
639
+ package_contract: packageContract,
640
+ scene_manifest: sceneManifest
641
+ }
642
+ };
643
+ }
644
+
645
+ /**
646
+ * Calculate lint pass rate dimension score (max 30 points).
647
+ * Formula: max(0, 30 - 10 * errors - 3 * warnings)
648
+ * @param {Object} lintResult - LintResult from lintScenePackage
649
+ * @returns {{ score: number, details: Object }}
650
+ */
651
+ function scoreLintPassRate(lintResult) {
652
+ const errorCount = (lintResult.errors || []).length;
653
+ const warningCount = (lintResult.warnings || []).length;
654
+
655
+ const errorDeductions = 10 * errorCount;
656
+ const warningDeductions = 3 * warningCount;
657
+ const score = Math.max(0, 30 - errorDeductions - warningDeductions);
658
+
659
+ return {
660
+ score,
661
+ details: {
662
+ error_deductions: errorDeductions,
663
+ warning_deductions: warningDeductions
664
+ }
665
+ };
666
+ }
667
+
668
+ /**
669
+ * Calculate documentation quality dimension score (max 20 points).
670
+ * Awards 10 for README, 5 for description, 5 for all variables having descriptions.
671
+ * @param {Object} lintResult - LintResult from lintScenePackage
672
+ * @returns {{ score: number, details: Object }}
673
+ */
674
+ function scoreDocumentationQuality(lintResult) {
675
+ const ctx = lintResult._context || {};
676
+
677
+ // 10 points for README presence
678
+ const readmePresent = ctx.hasReadme ? 10 : 0;
679
+
680
+ // 5 points for metadata.description present
681
+ const contract = ctx.contract || {};
682
+ const description = contract.metadata && contract.metadata.description;
683
+ const descriptionPresent = (typeof description === 'string' && description.trim() !== '') ? 5 : 0;
684
+
685
+ // 5 points for all variables having descriptions (no VARIABLE_MISSING_DESC warnings)
686
+ const warnings = lintResult.warnings || [];
687
+ const hasMissingDesc = warnings.some(w => w.code === 'VARIABLE_MISSING_DESC');
688
+ const variableDescriptions = hasMissingDesc ? 0 : 5;
689
+
690
+ return {
691
+ score: readmePresent + descriptionPresent + variableDescriptions,
692
+ details: {
693
+ readme_present: readmePresent,
694
+ description_present: descriptionPresent,
695
+ variable_descriptions: variableDescriptions
696
+ }
697
+ };
698
+ }
699
+
700
+ /**
701
+ * Calculate governance completeness dimension score (max 20 points).
702
+ * Awards 5 points each for risk_level, approval, idempotency, rollback_supported being set.
703
+ * @param {Object} lintResult - LintResult from lintScenePackage
704
+ * @returns {{ score: number, details: Object }}
705
+ */
706
+ function scoreGovernanceCompleteness(lintResult) {
707
+ const ctx = lintResult._context || {};
708
+ const contract = ctx.contract || {};
709
+ const governance = contract.governance || (contract.spec && contract.spec.governance_contract) || {};
710
+
711
+ // 5 points for valid risk_level
712
+ const riskLevel = (governance.risk_level && VALID_RISK_LEVELS.includes(governance.risk_level)) ? 5 : 0;
713
+
714
+ // 5 points for approval.required being boolean
715
+ const approval = (governance.approval && typeof governance.approval.required === 'boolean') ? 5 : 0;
716
+
717
+ // 5 points for idempotency.required being boolean
718
+ const idempotency = (governance.idempotency && typeof governance.idempotency.required === 'boolean') ? 5 : 0;
719
+
720
+ // 5 points for rollback_supported being boolean
721
+ const rollback = (typeof governance.rollback_supported === 'boolean') ? 5 : 0;
722
+
723
+ return {
724
+ score: riskLevel + approval + idempotency + rollback,
725
+ details: {
726
+ risk_level: riskLevel,
727
+ approval,
728
+ idempotency,
729
+ rollback
730
+ }
731
+ };
732
+ }
733
+
734
+ /**
735
+ * Score agent readiness based on agent_hints in the contract.
736
+ * Optional bonus dimension (max 10 points):
737
+ * - summary non-empty string: +4
738
+ * - complexity valid (low/medium/high): +3
739
+ * - suggested_sequence non-empty array: +3
740
+ * Returns 0 when agent_hints doesn't exist.
741
+ * @param {Object} lintResult - LintResult from lintScenePackage
742
+ * @returns {{ score: number, details: Object }}
743
+ */
744
+ function scoreAgentReadiness(lintResult) {
745
+ const ctx = lintResult._context || {};
746
+ const contract = ctx.contract || {};
747
+ const hints = contract.agent_hints;
748
+
749
+ if (!hints || typeof hints !== 'object') {
750
+ return { score: 0, details: {} };
751
+ }
752
+
753
+ const summary = (typeof hints.summary === 'string' && hints.summary.length > 0) ? 4 : 0;
754
+ const complexity = (['low', 'medium', 'high'].includes(hints.complexity)) ? 3 : 0;
755
+ const suggestedSequence = (Array.isArray(hints.suggested_sequence) && hints.suggested_sequence.length > 0) ? 3 : 0;
756
+
757
+ return {
758
+ score: summary + complexity + suggestedSequence,
759
+ details: {
760
+ summary,
761
+ complexity,
762
+ suggested_sequence: suggestedSequence
763
+ }
764
+ };
765
+ }
766
+
767
+ /**
768
+ * Calculate overall quality score based on LintResult.
769
+ * Aggregates four dimensions: contract validity, lint pass rate, documentation quality, governance completeness.
770
+ * Plus optional bonus: agent_readiness (max 10, can exceed 100 base).
771
+ * @param {Object} lintResult - LintResult from lintScenePackage
772
+ * @param {Object} [options] - { threshold: number }
773
+ * @returns {Object} ScoreResult
774
+ */
775
+ function calculateQualityScore(lintResult, options = {}) {
776
+ const threshold = typeof options.threshold === 'number' ? options.threshold : 60;
777
+
778
+ const contractValidity = scoreContractValidity(lintResult);
779
+ const lintPassRate = scoreLintPassRate(lintResult);
780
+ const documentationQuality = scoreDocumentationQuality(lintResult);
781
+ const governanceCompleteness = scoreGovernanceCompleteness(lintResult);
782
+ const agentReadiness = scoreAgentReadiness(lintResult);
783
+
784
+ const score = contractValidity.score + lintPassRate.score + documentationQuality.score + governanceCompleteness.score + agentReadiness.score;
785
+
786
+ return {
787
+ score,
788
+ pass: score >= threshold,
789
+ threshold,
790
+ dimensions: {
791
+ contract_validity: {
792
+ score: contractValidity.score,
793
+ max: SCORE_WEIGHTS.contractValidity,
794
+ details: contractValidity.details
795
+ },
796
+ lint_pass_rate: {
797
+ score: lintPassRate.score,
798
+ max: SCORE_WEIGHTS.lintPassRate,
799
+ details: lintPassRate.details
800
+ },
801
+ documentation_quality: {
802
+ score: documentationQuality.score,
803
+ max: SCORE_WEIGHTS.documentationQuality,
804
+ details: documentationQuality.details
805
+ },
806
+ governance_completeness: {
807
+ score: governanceCompleteness.score,
808
+ max: SCORE_WEIGHTS.governanceCompleteness,
809
+ details: governanceCompleteness.details
810
+ },
811
+ agent_readiness: {
812
+ score: agentReadiness.score,
813
+ max: 10,
814
+ details: agentReadiness.details
815
+ }
816
+ }
817
+ };
818
+ }
819
+
820
+ // ─── Module Exports ───────────────────────────────────────────────
821
+
822
+ module.exports = {
823
+ // Constants
824
+ KNOWN_BINDING_REF_PREFIXES,
825
+ VALID_RISK_LEVELS,
826
+ KEBAB_CASE_PATTERN,
827
+ SEMVER_PATTERN,
828
+ REQUIRED_PACKAGE_FIELDS,
829
+ REQUIRED_MANIFEST_FIELDS,
830
+ SCORE_WEIGHTS,
831
+ // Lint Engine Functions
832
+ createLintItem,
833
+ checkManifestCompleteness,
834
+ checkSceneManifestCompleteness,
835
+ checkBindingRefFormat,
836
+ checkGovernanceReasonableness,
837
+ checkPackageConsistency,
838
+ checkTemplateVariables,
839
+ checkDocumentation,
840
+ checkActionAbstraction,
841
+ checkDataLineage,
842
+ checkOntologySemanticCoverage,
843
+ checkAgentHints,
844
+ lintScenePackage,
845
+ // Score Calculator Functions
846
+ scoreContractValidity,
847
+ scoreLintPassRate,
848
+ scoreDocumentationQuality,
849
+ scoreGovernanceCompleteness,
850
+ scoreAgentReadiness,
851
+ calculateQualityScore
852
+ };