@shrkcrft/inspector 0.1.0-alpha.30 → 0.1.0-alpha.32
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/dist/acceptance-replay.js +1 -1
- package/dist/agent-contract-templates.js +6 -6
- package/dist/agent-contract.d.ts.map +1 -1
- package/dist/agent-contract.js +13 -8
- package/dist/agent-orchestration.js +2 -2
- package/dist/agent-task-prep.d.ts +16 -0
- package/dist/agent-task-prep.d.ts.map +1 -1
- package/dist/agent-task-prep.js +60 -5
- package/dist/ai-readiness.d.ts.map +1 -1
- package/dist/ai-readiness.js +21 -3
- package/dist/architecture-map.d.ts +16 -0
- package/dist/architecture-map.d.ts.map +1 -1
- package/dist/architecture-map.js +42 -26
- package/dist/area-explore.d.ts +2 -2
- package/dist/area-explore.d.ts.map +1 -1
- package/dist/area-explore.js +36 -16
- package/dist/area-map.d.ts +56 -11
- package/dist/area-map.d.ts.map +1 -1
- package/dist/area-map.js +106 -25
- package/dist/asset-doctor-proposed-exit.d.ts +21 -0
- package/dist/asset-doctor-proposed-exit.d.ts.map +1 -0
- package/dist/asset-doctor-proposed-exit.js +22 -0
- package/dist/asset-provenance.js +1 -1
- package/dist/boundaries-changed-only.d.ts +24 -1
- package/dist/boundaries-changed-only.d.ts.map +1 -1
- package/dist/boundaries-changed-only.js +12 -2
- package/dist/boundary-check-options.model.d.ts +40 -0
- package/dist/boundary-check-options.model.d.ts.map +1 -0
- package/dist/boundary-check-options.model.js +1 -0
- package/dist/boundary-check-result.model.d.ts +89 -0
- package/dist/boundary-check-result.model.d.ts.map +1 -0
- package/dist/boundary-check-result.model.js +1 -0
- package/dist/boundary-configuration-status.d.ts +38 -0
- package/dist/boundary-configuration-status.d.ts.map +1 -0
- package/dist/boundary-configuration-status.js +118 -0
- package/dist/boundary-configuration-status.model.d.ts +37 -0
- package/dist/boundary-configuration-status.model.d.ts.map +1 -0
- package/dist/boundary-configuration-status.model.js +1 -0
- package/dist/boundary-load-issue.model.d.ts +30 -0
- package/dist/boundary-load-issue.model.d.ts.map +1 -0
- package/dist/boundary-load-issue.model.js +1 -0
- package/dist/boundary-rule-check.model.d.ts +44 -0
- package/dist/boundary-rule-check.model.d.ts.map +1 -0
- package/dist/boundary-rule-check.model.js +1 -0
- package/dist/boundary-rule-invalidation.model.d.ts +31 -0
- package/dist/boundary-rule-invalidation.model.d.ts.map +1 -0
- package/dist/boundary-rule-invalidation.model.js +1 -0
- package/dist/boundary-rule-set-diff.model.d.ts +75 -0
- package/dist/boundary-rule-set-diff.model.d.ts.map +1 -0
- package/dist/boundary-rule-set-diff.model.js +1 -0
- package/dist/boundary-rule-sources.d.ts +28 -0
- package/dist/boundary-rule-sources.d.ts.map +1 -0
- package/dist/boundary-rule-sources.js +110 -0
- package/dist/boundary-stale-exception-finding.model.d.ts +12 -0
- package/dist/boundary-stale-exception-finding.model.d.ts.map +1 -0
- package/dist/boundary-stale-exception-finding.model.js +1 -0
- package/dist/boundary-unit-liveness.d.ts +35 -0
- package/dist/boundary-unit-liveness.d.ts.map +1 -0
- package/dist/boundary-unit-liveness.js +71 -0
- package/dist/change-intent.d.ts +3 -0
- package/dist/change-intent.d.ts.map +1 -1
- package/dist/change-intent.js +61 -27
- package/dist/changes-summary.d.ts.map +1 -1
- package/dist/changes-summary.js +19 -9
- package/dist/ci-integrity-report.js +1 -1
- package/dist/ci-predict.d.ts.map +1 -1
- package/dist/ci-predict.js +38 -5
- package/dist/code-intelligence-doctor.d.ts +6 -0
- package/dist/code-intelligence-doctor.d.ts.map +1 -1
- package/dist/code-intelligence-doctor.js +22 -7
- package/dist/codemod-assist.d.ts.map +1 -1
- package/dist/codemod-assist.js +3 -1
- package/dist/command-recommender.d.ts +45 -2
- package/dist/command-recommender.d.ts.map +1 -1
- package/dist/command-recommender.js +41 -263
- package/dist/command-resolution-status.d.ts +44 -0
- package/dist/command-resolution-status.d.ts.map +1 -0
- package/dist/command-resolution-status.js +44 -0
- package/dist/command-safety-level.d.ts +15 -0
- package/dist/command-safety-level.d.ts.map +1 -0
- package/dist/command-safety-level.js +43 -0
- package/dist/command-taxonomy.js +1 -1
- package/dist/comment-syntax.d.ts +15 -0
- package/dist/comment-syntax.d.ts.map +1 -0
- package/dist/comment-syntax.js +15 -0
- package/dist/compliance-evidence.js +1 -1
- package/dist/compliance-profiles.js +1 -1
- package/dist/construct-registry.d.ts +40 -0
- package/dist/construct-registry.d.ts.map +1 -1
- package/dist/construct-registry.js +186 -82
- package/dist/context-tuning.d.ts +4 -0
- package/dist/context-tuning.d.ts.map +1 -1
- package/dist/context-tuning.js +10 -7
- package/dist/contract-template-registry.d.ts +10 -0
- package/dist/contract-template-registry.d.ts.map +1 -1
- package/dist/contract-template-registry.js +59 -33
- package/dist/contribution-kind.d.ts +52 -0
- package/dist/contribution-kind.d.ts.map +1 -0
- package/dist/contribution-kind.js +52 -0
- package/dist/contribution-load-failures.d.ts +122 -0
- package/dist/contribution-load-failures.d.ts.map +1 -0
- package/dist/contribution-load-failures.js +592 -0
- package/dist/contributions-report.d.ts +28 -0
- package/dist/contributions-report.d.ts.map +1 -0
- package/dist/contributions-report.js +242 -0
- package/dist/convention-applicability.d.ts +46 -0
- package/dist/convention-applicability.d.ts.map +1 -0
- package/dist/convention-applicability.js +247 -0
- package/dist/convention-filter-level.d.ts +13 -0
- package/dist/convention-filter-level.d.ts.map +1 -0
- package/dist/convention-filter-level.js +13 -0
- package/dist/convention-registry.d.ts +51 -4
- package/dist/convention-registry.d.ts.map +1 -1
- package/dist/convention-registry.js +135 -60
- package/dist/coverage-report.d.ts.map +1 -1
- package/dist/coverage-report.js +6 -6
- package/dist/custom-checks.d.ts +60 -1
- package/dist/custom-checks.d.ts.map +1 -1
- package/dist/custom-checks.js +161 -6
- package/dist/dashboard/dashboard-data.d.ts +6 -0
- package/dist/dashboard/dashboard-data.d.ts.map +1 -1
- package/dist/dashboard/dashboard-data.js +99 -8
- package/dist/dashboard/dashboard-knowledge.d.ts.map +1 -1
- package/dist/dashboard/dashboard-knowledge.js +14 -2
- package/dist/decision-records.d.ts +24 -0
- package/dist/decision-records.d.ts.map +1 -1
- package/dist/decision-records.js +231 -84
- package/dist/declared-cross-references.d.ts +61 -0
- package/dist/declared-cross-references.d.ts.map +1 -0
- package/dist/declared-cross-references.js +550 -0
- package/dist/declared-gate-plane-rules.d.ts +24 -0
- package/dist/declared-gate-plane-rules.d.ts.map +1 -0
- package/dist/declared-gate-plane-rules.js +39 -0
- package/dist/declared-reference-coverage.d.ts +27 -0
- package/dist/declared-reference-coverage.d.ts.map +1 -0
- package/dist/declared-reference-coverage.js +55 -0
- package/dist/declared-xref-status.d.ts +19 -0
- package/dist/declared-xref-status.d.ts.map +1 -0
- package/dist/declared-xref-status.js +19 -0
- package/dist/delegate-pack-recipes.d.ts +13 -2
- package/dist/delegate-pack-recipes.d.ts.map +1 -1
- package/dist/delegate-pack-recipes.js +45 -14
- package/dist/demo-package.js +2 -2
- package/dist/demo-script.js +2 -2
- package/dist/demo-workflow.js +3 -3
- package/dist/dev-cycle.js +1 -1
- package/dist/dev-session.js +1 -1
- package/dist/doc-references.d.ts +23 -2
- package/dist/doc-references.d.ts.map +1 -1
- package/dist/doc-references.js +96 -17
- package/dist/doctor-result.d.ts +8 -0
- package/dist/doctor-result.d.ts.map +1 -1
- package/dist/doctor-verdict-kind.d.ts +19 -0
- package/dist/doctor-verdict-kind.d.ts.map +1 -0
- package/dist/doctor-verdict-kind.js +19 -0
- package/dist/doctor-verdict.d.ts +18 -0
- package/dist/doctor-verdict.d.ts.map +1 -0
- package/dist/doctor-verdict.js +28 -0
- package/dist/drift.d.ts +17 -0
- package/dist/drift.d.ts.map +1 -1
- package/dist/drift.js +40 -10
- package/dist/entrypoint-matrix.d.ts.map +1 -1
- package/dist/entrypoint-matrix.js +4 -1
- package/dist/feedback-ingestion.d.ts +28 -2
- package/dist/feedback-ingestion.d.ts.map +1 -1
- package/dist/feedback-ingestion.js +75 -20
- package/dist/file-languages.d.ts +9 -0
- package/dist/file-languages.d.ts.map +1 -0
- package/dist/file-languages.js +91 -0
- package/dist/fix-preview.d.ts.map +1 -1
- package/dist/fix-preview.js +87 -22
- package/dist/framework-extractor-outcomes.d.ts +27 -0
- package/dist/framework-extractor-outcomes.d.ts.map +1 -0
- package/dist/framework-extractor-outcomes.js +83 -0
- package/dist/fuzzy-impact.d.ts.map +1 -1
- package/dist/fuzzy-impact.js +3 -2
- package/dist/git-helpers.d.ts +29 -4
- package/dist/git-helpers.d.ts.map +1 -1
- package/dist/git-helpers.js +69 -10
- package/dist/healing-plan.d.ts.map +1 -1
- package/dist/healing-plan.js +3 -4
- package/dist/helper-catalog.d.ts +25 -0
- package/dist/helper-catalog.d.ts.map +1 -0
- package/dist/helper-catalog.js +91 -0
- package/dist/helper-registry.d.ts.map +1 -1
- package/dist/helper-registry.js +17 -1
- package/dist/helper-view.d.ts +33 -0
- package/dist/helper-view.d.ts.map +1 -0
- package/dist/helper-view.js +1 -0
- package/dist/i-asset-doctor-flags.d.ts +16 -0
- package/dist/i-asset-doctor-flags.d.ts.map +1 -0
- package/dist/i-asset-doctor-flags.js +1 -0
- package/dist/i-command-resolution.d.ts +22 -0
- package/dist/i-command-resolution.d.ts.map +1 -0
- package/dist/i-command-resolution.js +1 -0
- package/dist/i-command-resolve-options.d.ts +24 -0
- package/dist/i-command-resolve-options.d.ts.map +1 -0
- package/dist/i-command-resolve-options.js +1 -0
- package/dist/i-command-safety.d.ts +13 -0
- package/dist/i-command-safety.d.ts.map +1 -0
- package/dist/i-command-safety.js +1 -0
- package/dist/i-contribution-accepted-entry.d.ts +18 -0
- package/dist/i-contribution-accepted-entry.d.ts.map +1 -0
- package/dist/i-contribution-accepted-entry.js +1 -0
- package/dist/i-contribution-entry-rejection.d.ts +21 -0
- package/dist/i-contribution-entry-rejection.d.ts.map +1 -0
- package/dist/i-contribution-entry-rejection.js +1 -0
- package/dist/i-contribution-file-issue.d.ts +18 -0
- package/dist/i-contribution-file-issue.d.ts.map +1 -0
- package/dist/i-contribution-file-issue.js +1 -0
- package/dist/i-contribution-file-report.d.ts +40 -0
- package/dist/i-contribution-file-report.d.ts.map +1 -0
- package/dist/i-contribution-file-report.js +1 -0
- package/dist/i-contribution-file-validation.d.ts +32 -0
- package/dist/i-contribution-file-validation.d.ts.map +1 -0
- package/dist/i-contribution-file-validation.js +1 -0
- package/dist/i-contributions-report.d.ts +31 -0
- package/dist/i-contributions-report.d.ts.map +1 -0
- package/dist/i-contributions-report.js +1 -0
- package/dist/i-convention-applicability-reason.d.ts +29 -0
- package/dist/i-convention-applicability-reason.d.ts.map +1 -0
- package/dist/i-convention-applicability-reason.js +1 -0
- package/dist/i-convention-applicability.d.ts +9 -0
- package/dist/i-convention-applicability.d.ts.map +1 -0
- package/dist/i-convention-applicability.js +1 -0
- package/dist/i-convention-scope.d.ts +19 -0
- package/dist/i-convention-scope.d.ts.map +1 -0
- package/dist/i-convention-scope.js +1 -0
- package/dist/i-declared-xref-field.d.ts +25 -0
- package/dist/i-declared-xref-field.d.ts.map +1 -0
- package/dist/i-declared-xref-field.js +1 -0
- package/dist/i-declared-xref-issue.d.ts +25 -0
- package/dist/i-declared-xref-issue.d.ts.map +1 -0
- package/dist/i-declared-xref-issue.js +1 -0
- package/dist/i-declared-xref-report.d.ts +48 -0
- package/dist/i-declared-xref-report.d.ts.map +1 -0
- package/dist/i-declared-xref-report.js +1 -0
- package/dist/i-declared-xref-row.d.ts +29 -0
- package/dist/i-declared-xref-row.d.ts.map +1 -0
- package/dist/i-declared-xref-row.js +1 -0
- package/dist/i-doctor-verdict.d.ts +10 -0
- package/dist/i-doctor-verdict.d.ts.map +1 -0
- package/dist/i-doctor-verdict.js +1 -0
- package/dist/i-file-language.d.ts +11 -0
- package/dist/i-file-language.d.ts.map +1 -0
- package/dist/i-file-language.js +1 -0
- package/dist/i-kind-outcomes.d.ts +13 -0
- package/dist/i-kind-outcomes.d.ts.map +1 -0
- package/dist/i-kind-outcomes.js +1 -0
- package/dist/i-not-applicable-convention.d.ts +17 -0
- package/dist/i-not-applicable-convention.d.ts.map +1 -0
- package/dist/i-not-applicable-convention.js +1 -0
- package/dist/i-pack-doctor-verdict.d.ts +15 -0
- package/dist/i-pack-doctor-verdict.d.ts.map +1 -0
- package/dist/i-pack-doctor-verdict.js +1 -0
- package/dist/i-probed-id-field.d.ts +17 -0
- package/dist/i-probed-id-field.d.ts.map +1 -0
- package/dist/i-probed-id-field.js +1 -0
- package/dist/i-reference-kind-declaration.d.ts +38 -0
- package/dist/i-reference-kind-declaration.d.ts.map +1 -0
- package/dist/i-reference-kind-declaration.js +1 -0
- package/dist/i-reference-pack-origin.d.ts +13 -0
- package/dist/i-reference-pack-origin.d.ts.map +1 -0
- package/dist/i-reference-pack-origin.js +1 -0
- package/dist/i-reference-warm-options.d.ts +21 -0
- package/dist/i-reference-warm-options.d.ts.map +1 -0
- package/dist/i-reference-warm-options.js +1 -0
- package/dist/i-registry-outcomes.d.ts +16 -0
- package/dist/i-registry-outcomes.d.ts.map +1 -0
- package/dist/i-registry-outcomes.js +1 -0
- package/dist/i-scaffold-pattern-doctor-report.d.ts +35 -0
- package/dist/i-scaffold-pattern-doctor-report.d.ts.map +1 -0
- package/dist/i-scaffold-pattern-doctor-report.js +1 -0
- package/dist/i-search-tuning-key-probe.d.ts +30 -0
- package/dist/i-search-tuning-key-probe.d.ts.map +1 -0
- package/dist/i-search-tuning-key-probe.js +1 -0
- package/dist/i-search-tuning-lint-issue.d.ts +61 -0
- package/dist/i-search-tuning-lint-issue.d.ts.map +1 -0
- package/dist/i-search-tuning-lint-issue.js +1 -0
- package/dist/i-search-tuning-lint-report.d.ts +38 -0
- package/dist/i-search-tuning-lint-report.d.ts.map +1 -0
- package/dist/i-search-tuning-lint-report.js +1 -0
- package/dist/i-stale-path-hint-input.d.ts +18 -0
- package/dist/i-stale-path-hint-input.d.ts.map +1 -0
- package/dist/i-stale-path-hint-input.js +1 -0
- package/dist/i-unresolvable-reference-scan.d.ts +14 -0
- package/dist/i-unresolvable-reference-scan.d.ts.map +1 -0
- package/dist/i-unresolvable-reference-scan.js +1 -0
- package/dist/i-unresolvable-reference.d.ts +29 -0
- package/dist/i-unresolvable-reference.d.ts.map +1 -0
- package/dist/i-unresolvable-reference.js +1 -0
- package/dist/i-unverifiable-remedy-group.d.ts +10 -0
- package/dist/i-unverifiable-remedy-group.d.ts.map +1 -0
- package/dist/i-unverifiable-remedy-group.js +1 -0
- package/dist/i-workspace-profile-payload.d.ts +12 -0
- package/dist/i-workspace-profile-payload.d.ts.map +1 -0
- package/dist/i-workspace-profile-payload.js +1 -0
- package/dist/impact-analysis.d.ts +15 -0
- package/dist/impact-analysis.d.ts.map +1 -1
- package/dist/impact-analysis.js +58 -13
- package/dist/impact-render.d.ts.map +1 -1
- package/dist/impact-render.js +22 -0
- package/dist/import-hygiene.d.ts +35 -1
- package/dist/import-hygiene.d.ts.map +1 -1
- package/dist/import-hygiene.js +107 -13
- package/dist/index.d.ts +158 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +197 -0
- package/dist/inspection-discovery.d.ts +45 -0
- package/dist/inspection-discovery.d.ts.map +1 -0
- package/dist/inspection-discovery.js +70 -0
- package/dist/knowledge-advisory-code.d.ts +17 -0
- package/dist/knowledge-advisory-code.d.ts.map +1 -0
- package/dist/knowledge-advisory-code.js +17 -0
- package/dist/knowledge-aged-entry.d.ts +10 -0
- package/dist/knowledge-aged-entry.d.ts.map +1 -0
- package/dist/knowledge-aged-entry.js +1 -0
- package/dist/knowledge-authoring.d.ts +25 -2
- package/dist/knowledge-authoring.d.ts.map +1 -1
- package/dist/knowledge-authoring.js +53 -13
- package/dist/knowledge-contribution-slots.d.ts +18 -0
- package/dist/knowledge-contribution-slots.d.ts.map +1 -0
- package/dist/knowledge-contribution-slots.js +25 -0
- package/dist/knowledge-entry-rejections.d.ts +17 -0
- package/dist/knowledge-entry-rejections.d.ts.map +1 -0
- package/dist/knowledge-entry-rejections.js +79 -0
- package/dist/knowledge-entry-verdict-record.d.ts +34 -0
- package/dist/knowledge-entry-verdict-record.d.ts.map +1 -0
- package/dist/knowledge-entry-verdict-record.js +1 -0
- package/dist/knowledge-entry-verdict.d.ts +17 -0
- package/dist/knowledge-entry-verdict.d.ts.map +1 -0
- package/dist/knowledge-entry-verdict.js +17 -0
- package/dist/knowledge-graph.d.ts +6 -0
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +18 -7
- package/dist/knowledge-kind-bucket.d.ts +19 -0
- package/dist/knowledge-kind-bucket.d.ts.map +1 -0
- package/dist/knowledge-kind-bucket.js +1 -0
- package/dist/knowledge-load-failure.d.ts +23 -0
- package/dist/knowledge-load-failure.d.ts.map +1 -0
- package/dist/knowledge-load-failure.js +1 -0
- package/dist/knowledge-min-referenced-valve.d.ts +12 -0
- package/dist/knowledge-min-referenced-valve.d.ts.map +1 -0
- package/dist/knowledge-min-referenced-valve.js +12 -0
- package/dist/knowledge-reference-kind-bucket.d.ts +11 -0
- package/dist/knowledge-reference-kind-bucket.d.ts.map +1 -0
- package/dist/knowledge-reference-kind-bucket.js +1 -0
- package/dist/knowledge-rejected-entry.d.ts +28 -0
- package/dist/knowledge-rejected-entry.d.ts.map +1 -0
- package/dist/knowledge-rejected-entry.js +1 -0
- package/dist/knowledge-rename-command.d.ts +20 -0
- package/dist/knowledge-rename-command.d.ts.map +1 -0
- package/dist/knowledge-rename-command.js +34 -0
- package/dist/knowledge-rename-verb.d.ts +11 -0
- package/dist/knowledge-rename-verb.d.ts.map +1 -0
- package/dist/knowledge-rename-verb.js +11 -0
- package/dist/knowledge-rename.d.ts +1 -1
- package/dist/knowledge-rename.d.ts.map +1 -1
- package/dist/knowledge-rename.js +6 -5
- package/dist/knowledge-stale-advisory.d.ts +14 -0
- package/dist/knowledge-stale-advisory.d.ts.map +1 -0
- package/dist/knowledge-stale-advisory.js +1 -0
- package/dist/knowledge-stale-coverage.d.ts +19 -0
- package/dist/knowledge-stale-coverage.d.ts.map +1 -0
- package/dist/knowledge-stale-coverage.js +1 -0
- package/dist/knowledge-stale-gate-flags.d.ts +33 -0
- package/dist/knowledge-stale-gate-flags.d.ts.map +1 -0
- package/dist/knowledge-stale-gate-flags.js +1 -0
- package/dist/knowledge-stale-gate-input.d.ts +46 -0
- package/dist/knowledge-stale-gate-input.d.ts.map +1 -0
- package/dist/knowledge-stale-gate-input.js +1 -0
- package/dist/knowledge-stale-gate-result.d.ts +33 -0
- package/dist/knowledge-stale-gate-result.d.ts.map +1 -0
- package/dist/knowledge-stale-gate-result.js +1 -0
- package/dist/knowledge-stale-gate-rule.d.ts +33 -0
- package/dist/knowledge-stale-gate-rule.d.ts.map +1 -0
- package/dist/knowledge-stale-gate-rule.js +1 -0
- package/dist/knowledge-stale-gate-violation.d.ts +13 -0
- package/dist/knowledge-stale-gate-violation.d.ts.map +1 -0
- package/dist/knowledge-stale-gate-violation.js +1 -0
- package/dist/knowledge-stale-gate.d.ts +85 -0
- package/dist/knowledge-stale-gate.d.ts.map +1 -0
- package/dist/knowledge-stale-gate.js +501 -0
- package/dist/knowledge-stale-quality-gate.d.ts +26 -0
- package/dist/knowledge-stale-quality-gate.d.ts.map +1 -0
- package/dist/knowledge-stale-quality-gate.js +140 -0
- package/dist/knowledge-stale.d.ts +132 -9
- package/dist/knowledge-stale.d.ts.map +1 -1
- package/dist/knowledge-stale.js +1193 -294
- package/dist/knowledge-unverifiable-reason.d.ts +12 -0
- package/dist/knowledge-unverifiable-reason.d.ts.map +1 -0
- package/dist/knowledge-unverifiable-reason.js +12 -0
- package/dist/knowledge-unverifiable-remedy.d.ts +56 -0
- package/dist/knowledge-unverifiable-remedy.d.ts.map +1 -0
- package/dist/knowledge-unverifiable-remedy.js +0 -0
- package/dist/loader-diagnostics.d.ts +14 -2
- package/dist/loader-diagnostics.d.ts.map +1 -1
- package/dist/match-terms.d.ts +48 -0
- package/dist/match-terms.d.ts.map +1 -0
- package/dist/match-terms.js +155 -0
- package/dist/migration-profile-registry.d.ts +10 -0
- package/dist/migration-profile-registry.d.ts.map +1 -1
- package/dist/migration-profile-registry.js +57 -34
- package/dist/migration-readiness.d.ts.map +1 -1
- package/dist/migration-readiness.js +1 -3
- package/dist/nearest-id.d.ts +10 -26
- package/dist/nearest-id.d.ts.map +1 -1
- package/dist/nearest-id.js +9 -59
- package/dist/pack-asset-freshness.d.ts +65 -0
- package/dist/pack-asset-freshness.d.ts.map +1 -0
- package/dist/pack-asset-freshness.js +340 -0
- package/dist/pack-author-ux.d.ts +24 -3
- package/dist/pack-author-ux.d.ts.map +1 -1
- package/dist/pack-author-ux.js +87 -93
- package/dist/pack-contributions-inventory.d.ts +79 -39
- package/dist/pack-contributions-inventory.d.ts.map +1 -1
- package/dist/pack-contributions-inventory.js +445 -286
- package/dist/pack-doctor-verdict.d.ts +29 -0
- package/dist/pack-doctor-verdict.d.ts.map +1 -0
- package/dist/pack-doctor-verdict.js +45 -0
- package/dist/pack-doctor.d.ts +74 -1
- package/dist/pack-doctor.d.ts.map +1 -1
- package/dist/pack-doctor.js +281 -1
- package/dist/pack-helper-plan.d.ts +24 -0
- package/dist/pack-helper-plan.d.ts.map +1 -0
- package/dist/pack-helper-plan.js +77 -0
- package/dist/pack-helper-registry.d.ts +14 -0
- package/dist/pack-helper-registry.d.ts.map +1 -1
- package/dist/pack-helper-registry.js +76 -37
- package/dist/pack-manifest-reader.d.ts +8 -0
- package/dist/pack-manifest-reader.d.ts.map +1 -0
- package/dist/pack-manifest-reader.js +79 -0
- package/dist/pack-release-check.d.ts +14 -1
- package/dist/pack-release-check.d.ts.map +1 -1
- package/dist/pack-release-check.js +23 -1
- package/dist/pack-signature-status.d.ts +18 -6
- package/dist/pack-signature-status.d.ts.map +1 -1
- package/dist/pack-signature-status.js +59 -105
- package/dist/pack-test-runner.d.ts +27 -5
- package/dist/pack-test-runner.d.ts.map +1 -1
- package/dist/pack-test-runner.js +91 -35
- package/dist/pack-typecheck.d.ts +8 -0
- package/dist/pack-typecheck.d.ts.map +1 -0
- package/dist/pack-typecheck.js +76 -0
- package/dist/plan-review.d.ts +2 -0
- package/dist/plan-review.d.ts.map +1 -1
- package/dist/plan-review.js +17 -5
- package/dist/plan-simulation.d.ts.map +1 -1
- package/dist/plan-simulation.js +16 -5
- package/dist/playbook-load-issue.d.ts +16 -0
- package/dist/playbook-load-issue.d.ts.map +1 -0
- package/dist/playbook-load-issue.js +1 -0
- package/dist/playbook-registry.d.ts +32 -1
- package/dist/playbook-registry.d.ts.map +1 -1
- package/dist/playbook-registry.js +144 -55
- package/dist/policy-declaration.d.ts +18 -0
- package/dist/policy-declaration.d.ts.map +1 -0
- package/dist/policy-declaration.js +1 -0
- package/dist/policy-engine.d.ts +8 -0
- package/dist/policy-engine.d.ts.map +1 -1
- package/dist/policy-engine.js +26 -9
- package/dist/policy-overrides.d.ts.map +1 -1
- package/dist/policy-overrides.js +4 -4
- package/dist/policy-registry.d.ts +27 -0
- package/dist/policy-registry.d.ts.map +1 -1
- package/dist/policy-registry.js +87 -20
- package/dist/probed-id-fields.d.ts +21 -0
- package/dist/probed-id-fields.d.ts.map +1 -0
- package/dist/probed-id-fields.js +25 -0
- package/dist/probed-id-source.d.ts +11 -0
- package/dist/probed-id-source.d.ts.map +1 -0
- package/dist/probed-id-source.js +11 -0
- package/dist/profile-registry.d.ts +25 -2
- package/dist/profile-registry.d.ts.map +1 -1
- package/dist/profile-registry.js +59 -6
- package/dist/propose-knowledge.js +3 -2
- package/dist/quality-gate-examination.d.ts +15 -0
- package/dist/quality-gate-examination.d.ts.map +1 -0
- package/dist/quality-gate-examination.js +15 -0
- package/dist/quality-gate-row-status.d.ts +19 -0
- package/dist/quality-gate-row-status.d.ts.map +1 -0
- package/dist/quality-gate-row-status.js +28 -0
- package/dist/quality-gate-status.d.ts +16 -0
- package/dist/quality-gate-status.d.ts.map +1 -0
- package/dist/quality-gate-status.js +16 -0
- package/dist/quality-html.d.ts.map +1 -1
- package/dist/quality-html.js +14 -3
- package/dist/quality-report-coverage.d.ts +26 -0
- package/dist/quality-report-coverage.d.ts.map +1 -0
- package/dist/quality-report-coverage.js +48 -0
- package/dist/quality-report.d.ts +28 -1
- package/dist/quality-report.d.ts.map +1 -1
- package/dist/quality-report.js +263 -35
- package/dist/query-intent-kind.d.ts +18 -0
- package/dist/query-intent-kind.d.ts.map +1 -0
- package/dist/query-intent-kind.js +18 -0
- package/dist/query-intent-result.d.ts +23 -0
- package/dist/query-intent-result.d.ts.map +1 -0
- package/dist/query-intent-result.js +1 -0
- package/dist/query-intent.d.ts +18 -0
- package/dist/query-intent.d.ts.map +1 -0
- package/dist/query-intent.js +336 -0
- package/dist/query-resolver.d.ts +18 -0
- package/dist/query-resolver.d.ts.map +1 -1
- package/dist/query-resolver.js +7 -4
- package/dist/ranked-recommendations.d.ts +36 -0
- package/dist/ranked-recommendations.d.ts.map +1 -0
- package/dist/ranked-recommendations.js +1 -0
- package/dist/ranker-explainability.d.ts.map +1 -1
- package/dist/ranker-explainability.js +38 -10
- package/dist/recommend-recipe.d.ts +17 -0
- package/dist/recommend-recipe.d.ts.map +1 -0
- package/dist/recommend-recipe.js +1 -0
- package/dist/recommend-recipes.d.ts +9 -0
- package/dist/recommend-recipes.d.ts.map +1 -0
- package/dist/recommend-recipes.js +106 -0
- package/dist/recommendation-candidate.d.ts +30 -0
- package/dist/recommendation-candidate.d.ts.map +1 -0
- package/dist/recommendation-candidate.js +1 -0
- package/dist/recommendation-confidence.d.ts +27 -0
- package/dist/recommendation-confidence.d.ts.map +1 -0
- package/dist/recommendation-confidence.js +1 -0
- package/dist/recommendation-ranking-options.d.ts +24 -0
- package/dist/recommendation-ranking-options.d.ts.map +1 -0
- package/dist/recommendation-ranking-options.js +1 -0
- package/dist/recommendation-ranking.d.ts +50 -0
- package/dist/recommendation-ranking.d.ts.map +1 -0
- package/dist/recommendation-ranking.js +527 -0
- package/dist/recommendation-source.d.ts +23 -0
- package/dist/recommendation-source.d.ts.map +1 -0
- package/dist/recommendation-source.js +23 -0
- package/dist/recommendation-suppression.d.ts +14 -0
- package/dist/recommendation-suppression.d.ts.map +1 -0
- package/dist/recommendation-suppression.js +14 -0
- package/dist/reference-asset-kind.d.ts +14 -0
- package/dist/reference-asset-kind.d.ts.map +1 -0
- package/dist/reference-asset-kind.js +14 -0
- package/dist/reference-content-check.d.ts +32 -0
- package/dist/reference-content-check.d.ts.map +1 -0
- package/dist/reference-content-check.js +143 -0
- package/dist/reference-failure.d.ts +31 -0
- package/dist/reference-failure.d.ts.map +1 -0
- package/dist/reference-failure.js +31 -0
- package/dist/reference-id-status.d.ts +15 -0
- package/dist/reference-id-status.d.ts.map +1 -0
- package/dist/reference-id-status.js +15 -0
- package/dist/reference-kind-declarations.d.ts +42 -0
- package/dist/reference-kind-declarations.d.ts.map +1 -0
- package/dist/reference-kind-declarations.js +142 -0
- package/dist/reference-lookup.d.ts +6 -0
- package/dist/reference-lookup.d.ts.map +1 -1
- package/dist/reference-lookup.js +13 -5
- package/dist/reference-pack-origin.d.ts +18 -0
- package/dist/reference-pack-origin.d.ts.map +1 -0
- package/dist/reference-pack-origin.js +38 -0
- package/dist/reference-registry.d.ts +109 -11
- package/dist/reference-registry.d.ts.map +1 -1
- package/dist/reference-registry.js +186 -30
- package/dist/reference-subject.d.ts +20 -0
- package/dist/reference-subject.d.ts.map +1 -0
- package/dist/reference-subject.js +1 -0
- package/dist/registration-hint-discovery-status.d.ts +25 -0
- package/dist/registration-hint-discovery-status.d.ts.map +1 -0
- package/dist/registration-hint-discovery-status.js +25 -0
- package/dist/registration-hint-registry.d.ts +80 -0
- package/dist/registration-hint-registry.d.ts.map +1 -1
- package/dist/registration-hint-registry.js +356 -138
- package/dist/registry-lifecycle.d.ts +200 -19
- package/dist/registry-lifecycle.d.ts.map +1 -1
- package/dist/registry-lifecycle.js +526 -136
- package/dist/release-readiness.d.ts.map +1 -1
- package/dist/release-readiness.js +10 -7
- package/dist/release-smoke.d.ts.map +1 -1
- package/dist/release-smoke.js +11 -7
- package/dist/report-site.d.ts.map +1 -1
- package/dist/report-site.js +25 -7
- package/dist/repository-intelligence.d.ts.map +1 -1
- package/dist/repository-intelligence.js +3 -2
- package/dist/repository-knowledge-model.d.ts.map +1 -1
- package/dist/repository-knowledge-model.js +18 -5
- package/dist/repository-stats.d.ts.map +1 -1
- package/dist/repository-stats.js +3 -77
- package/dist/resolve-project-config.d.ts +64 -0
- package/dist/resolve-project-config.d.ts.map +1 -1
- package/dist/resolve-project-config.js +250 -33
- package/dist/reuse/compute-reuse-coverage.d.ts +29 -0
- package/dist/reuse/compute-reuse-coverage.d.ts.map +1 -0
- package/dist/reuse/compute-reuse-coverage.js +198 -0
- package/dist/reuse/rank-reuse-candidates.d.ts +38 -0
- package/dist/reuse/rank-reuse-candidates.d.ts.map +1 -0
- package/dist/reuse/rank-reuse-candidates.js +212 -0
- package/dist/reuse/rank-reuse-options.d.ts +20 -0
- package/dist/reuse/rank-reuse-options.d.ts.map +1 -0
- package/dist/reuse/rank-reuse-options.js +1 -0
- package/dist/reuse/rank-reuse-suggestions.d.ts +15 -0
- package/dist/reuse/rank-reuse-suggestions.d.ts.map +1 -0
- package/dist/reuse/rank-reuse-suggestions.js +31 -0
- package/dist/reuse/resolve-curated-reuse.d.ts +43 -0
- package/dist/reuse/resolve-curated-reuse.d.ts.map +1 -0
- package/dist/reuse/resolve-curated-reuse.js +115 -0
- package/dist/reuse/reuse-candidate.d.ts +29 -0
- package/dist/reuse/reuse-candidate.d.ts.map +1 -0
- package/dist/reuse/reuse-candidate.js +1 -0
- package/dist/reuse/reuse-coverage-report.d.ts +74 -0
- package/dist/reuse/reuse-coverage-report.d.ts.map +1 -0
- package/dist/reuse/reuse-coverage-report.js +1 -0
- package/dist/reuse/reuse-curated-coverage.d.ts +36 -0
- package/dist/reuse/reuse-curated-coverage.d.ts.map +1 -0
- package/dist/reuse/reuse-curated-coverage.js +1 -0
- package/dist/reuse/reuse-curated-resolution.d.ts +36 -0
- package/dist/reuse/reuse-curated-resolution.d.ts.map +1 -0
- package/dist/reuse/reuse-curated-resolution.js +1 -0
- package/dist/reuse/reuse-curated-status.d.ts +21 -0
- package/dist/reuse/reuse-curated-status.d.ts.map +1 -0
- package/dist/reuse/reuse-curated-status.js +21 -0
- package/dist/reuse/reuse-match-detail.d.ts +15 -0
- package/dist/reuse/reuse-match-detail.d.ts.map +1 -0
- package/dist/reuse/reuse-match-detail.js +1 -0
- package/dist/reuse/reuse-ranking.d.ts +37 -0
- package/dist/reuse/reuse-ranking.d.ts.map +1 -0
- package/dist/reuse/reuse-ranking.js +1 -0
- package/dist/reuse/reuse-suggestion.d.ts +19 -0
- package/dist/reuse/reuse-suggestion.d.ts.map +1 -0
- package/dist/reuse/reuse-suggestion.js +1 -0
- package/dist/reuse/reuse-symbol-lookup.d.ts +28 -0
- package/dist/reuse/reuse-symbol-lookup.d.ts.map +1 -0
- package/dist/reuse/reuse-symbol-lookup.js +1 -0
- package/dist/reuse/reuse-tokenize.d.ts +11 -0
- package/dist/reuse/reuse-tokenize.d.ts.map +1 -0
- package/dist/reuse/reuse-tokenize.js +26 -0
- package/dist/reuse/score-reuse-name.d.ts +16 -0
- package/dist/reuse/score-reuse-name.d.ts.map +1 -0
- package/dist/reuse/score-reuse-name.js +35 -0
- package/dist/reuse/score-reuse-primitive.d.ts +33 -0
- package/dist/reuse/score-reuse-primitive.d.ts.map +1 -0
- package/dist/reuse/score-reuse-primitive.js +92 -0
- package/dist/review-packet.d.ts +2 -0
- package/dist/review-packet.d.ts.map +1 -1
- package/dist/review-packet.js +13 -6
- package/dist/role-views.d.ts.map +1 -1
- package/dist/role-views.js +16 -16
- package/dist/routing-recommends-channels.d.ts +24 -0
- package/dist/routing-recommends-channels.d.ts.map +1 -0
- package/dist/routing-recommends-channels.js +19 -0
- package/dist/run-boundary-check.d.ts +118 -0
- package/dist/run-boundary-check.d.ts.map +1 -0
- package/dist/run-boundary-check.js +592 -0
- package/dist/safety-audit.js +1 -1
- package/dist/scaffold-coverage.js +1 -1
- package/dist/scaffold-pattern-doctor-report.d.ts +19 -0
- package/dist/scaffold-pattern-doctor-report.d.ts.map +1 -0
- package/dist/scaffold-pattern-doctor-report.js +54 -0
- package/dist/scaffold-patterns.d.ts +75 -1
- package/dist/scaffold-patterns.d.ts.map +1 -1
- package/dist/scaffold-patterns.js +375 -84
- package/dist/schema-inventory.d.ts.map +1 -1
- package/dist/schema-inventory.js +4 -3
- package/dist/search-document-id.d.ts +52 -0
- package/dist/search-document-id.d.ts.map +1 -0
- package/dist/search-document-id.js +72 -0
- package/dist/search-document-prefix.d.ts +7 -0
- package/dist/search-document-prefix.d.ts.map +1 -0
- package/dist/search-document-prefix.js +1 -0
- package/dist/search-index.d.ts.map +1 -1
- package/dist/search-index.js +20 -23
- package/dist/search-tuning-explain.d.ts +6 -0
- package/dist/search-tuning-explain.d.ts.map +1 -1
- package/dist/search-tuning-explain.js +19 -17
- package/dist/search-tuning-key-probes.d.ts +16 -0
- package/dist/search-tuning-key-probes.d.ts.map +1 -0
- package/dist/search-tuning-key-probes.js +65 -0
- package/dist/search-tuning-key-resolution.d.ts +25 -0
- package/dist/search-tuning-key-resolution.d.ts.map +1 -0
- package/dist/search-tuning-key-resolution.js +1 -0
- package/dist/search-tuning-key-resolver.d.ts +4 -0
- package/dist/search-tuning-key-resolver.d.ts.map +1 -0
- package/dist/search-tuning-key-resolver.js +107 -0
- package/dist/search-tuning-key-status.d.ts +24 -0
- package/dist/search-tuning-key-status.d.ts.map +1 -0
- package/dist/search-tuning-key-status.js +24 -0
- package/dist/search-tuning-lint.d.ts +9 -0
- package/dist/search-tuning-lint.d.ts.map +1 -0
- package/dist/search-tuning-lint.js +468 -0
- package/dist/search-tuning-registry.d.ts +43 -0
- package/dist/search-tuning-registry.d.ts.map +1 -1
- package/dist/search-tuning-registry.js +214 -49
- package/dist/self-audit.d.ts +12 -0
- package/dist/self-audit.d.ts.map +1 -1
- package/dist/self-audit.js +36 -10
- package/dist/self-config-doctor-v2.d.ts +145 -3
- package/dist/self-config-doctor-v2.d.ts.map +1 -1
- package/dist/self-config-doctor-v2.js +1369 -182
- package/dist/self-config-doctor.d.ts +20 -1
- package/dist/self-config-doctor.d.ts.map +1 -1
- package/dist/self-config-doctor.js +55 -281
- package/dist/settled-unit-states.d.ts +9 -0
- package/dist/settled-unit-states.d.ts.map +1 -0
- package/dist/settled-unit-states.js +14 -0
- package/dist/sharkcraft-inspector.d.ts +34 -0
- package/dist/sharkcraft-inspector.d.ts.map +1 -1
- package/dist/sharkcraft-inspector.js +314 -27
- package/dist/spec/spec-evidence.d.ts.map +1 -1
- package/dist/spec/spec-evidence.js +14 -20
- package/dist/split-identifier.d.ts +21 -0
- package/dist/split-identifier.d.ts.map +1 -0
- package/dist/split-identifier.js +27 -0
- package/dist/stale-path-hint.d.ts +3 -0
- package/dist/stale-path-hint.d.ts.map +1 -0
- package/dist/stale-path-hint.js +47 -0
- package/dist/start-here.d.ts.map +1 -1
- package/dist/start-here.js +1 -2
- package/dist/substitute-placeholders.d.ts +8 -0
- package/dist/substitute-placeholders.d.ts.map +1 -0
- package/dist/substitute-placeholders.js +9 -0
- package/dist/symbol-index.d.ts +33 -0
- package/dist/symbol-index.d.ts.map +1 -1
- package/dist/symbol-index.js +241 -3
- package/dist/symbol-member-entry.d.ts +26 -0
- package/dist/symbol-member-entry.d.ts.map +1 -0
- package/dist/symbol-member-entry.js +1 -0
- package/dist/symbol-member-kind.d.ts +14 -0
- package/dist/symbol-member-kind.d.ts.map +1 -0
- package/dist/symbol-member-kind.js +14 -0
- package/dist/task-packet.d.ts.map +1 -1
- package/dist/task-packet.js +52 -9
- package/dist/task-ranker.d.ts +8 -0
- package/dist/task-ranker.d.ts.map +1 -1
- package/dist/task-ranker.js +25 -13
- package/dist/task-risk.js +1 -1
- package/dist/task-routing-hint-registry.d.ts +20 -1
- package/dist/task-routing-hint-registry.d.ts.map +1 -1
- package/dist/task-routing-hint-registry.js +186 -42
- package/dist/template-authoring.d.ts +1 -1
- package/dist/template-authoring.d.ts.map +1 -1
- package/dist/template-authoring.js +2 -1
- package/dist/template-drift.d.ts.map +1 -1
- package/dist/template-drift.js +30 -9
- package/dist/template-lint.d.ts.map +1 -1
- package/dist/template-lint.js +160 -2
- package/dist/term-query.d.ts +10 -0
- package/dist/term-query.d.ts.map +1 -0
- package/dist/term-query.js +1 -0
- package/dist/test-definitions.d.ts +6 -3
- package/dist/test-definitions.d.ts.map +1 -1
- package/dist/test-runner.d.ts +95 -10
- package/dist/test-runner.d.ts.map +1 -1
- package/dist/test-runner.js +309 -308
- package/dist/tuning-query-tokens.d.ts +29 -0
- package/dist/tuning-query-tokens.d.ts.map +1 -0
- package/dist/tuning-query-tokens.js +51 -0
- package/dist/typecheck-files.d.ts +36 -0
- package/dist/typecheck-files.d.ts.map +1 -0
- package/dist/typecheck-files.js +158 -0
- package/dist/uncertainty.js +1 -1
- package/dist/universal-search.d.ts.map +1 -1
- package/dist/universal-search.js +5 -3
- package/dist/unregistered-exports.d.ts +19 -0
- package/dist/unregistered-exports.d.ts.map +1 -0
- package/dist/unregistered-exports.js +141 -0
- package/dist/unresolvable-reason.d.ts +11 -0
- package/dist/unresolvable-reason.d.ts.map +1 -0
- package/dist/unresolvable-reason.js +11 -0
- package/dist/upgrade-advisor.d.ts.map +1 -1
- package/dist/upgrade-advisor.js +16 -9
- package/dist/validate-contribution-file.d.ts +4 -0
- package/dist/validate-contribution-file.d.ts.map +1 -0
- package/dist/validate-contribution-file.js +233 -0
- package/dist/why-file.d.ts +3 -0
- package/dist/why-file.d.ts.map +1 -1
- package/dist/why-file.js +12 -7
- package/package.json +17 -17
package/dist/knowledge-stale.js
CHANGED
|
@@ -1,27 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Knowledge stale-check.
|
|
2
|
+
* Knowledge stale-check — THE staleness engine (every consumer reads it).
|
|
3
3
|
*
|
|
4
|
-
* Walks every knowledge entry's `references[]` + `anchors[]` and
|
|
5
|
-
*
|
|
4
|
+
* Walks every knowledge entry's `references[]` + `anchors[]` — and the
|
|
5
|
+
* references boundary rules and policy checks declare — and checks whether each
|
|
6
|
+
* target still resolves against the current workspace, and (with `contains` /
|
|
7
|
+
* `matches` / `count`) still says what the asset claims.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* It classifies every entry in scope into ONE of three buckets: `verified`,
|
|
10
|
+
* `stale`, `unverifiable`. An entry with nothing checkable used to contribute
|
|
11
|
+
* nothing and read as healthy — the less verifiable a corpus, the healthier it
|
|
12
|
+
* looked. The buckets make that shortfall a number the verdict can refuse on.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
14
|
+
* Callers MUST `await warmReferenceRegistries(inspection)` first: the
|
|
15
|
+
* playbook / policy / construct / helper registries are async-filled, and an
|
|
16
|
+
* unwarmed registry is reported `unknown` (NOT VERIFIED) here — never `stale`
|
|
17
|
+
* against a correct id.
|
|
18
|
+
*
|
|
19
|
+
* Pure file-system + registry lookups — no network, no whole-program
|
|
20
|
+
* compilation. Symbol checks use the single-file AST index.
|
|
21
|
+
*
|
|
22
|
+
* Schema: sharkcraft.knowledge-stale/v1 (additive fields since round 11)
|
|
12
23
|
*/
|
|
13
24
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
14
25
|
import * as nodePath from 'node:path';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
26
|
+
import { globListSelects, globToRegex } from '@shrkcrft/boundaries';
|
|
27
|
+
import { KnowledgeReferenceRoot } from '@shrkcrft/core';
|
|
28
|
+
import { KNOWLEDGE_REFERENCE_KINDS, referenceRootProblem, anchorShapeProblem, anchorsListProblem, declaredAnchorItems, declaredReferenceItems, knowledgeAnchors, knowledgeSourceFormat, referenceShapeProblem, referencesListProblem, todayUtcIso, verifiedOnAgeDays, } from '@shrkcrft/knowledge';
|
|
29
|
+
import { isCacheBackedKind, isReferenceCacheWarm, referenceIdExists, referenceIdsFor, } from "./reference-registry.js";
|
|
30
|
+
import { listPolicyDeclarations } from "./policy-registry.js";
|
|
31
|
+
import { checkReferenceContent, checkReferenceCount, hasContentAssertion, } from "./reference-content-check.js";
|
|
32
|
+
import { ReferenceFailure } from "./reference-failure.js";
|
|
33
|
+
import { KnowledgeEntryVerdict } from "./knowledge-entry-verdict.js";
|
|
34
|
+
import { KnowledgeUnverifiableReason } from "./knowledge-unverifiable-reason.js";
|
|
35
|
+
import { KnowledgeAdvisoryCode } from "./knowledge-advisory-code.js";
|
|
36
|
+
import { knowledgeRejectedEntries } from "./knowledge-entry-rejections.js";
|
|
37
|
+
import { ReferenceAssetKind } from "./reference-asset-kind.js";
|
|
38
|
+
import { stalePathHint } from "./stale-path-hint.js";
|
|
39
|
+
import { buildSymbolIndex } from "./symbol-index.js";
|
|
40
|
+
// The `command` reference case (and its anchor twin) — one injected resolver.
|
|
41
|
+
import { COMMAND_INDEX_NOT_INJECTED, resolveShrkCommandReference } from "./reference-registry.js";
|
|
42
|
+
import { CommandResolutionStatus } from "./command-resolution-status.js";
|
|
17
43
|
import { resolveSymbolInFile, SymbolResolution } from "./symbol-index.js";
|
|
44
|
+
import { packOriginByName, packOriginOfFile } from "./reference-pack-origin.js";
|
|
18
45
|
export const KNOWLEDGE_STALE_SCHEMA = 'sharkcraft.knowledge-stale/v1';
|
|
46
|
+
/** The `byReferenceKind` bucket of an item with no kind to bucket under (a string, a non-list value). */
|
|
47
|
+
const MALFORMED_KIND_BUCKET = '(malformed)';
|
|
48
|
+
/**
|
|
49
|
+
* A declared value as a check row carries it: `null` / `undefined` become their
|
|
50
|
+
* text, so no renderer dereferences them; anything else as declared
|
|
51
|
+
* (`formatKnowledgeReference` renders a non-object as written). The CHECK reads
|
|
52
|
+
* the original, so a `null` item says "is not an object (got null)" — the
|
|
53
|
+
* validator's words — not "is the string \"null\"".
|
|
54
|
+
*/
|
|
55
|
+
function displayedReference(value) {
|
|
56
|
+
return (value === null || value === undefined ? String(value) : value);
|
|
57
|
+
}
|
|
19
58
|
export var ReferenceCheckOutcome;
|
|
20
59
|
(function (ReferenceCheckOutcome) {
|
|
21
60
|
ReferenceCheckOutcome["Ok"] = "ok";
|
|
22
61
|
ReferenceCheckOutcome["Stale"] = "stale";
|
|
23
62
|
ReferenceCheckOutcome["Missing"] = "missing";
|
|
63
|
+
/** Well-formed, but the check cannot evaluate it (a `url`, an unwarmed registry). */
|
|
24
64
|
ReferenceCheckOutcome["Unknown"] = "unknown";
|
|
65
|
+
/**
|
|
66
|
+
* MALFORMED — a kind outside the vocabulary, or the field its kind cannot be
|
|
67
|
+
* checked without is missing (a symbol reference with no `symbol`). It
|
|
68
|
+
* verifies nothing; it used to share `unknown` with an unfetched url and
|
|
69
|
+
* count toward a green run. Now it is a coverage shortfall on the verdict
|
|
70
|
+
* (not verified) and fails under `--fail-on invalid`.
|
|
71
|
+
*/
|
|
72
|
+
ReferenceCheckOutcome["Invalid"] = "invalid";
|
|
25
73
|
})(ReferenceCheckOutcome || (ReferenceCheckOutcome = {}));
|
|
26
74
|
export var SymbolConfidence;
|
|
27
75
|
(function (SymbolConfidence) {
|
|
@@ -75,193 +123,543 @@ function dirExists(projectRoot, rel) {
|
|
|
75
123
|
* hoped-for shape compiles perfectly and fails silently forever; the registry
|
|
76
124
|
* imports the real accessors so the compiler can see a rename.
|
|
77
125
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
126
|
+
/**
|
|
127
|
+
* A `command` reference (or anchor) through THE injected command resolver.
|
|
128
|
+
*
|
|
129
|
+
* Without a resolver (outside the CLI) the command was NOT checked: that is
|
|
130
|
+
* `Unknown` with a loud NOT VERIFIED message — never `Ok`. It used to be `Ok`
|
|
131
|
+
* for every `shrk …` string, which certified three dead commands in shrk's own
|
|
132
|
+
* knowledge as "Command available".
|
|
133
|
+
*
|
|
134
|
+
* Read as a command REFERENCE (`resolveShrkCommandReference`): a bare
|
|
135
|
+
* `frobnicate` is `shrk frobnicate` — stale — exactly as the self-config
|
|
136
|
+
* doctor and the agent-test runner read it, never `not-shrk` → `ok`.
|
|
137
|
+
*/
|
|
138
|
+
function checkCommandString(inspection, id, label) {
|
|
139
|
+
const resolution = resolveShrkCommandReference(inspection, id);
|
|
140
|
+
switch (resolution.status) {
|
|
141
|
+
case CommandResolutionStatus.Unverified:
|
|
142
|
+
return {
|
|
143
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
144
|
+
message: `${label} ${id}: ${COMMAND_INDEX_NOT_INJECTED}`,
|
|
145
|
+
suggestion: 'Run the check through the CLI (`shrk knowledge stale-check`), which injects the live command index.',
|
|
146
|
+
};
|
|
147
|
+
case CommandResolutionStatus.Ok:
|
|
148
|
+
case CommandResolutionStatus.PrefixOnly:
|
|
149
|
+
case CommandResolutionStatus.NotShrk:
|
|
150
|
+
return {
|
|
151
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
152
|
+
message: resolution.status === CommandResolutionStatus.Ok
|
|
153
|
+
? `Command available: ${id}`
|
|
154
|
+
: `Command ${id}: ${resolution.status}${resolution.reason ? ` (${resolution.reason})` : ''}`,
|
|
155
|
+
};
|
|
156
|
+
default: {
|
|
157
|
+
const closest = resolution.closest ?? [];
|
|
158
|
+
return {
|
|
159
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
160
|
+
message: `Command not registered: ${id} (${resolution.status}${resolution.reason ? ` — ${resolution.reason}` : ''})`,
|
|
161
|
+
suggestion: closest.length > 0
|
|
162
|
+
? `Did you mean \`${closest[0]}\`? Update the reference (or run \`shrk surface list\` for every real command).`
|
|
163
|
+
: 'Update the reference — run `shrk surface list` for every real command.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
86
167
|
}
|
|
87
|
-
|
|
88
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Why an id could not be CHECKED against a cache-backed registry, or undefined
|
|
170
|
+
* when the registry is loaded and non-empty.
|
|
171
|
+
*
|
|
172
|
+
* A negative answer from an unloaded (or empty) registry proves nothing: every
|
|
173
|
+
* id checked against it would be reported stale, including correct ones — the
|
|
174
|
+
* fastest way to get a check switched off. So it reads as NOT VERIFIED
|
|
175
|
+
* (`unknown`), the same safety net the doc-reference linter uses. A POSITIVE
|
|
176
|
+
* answer is always trusted.
|
|
177
|
+
*/
|
|
178
|
+
function unloadedRegistryReason(inspection, kind) {
|
|
179
|
+
if (!isCacheBackedKind(kind))
|
|
180
|
+
return undefined;
|
|
181
|
+
if (!isReferenceCacheWarm(inspection)) {
|
|
182
|
+
return `the ${kind} registry was not loaded — call warmReferenceRegistries() before building the report (NOT VERIFIED)`;
|
|
183
|
+
}
|
|
184
|
+
if (referenceIdsFor(inspection, kind).length === 0) {
|
|
185
|
+
return `the ${kind} registry is empty — it failed to load, or this repo declares no ${kind}s (NOT VERIFIED)`;
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
89
188
|
}
|
|
90
|
-
|
|
91
|
-
|
|
189
|
+
/**
|
|
190
|
+
* An id-keyed reference, through THE shared resolver. The helper kind reads
|
|
191
|
+
* built-ins AND pack helpers there; a private built-in-only list used to answer
|
|
192
|
+
* here, calling every pack helper stale.
|
|
193
|
+
*/
|
|
194
|
+
function checkRegisteredId(inspection, kind, id, label) {
|
|
195
|
+
if (referenceIdExists(inspection, kind, id)) {
|
|
196
|
+
return { outcome: ReferenceCheckOutcome.Ok, message: `${label} exists: ${id}` };
|
|
197
|
+
}
|
|
198
|
+
const unloaded = unloadedRegistryReason(inspection, kind);
|
|
199
|
+
if (unloaded) {
|
|
200
|
+
return {
|
|
201
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
202
|
+
failure: ReferenceFailure.Unverifiable,
|
|
203
|
+
message: `${label} ${id}: ${unloaded}`,
|
|
204
|
+
suggestion: 'Run through the CLI (`shrk knowledge stale-check`), which loads every registry first.',
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return staleId(kind, id);
|
|
92
208
|
}
|
|
93
|
-
|
|
94
|
-
|
|
209
|
+
/** Workspace package name → its directory (relative), per inspection. */
|
|
210
|
+
const WORKSPACE_PACKAGES = new WeakMap();
|
|
211
|
+
function readPackageJson(abs) {
|
|
212
|
+
try {
|
|
213
|
+
return JSON.parse(readFileSync(abs, 'utf8'));
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
95
218
|
}
|
|
96
|
-
|
|
97
|
-
|
|
219
|
+
/** Directories (relative) matching one `workspaces` glob. */
|
|
220
|
+
function expandWorkspaceGlob(projectRoot, pattern) {
|
|
221
|
+
const norm = pattern.replace(/^\.\//, '').replace(/\/+$/, '');
|
|
222
|
+
const segs = norm.split('/');
|
|
223
|
+
const firstGlob = segs.findIndex((s) => /[*?[{]/.test(s));
|
|
224
|
+
if (firstGlob === -1)
|
|
225
|
+
return [norm];
|
|
226
|
+
const maxDepth = segs.includes('**') ? 4 : segs.length - firstGlob;
|
|
227
|
+
const out = [];
|
|
228
|
+
const walk = (rel, depth) => {
|
|
229
|
+
if (depth > maxDepth)
|
|
230
|
+
return;
|
|
231
|
+
let names;
|
|
232
|
+
try {
|
|
233
|
+
names = readdirSync(nodePath.join(projectRoot, rel));
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
for (const n of names) {
|
|
239
|
+
if (n === 'node_modules' || n.startsWith('.'))
|
|
240
|
+
continue;
|
|
241
|
+
const child = rel ? `${rel}/${n}` : n;
|
|
242
|
+
if (!dirExists(projectRoot, child))
|
|
243
|
+
continue;
|
|
244
|
+
// One `workspaces` pattern against one directory — not a gate-plane list.
|
|
245
|
+
if (globToRegex(norm).test(child))
|
|
246
|
+
out.push(child);
|
|
247
|
+
walk(child, depth + 1);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
walk(segs.slice(0, firstGlob).join('/'), 1);
|
|
251
|
+
return out;
|
|
98
252
|
}
|
|
99
|
-
|
|
100
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Every workspace package the root `package.json` declares — the packages its
|
|
255
|
+
* `workspaces` globs reach, by their `name`. This replaces a read of
|
|
256
|
+
* `inspection.packages`, a property the inspection never has (the phantom-cast
|
|
257
|
+
* bug class): it answered "not a package" for every id and fell through to a
|
|
258
|
+
* hard-coded `packages/<name>` layout.
|
|
259
|
+
*/
|
|
260
|
+
function workspacePackages(inspection) {
|
|
261
|
+
const cached = WORKSPACE_PACKAGES.get(inspection);
|
|
262
|
+
if (cached)
|
|
263
|
+
return cached;
|
|
264
|
+
const out = new Map();
|
|
265
|
+
const root = readPackageJson(nodePath.join(inspection.projectRoot, 'package.json'));
|
|
266
|
+
if (root) {
|
|
267
|
+
if (typeof root.name === 'string')
|
|
268
|
+
out.set(root.name, '.');
|
|
269
|
+
const ws = root.workspaces;
|
|
270
|
+
const patterns = Array.isArray(ws)
|
|
271
|
+
? ws
|
|
272
|
+
: Array.isArray(ws?.packages)
|
|
273
|
+
? (ws.packages)
|
|
274
|
+
: [];
|
|
275
|
+
for (const pattern of patterns) {
|
|
276
|
+
if (typeof pattern !== 'string' || pattern.startsWith('!'))
|
|
277
|
+
continue;
|
|
278
|
+
for (const dir of expandWorkspaceGlob(inspection.projectRoot, pattern)) {
|
|
279
|
+
const pkg = readPackageJson(nodePath.join(inspection.projectRoot, dir, 'package.json'));
|
|
280
|
+
if (pkg && typeof pkg.name === 'string' && !out.has(pkg.name))
|
|
281
|
+
out.set(pkg.name, dir);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
WORKSPACE_PACKAGES.set(inspection, out);
|
|
286
|
+
return out;
|
|
101
287
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
288
|
+
function checkPackageReference(inspection, id, origin) {
|
|
289
|
+
const dir = workspacePackages(inspection).get(id);
|
|
290
|
+
if (dir) {
|
|
291
|
+
return { outcome: ReferenceCheckOutcome.Ok, message: `Package exists: ${id} (${dir})` };
|
|
292
|
+
}
|
|
293
|
+
// A pack's entry naming the pack that ships it (round 15 follow-up): the
|
|
294
|
+
// consumer's root package.json names neither its installed packs nor their
|
|
295
|
+
// names, but the contributing pack is installed by definition.
|
|
296
|
+
if (origin && origin.packageName === id) {
|
|
297
|
+
return {
|
|
298
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
299
|
+
message: `Package exists: ${id} (the contributing pack, installed at ${origin.displayRoot})`,
|
|
300
|
+
};
|
|
110
301
|
}
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
302
|
+
// Last-resort backstop for repos without a `workspaces` field — reported as
|
|
303
|
+
// probable, because a directory name is not a package name.
|
|
304
|
+
const rel = id.startsWith('@') ? (id.split('/')[1] ?? '') : id;
|
|
305
|
+
if (rel && dirExists(inspection.projectRoot, `packages/${rel}`)) {
|
|
306
|
+
return {
|
|
307
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
308
|
+
message: `Package exists (probable): packages/${rel}/ is present, but no workspace package.json declares the name ${id}.`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return staleId('package', id);
|
|
114
312
|
}
|
|
115
313
|
function normalizeRel(p) {
|
|
116
314
|
return p.split(/[\\/]/).join('/').replace(/^\.\//, '');
|
|
117
315
|
}
|
|
316
|
+
/** `Foo` for `Foo.bar` / `Foo.prototype.bar`; null for a bare name. */
|
|
317
|
+
function qualifiedOwner(sym) {
|
|
318
|
+
const cleaned = sym.replace(/\.prototype\./g, '.');
|
|
319
|
+
const dot = cleaned.indexOf('.');
|
|
320
|
+
return dot > 0 && dot < cleaned.length - 1 ? cleaned.slice(0, dot) : null;
|
|
321
|
+
}
|
|
118
322
|
function checkSymbolReference(projectRoot, ref, graph) {
|
|
119
323
|
const sym = ref.symbol ?? '';
|
|
120
324
|
if (!sym) {
|
|
121
325
|
return {
|
|
122
|
-
outcome: ReferenceCheckOutcome.
|
|
326
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
123
327
|
confidence: SymbolConfidence.Unknown,
|
|
124
|
-
|
|
328
|
+
failure: ReferenceFailure.Malformed,
|
|
329
|
+
message: 'malformed reference: symbol reference missing required field `symbol`.',
|
|
125
330
|
};
|
|
126
331
|
}
|
|
332
|
+
// `Owner.member`: the graph indexes top-level symbols only, so it answers for
|
|
333
|
+
// the OWNER (moved-detection); the member itself is verified by the AST on
|
|
334
|
+
// the owner's declaring file.
|
|
335
|
+
const owner = qualifiedOwner(sym);
|
|
336
|
+
const lookup = owner ?? sym;
|
|
127
337
|
// Graph-resolved, cross-file path (preferred when a graph is supplied).
|
|
128
338
|
// A single-file AST scan cannot tell a *moved* symbol from a deleted one;
|
|
129
339
|
// the graph's global symbol index can, so consult it first.
|
|
130
340
|
if (graph) {
|
|
131
341
|
const decl = graph
|
|
132
|
-
.findSymbol(
|
|
342
|
+
.findSymbol(lookup)
|
|
133
343
|
.map((n) => n.path)
|
|
134
344
|
.filter((p) => typeof p === 'string' && p.length > 0)
|
|
135
345
|
.map(normalizeRel);
|
|
136
346
|
if (decl.length > 0) {
|
|
137
347
|
if (ref.path) {
|
|
138
348
|
const target = normalizeRel(ref.path);
|
|
139
|
-
if (decl.includes(target)) {
|
|
349
|
+
if (!decl.includes(target)) {
|
|
350
|
+
// Symbol exists but no longer at the pinned file — it moved.
|
|
351
|
+
return {
|
|
352
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
353
|
+
confidence: SymbolConfidence.Missing,
|
|
354
|
+
failure: ReferenceFailure.AnchorMissing,
|
|
355
|
+
message: `Symbol \`${lookup}\` is no longer in ${ref.path}; the graph resolves it to ${decl
|
|
356
|
+
.slice(0, 3)
|
|
357
|
+
.join(', ')}.`,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (!owner && !hasContentAssertion(ref)) {
|
|
140
361
|
return {
|
|
141
362
|
outcome: ReferenceCheckOutcome.Ok,
|
|
142
363
|
confidence: SymbolConfidence.Exact,
|
|
143
364
|
message: `\`${sym}\` resolves to ${ref.path} (graph).`,
|
|
144
365
|
};
|
|
145
366
|
}
|
|
146
|
-
//
|
|
367
|
+
// The owner is at the pinned file; the member (and any content span)
|
|
368
|
+
// is verified by the AST below.
|
|
369
|
+
}
|
|
370
|
+
else if (!owner) {
|
|
371
|
+
// No file pin, but the graph resolved it cross-file.
|
|
147
372
|
return {
|
|
148
|
-
outcome: ReferenceCheckOutcome.
|
|
149
|
-
confidence: SymbolConfidence.
|
|
150
|
-
message:
|
|
151
|
-
|
|
152
|
-
|
|
373
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
374
|
+
confidence: SymbolConfidence.Probable,
|
|
375
|
+
message: `\`${sym}\` resolves via the code graph to ${decl.slice(0, 3).join(', ')}.`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
else if (decl.length === 1) {
|
|
379
|
+
return resolveSymbolAtPath(projectRoot, decl[0], sym, false);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
return {
|
|
383
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
384
|
+
confidence: SymbolConfidence.Unknown,
|
|
385
|
+
failure: ReferenceFailure.Unverifiable,
|
|
386
|
+
message: `\`${owner}\` is declared in ${decl.length} files (${decl.slice(0, 3).join(', ')}) — pin the path to check \`${sym}\`.`,
|
|
153
387
|
};
|
|
154
388
|
}
|
|
155
|
-
// No file pin, but the graph resolved it cross-file.
|
|
156
|
-
return {
|
|
157
|
-
outcome: ReferenceCheckOutcome.Ok,
|
|
158
|
-
confidence: SymbolConfidence.Probable,
|
|
159
|
-
message: `\`${sym}\` resolves via the code graph to ${decl.slice(0, 3).join(', ')}.`,
|
|
160
|
-
};
|
|
161
389
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
390
|
+
else if (!ref.path) {
|
|
391
|
+
// Absent from the graph. The graph may be partial (locals, unindexed
|
|
392
|
+
// languages), so fall through to the AST/text backstop when a file is
|
|
393
|
+
// pinned; otherwise we cannot verify.
|
|
166
394
|
return {
|
|
167
395
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
168
396
|
confidence: SymbolConfidence.Unknown,
|
|
397
|
+
failure: ReferenceFailure.Unverifiable,
|
|
169
398
|
message: `Symbol reference \`${sym}\` has no file pin and is absent from the code graph; stale-check cannot verify.`,
|
|
170
399
|
};
|
|
171
400
|
}
|
|
172
401
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
// Fallback to text scan.
|
|
221
|
-
try {
|
|
222
|
-
const text = readFileSync(file, 'utf8');
|
|
223
|
-
const declRe = new RegExp(`(export\\s+(?:async\\s+)?(?:function|class|interface|enum|type|const|let|var)\\s+|class\\s+|function\\s+)${escapeRe(sym)}\\b`);
|
|
224
|
-
if (declRe.test(text)) {
|
|
225
|
-
return {
|
|
226
|
-
outcome: ReferenceCheckOutcome.Ok,
|
|
227
|
-
confidence: SymbolConfidence.Exact,
|
|
228
|
-
message: `Found declaration of \`${sym}\` in ${ref.path}.`,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
if (text.includes(sym)) {
|
|
232
|
-
return {
|
|
233
|
-
outcome: ReferenceCheckOutcome.Ok,
|
|
234
|
-
confidence: SymbolConfidence.Probable,
|
|
235
|
-
message: `\`${sym}\` appears in ${ref.path}, but not as an exported declaration.`,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
402
|
+
if (ref.path)
|
|
403
|
+
return resolveSymbolAtPath(projectRoot, ref.path, sym, true);
|
|
404
|
+
// No file pinned — best-effort confidence is `unknown`.
|
|
405
|
+
return {
|
|
406
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
407
|
+
confidence: SymbolConfidence.Unknown,
|
|
408
|
+
failure: ReferenceFailure.Unverifiable,
|
|
409
|
+
message: `Symbol reference \`${sym}\` has no file pin; stale-check cannot verify (pin it as \`symbol:${sym}@<path>\`).`,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/** Resolve `sym` in one file through the AST index (text-scan backstop). */
|
|
413
|
+
function resolveSymbolAtPath(projectRoot, relPath, sym, pinned) {
|
|
414
|
+
const file = nodePath.join(projectRoot, relPath);
|
|
415
|
+
if (!existsSync(file)) {
|
|
416
|
+
return {
|
|
417
|
+
outcome: ReferenceCheckOutcome.Missing,
|
|
418
|
+
confidence: SymbolConfidence.Missing,
|
|
419
|
+
failure: ReferenceFailure.PathMissing,
|
|
420
|
+
message: `Referenced file does not exist: ${relPath}`,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const via = pinned ? '' : ` (declaring file ${relPath}, from the code graph)`;
|
|
424
|
+
// AST-backed resolution (falls back to text-scan if parse fails).
|
|
425
|
+
try {
|
|
426
|
+
const res = resolveSymbolInFile(file, sym);
|
|
427
|
+
switch (res.resolution) {
|
|
428
|
+
case SymbolResolution.ExactExport:
|
|
429
|
+
case SymbolResolution.ExactLocal:
|
|
430
|
+
case SymbolResolution.ExactReExport:
|
|
431
|
+
case SymbolResolution.ExactMember:
|
|
432
|
+
case SymbolResolution.ExactLocalMember:
|
|
433
|
+
return {
|
|
434
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
435
|
+
confidence: pinned ? SymbolConfidence.Exact : SymbolConfidence.Probable,
|
|
436
|
+
message: res.message + via,
|
|
437
|
+
...(res.span ? { span: res.span } : {}),
|
|
438
|
+
};
|
|
439
|
+
case SymbolResolution.ProbableText:
|
|
440
|
+
return {
|
|
441
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
442
|
+
confidence: SymbolConfidence.Probable,
|
|
443
|
+
message: res.message + via,
|
|
444
|
+
};
|
|
445
|
+
case SymbolResolution.Missing:
|
|
238
446
|
return {
|
|
239
447
|
outcome: ReferenceCheckOutcome.Stale,
|
|
240
448
|
confidence: SymbolConfidence.Missing,
|
|
241
|
-
|
|
449
|
+
failure: ReferenceFailure.AnchorMissing,
|
|
450
|
+
message: res.message + via,
|
|
451
|
+
...(res.suggestedSymbol ? { suggestedSymbol: res.suggestedSymbol } : {}),
|
|
242
452
|
};
|
|
243
|
-
|
|
244
|
-
catch {
|
|
453
|
+
default:
|
|
245
454
|
return {
|
|
246
455
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
247
456
|
confidence: SymbolConfidence.Unknown,
|
|
248
|
-
|
|
457
|
+
failure: ReferenceFailure.Unverifiable,
|
|
458
|
+
message: res.message + via,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
// Fallback to text scan.
|
|
464
|
+
const token = qualifiedOwner(sym) ? sym.slice(sym.lastIndexOf('.') + 1) : sym;
|
|
465
|
+
try {
|
|
466
|
+
const text = readFileSync(file, 'utf8');
|
|
467
|
+
const declRe = new RegExp(`(export\\s+(?:async\\s+)?(?:function|class|interface|enum|type|const|let|var)\\s+|class\\s+|function\\s+)${escapeRe(token)}\\b`);
|
|
468
|
+
if (declRe.test(text)) {
|
|
469
|
+
return {
|
|
470
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
471
|
+
confidence: SymbolConfidence.Exact,
|
|
472
|
+
message: `Found declaration of \`${sym}\` in ${relPath}.`,
|
|
249
473
|
};
|
|
250
474
|
}
|
|
475
|
+
if (text.includes(token)) {
|
|
476
|
+
return {
|
|
477
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
478
|
+
confidence: SymbolConfidence.Probable,
|
|
479
|
+
message: `\`${sym}\` appears in ${relPath}, but not as an exported declaration.`,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
484
|
+
confidence: SymbolConfidence.Missing,
|
|
485
|
+
failure: ReferenceFailure.AnchorMissing,
|
|
486
|
+
message: `Symbol \`${sym}\` not found in ${relPath}.`,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return {
|
|
491
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
492
|
+
confidence: SymbolConfidence.Unknown,
|
|
493
|
+
failure: ReferenceFailure.Unverifiable,
|
|
494
|
+
message: `Failed to read ${relPath}.`,
|
|
495
|
+
};
|
|
251
496
|
}
|
|
252
497
|
}
|
|
253
|
-
// No file pinned — best-effort confidence is `unknown`.
|
|
254
|
-
return {
|
|
255
|
-
outcome: ReferenceCheckOutcome.Unknown,
|
|
256
|
-
confidence: SymbolConfidence.Unknown,
|
|
257
|
-
message: `Symbol reference \`${sym}\` has no file pin; stale-check cannot verify.`,
|
|
258
|
-
};
|
|
259
498
|
}
|
|
260
499
|
function escapeRe(s) {
|
|
261
500
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
262
501
|
}
|
|
263
|
-
|
|
264
|
-
|
|
502
|
+
/**
|
|
503
|
+
* Check one reference: does its target exist — and, when it declares
|
|
504
|
+
* `contains` / `matches` / `count`, does the target still say what the asset
|
|
505
|
+
* claims. Every non-ok result carries a {@link ReferenceFailure}.
|
|
506
|
+
*/
|
|
507
|
+
function checkReference(inspection, ref, graph, excludeDirs = [], origin, subject) {
|
|
508
|
+
// THE item-shape predicate the validator applies (a string the grammar
|
|
509
|
+
// refused, a number, a non-string `path`): MALFORMED here — never a crash in a
|
|
510
|
+
// path join, never a silent row.
|
|
511
|
+
const shape = referenceShapeProblem(ref);
|
|
512
|
+
if (shape) {
|
|
513
|
+
return {
|
|
514
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
515
|
+
failure: ReferenceFailure.Malformed,
|
|
516
|
+
message: `malformed reference: ${shape}.`,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
// THE `root` predicate the validator applies (round 15 follow-up): `root:
|
|
520
|
+
// pack` on an asset no pack contributes is MALFORMED — never a silent
|
|
521
|
+
// fallback to the project root, which would read a different file.
|
|
522
|
+
const rootProblem = referenceRootProblem(ref, origin !== undefined);
|
|
523
|
+
if (rootProblem?.severity === 'error') {
|
|
524
|
+
return {
|
|
525
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
526
|
+
failure: ReferenceFailure.Malformed,
|
|
527
|
+
message: `malformed reference: ${rootProblem.message}.`,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
// A `root: pack` that has an effect (the predicate's warning means none)
|
|
531
|
+
// moves every path read — the target, `contains` / `matches`, a `count`
|
|
532
|
+
// source — into the contributing pack's directory. The consumer's code graph
|
|
533
|
+
// never indexes a pack's files, and its count exclusions are consumer paths.
|
|
534
|
+
const pack = origin !== undefined && rootProblem === undefined && isPackRooted(ref) ? origin : undefined;
|
|
535
|
+
const rootDir = pack ? pack.packageRoot : inspection.projectRoot;
|
|
536
|
+
const base = checkReferenceTarget(inspection, ref, pack ? undefined : graph, rootDir, origin);
|
|
537
|
+
const asserted = base.outcome === ReferenceCheckOutcome.Ok ? applyAssertions(rootDir, ref, base, pack ? [] : excludeDirs) : base;
|
|
538
|
+
const result = asserted.outcome === ReferenceCheckOutcome.Ok || asserted.failure !== undefined
|
|
539
|
+
? asserted
|
|
540
|
+
: { ...asserted, failure: defaultFailure(ref.kind, asserted.outcome) };
|
|
541
|
+
const labelled = pack ? packRootedResult(result, pack) : result;
|
|
542
|
+
return withStalePathHint(labelled, ref, origin, pack !== undefined, subject);
|
|
543
|
+
}
|
|
544
|
+
/** A reference whose path resolves against its contributing pack's directory (`root: pack`). */
|
|
545
|
+
function isPackRooted(ref) {
|
|
546
|
+
return ref !== null && typeof ref === 'object' && ref.root === KnowledgeReferenceRoot.Pack;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Every row of a pack-rooted reference names the root it resolved against —
|
|
550
|
+
* the renderers (text, gate violations, markdown, MCP) all print the message.
|
|
551
|
+
* A missing path is fixed in the pack, never by a consumer-side rename (the
|
|
552
|
+
* hint is {@link stalePathHint}'s).
|
|
553
|
+
*/
|
|
554
|
+
function packRootedResult(result, pack) {
|
|
555
|
+
return { ...result, message: `${result.message} (root: pack — ${pack.packageName} at ${pack.displayRoot})` };
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* A missing path's hint, from THE authority ({@link stalePathHint}, round 15
|
|
559
|
+
* closing A3): who declared the reference decides who fixes it — a pack (the
|
|
560
|
+
* round-15 repro: a pack doc referencing its own file read STALE, with a hint
|
|
561
|
+
* to rename a file the consumer does not own), a Markdown entry's frontmatter,
|
|
562
|
+
* or a rename. No other code path sets a missing path's `suggestion`.
|
|
563
|
+
*/
|
|
564
|
+
function withStalePathHint(result, ref, origin, packRooted, subject) {
|
|
565
|
+
if (result.failure !== ReferenceFailure.PathMissing || typeof ref.path !== 'string' || ref.path === '')
|
|
566
|
+
return result;
|
|
567
|
+
const suggestion = stalePathHint({
|
|
568
|
+
kind: String(ref.kind),
|
|
569
|
+
path: normalizeRel(ref.path),
|
|
570
|
+
packRooted,
|
|
571
|
+
...(origin !== undefined ? { pack: origin } : {}),
|
|
572
|
+
...(subject?.sourceFormat !== undefined ? { sourceFormat: subject.sourceFormat } : {}),
|
|
573
|
+
...(subject?.source !== undefined ? { source: subject.source } : {}),
|
|
574
|
+
});
|
|
575
|
+
return suggestion === undefined ? result : { ...result, suggestion };
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* A reference as the changeset scope sees it: a pack-rooted path — and a
|
|
579
|
+
* pack-rooted `count` source's globs, the tree the count is MEASURED over —
|
|
580
|
+
* made project-relative. Unprefixed, a count over the pack's `src/*.ts` was
|
|
581
|
+
* scoped in by a change to the CONSUMER's `src/` and never by the pack's.
|
|
582
|
+
*/
|
|
583
|
+
function scopedReference(ref, origin) {
|
|
584
|
+
if (!origin || !isPackRooted(ref) || origin.displayRoot === '.')
|
|
585
|
+
return ref;
|
|
586
|
+
const at = (rel) => `${origin.displayRoot}/${normalizeRel(rel)}`;
|
|
587
|
+
const globs = ref.count?.source?.files;
|
|
588
|
+
return {
|
|
589
|
+
...ref,
|
|
590
|
+
...(typeof ref.path === 'string' ? { path: at(ref.path) } : {}),
|
|
591
|
+
...(ref.count && Array.isArray(globs)
|
|
592
|
+
? {
|
|
593
|
+
count: {
|
|
594
|
+
...ref.count,
|
|
595
|
+
source: {
|
|
596
|
+
...ref.count.source,
|
|
597
|
+
files: globs.map((g) => (typeof g === 'string' && g.startsWith('!') ? `!${at(g.slice(1))}` : at(String(g)))),
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
}
|
|
601
|
+
: {}),
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/** The failure mode of a non-ok check that did not name one. */
|
|
605
|
+
function defaultFailure(kind, outcome) {
|
|
606
|
+
if (outcome === ReferenceCheckOutcome.Unknown)
|
|
607
|
+
return ReferenceFailure.Unverifiable;
|
|
608
|
+
if (outcome === ReferenceCheckOutcome.Invalid)
|
|
609
|
+
return ReferenceFailure.Malformed;
|
|
610
|
+
if (kind === 'file' || kind === 'directory')
|
|
611
|
+
return ReferenceFailure.PathMissing;
|
|
612
|
+
if (kind === 'symbol')
|
|
613
|
+
return ReferenceFailure.AnchorMissing;
|
|
614
|
+
return ReferenceFailure.IdUnregistered;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Content and count assertions, over a target that EXISTS — a mismatch on a
|
|
618
|
+
* missing file would report one break twice.
|
|
619
|
+
*/
|
|
620
|
+
function applyAssertions(projectRoot, ref, base, excludeDirs) {
|
|
621
|
+
const content = checkReferenceContent(projectRoot, ref, ref.kind === 'symbol' ? base.span : undefined);
|
|
622
|
+
if (content && !content.ok)
|
|
623
|
+
return assertionFailure(base, content, 'content');
|
|
624
|
+
const count = checkReferenceCount(projectRoot, ref, countExcludeDirsFor(ref, excludeDirs));
|
|
625
|
+
if (count && !count.ok)
|
|
626
|
+
return assertionFailure(base, count, 'count');
|
|
627
|
+
const held = [content?.message, count?.message].filter((m) => m !== undefined);
|
|
628
|
+
if (held.length === 0)
|
|
629
|
+
return base;
|
|
630
|
+
return {
|
|
631
|
+
...base,
|
|
632
|
+
message: `${base.message} ${held.join(' ')}`,
|
|
633
|
+
...(count ? { expected: count.expected, actual: count.actual } : {}),
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function assertionFailure(base, a, what) {
|
|
637
|
+
const confidence = base.confidence ? { confidence: base.confidence } : {};
|
|
638
|
+
if (a.unverifiable) {
|
|
639
|
+
return {
|
|
640
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
641
|
+
...confidence,
|
|
642
|
+
failure: ReferenceFailure.Unverifiable,
|
|
643
|
+
message: `${base.message} Its ${what} assertion could not be evaluated: ${a.message}`,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
648
|
+
...confidence,
|
|
649
|
+
failure: a.failure ?? (what === 'count' ? ReferenceFailure.CountMismatch : ReferenceFailure.ContentMismatch),
|
|
650
|
+
...(a.expected !== undefined ? { expected: a.expected } : {}),
|
|
651
|
+
...(a.actual !== undefined ? { actual: a.actual } : {}),
|
|
652
|
+
message: a.message,
|
|
653
|
+
suggestion: what === 'count'
|
|
654
|
+
? `Update the claim in the entry, then set count.expected: ${String(a.actual)}.`
|
|
655
|
+
: 'Update the entry (and the assertion) to what the code says now.',
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
/** Does the reference's TARGET exist? (Assertions are layered on by {@link checkReference}.) */
|
|
659
|
+
function checkReferenceTarget(inspection, ref, graph, rootDir = inspection.projectRoot, origin) {
|
|
660
|
+
// The directory a path resolves against: the project root, or the
|
|
661
|
+
// contributing pack's directory for a `root: pack` reference.
|
|
662
|
+
const projectRoot = rootDir;
|
|
265
663
|
switch (ref.kind) {
|
|
266
664
|
case 'file': {
|
|
267
665
|
if (!ref.path)
|
|
@@ -279,8 +677,9 @@ function checkReference(inspection, ref, graph) {
|
|
|
279
677
|
}
|
|
280
678
|
return {
|
|
281
679
|
outcome: ReferenceCheckOutcome.Stale,
|
|
680
|
+
failure: ReferenceFailure.PathMissing,
|
|
282
681
|
message: `Directory missing: ${ref.path}`,
|
|
283
|
-
|
|
682
|
+
// The hint is {@link stalePathHint}'s — who declared it decides (A3).
|
|
284
683
|
};
|
|
285
684
|
}
|
|
286
685
|
case 'symbol': {
|
|
@@ -291,128 +690,323 @@ function checkReference(inspection, ref, graph) {
|
|
|
291
690
|
const id = ref.id ?? ref.command ?? '';
|
|
292
691
|
if (!id)
|
|
293
692
|
return missingField('command', 'id or command');
|
|
294
|
-
|
|
295
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Command available: ${id}` };
|
|
296
|
-
}
|
|
297
|
-
return {
|
|
298
|
-
outcome: ReferenceCheckOutcome.Stale,
|
|
299
|
-
message: `Command not registered: ${id}`,
|
|
300
|
-
suggestion: 'Register the command in the command catalog or update the reference.',
|
|
301
|
-
};
|
|
693
|
+
return checkCommandString(inspection, id, 'Command');
|
|
302
694
|
}
|
|
303
695
|
case 'template': {
|
|
304
696
|
if (!ref.id)
|
|
305
697
|
return missingField('template', 'id');
|
|
306
|
-
|
|
307
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Template exists: ${ref.id}` };
|
|
308
|
-
}
|
|
309
|
-
return staleId('template', ref.id);
|
|
698
|
+
return checkRegisteredId(inspection, 'template', ref.id, 'Template');
|
|
310
699
|
}
|
|
311
700
|
case 'playbook': {
|
|
312
701
|
if (!ref.id)
|
|
313
702
|
return missingField('playbook', 'id');
|
|
314
|
-
|
|
315
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Playbook exists: ${ref.id}` };
|
|
316
|
-
}
|
|
317
|
-
return staleId('playbook', ref.id);
|
|
703
|
+
return checkRegisteredId(inspection, 'playbook', ref.id, 'Playbook');
|
|
318
704
|
}
|
|
319
705
|
case 'construct': {
|
|
320
706
|
if (!ref.id)
|
|
321
707
|
return missingField('construct', 'id');
|
|
322
|
-
|
|
323
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Construct exists: ${ref.id}` };
|
|
324
|
-
}
|
|
325
|
-
return staleId('construct', ref.id);
|
|
708
|
+
return checkRegisteredId(inspection, 'construct', ref.id, 'Construct');
|
|
326
709
|
}
|
|
327
710
|
case 'helper': {
|
|
328
711
|
if (!ref.id)
|
|
329
712
|
return missingField('helper', 'id');
|
|
330
|
-
|
|
331
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Helper exists: ${ref.id}` };
|
|
332
|
-
}
|
|
333
|
-
return staleId('helper', ref.id);
|
|
713
|
+
return checkRegisteredId(inspection, 'helper', ref.id, 'Helper');
|
|
334
714
|
}
|
|
335
715
|
case 'policy': {
|
|
336
716
|
if (!ref.id)
|
|
337
717
|
return missingField('policy', 'id');
|
|
338
|
-
|
|
339
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Policy exists: ${ref.id}` };
|
|
340
|
-
}
|
|
341
|
-
return staleId('policy', ref.id);
|
|
718
|
+
return checkRegisteredId(inspection, 'policy', ref.id, 'Policy');
|
|
342
719
|
}
|
|
343
720
|
case 'boundary-rule': {
|
|
344
721
|
if (!ref.id)
|
|
345
722
|
return missingField('boundary-rule', 'id');
|
|
346
|
-
|
|
347
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Boundary rule exists: ${ref.id}` };
|
|
348
|
-
}
|
|
349
|
-
return staleId('boundary-rule', ref.id);
|
|
723
|
+
return checkRegisteredId(inspection, 'boundary-rule', ref.id, 'Boundary rule');
|
|
350
724
|
}
|
|
351
725
|
case 'path-convention': {
|
|
352
726
|
if (!ref.id)
|
|
353
727
|
return missingField('path-convention', 'id');
|
|
354
|
-
|
|
355
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Path convention exists: ${ref.id}` };
|
|
356
|
-
}
|
|
357
|
-
return staleId('path-convention', ref.id);
|
|
728
|
+
return checkRegisteredId(inspection, 'path-convention', ref.id, 'Path convention');
|
|
358
729
|
}
|
|
359
730
|
case 'package': {
|
|
360
731
|
if (!ref.id)
|
|
361
732
|
return missingField('package', 'id');
|
|
362
|
-
|
|
363
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Package exists: ${ref.id}` };
|
|
364
|
-
}
|
|
365
|
-
return staleId('package', ref.id);
|
|
733
|
+
return checkPackageReference(inspection, ref.id, origin);
|
|
366
734
|
}
|
|
367
735
|
case 'url': {
|
|
368
736
|
// We never fetch URLs. Mark them unknown unless we can resolve to a
|
|
369
737
|
// local docs file.
|
|
370
738
|
return {
|
|
371
739
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
740
|
+
failure: ReferenceFailure.Unverifiable,
|
|
372
741
|
message: 'URL references are not verified (no network).',
|
|
373
742
|
};
|
|
374
743
|
}
|
|
744
|
+
default:
|
|
745
|
+
// A kind outside the vocabulary still loads (the validator reports it as
|
|
746
|
+
// an error); here it is MALFORMED — never a crash, never a silent row.
|
|
747
|
+
return {
|
|
748
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
749
|
+
failure: ReferenceFailure.Malformed,
|
|
750
|
+
message: `malformed reference: unsupported kind "${String(ref.kind)}" — expected one of: ${KNOWLEDGE_REFERENCE_KINDS.join(', ')}.`,
|
|
751
|
+
};
|
|
375
752
|
}
|
|
376
753
|
}
|
|
377
754
|
function missingField(kind, field) {
|
|
378
755
|
return {
|
|
379
|
-
outcome: ReferenceCheckOutcome.
|
|
380
|
-
|
|
756
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
757
|
+
failure: ReferenceFailure.Malformed,
|
|
758
|
+
message: `malformed reference: ${kind} reference missing required field \`${field}\`.`,
|
|
381
759
|
};
|
|
382
760
|
}
|
|
761
|
+
/** A missing file — its hint is {@link stalePathHint}'s, who declared the reference decides (A3). */
|
|
383
762
|
function staleFile(rel) {
|
|
384
763
|
return {
|
|
385
764
|
outcome: ReferenceCheckOutcome.Stale,
|
|
765
|
+
failure: ReferenceFailure.PathMissing,
|
|
386
766
|
message: `File missing: ${rel}`,
|
|
387
|
-
suggestion: 'Restore the file or run `shrk knowledge rename-file <old> <new> --dry-run`.',
|
|
388
767
|
};
|
|
389
768
|
}
|
|
390
769
|
function staleId(kind, id) {
|
|
391
770
|
return {
|
|
392
771
|
outcome: ReferenceCheckOutcome.Stale,
|
|
772
|
+
failure: ReferenceFailure.IdUnregistered,
|
|
393
773
|
message: `${kind} not found: ${id}`,
|
|
394
774
|
suggestion: 'Register the target or remove the reference.',
|
|
395
775
|
};
|
|
396
776
|
}
|
|
397
|
-
function
|
|
777
|
+
function normalizeSlashes(p) {
|
|
778
|
+
return p.split(/[\\/]/).join('/');
|
|
779
|
+
}
|
|
780
|
+
/** `./src/a.ts` / `src\a.ts` / `src/svc/` → `src/a.ts` / `src/svc`. */
|
|
781
|
+
function normalizeScopePath(p) {
|
|
782
|
+
return normalizeSlashes(p).replace(/^(?:\.\/)+/, '').replace(/\/+$/, '');
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* A changed path IS the target, or lies UNDER it — a `directory` reference
|
|
786
|
+
* whose files changed, or a directory deleted wholesale (git lists its files,
|
|
787
|
+
* never the directory itself).
|
|
788
|
+
*/
|
|
789
|
+
function pathTouched(target, changed) {
|
|
790
|
+
if (!target)
|
|
791
|
+
return false;
|
|
792
|
+
const p = normalizeScopePath(target);
|
|
793
|
+
if (p === '' || p === '.')
|
|
794
|
+
return false;
|
|
795
|
+
const under = `${p}/`;
|
|
796
|
+
return changed.some((c) => c === p || c.startsWith(under));
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Is a subject — a knowledge entry, a boundary rule, a policy check — in a
|
|
800
|
+
* changeset's scope? THE one scoping rule for `--changed-only` / `--files`:
|
|
801
|
+
*
|
|
802
|
+
* - the file that DECLARES it changed (its references were added or edited —
|
|
803
|
+
* a typo in a new reference must be checked by the run that introduced it);
|
|
804
|
+
* - a changed path equals a reference / anchor path, or lies under one;
|
|
805
|
+
* - a changed path matches a `count` source glob (the number re-derives from
|
|
806
|
+
* those globs, so a change there can move it with no pinned path changing).
|
|
807
|
+
*
|
|
808
|
+
* `source` is project-relative (`relativeSource`); `(unknown source)` matches
|
|
809
|
+
* nothing.
|
|
810
|
+
*/
|
|
811
|
+
function subjectTouchesChangedFiles(subject, changed) {
|
|
398
812
|
if (changed.length === 0)
|
|
399
813
|
return true;
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
814
|
+
const changedList = changed.map(normalizeScopePath);
|
|
815
|
+
if (changedList.includes(normalizeScopePath(subject.source)))
|
|
816
|
+
return true;
|
|
817
|
+
for (const ref of subject.references ?? []) {
|
|
818
|
+
// A malformed item (a string, a number) points at no path.
|
|
819
|
+
if (ref === null || typeof ref !== 'object')
|
|
820
|
+
continue;
|
|
821
|
+
if (pathTouched(typeof ref.path === 'string' ? ref.path : undefined, changedList))
|
|
822
|
+
return true;
|
|
823
|
+
// The scope the count is MEASURED over (`inspectSource` selects through
|
|
824
|
+
// `globListSelects`), so a change to a file its `!` excludes is no touch.
|
|
825
|
+
const globs = ref.count?.source?.files;
|
|
826
|
+
if (globs && globs.length > 0 && changedList.some((c) => globListSelects(c, globs)))
|
|
403
827
|
return true;
|
|
404
828
|
}
|
|
405
|
-
for (const anchor of
|
|
406
|
-
if (anchor.path
|
|
829
|
+
for (const anchor of subject.anchors ?? []) {
|
|
830
|
+
if (pathTouched(anchor.path, changedList))
|
|
407
831
|
return true;
|
|
408
832
|
}
|
|
409
833
|
return false;
|
|
410
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* The directories a `count` source never walks by default: the sharkcraft dir.
|
|
837
|
+
* Its `.ts` files hold the claims themselves — prose saying `registerService("<name>")`
|
|
838
|
+
* inside a count glob would count itself, and the mismatch hint would tell the
|
|
839
|
+
* author to bake that self-match into `expected` (policy-lint and finish prune
|
|
840
|
+
* the same dir for the same reason).
|
|
841
|
+
*/
|
|
842
|
+
function defaultCountExcludeDirs(inspection) {
|
|
843
|
+
const dir = inspection.sharkcraftDir;
|
|
844
|
+
if (!dir)
|
|
845
|
+
return [];
|
|
846
|
+
const rel = normalizeScopePath(nodePath.relative(inspection.projectRoot, dir));
|
|
847
|
+
return rel && rel !== '.' && !rel.startsWith('..') && !nodePath.isAbsolute(rel) ? [rel] : [];
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* The exclusions ONE count source walks under: an exclusion is dropped when the
|
|
851
|
+
* source's own globs target inside it (`sharkcraft/**` counts there on purpose).
|
|
852
|
+
*/
|
|
853
|
+
function countExcludeDirsFor(ref, excludeDirs) {
|
|
854
|
+
const globs = ref.count?.source?.files ?? [];
|
|
855
|
+
if (excludeDirs.length === 0 || globs.length === 0)
|
|
856
|
+
return excludeDirs;
|
|
857
|
+
const prefixes = globs.filter((g) => !g.startsWith('!')).map((g) => staticGlobPrefix(g));
|
|
858
|
+
return excludeDirs.filter((d) => !prefixes.some((p) => p === d || p.startsWith(`${d}/`)));
|
|
859
|
+
}
|
|
860
|
+
/** Every {@link ReferenceFailure} at 0 — the report always carries the full map. */
|
|
861
|
+
function emptyFailureCounts() {
|
|
862
|
+
const out = {};
|
|
863
|
+
for (const f of Object.values(ReferenceFailure))
|
|
864
|
+
out[f] = 0;
|
|
865
|
+
return out;
|
|
866
|
+
}
|
|
867
|
+
function emptyKindBucket() {
|
|
868
|
+
return { scanned: 0, zeroReferences: 0, referencesChecked: 0, verified: 0, stale: 0, unverifiable: 0 };
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* ok / stale / missing are real checks; unknown and invalid (malformed) proved
|
|
872
|
+
* nothing either way. THE predicate — the stale-check's entry buckets, its
|
|
873
|
+
* verdict fold (`declaredReferenceCoverage`) and the `shrk gate`
|
|
874
|
+
* knowledge-symbol gate all read it.
|
|
875
|
+
*/
|
|
876
|
+
export function isCheckableOutcome(o) {
|
|
877
|
+
return o !== ReferenceCheckOutcome.Unknown && o !== ReferenceCheckOutcome.Invalid;
|
|
878
|
+
}
|
|
879
|
+
function isFailingOutcome(o) {
|
|
880
|
+
return o === ReferenceCheckOutcome.Stale || o === ReferenceCheckOutcome.Missing;
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* One subject's bucket from its checks: any failing check makes it stale; any
|
|
884
|
+
* passing one (and no failing) makes it verified; NOTHING checkable makes it
|
|
885
|
+
* unverifiable — never healthy.
|
|
886
|
+
*/
|
|
887
|
+
function classifyChecks(checkable, failing) {
|
|
888
|
+
if (failing > 0)
|
|
889
|
+
return KnowledgeEntryVerdict.Stale;
|
|
890
|
+
if (checkable > 0)
|
|
891
|
+
return KnowledgeEntryVerdict.Verified;
|
|
892
|
+
return KnowledgeEntryVerdict.Unverifiable;
|
|
893
|
+
}
|
|
894
|
+
function bumpBucket(b, declared, verdict) {
|
|
895
|
+
b.scanned += 1;
|
|
896
|
+
if (declared === 0)
|
|
897
|
+
b.zeroReferences += 1;
|
|
898
|
+
b.referencesChecked += declared;
|
|
899
|
+
if (verdict === KnowledgeEntryVerdict.Verified)
|
|
900
|
+
b.verified += 1;
|
|
901
|
+
else if (verdict === KnowledgeEntryVerdict.Stale)
|
|
902
|
+
b.stale += 1;
|
|
903
|
+
else
|
|
904
|
+
b.unverifiable += 1;
|
|
905
|
+
}
|
|
906
|
+
function tallyOutcome(b, outcome) {
|
|
907
|
+
b.checked += 1;
|
|
908
|
+
if (outcome === ReferenceCheckOutcome.Ok)
|
|
909
|
+
b.ok += 1;
|
|
910
|
+
else if (outcome === ReferenceCheckOutcome.Stale)
|
|
911
|
+
b.stale += 1;
|
|
912
|
+
else if (outcome === ReferenceCheckOutcome.Missing)
|
|
913
|
+
b.missing += 1;
|
|
914
|
+
else if (outcome === ReferenceCheckOutcome.Invalid)
|
|
915
|
+
b.invalid += 1;
|
|
916
|
+
else
|
|
917
|
+
b.unknown += 1;
|
|
918
|
+
}
|
|
919
|
+
function relativeSource(projectRoot, origin) {
|
|
920
|
+
if (!origin)
|
|
921
|
+
return '(unknown source)';
|
|
922
|
+
return normalizeSlashes(nodePath.isAbsolute(origin) ? nodePath.relative(projectRoot, origin) : origin);
|
|
923
|
+
}
|
|
924
|
+
function pct1(part, whole) {
|
|
925
|
+
return whole > 0 ? Math.round((part / whole) * 1000) / 10 : 0;
|
|
926
|
+
}
|
|
927
|
+
/** The static directory part of a glob (`apps/legacy/**` → `apps/legacy`); `''` when it starts with a wildcard. */
|
|
928
|
+
function staticGlobPrefix(glob) {
|
|
929
|
+
const segs = normalizeSlashes(glob).replace(/^\.\//, '').split('/');
|
|
930
|
+
const firstGlob = segs.findIndex((s) => /[*?[{]/.test(s));
|
|
931
|
+
return (firstGlob === -1 ? segs : segs.slice(0, firstGlob)).join('/');
|
|
932
|
+
}
|
|
933
|
+
const BACKTICKED_IDENTIFIER = /`([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?)(?:\(\))?`/g;
|
|
934
|
+
const TS_SOURCE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
935
|
+
const PATH_ONLY_ADVISORY_CAP = 5;
|
|
936
|
+
/**
|
|
937
|
+
* The path-only advisory: an entry whose references are all paths, whose prose
|
|
938
|
+
* names (in backticks) a symbol one of its referenced files DECLARES. Renaming
|
|
939
|
+
* that symbol would leave the entry wrong while every reference still resolves
|
|
940
|
+
* — the strong form (`symbol` pinned to the path) catches it. Never gating.
|
|
941
|
+
*/
|
|
942
|
+
function pathOnlyAdvisories(projectRoot, entry, refs, cache) {
|
|
943
|
+
// A malformed item (a string, a null, a non-string path) earns no advisory — it is an INVALID row.
|
|
944
|
+
if (refs.length === 0 ||
|
|
945
|
+
!refs.every((r) => referenceShapeProblem(r) === undefined && (r.kind === 'file' || r.kind === 'directory'))) {
|
|
946
|
+
return [];
|
|
947
|
+
}
|
|
948
|
+
const files = refs
|
|
949
|
+
// A pack-rooted path names a file in the pack, not in this tree (round 15 follow-up).
|
|
950
|
+
.filter((r) => r.kind === 'file' && r.path !== undefined && TS_SOURCE.test(r.path) && !isPackRooted(r))
|
|
951
|
+
.map((r) => r.path);
|
|
952
|
+
if (files.length === 0)
|
|
953
|
+
return [];
|
|
954
|
+
const names = new Set();
|
|
955
|
+
for (const text of [entry.summary ?? '', typeof entry.content === 'string' ? entry.content : '']) {
|
|
956
|
+
for (const m of text.matchAll(BACKTICKED_IDENTIFIER))
|
|
957
|
+
names.add(m[1]);
|
|
958
|
+
}
|
|
959
|
+
const out = [];
|
|
960
|
+
for (const name of names) {
|
|
961
|
+
if (out.length >= PATH_ONLY_ADVISORY_CAP)
|
|
962
|
+
break;
|
|
963
|
+
for (const rel of files) {
|
|
964
|
+
let idx = cache.get(rel);
|
|
965
|
+
if (idx === undefined) {
|
|
966
|
+
const abs = nodePath.join(projectRoot, rel);
|
|
967
|
+
idx = existsSync(abs) ? buildSymbolIndex(abs) : null;
|
|
968
|
+
cache.set(rel, idx);
|
|
969
|
+
}
|
|
970
|
+
if (!idx?.parsed)
|
|
971
|
+
continue;
|
|
972
|
+
const declared = name.includes('.')
|
|
973
|
+
? (idx.members ?? []).some((m) => `${m.owner}.${m.name}` === name)
|
|
974
|
+
: idx.exports.some((e) => e.name === name);
|
|
975
|
+
if (!declared)
|
|
976
|
+
continue;
|
|
977
|
+
out.push({
|
|
978
|
+
code: KnowledgeAdvisoryCode.PathOnlyReference,
|
|
979
|
+
subjectId: entry.id,
|
|
980
|
+
assetKind: ReferenceAssetKind.Knowledge,
|
|
981
|
+
message: `entry names \`${name}\`, which ${rel} declares, but pins only the path — a rename inside the file would pass unnoticed. Add { kind: 'symbol', symbol: '${name}', path: '${rel}' }.`,
|
|
982
|
+
suggestion: { kind: 'symbol', symbol: name, path: rel },
|
|
983
|
+
});
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return out;
|
|
988
|
+
}
|
|
411
989
|
export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
412
990
|
const strategy = options.renameStrategy ?? RenameStrategy.Strict;
|
|
991
|
+
const projectRoot = inspection.projectRoot;
|
|
992
|
+
// Default: never walk the sharkcraft dir — the claims live there and would
|
|
993
|
+
// count themselves (every caller gets this; none has to remember to pass it).
|
|
994
|
+
const excludeDirs = options.excludeDirs ?? defaultCountExcludeDirs(inspection);
|
|
413
995
|
const referenceChecks = [];
|
|
414
996
|
const anchorChecks = [];
|
|
415
|
-
const
|
|
997
|
+
const assetReferenceChecks = [];
|
|
998
|
+
const counts = { ok: 0, stale: 0, missing: 0, unknown: 0, invalid: 0 };
|
|
999
|
+
const failureCounts = emptyFailureCounts();
|
|
1000
|
+
const byAssetKind = {
|
|
1001
|
+
[ReferenceAssetKind.Knowledge]: emptyKindBucket(),
|
|
1002
|
+
[ReferenceAssetKind.BoundaryRule]: emptyKindBucket(),
|
|
1003
|
+
[ReferenceAssetKind.Policy]: emptyKindBucket(),
|
|
1004
|
+
};
|
|
1005
|
+
const byEntryType = {};
|
|
1006
|
+
const byReferenceKind = {};
|
|
1007
|
+
const entryVerdicts = [];
|
|
1008
|
+
const advisories = [];
|
|
1009
|
+
const symbolIndexCache = new Map();
|
|
416
1010
|
let totalReferences = 0;
|
|
417
1011
|
let totalAnchors = 0;
|
|
418
1012
|
// Lazy symbol → file index, built on first stale-symbol need.
|
|
@@ -439,108 +1033,237 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
439
1033
|
dirBasenameIndex = buildBasenameDirIndex(inspection.projectRoot);
|
|
440
1034
|
return dirBasenameIndex;
|
|
441
1035
|
};
|
|
1036
|
+
/**
|
|
1037
|
+
* Check ONE declared reference and attach its rename signal — the same path
|
|
1038
|
+
* for a knowledge entry, a boundary rule and a policy check, so every asset
|
|
1039
|
+
* kind is verified by one checker.
|
|
1040
|
+
*/
|
|
1041
|
+
const checkOne = (subjectId, ref, assetKind, origin, subject) => {
|
|
1042
|
+
const result = checkReference(inspection, ref, options.graph, excludeDirs, origin, subject);
|
|
1043
|
+
const outcome = result.outcome;
|
|
1044
|
+
const check = {
|
|
1045
|
+
entryId: subjectId,
|
|
1046
|
+
reference: displayedReference(ref),
|
|
1047
|
+
outcome,
|
|
1048
|
+
message: result.message,
|
|
1049
|
+
...(result.confidence ? { symbolConfidence: result.confidence } : {}),
|
|
1050
|
+
...(result.suggestion ? { suggestion: result.suggestion } : {}),
|
|
1051
|
+
...(result.failure ? { failure: result.failure } : {}),
|
|
1052
|
+
...(result.expected !== undefined ? { expected: result.expected } : {}),
|
|
1053
|
+
...(result.actual !== undefined ? { actual: result.actual } : {}),
|
|
1054
|
+
...(assetKind ? { assetKind } : {}),
|
|
1055
|
+
};
|
|
1056
|
+
// A malformed item (a string, a number) has no kind to bucket under.
|
|
1057
|
+
const kindKey = ref !== null && typeof ref === 'object' && typeof ref.kind === 'string' ? ref.kind : MALFORMED_KIND_BUCKET;
|
|
1058
|
+
tallyOutcome((byReferenceKind[kindKey] ??= { checked: 0, ok: 0, stale: 0, missing: 0, unknown: 0, invalid: 0 }), outcome);
|
|
1059
|
+
if (result.failure)
|
|
1060
|
+
failureCounts[result.failure] += 1;
|
|
1061
|
+
const isStaleOrMissing = outcome === ReferenceCheckOutcome.Stale || outcome === ReferenceCheckOutcome.Missing;
|
|
1062
|
+
// A pack's reference is fixed in the pack (round 15 follow-up): a candidate
|
|
1063
|
+
// from THIS tree is never its rename — `fix --knowledge-stale` would point
|
|
1064
|
+
// a pack's entry at a consumer file. Its hint names `root: pack` instead.
|
|
1065
|
+
const findRename = isStaleOrMissing && origin === undefined;
|
|
1066
|
+
// A bare MEMBER name: the qualified spelling resolves in the same file, so
|
|
1067
|
+
// it is the replacement (`shrk fix --knowledge-stale` applies it).
|
|
1068
|
+
if (isStaleOrMissing && result.suggestedSymbol) {
|
|
1069
|
+
check.replaceWith = {
|
|
1070
|
+
symbol: result.suggestedSymbol,
|
|
1071
|
+
rationale: `\`${ref.symbol}\` is a member, not a top-level declaration — \`${result.suggestedSymbol}\` resolves in ${ref.path ?? 'the pinned file'}.`,
|
|
1072
|
+
strategy: RenameStrategy.Strict,
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
// Symbol rename detection. Strict mode: emit `replaceWith.path`
|
|
1076
|
+
// only for the single unambiguous candidate. Wide mode also
|
|
1077
|
+
// emits scored candidate lists for the multi-candidate cases that
|
|
1078
|
+
// strict silently drops.
|
|
1079
|
+
if (findRename && ref.kind === 'symbol' && ref.symbol && !check.replaceWith) {
|
|
1080
|
+
const all = (getSymbolIndex().get(ref.symbol) ?? []).filter((p) => p !== ref.path);
|
|
1081
|
+
if (all.length === 1) {
|
|
1082
|
+
check.replaceWith = {
|
|
1083
|
+
path: all[0],
|
|
1084
|
+
rationale: `\`${ref.symbol}\` is exported from \`${all[0]}\` — sole candidate.`,
|
|
1085
|
+
strategy: RenameStrategy.Strict,
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
else if (strategy === RenameStrategy.Wide && all.length > 1) {
|
|
1089
|
+
check.replaceWith = buildWideReplacement({
|
|
1090
|
+
stalePath: ref.path ?? '',
|
|
1091
|
+
paths: all,
|
|
1092
|
+
kindLabel: `symbol \`${ref.symbol}\``,
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
// File rename detection (directory move, basename match).
|
|
1097
|
+
if (findRename && ref.kind === 'file' && ref.path && !check.replaceWith) {
|
|
1098
|
+
const indexed = getFileBasenameIndex();
|
|
1099
|
+
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
1100
|
+
if (uniq) {
|
|
1101
|
+
check.replaceWith = {
|
|
1102
|
+
path: uniq,
|
|
1103
|
+
rationale: `File basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\` (likely directory rename).`,
|
|
1104
|
+
strategy: RenameStrategy.Strict,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
else if (strategy === RenameStrategy.Wide) {
|
|
1108
|
+
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
1109
|
+
if (all.length > 0) {
|
|
1110
|
+
check.replaceWith = buildWideReplacement({
|
|
1111
|
+
stalePath: ref.path,
|
|
1112
|
+
paths: all,
|
|
1113
|
+
kindLabel: `file \`${nodePath.basename(ref.path)}\``,
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
// Directory rename detection.
|
|
1119
|
+
if (findRename && ref.kind === 'directory' && ref.path && !check.replaceWith) {
|
|
1120
|
+
const indexed = getDirBasenameIndex();
|
|
1121
|
+
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
1122
|
+
if (uniq) {
|
|
1123
|
+
check.replaceWith = {
|
|
1124
|
+
path: uniq,
|
|
1125
|
+
rationale: `Directory basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\`.`,
|
|
1126
|
+
strategy: RenameStrategy.Strict,
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
else if (strategy === RenameStrategy.Wide) {
|
|
1130
|
+
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
1131
|
+
if (all.length > 0) {
|
|
1132
|
+
check.replaceWith = buildWideReplacement({
|
|
1133
|
+
stalePath: ref.path,
|
|
1134
|
+
paths: all,
|
|
1135
|
+
kindLabel: `directory \`${nodePath.basename(ref.path)}\``,
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return check;
|
|
1141
|
+
};
|
|
442
1142
|
for (const entry of inspection.knowledgeEntries) {
|
|
443
|
-
|
|
1143
|
+
// EVERY declared item is judged (`declaredReferenceItems` /
|
|
1144
|
+
// `declaredAnchorItems`): a non-list `references` or `anchors` is never
|
|
1145
|
+
// iterated (it crashed every inspection-backed verb) — each is reported
|
|
1146
|
+
// below as one INVALID row — and a malformed item is checked into an
|
|
1147
|
+
// INVALID row carrying the validator's words (`displayedReference`).
|
|
1148
|
+
const refs = declaredReferenceItems(entry);
|
|
1149
|
+
const anchorItems = declaredAnchorItems(entry);
|
|
1150
|
+
// The contributing pack (round 15 follow-up) — what a `root: pack` reference resolves against.
|
|
1151
|
+
const entrySource = inspection.entrySources?.get(entry.id);
|
|
1152
|
+
const packOrigin = entrySource?.type === 'pack' ? packOriginByName(inspection, entrySource.packageName) : undefined;
|
|
1153
|
+
if (options.changedFiles &&
|
|
1154
|
+
!subjectTouchesChangedFiles({
|
|
1155
|
+
source: relativeSource(projectRoot, entry.source?.origin),
|
|
1156
|
+
references: refs.map((r) => scopedReference(r, packOrigin)),
|
|
1157
|
+
anchors: knowledgeAnchors(entry),
|
|
1158
|
+
}, options.changedFiles)) {
|
|
444
1159
|
continue;
|
|
445
1160
|
}
|
|
446
|
-
|
|
1161
|
+
const sourceFormat = knowledgeSourceFormat(entry);
|
|
1162
|
+
let checkable = 0;
|
|
1163
|
+
let failing = 0;
|
|
1164
|
+
// A non-list `references` / `anchors` value (TypeScript or Markdown), or a
|
|
1165
|
+
// malformed anchor item: declared, never checkable — MALFORMED, the same
|
|
1166
|
+
// problem the validator reports. One INVALID row each.
|
|
1167
|
+
const malformedRow = (declaredValue, message) => {
|
|
1168
|
+
counts.invalid += 1;
|
|
1169
|
+
failureCounts[ReferenceFailure.Malformed] += 1;
|
|
1170
|
+
tallyOutcome((byReferenceKind[MALFORMED_KIND_BUCKET] ??= { checked: 0, ok: 0, stale: 0, missing: 0, unknown: 0, invalid: 0 }), ReferenceCheckOutcome.Invalid);
|
|
1171
|
+
referenceChecks.push({
|
|
1172
|
+
entryId: entry.id,
|
|
1173
|
+
// As declared: a string renders as written, a map as `kind:value`.
|
|
1174
|
+
reference: displayedReference(declaredValue),
|
|
1175
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
1176
|
+
failure: ReferenceFailure.Malformed,
|
|
1177
|
+
message,
|
|
1178
|
+
});
|
|
1179
|
+
};
|
|
1180
|
+
const listProblem = referencesListProblem(entry.references, sourceFormat);
|
|
1181
|
+
if (listProblem) {
|
|
447
1182
|
totalReferences += 1;
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
1183
|
+
malformedRow(entry.references, `malformed reference: ${listProblem}.`);
|
|
1184
|
+
}
|
|
1185
|
+
// An anchor row names WHICH anchor (`anchors`, `anchor #N` — the doctor's
|
|
1186
|
+
// spelling): rendered through the reference grammar, an anchor object read
|
|
1187
|
+
// `undefined:<id>`.
|
|
1188
|
+
const anchorsProblem = anchorsListProblem(entry.anchors);
|
|
1189
|
+
if (anchorsProblem) {
|
|
1190
|
+
totalAnchors += 1;
|
|
1191
|
+
malformedRow('anchors', `malformed anchor: ${anchorsProblem}.`);
|
|
1192
|
+
}
|
|
1193
|
+
// Who fixes a missing path (A3): a Markdown entry names its `references:` frontmatter.
|
|
1194
|
+
const hintSubject = { sourceFormat, source: relativeSource(projectRoot, entry.source?.origin) };
|
|
1195
|
+
for (const ref of refs) {
|
|
1196
|
+
totalReferences += 1;
|
|
1197
|
+
const check = checkOne(entry.id, ref, undefined, packOrigin, hintSubject);
|
|
1198
|
+
if (check.outcome === ReferenceCheckOutcome.Ok)
|
|
451
1199
|
counts.ok += 1;
|
|
452
|
-
else if (outcome === ReferenceCheckOutcome.Stale)
|
|
1200
|
+
else if (check.outcome === ReferenceCheckOutcome.Stale)
|
|
453
1201
|
counts.stale += 1;
|
|
454
|
-
else if (outcome === ReferenceCheckOutcome.Missing)
|
|
1202
|
+
else if (check.outcome === ReferenceCheckOutcome.Missing)
|
|
455
1203
|
counts.missing += 1;
|
|
1204
|
+
else if (check.outcome === ReferenceCheckOutcome.Invalid)
|
|
1205
|
+
counts.invalid += 1;
|
|
456
1206
|
else
|
|
457
1207
|
counts.unknown += 1;
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
message: result.message,
|
|
463
|
-
...(result.confidence ? { symbolConfidence: result.confidence } : {}),
|
|
464
|
-
...(result.suggestion ? { suggestion: result.suggestion } : {}),
|
|
465
|
-
};
|
|
466
|
-
const isStaleOrMissing = outcome === ReferenceCheckOutcome.Stale || outcome === ReferenceCheckOutcome.Missing;
|
|
467
|
-
// Symbol rename detection. Strict mode: emit `replaceWith.path`
|
|
468
|
-
// only for the single unambiguous candidate. Wide mode also
|
|
469
|
-
// emits scored candidate lists for the multi-candidate cases that
|
|
470
|
-
// strict silently drops.
|
|
471
|
-
if (isStaleOrMissing && ref.kind === 'symbol' && ref.symbol) {
|
|
472
|
-
const all = (getSymbolIndex().get(ref.symbol) ?? []).filter((p) => p !== ref.path);
|
|
473
|
-
if (all.length === 1) {
|
|
474
|
-
check.replaceWith = {
|
|
475
|
-
path: all[0],
|
|
476
|
-
rationale: `\`${ref.symbol}\` is exported from \`${all[0]}\` — sole candidate.`,
|
|
477
|
-
strategy: RenameStrategy.Strict,
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
else if (strategy === RenameStrategy.Wide && all.length > 1) {
|
|
481
|
-
check.replaceWith = buildWideReplacement({
|
|
482
|
-
stalePath: ref.path ?? '',
|
|
483
|
-
paths: all,
|
|
484
|
-
kindLabel: `symbol \`${ref.symbol}\``,
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
// File rename detection (directory move, basename match).
|
|
489
|
-
if (isStaleOrMissing && ref.kind === 'file' && ref.path && !check.replaceWith) {
|
|
490
|
-
const indexed = getFileBasenameIndex();
|
|
491
|
-
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
492
|
-
if (uniq) {
|
|
493
|
-
check.replaceWith = {
|
|
494
|
-
path: uniq,
|
|
495
|
-
rationale: `File basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\` (likely directory rename).`,
|
|
496
|
-
strategy: RenameStrategy.Strict,
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
else if (strategy === RenameStrategy.Wide) {
|
|
500
|
-
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
501
|
-
if (all.length > 0) {
|
|
502
|
-
check.replaceWith = buildWideReplacement({
|
|
503
|
-
stalePath: ref.path,
|
|
504
|
-
paths: all,
|
|
505
|
-
kindLabel: `file \`${nodePath.basename(ref.path)}\``,
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
// Directory rename detection.
|
|
511
|
-
if (isStaleOrMissing && ref.kind === 'directory' && ref.path && !check.replaceWith) {
|
|
512
|
-
const indexed = getDirBasenameIndex();
|
|
513
|
-
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
514
|
-
if (uniq) {
|
|
515
|
-
check.replaceWith = {
|
|
516
|
-
path: uniq,
|
|
517
|
-
rationale: `Directory basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\`.`,
|
|
518
|
-
strategy: RenameStrategy.Strict,
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
else if (strategy === RenameStrategy.Wide) {
|
|
522
|
-
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
523
|
-
if (all.length > 0) {
|
|
524
|
-
check.replaceWith = buildWideReplacement({
|
|
525
|
-
stalePath: ref.path,
|
|
526
|
-
paths: all,
|
|
527
|
-
kindLabel: `directory \`${nodePath.basename(ref.path)}\``,
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
}
|
|
1208
|
+
if (isCheckableOutcome(check.outcome)) {
|
|
1209
|
+
checkable += 1;
|
|
1210
|
+
if (isFailingOutcome(check.outcome))
|
|
1211
|
+
failing += 1;
|
|
531
1212
|
}
|
|
532
1213
|
referenceChecks.push(check);
|
|
533
1214
|
}
|
|
534
|
-
for (const
|
|
1215
|
+
for (const [i, item] of anchorItems.entries()) {
|
|
535
1216
|
totalAnchors += 1;
|
|
1217
|
+
// THE anchor item-shape predicate the validator applies: a `null` item
|
|
1218
|
+
// crashed `checkAnchor` (`anchor.kind`), a non-string path its path join.
|
|
1219
|
+
const shape = anchorShapeProblem(item);
|
|
1220
|
+
if (shape) {
|
|
1221
|
+
malformedRow(`anchor #${i + 1}`, `malformed anchor: ${shape}.`);
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
const anchor = item;
|
|
536
1225
|
const inspected = checkAnchor(inspection, anchor, options.graph);
|
|
1226
|
+
const failure = inspected.outcome === ReferenceCheckOutcome.Ok
|
|
1227
|
+
? undefined
|
|
1228
|
+
: (inspected.failure ?? defaultFailure(anchor.kind, inspected.outcome));
|
|
1229
|
+
if (failure)
|
|
1230
|
+
failureCounts[failure] += 1;
|
|
1231
|
+
if (isCheckableOutcome(inspected.outcome)) {
|
|
1232
|
+
checkable += 1;
|
|
1233
|
+
if (isFailingOutcome(inspected.outcome))
|
|
1234
|
+
failing += 1;
|
|
1235
|
+
}
|
|
537
1236
|
anchorChecks.push({
|
|
538
1237
|
entryId: entry.id,
|
|
539
1238
|
anchor,
|
|
540
1239
|
outcome: inspected.outcome,
|
|
541
1240
|
message: inspected.message,
|
|
1241
|
+
...(failure ? { failure } : {}),
|
|
542
1242
|
});
|
|
543
1243
|
}
|
|
1244
|
+
const verdict = classifyChecks(checkable, failing);
|
|
1245
|
+
const declared = refs.length + anchorItems.length + (listProblem ? 1 : 0) + (anchorsProblem ? 1 : 0);
|
|
1246
|
+
const reason = verdict !== KnowledgeEntryVerdict.Unverifiable
|
|
1247
|
+
? undefined
|
|
1248
|
+
: declared === 0
|
|
1249
|
+
? KnowledgeUnverifiableReason.NoReferences
|
|
1250
|
+
: KnowledgeUnverifiableReason.OnlyUnverifiableReferences;
|
|
1251
|
+
const type = String(entry.type);
|
|
1252
|
+
const origin = inspection.entrySources?.get(entry.id);
|
|
1253
|
+
entryVerdicts.push({
|
|
1254
|
+
entryId: entry.id,
|
|
1255
|
+
verdict,
|
|
1256
|
+
...(reason ? { reason } : {}),
|
|
1257
|
+
source: relativeSource(projectRoot, entry.source?.origin),
|
|
1258
|
+
sourceFormat,
|
|
1259
|
+
...(origin?.type === 'pack' && origin.packageName ? { pack: origin.packageName } : {}),
|
|
1260
|
+
type,
|
|
1261
|
+
checkable,
|
|
1262
|
+
failing,
|
|
1263
|
+
});
|
|
1264
|
+
bumpBucket(byAssetKind[ReferenceAssetKind.Knowledge], declared, verdict);
|
|
1265
|
+
bumpBucket((byEntryType[type] ??= emptyKindBucket()), declared, verdict);
|
|
1266
|
+
advisories.push(...pathOnlyAdvisories(projectRoot, entry, refs, symbolIndexCache));
|
|
544
1267
|
}
|
|
545
1268
|
// Content-similarity boost: when multiple wide replacements in the
|
|
546
1269
|
// SAME entry name the same candidate path, raise that candidate's score
|
|
@@ -548,6 +1271,143 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
548
1271
|
// followed" case where a single per-reference signal is weak but the
|
|
549
1272
|
// aggregate is strong.
|
|
550
1273
|
applyEntryCorroborationBoost(referenceChecks);
|
|
1274
|
+
// Boundary rules and policy checks declare references too — swept by the
|
|
1275
|
+
// same checker, counted per kind, and NOT folded into the knowledge buckets
|
|
1276
|
+
// (their coverage is reported, not gated by default).
|
|
1277
|
+
const includeAssets = options.includeAssets !== false;
|
|
1278
|
+
const boundaryRules = includeAssets ? (inspection.boundaryRegistry?.list() ?? []) : [];
|
|
1279
|
+
const policyDecls = includeAssets ? listPolicyDeclarations(inspection) : [];
|
|
1280
|
+
const subjects = [
|
|
1281
|
+
...boundaryRules.map((rule) => ({
|
|
1282
|
+
assetKind: ReferenceAssetKind.BoundaryRule,
|
|
1283
|
+
id: rule.id,
|
|
1284
|
+
source: relativeSource(projectRoot, inspection.boundarySources?.get(rule.id)?.file),
|
|
1285
|
+
references: rule.references ?? [],
|
|
1286
|
+
})),
|
|
1287
|
+
...policyDecls.map((p) => ({
|
|
1288
|
+
assetKind: ReferenceAssetKind.Policy,
|
|
1289
|
+
id: p.qualifiedId,
|
|
1290
|
+
source: relativeSource(projectRoot, p.sourceFile),
|
|
1291
|
+
references: p.references ?? [],
|
|
1292
|
+
})),
|
|
1293
|
+
];
|
|
1294
|
+
// The contributing pack of a boundary rule / policy check a pack declares
|
|
1295
|
+
// (round 15 follow-up) — the same `root: pack` resolution a knowledge entry gets.
|
|
1296
|
+
const subjectPackOrigin = (subject) => {
|
|
1297
|
+
if (subject.assetKind === ReferenceAssetKind.BoundaryRule) {
|
|
1298
|
+
const src = inspection.boundarySources?.get(subject.id);
|
|
1299
|
+
return src?.type === 'pack' ? packOriginByName(inspection, src.packageName) : undefined;
|
|
1300
|
+
}
|
|
1301
|
+
const decl = policyDecls.find((p) => p.qualifiedId === subject.id);
|
|
1302
|
+
return decl?.source === 'pack' ? packOriginOfFile(inspection, decl.sourceFile) : undefined;
|
|
1303
|
+
};
|
|
1304
|
+
for (const subject of subjects) {
|
|
1305
|
+
const packOrigin = subjectPackOrigin(subject);
|
|
1306
|
+
if (options.changedFiles &&
|
|
1307
|
+
!subjectTouchesChangedFiles({
|
|
1308
|
+
source: subject.source ?? '(unknown source)',
|
|
1309
|
+
references: subject.references.map((r) => scopedReference(r, packOrigin)),
|
|
1310
|
+
}, options.changedFiles)) {
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
let checkable = 0;
|
|
1314
|
+
let failing = 0;
|
|
1315
|
+
for (const ref of subject.references) {
|
|
1316
|
+
const check = checkOne(subject.id, ref, subject.assetKind, packOrigin);
|
|
1317
|
+
if (isCheckableOutcome(check.outcome)) {
|
|
1318
|
+
checkable += 1;
|
|
1319
|
+
if (isFailingOutcome(check.outcome))
|
|
1320
|
+
failing += 1;
|
|
1321
|
+
}
|
|
1322
|
+
assetReferenceChecks.push(check);
|
|
1323
|
+
}
|
|
1324
|
+
bumpBucket(byAssetKind[subject.assetKind], subject.references.length, classifyChecks(checkable, failing));
|
|
1325
|
+
}
|
|
1326
|
+
// IMPLICIT references (boundary rules only — their scope is data): a `from`
|
|
1327
|
+
// glob whose static prefix does not exist governs nothing there. Advisory
|
|
1328
|
+
// unless `--fail-on implicit`; never counted in `failureCounts`. Policy
|
|
1329
|
+
// checks keep their scope inside `evaluate()`, which cannot be read without
|
|
1330
|
+
// running it — that gap is reported, not guessed at.
|
|
1331
|
+
if (!options.changedFiles) {
|
|
1332
|
+
for (const rule of boundaryRules) {
|
|
1333
|
+
for (const glob of rule.from ?? []) {
|
|
1334
|
+
if (glob.startsWith('!'))
|
|
1335
|
+
continue;
|
|
1336
|
+
const prefix = staticGlobPrefix(glob);
|
|
1337
|
+
if (!prefix || fileExists(projectRoot, prefix))
|
|
1338
|
+
continue;
|
|
1339
|
+
const message = `boundary rule ${rule.id} scopes '${glob}' but ${prefix} does not exist — the rule governs nothing there.`;
|
|
1340
|
+
assetReferenceChecks.push({
|
|
1341
|
+
entryId: rule.id,
|
|
1342
|
+
reference: { kind: 'directory', path: prefix, note: `implicit: from '${glob}'` },
|
|
1343
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1344
|
+
failure: ReferenceFailure.PathMissing,
|
|
1345
|
+
implicit: true,
|
|
1346
|
+
assetKind: ReferenceAssetKind.BoundaryRule,
|
|
1347
|
+
message,
|
|
1348
|
+
suggestion: 'Fix or remove the glob (gate it with --fail-on implicit).',
|
|
1349
|
+
});
|
|
1350
|
+
advisories.push({
|
|
1351
|
+
code: KnowledgeAdvisoryCode.ImplicitPathMissing,
|
|
1352
|
+
subjectId: rule.id,
|
|
1353
|
+
assetKind: ReferenceAssetKind.BoundaryRule,
|
|
1354
|
+
message,
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
const policySweep = {
|
|
1360
|
+
loaded: isReferenceCacheWarm(inspection),
|
|
1361
|
+
declared: policyDecls.length,
|
|
1362
|
+
withReferences: policyDecls.filter((p) => (p.references?.length ?? 0) > 0).length,
|
|
1363
|
+
};
|
|
1364
|
+
if (policySweep.declared > policySweep.withReferences) {
|
|
1365
|
+
advisories.push({
|
|
1366
|
+
code: KnowledgeAdvisoryCode.PolicyScopeUnverifiable,
|
|
1367
|
+
subjectId: 'policy',
|
|
1368
|
+
assetKind: ReferenceAssetKind.Policy,
|
|
1369
|
+
message: `policy: ${policySweep.declared} declared check(s), ${policySweep.withReferences} with references — the scope of the rest is unverifiable (it lives inside evaluate()).`,
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
const verified = entryVerdicts.filter((v) => v.verdict === KnowledgeEntryVerdict.Verified).length;
|
|
1373
|
+
const stale = entryVerdicts.filter((v) => v.verdict === KnowledgeEntryVerdict.Stale).length;
|
|
1374
|
+
const unverifiableIds = entryVerdicts
|
|
1375
|
+
.filter((v) => v.verdict === KnowledgeEntryVerdict.Unverifiable)
|
|
1376
|
+
.map((v) => v.entryId);
|
|
1377
|
+
const entriesInScope = entryVerdicts.length;
|
|
1378
|
+
const coverage = {
|
|
1379
|
+
entriesInScope,
|
|
1380
|
+
verified,
|
|
1381
|
+
stale,
|
|
1382
|
+
unverifiable: unverifiableIds.length,
|
|
1383
|
+
unverifiablePct: pct1(unverifiableIds.length, entriesInScope),
|
|
1384
|
+
referencedRatio: entriesInScope > 0 ? (verified + stale) / entriesInScope : 0,
|
|
1385
|
+
};
|
|
1386
|
+
let age;
|
|
1387
|
+
if (options.staleAfterDays !== undefined) {
|
|
1388
|
+
const asOf = options.asOf ?? todayUtcIso();
|
|
1389
|
+
const inScope = new Set(entryVerdicts.map((v) => v.entryId));
|
|
1390
|
+
const aged = [];
|
|
1391
|
+
const neverVerified = [];
|
|
1392
|
+
for (const entry of inspection.knowledgeEntries) {
|
|
1393
|
+
if (!inScope.has(entry.id))
|
|
1394
|
+
continue;
|
|
1395
|
+
const days = entry.verifiedOn ? verifiedOnAgeDays(entry.verifiedOn, asOf) : undefined;
|
|
1396
|
+
if (days === undefined) {
|
|
1397
|
+
neverVerified.push(entry.id);
|
|
1398
|
+
}
|
|
1399
|
+
else if (days > options.staleAfterDays) {
|
|
1400
|
+
aged.push({
|
|
1401
|
+
entryId: entry.id,
|
|
1402
|
+
verifiedOn: entry.verifiedOn,
|
|
1403
|
+
ageDays: days,
|
|
1404
|
+
source: relativeSource(projectRoot, entry.source?.origin),
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
aged.sort((a, b) => b.ageDays - a.ageDays || a.entryId.localeCompare(b.entryId));
|
|
1409
|
+
age = { asOf, staleAfterDays: options.staleAfterDays, aged, neverVerified };
|
|
1410
|
+
}
|
|
551
1411
|
return {
|
|
552
1412
|
schema: KNOWLEDGE_STALE_SCHEMA,
|
|
553
1413
|
entries: inspection.knowledgeEntries.length,
|
|
@@ -556,6 +1416,19 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
556
1416
|
counts,
|
|
557
1417
|
referenceChecks,
|
|
558
1418
|
anchorChecks,
|
|
1419
|
+
entriesInScope,
|
|
1420
|
+
coverage,
|
|
1421
|
+
entryVerdicts,
|
|
1422
|
+
unverifiableIds,
|
|
1423
|
+
rejectedEntries: knowledgeRejectedEntries(inspection),
|
|
1424
|
+
failureCounts,
|
|
1425
|
+
byAssetKind,
|
|
1426
|
+
byEntryType,
|
|
1427
|
+
byReferenceKind,
|
|
1428
|
+
assetReferenceChecks,
|
|
1429
|
+
advisories,
|
|
1430
|
+
policySweep,
|
|
1431
|
+
...(age ? { age } : {}),
|
|
559
1432
|
};
|
|
560
1433
|
}
|
|
561
1434
|
/**
|
|
@@ -872,51 +1745,77 @@ function checkAnchor(inspection, anchor, graph) {
|
|
|
872
1745
|
switch (anchor.kind) {
|
|
873
1746
|
case 'file':
|
|
874
1747
|
if (!anchor.path) {
|
|
875
|
-
return {
|
|
1748
|
+
return {
|
|
1749
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
1750
|
+
failure: ReferenceFailure.Malformed,
|
|
1751
|
+
message: 'malformed anchor: file anchor missing required field `path`.',
|
|
1752
|
+
};
|
|
876
1753
|
}
|
|
877
1754
|
if (fileExists(inspection.projectRoot, anchor.path)) {
|
|
878
1755
|
return { outcome: ReferenceCheckOutcome.Ok, message: `anchor file exists: ${anchor.path}` };
|
|
879
1756
|
}
|
|
880
|
-
return {
|
|
1757
|
+
return {
|
|
1758
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1759
|
+
failure: ReferenceFailure.PathMissing,
|
|
1760
|
+
message: `anchor file missing: ${anchor.path}`,
|
|
1761
|
+
};
|
|
881
1762
|
case 'symbol':
|
|
882
1763
|
return checkSymbolAnchor(inspection, anchor, graph);
|
|
883
|
-
case 'command':
|
|
884
|
-
if (anchor.targetId
|
|
885
|
-
return { outcome: ReferenceCheckOutcome.
|
|
1764
|
+
case 'command': {
|
|
1765
|
+
if (!anchor.targetId) {
|
|
1766
|
+
return { outcome: ReferenceCheckOutcome.Stale, message: 'command anchor unresolved: ?' };
|
|
886
1767
|
}
|
|
887
|
-
|
|
1768
|
+
const r = checkCommandString(inspection, anchor.targetId, 'command anchor');
|
|
1769
|
+
return { outcome: r.outcome, message: r.message };
|
|
1770
|
+
}
|
|
888
1771
|
case 'construct':
|
|
889
|
-
|
|
890
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `construct exists: ${anchor.targetId}` };
|
|
891
|
-
}
|
|
892
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `construct anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1772
|
+
return checkAnchorId(inspection, 'construct', anchor.targetId);
|
|
893
1773
|
case 'template':
|
|
894
|
-
|
|
895
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `template exists: ${anchor.targetId}` };
|
|
896
|
-
}
|
|
897
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `template anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1774
|
+
return checkAnchorId(inspection, 'template', anchor.targetId);
|
|
898
1775
|
case 'helper':
|
|
899
|
-
|
|
900
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `helper exists: ${anchor.targetId}` };
|
|
901
|
-
}
|
|
902
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `helper anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1776
|
+
return checkAnchorId(inspection, 'helper', anchor.targetId);
|
|
903
1777
|
case 'playbook':
|
|
904
|
-
|
|
905
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `playbook exists: ${anchor.targetId}` };
|
|
906
|
-
}
|
|
907
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `playbook anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1778
|
+
return checkAnchorId(inspection, 'playbook', anchor.targetId);
|
|
908
1779
|
case 'policy':
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1780
|
+
return checkAnchorId(inspection, 'policy', anchor.targetId);
|
|
1781
|
+
default:
|
|
1782
|
+
return {
|
|
1783
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
1784
|
+
failure: ReferenceFailure.Malformed,
|
|
1785
|
+
message: `malformed anchor: unsupported anchor kind "${String(anchor.kind)}".`,
|
|
1786
|
+
};
|
|
913
1787
|
}
|
|
914
1788
|
}
|
|
1789
|
+
/**
|
|
1790
|
+
* An id-keyed anchor, through the same guarded resolver as a reference — an
|
|
1791
|
+
* unloaded registry is NOT VERIFIED, never "unresolved".
|
|
1792
|
+
*/
|
|
1793
|
+
function checkAnchorId(inspection, kind, targetId) {
|
|
1794
|
+
if (!targetId) {
|
|
1795
|
+
return {
|
|
1796
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1797
|
+
failure: ReferenceFailure.IdUnregistered,
|
|
1798
|
+
message: `${kind} anchor unresolved: ?`,
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
const r = checkRegisteredId(inspection, kind, targetId, kind);
|
|
1802
|
+
if (r.outcome === ReferenceCheckOutcome.Ok) {
|
|
1803
|
+
return { outcome: ReferenceCheckOutcome.Ok, message: `${kind} exists: ${targetId}` };
|
|
1804
|
+
}
|
|
1805
|
+
if (r.outcome === ReferenceCheckOutcome.Unknown) {
|
|
1806
|
+
return { outcome: r.outcome, message: r.message, failure: ReferenceFailure.Unverifiable };
|
|
1807
|
+
}
|
|
1808
|
+
return {
|
|
1809
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1810
|
+
failure: ReferenceFailure.IdUnregistered,
|
|
1811
|
+
message: `${kind} anchor unresolved: ${targetId}`,
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
915
1814
|
function checkSymbolAnchor(inspection, anchor, graph) {
|
|
916
1815
|
const r = checkSymbolReference(inspection.projectRoot, {
|
|
917
1816
|
kind: 'symbol',
|
|
918
1817
|
symbol: anchor.symbol,
|
|
919
1818
|
...(anchor.path ? { path: anchor.path } : {}),
|
|
920
1819
|
}, graph);
|
|
921
|
-
return { outcome: r.outcome, message: r.message };
|
|
1820
|
+
return { outcome: r.outcome, message: r.message, ...(r.failure ? { failure: r.failure } : {}) };
|
|
922
1821
|
}
|