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.
- package/CHANGELOG.md +2513 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/README.zh.md +630 -0
- package/bin/kiro-spec-engine.js +796 -0
- package/bin/kse.js +3 -0
- package/bin/sce.js +3 -0
- package/bin/sco.js +3 -0
- package/docs/331-poc-adaptation-roadmap.md +156 -0
- package/docs/331-poc-dual-track-integration-guide.md +120 -0
- package/docs/331-poc-weekly-delivery-checklist.md +52 -0
- package/docs/OFFLINE_INSTALL.md +96 -0
- package/docs/README.md +279 -0
- package/docs/adopt-migration-guide.md +599 -0
- package/docs/adoption-guide.md +616 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/architecture.md +733 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice-review.md +208 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +459 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.md +492 -0
- package/docs/autonomous-control-guide.md +851 -0
- package/docs/command-reference.md +1368 -0
- package/docs/community.md +115 -0
- package/docs/cross-tool-guide.md +555 -0
- package/docs/developer-guide.md +619 -0
- package/docs/document-governance.md +865 -0
- package/docs/environment-management-guide.md +526 -0
- package/docs/examples/add-export-command/design.md +194 -0
- package/docs/examples/add-export-command/requirements.md +110 -0
- package/docs/examples/add-export-command/tasks.md +88 -0
- package/docs/examples/add-rest-api/design.md +855 -0
- package/docs/examples/add-rest-api/requirements.md +323 -0
- package/docs/examples/add-rest-api/tasks.md +355 -0
- package/docs/examples/add-user-dashboard/design.md +192 -0
- package/docs/examples/add-user-dashboard/requirements.md +143 -0
- package/docs/examples/add-user-dashboard/tasks.md +91 -0
- package/docs/faq.md +697 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +156 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +24 -0
- package/docs/images/wechat-qr.png +0 -0
- package/docs/integration-modes.md +529 -0
- package/docs/integration-philosophy.md +313 -0
- package/docs/knowledge-management-guide.md +263 -0
- package/docs/manual-workflows-guide.md +418 -0
- package/docs/moqui-capability-matrix.md +73 -0
- package/docs/moqui-template-core-library-playbook.md +109 -0
- package/docs/multi-agent-coordination-guide.md +553 -0
- package/docs/multi-repo-management-guide.md +1344 -0
- package/docs/quick-start-with-ai-tools.md +375 -0
- package/docs/quick-start.md +146 -0
- package/docs/release-checklist.md +121 -0
- package/docs/releases/README.md +13 -0
- package/docs/releases/v1.46.2-validation.md +45 -0
- package/docs/releases/v1.46.2.md +50 -0
- package/docs/scene-runtime-guide.md +347 -0
- package/docs/spec-collaboration-guide.md +369 -0
- package/docs/spec-locking-guide.md +225 -0
- package/docs/spec-numbering-guide.md +348 -0
- package/docs/spec-workflow.md +519 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/docs/team-collaboration-guide.md +465 -0
- package/docs/testing-strategy.md +272 -0
- package/docs/tools/claude-guide.md +654 -0
- package/docs/tools/cursor-guide.md +706 -0
- package/docs/tools/generic-guide.md +446 -0
- package/docs/tools/kiro-guide.md +308 -0
- package/docs/tools/vscode-guide.md +445 -0
- package/docs/tools/windsurf-guide.md +391 -0
- package/docs/troubleshooting.md +1135 -0
- package/docs/upgrade-guide.md +639 -0
- package/docs/value-observability-guide.md +127 -0
- package/docs/zh/README.md +341 -0
- package/docs/zh/quick-start.md +764 -0
- package/docs/zh/release-checklist.md +121 -0
- package/docs/zh/releases/README.md +13 -0
- package/docs/zh/releases/v1.46.2-validation.md +45 -0
- package/docs/zh/releases/v1.46.2.md +50 -0
- package/docs/zh/spec-numbering-guide.md +348 -0
- package/docs/zh/tools/claude-guide.md +349 -0
- package/docs/zh/tools/cursor-guide.md +281 -0
- package/docs/zh/tools/generic-guide.md +499 -0
- package/docs/zh/tools/kiro-guide.md +342 -0
- package/docs/zh/tools/vscode-guide.md +449 -0
- package/docs/zh/tools/windsurf-guide.md +378 -0
- package/docs/zh/value-observability-guide.md +127 -0
- package/docs//344/272/244/344/273/230/346/270/205/345/215/225.md +75 -0
- package/lib/adoption/adoption-logger.js +487 -0
- package/lib/adoption/adoption-strategy.js +538 -0
- package/lib/adoption/backup-manager.js +420 -0
- package/lib/adoption/conflict-resolver.js +410 -0
- package/lib/adoption/detection-engine.js +275 -0
- package/lib/adoption/diff-viewer.js +226 -0
- package/lib/adoption/error-formatter.js +509 -0
- package/lib/adoption/file-classifier.js +385 -0
- package/lib/adoption/progress-reporter.js +534 -0
- package/lib/adoption/smart-orchestrator.js +470 -0
- package/lib/adoption/strategy-selector.js +218 -0
- package/lib/adoption/summary-generator.js +493 -0
- package/lib/adoption/template-sync.js +605 -0
- package/lib/auto/autonomous-engine.js +485 -0
- package/lib/auto/checkpoint-manager.js +300 -0
- package/lib/auto/close-loop-runner.js +2476 -0
- package/lib/auto/config-schema.js +176 -0
- package/lib/auto/decision-engine.js +344 -0
- package/lib/auto/error-recovery-manager.js +580 -0
- package/lib/auto/goal-decomposer.js +278 -0
- package/lib/auto/progress-tracker.js +502 -0
- package/lib/auto/safety-manager.js +186 -0
- package/lib/auto/semantic-decomposer.js +137 -0
- package/lib/auto/state-manager.js +126 -0
- package/lib/auto/task-queue-manager.js +340 -0
- package/lib/backup/backup-system.js +372 -0
- package/lib/backup/selective-backup.js +207 -0
- package/lib/collab/agent-registry.js +240 -0
- package/lib/collab/collab-manager.js +285 -0
- package/lib/collab/contract-manager.js +320 -0
- package/lib/collab/coordinator.js +370 -0
- package/lib/collab/dependency-manager.js +280 -0
- package/lib/collab/index.js +20 -0
- package/lib/collab/integration-manager.js +202 -0
- package/lib/collab/merge-coordinator.js +252 -0
- package/lib/collab/metadata-manager.js +233 -0
- package/lib/collab/multi-agent-config.js +120 -0
- package/lib/collab/spec-lifecycle-manager.js +304 -0
- package/lib/collab/sync-barrier.js +88 -0
- package/lib/collab/visualizer.js +208 -0
- package/lib/commands/adopt.js +749 -0
- package/lib/commands/auto.js +19559 -0
- package/lib/commands/collab.js +275 -0
- package/lib/commands/context.js +99 -0
- package/lib/commands/docs.js +808 -0
- package/lib/commands/doctor.js +273 -0
- package/lib/commands/env.js +420 -0
- package/lib/commands/knowledge.js +309 -0
- package/lib/commands/lock.js +235 -0
- package/lib/commands/ops.js +409 -0
- package/lib/commands/orchestrate.js +446 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/repo.js +118 -0
- package/lib/commands/rollback.js +219 -0
- package/lib/commands/scene.js +15549 -0
- package/lib/commands/spec-bootstrap.js +147 -0
- package/lib/commands/spec-gate.js +157 -0
- package/lib/commands/spec-pipeline.js +205 -0
- package/lib/commands/status.js +321 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/templates.js +654 -0
- package/lib/commands/upgrade.js +231 -0
- package/lib/commands/value.js +569 -0
- package/lib/commands/watch.js +684 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace-multi.js +325 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/data/moqui-capability-lexicon.json +45 -0
- package/lib/environment/backup-system.js +189 -0
- package/lib/environment/environment-manager.js +379 -0
- package/lib/environment/environment-registry.js +168 -0
- package/lib/gitignore/gitignore-backup.js +229 -0
- package/lib/gitignore/gitignore-detector.js +239 -0
- package/lib/gitignore/gitignore-integration.js +267 -0
- package/lib/gitignore/gitignore-transformer.js +193 -0
- package/lib/gitignore/layered-rules-template.js +42 -0
- package/lib/governance/archive-tool.js +284 -0
- package/lib/governance/cleanup-tool.js +237 -0
- package/lib/governance/config-manager.js +186 -0
- package/lib/governance/diagnostic-engine.js +271 -0
- package/lib/governance/doc-reference-checker.js +200 -0
- package/lib/governance/execution-logger.js +243 -0
- package/lib/governance/file-scanner.js +285 -0
- package/lib/governance/hooks-manager.js +333 -0
- package/lib/governance/reporter.js +337 -0
- package/lib/governance/validation-engine.js +181 -0
- package/lib/i18n.js +79 -0
- package/lib/knowledge/entry-manager.js +208 -0
- package/lib/knowledge/index-manager.js +261 -0
- package/lib/knowledge/knowledge-manager.js +273 -0
- package/lib/knowledge/template-manager.js +191 -0
- package/lib/lock/index.js +21 -0
- package/lib/lock/lock-file.js +192 -0
- package/lib/lock/lock-manager.js +321 -0
- package/lib/lock/machine-identifier.js +135 -0
- package/lib/lock/steering-file-lock.js +207 -0
- package/lib/lock/task-lock-manager.js +345 -0
- package/lib/operations/audit-logger.js +293 -0
- package/lib/operations/feedback-manager.js +1147 -0
- package/lib/operations/index.js +23 -0
- package/lib/operations/models/index.js +170 -0
- package/lib/operations/operations-manager.js +151 -0
- package/lib/operations/operations-validator.js +280 -0
- package/lib/operations/permission-manager.js +354 -0
- package/lib/operations/template-loader.js +143 -0
- package/lib/orchestrator/agent-spawner.js +629 -0
- package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
- package/lib/orchestrator/index.js +19 -0
- package/lib/orchestrator/orchestration-engine.js +1270 -0
- package/lib/orchestrator/orchestrator-config.js +173 -0
- package/lib/orchestrator/status-monitor.js +591 -0
- package/lib/python-checker.js +209 -0
- package/lib/repo/config-manager.js +580 -0
- package/lib/repo/errors/config-error.js +13 -0
- package/lib/repo/errors/git-error.js +15 -0
- package/lib/repo/errors/repo-error.js +14 -0
- package/lib/repo/git-operations.js +181 -0
- package/lib/repo/handlers/.gitkeep +1 -0
- package/lib/repo/handlers/exec-handler.js +155 -0
- package/lib/repo/handlers/health-handler.js +169 -0
- package/lib/repo/handlers/init-handler.js +197 -0
- package/lib/repo/handlers/status-handler.js +176 -0
- package/lib/repo/output-formatter.js +184 -0
- package/lib/repo/path-resolver.js +178 -0
- package/lib/repo/repo-manager.js +514 -0
- package/lib/scene-runtime/audit-emitter.js +59 -0
- package/lib/scene-runtime/binding-plugin-loader.js +351 -0
- package/lib/scene-runtime/binding-registry.js +349 -0
- package/lib/scene-runtime/eval-bridge.js +44 -0
- package/lib/scene-runtime/index.js +19 -0
- package/lib/scene-runtime/moqui-adapter.js +620 -0
- package/lib/scene-runtime/moqui-client.js +606 -0
- package/lib/scene-runtime/moqui-extractor.js +2029 -0
- package/lib/scene-runtime/plan-compiler.js +208 -0
- package/lib/scene-runtime/policy-gate.js +58 -0
- package/lib/scene-runtime/runtime-executor.js +358 -0
- package/lib/scene-runtime/scene-loader.js +96 -0
- package/lib/scene-runtime/scene-ontology.js +959 -0
- package/lib/scene-runtime/scene-template-linter.js +852 -0
- package/lib/scene-runtime/templates/scene-template-erp-query-v0.1.yaml +28 -0
- package/lib/scene-runtime/templates/scene-template-hybrid-shadow-v0.1.yaml +34 -0
- package/lib/spec/bootstrap/context-collector.js +48 -0
- package/lib/spec/bootstrap/draft-generator.js +158 -0
- package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
- package/lib/spec/bootstrap/trace-emitter.js +59 -0
- package/lib/spec/multi-spec-orchestrate.js +93 -0
- package/lib/spec/pipeline/constants.js +6 -0
- package/lib/spec/pipeline/stage-adapters.js +118 -0
- package/lib/spec/pipeline/stage-runner.js +146 -0
- package/lib/spec/pipeline/state-store.js +119 -0
- package/lib/spec-gate/engine/gate-engine.js +165 -0
- package/lib/spec-gate/policy/default-policy.js +22 -0
- package/lib/spec-gate/policy/policy-loader.js +103 -0
- package/lib/spec-gate/result-emitter.js +81 -0
- package/lib/spec-gate/rules/default-rules.js +156 -0
- package/lib/spec-gate/rules/rule-registry.js +51 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/compliance-auto-fixer.js +204 -0
- package/lib/steering/compliance-cache.js +99 -0
- package/lib/steering/compliance-error-reporter.js +70 -0
- package/lib/steering/context-sync-manager.js +273 -0
- package/lib/steering/index.js +92 -0
- package/lib/steering/spec-steering.js +230 -0
- package/lib/steering/steering-compliance-checker.js +73 -0
- package/lib/steering/steering-loader.js +144 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/index.js +12 -0
- package/lib/task/task-claimer.js +489 -0
- package/lib/task/task-status-store.js +418 -0
- package/lib/templates/cache-manager.js +440 -0
- package/lib/templates/content-generalizer.js +247 -0
- package/lib/templates/frontmatter-generator.js +128 -0
- package/lib/templates/git-handler.js +471 -0
- package/lib/templates/metadata-collector.js +328 -0
- package/lib/templates/path-utils.js +144 -0
- package/lib/templates/registry-parser.js +505 -0
- package/lib/templates/spec-reader.js +216 -0
- package/lib/templates/template-applicator.js +249 -0
- package/lib/templates/template-creator.js +256 -0
- package/lib/templates/template-error.js +143 -0
- package/lib/templates/template-exporter.js +502 -0
- package/lib/templates/template-manager.js +782 -0
- package/lib/templates/template-validator.js +361 -0
- package/lib/upgrade/migration-engine.js +382 -0
- package/lib/upgrade/migrations/.gitkeep +52 -0
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
- package/lib/utils/file-diff.js +177 -0
- package/lib/utils/fs-utils.js +274 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/utils/validation.js +324 -0
- package/lib/value/gate-summary-emitter.js +99 -0
- package/lib/value/metric-contract-loader.js +210 -0
- package/lib/value/risk-evaluator.js +117 -0
- package/lib/value/weekly-snapshot-builder.js +61 -0
- package/lib/version/version-checker.js +156 -0
- package/lib/version/version-manager.js +327 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/multi/global-config.js +150 -0
- package/lib/workspace/multi/index.js +22 -0
- package/lib/workspace/multi/path-utils.js +173 -0
- package/lib/workspace/multi/workspace-context-resolver.js +244 -0
- package/lib/workspace/multi/workspace-registry.js +196 -0
- package/lib/workspace/multi/workspace-state-manager.js +537 -0
- package/lib/workspace/multi/workspace.js +90 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/locales/en.json +114 -0
- package/locales/zh.json +114 -0
- package/package.json +102 -0
- package/template/.kiro/README.md +247 -0
- package/template/.kiro/hooks/check-spec-on-create.kiro.hook +17 -0
- package/template/.kiro/hooks/run-tests-on-save.kiro.hook +13 -0
- package/template/.kiro/hooks/sync-tasks-on-edit.kiro.hook +16 -0
- package/template/.kiro/specs/SPEC_WORKFLOW_GUIDE.md +134 -0
- package/template/.kiro/steering/CORE_PRINCIPLES.md +133 -0
- package/template/.kiro/steering/CURRENT_CONTEXT.md +30 -0
- package/template/.kiro/steering/ENVIRONMENT.md +35 -0
- package/template/.kiro/steering/RULES_GUIDE.md +46 -0
- package/template/.kiro/templates/operations/default/change-impact.md +112 -0
- package/template/.kiro/templates/operations/default/deployment.md +91 -0
- package/template/.kiro/templates/operations/default/feedback-response.md +269 -0
- package/template/.kiro/templates/operations/default/migration-plan.md +172 -0
- package/template/.kiro/templates/operations/default/monitoring.md +135 -0
- package/template/.kiro/templates/operations/default/operations.md +135 -0
- package/template/.kiro/templates/operations/default/rollback.md +143 -0
- package/template/.kiro/templates/operations/default/tools.yaml +364 -0
- package/template/.kiro/templates/operations/default/troubleshooting.md +123 -0
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer.py +676 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- package/template/.kiro/tools/workflow_quality_gate.py +100 -0
- 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
|
+
};
|