@shrkcrft/inspector 0.1.0-alpha.30 → 0.1.0-alpha.31
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 +51 -4
- 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/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/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 +106 -0
- package/dist/contribution-load-failures.d.ts.map +1 -0
- package/dist/contribution-load-failures.js +575 -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-registry.d.ts +22 -0
- package/dist/convention-registry.d.ts.map +1 -1
- package/dist/convention-registry.js +79 -39
- 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 +159 -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 +20 -0
- package/dist/decision-records.d.ts.map +1 -1
- package/dist/decision-records.js +68 -20
- 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 +50 -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 +68 -19
- package/dist/fix-preview.d.ts.map +1 -1
- package/dist/fix-preview.js +9 -5
- 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.js +1 -1
- 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 +24 -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-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-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-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-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-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-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 +141 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +172 -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 +24 -1
- package/dist/knowledge-authoring.d.ts.map +1 -1
- package/dist/knowledge-authoring.js +46 -7
- package/dist/knowledge-entry-verdict-record.d.ts +22 -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-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-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 +83 -0
- package/dist/knowledge-stale-gate.d.ts.map +1 -0
- package/dist/knowledge-stale-gate.js +429 -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 +110 -0
- package/dist/knowledge-stale.d.ts +122 -9
- package/dist/knowledge-stale.d.ts.map +1 -1
- package/dist/knowledge-stale.js +996 -291
- 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/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 +443 -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/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-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/resolve-project-config.d.ts +64 -0
- package/dist/resolve-project-config.d.ts.map +1 -1
- package/dist/resolve-project-config.js +240 -32
- 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 +1304 -181
- 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 +53 -280
- 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 +273 -26
- 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/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-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 +228 -0
- package/dist/why-file.d.ts +3 -0
- package/dist/why-file.d.ts.map +1 -1
- package/dist/why-file.js +7 -3
- package/package.json +17 -17
package/dist/knowledge-stale.js
CHANGED
|
@@ -1,19 +1,42 @@
|
|
|
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 { KNOWLEDGE_REFERENCE_KINDS, todayUtcIso, verifiedOnAgeDays, } from '@shrkcrft/knowledge';
|
|
28
|
+
import { isCacheBackedKind, isReferenceCacheWarm, referenceIdExists, referenceIdsFor, } from "./reference-registry.js";
|
|
29
|
+
import { listPolicyDeclarations } from "./policy-registry.js";
|
|
30
|
+
import { checkReferenceContent, checkReferenceCount, hasContentAssertion, } from "./reference-content-check.js";
|
|
31
|
+
import { ReferenceFailure } from "./reference-failure.js";
|
|
32
|
+
import { KnowledgeEntryVerdict } from "./knowledge-entry-verdict.js";
|
|
33
|
+
import { KnowledgeUnverifiableReason } from "./knowledge-unverifiable-reason.js";
|
|
34
|
+
import { KnowledgeAdvisoryCode } from "./knowledge-advisory-code.js";
|
|
35
|
+
import { ReferenceAssetKind } from "./reference-asset-kind.js";
|
|
36
|
+
import { buildSymbolIndex } from "./symbol-index.js";
|
|
37
|
+
// The `command` reference case (and its anchor twin) — one injected resolver.
|
|
38
|
+
import { COMMAND_INDEX_NOT_INJECTED, resolveShrkCommandReference } from "./reference-registry.js";
|
|
39
|
+
import { CommandResolutionStatus } from "./command-resolution-status.js";
|
|
17
40
|
import { resolveSymbolInFile, SymbolResolution } from "./symbol-index.js";
|
|
18
41
|
export const KNOWLEDGE_STALE_SCHEMA = 'sharkcraft.knowledge-stale/v1';
|
|
19
42
|
export var ReferenceCheckOutcome;
|
|
@@ -21,7 +44,16 @@ export var ReferenceCheckOutcome;
|
|
|
21
44
|
ReferenceCheckOutcome["Ok"] = "ok";
|
|
22
45
|
ReferenceCheckOutcome["Stale"] = "stale";
|
|
23
46
|
ReferenceCheckOutcome["Missing"] = "missing";
|
|
47
|
+
/** Well-formed, but the check cannot evaluate it (a `url`, an unwarmed registry). */
|
|
24
48
|
ReferenceCheckOutcome["Unknown"] = "unknown";
|
|
49
|
+
/**
|
|
50
|
+
* MALFORMED — a kind outside the vocabulary, or the field its kind cannot be
|
|
51
|
+
* checked without is missing (a symbol reference with no `symbol`). It
|
|
52
|
+
* verifies nothing; it used to share `unknown` with an unfetched url and
|
|
53
|
+
* count toward a green run. Now it is a coverage shortfall on the verdict
|
|
54
|
+
* (not verified) and fails under `--fail-on invalid`.
|
|
55
|
+
*/
|
|
56
|
+
ReferenceCheckOutcome["Invalid"] = "invalid";
|
|
25
57
|
})(ReferenceCheckOutcome || (ReferenceCheckOutcome = {}));
|
|
26
58
|
export var SymbolConfidence;
|
|
27
59
|
(function (SymbolConfidence) {
|
|
@@ -75,192 +107,443 @@ function dirExists(projectRoot, rel) {
|
|
|
75
107
|
* hoped-for shape compiles perfectly and fails silently forever; the registry
|
|
76
108
|
* imports the real accessors so the compiler can see a rename.
|
|
77
109
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
110
|
+
/**
|
|
111
|
+
* A `command` reference (or anchor) through THE injected command resolver.
|
|
112
|
+
*
|
|
113
|
+
* Without a resolver (outside the CLI) the command was NOT checked: that is
|
|
114
|
+
* `Unknown` with a loud NOT VERIFIED message — never `Ok`. It used to be `Ok`
|
|
115
|
+
* for every `shrk …` string, which certified three dead commands in shrk's own
|
|
116
|
+
* knowledge as "Command available".
|
|
117
|
+
*
|
|
118
|
+
* Read as a command REFERENCE (`resolveShrkCommandReference`): a bare
|
|
119
|
+
* `frobnicate` is `shrk frobnicate` — stale — exactly as the self-config
|
|
120
|
+
* doctor and the agent-test runner read it, never `not-shrk` → `ok`.
|
|
121
|
+
*/
|
|
122
|
+
function checkCommandString(inspection, id, label) {
|
|
123
|
+
const resolution = resolveShrkCommandReference(inspection, id);
|
|
124
|
+
switch (resolution.status) {
|
|
125
|
+
case CommandResolutionStatus.Unverified:
|
|
126
|
+
return {
|
|
127
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
128
|
+
message: `${label} ${id}: ${COMMAND_INDEX_NOT_INJECTED}`,
|
|
129
|
+
suggestion: 'Run the check through the CLI (`shrk knowledge stale-check`), which injects the live command index.',
|
|
130
|
+
};
|
|
131
|
+
case CommandResolutionStatus.Ok:
|
|
132
|
+
case CommandResolutionStatus.PrefixOnly:
|
|
133
|
+
case CommandResolutionStatus.NotShrk:
|
|
134
|
+
return {
|
|
135
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
136
|
+
message: resolution.status === CommandResolutionStatus.Ok
|
|
137
|
+
? `Command available: ${id}`
|
|
138
|
+
: `Command ${id}: ${resolution.status}${resolution.reason ? ` (${resolution.reason})` : ''}`,
|
|
139
|
+
};
|
|
140
|
+
default: {
|
|
141
|
+
const closest = resolution.closest ?? [];
|
|
142
|
+
return {
|
|
143
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
144
|
+
message: `Command not registered: ${id} (${resolution.status}${resolution.reason ? ` — ${resolution.reason}` : ''})`,
|
|
145
|
+
suggestion: closest.length > 0
|
|
146
|
+
? `Did you mean \`${closest[0]}\`? Update the reference (or run \`shrk surface list\` for every real command).`
|
|
147
|
+
: 'Update the reference — run `shrk surface list` for every real command.',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
86
151
|
}
|
|
87
|
-
|
|
88
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Why an id could not be CHECKED against a cache-backed registry, or undefined
|
|
154
|
+
* when the registry is loaded and non-empty.
|
|
155
|
+
*
|
|
156
|
+
* A negative answer from an unloaded (or empty) registry proves nothing: every
|
|
157
|
+
* id checked against it would be reported stale, including correct ones — the
|
|
158
|
+
* fastest way to get a check switched off. So it reads as NOT VERIFIED
|
|
159
|
+
* (`unknown`), the same safety net the doc-reference linter uses. A POSITIVE
|
|
160
|
+
* answer is always trusted.
|
|
161
|
+
*/
|
|
162
|
+
function unloadedRegistryReason(inspection, kind) {
|
|
163
|
+
if (!isCacheBackedKind(kind))
|
|
164
|
+
return undefined;
|
|
165
|
+
if (!isReferenceCacheWarm(inspection)) {
|
|
166
|
+
return `the ${kind} registry was not loaded — call warmReferenceRegistries() before building the report (NOT VERIFIED)`;
|
|
167
|
+
}
|
|
168
|
+
if (referenceIdsFor(inspection, kind).length === 0) {
|
|
169
|
+
return `the ${kind} registry is empty — it failed to load, or this repo declares no ${kind}s (NOT VERIFIED)`;
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
89
172
|
}
|
|
90
|
-
|
|
91
|
-
|
|
173
|
+
/**
|
|
174
|
+
* An id-keyed reference, through THE shared resolver. The helper kind reads
|
|
175
|
+
* built-ins AND pack helpers there; a private built-in-only list used to answer
|
|
176
|
+
* here, calling every pack helper stale.
|
|
177
|
+
*/
|
|
178
|
+
function checkRegisteredId(inspection, kind, id, label) {
|
|
179
|
+
if (referenceIdExists(inspection, kind, id)) {
|
|
180
|
+
return { outcome: ReferenceCheckOutcome.Ok, message: `${label} exists: ${id}` };
|
|
181
|
+
}
|
|
182
|
+
const unloaded = unloadedRegistryReason(inspection, kind);
|
|
183
|
+
if (unloaded) {
|
|
184
|
+
return {
|
|
185
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
186
|
+
failure: ReferenceFailure.Unverifiable,
|
|
187
|
+
message: `${label} ${id}: ${unloaded}`,
|
|
188
|
+
suggestion: 'Run through the CLI (`shrk knowledge stale-check`), which loads every registry first.',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return staleId(kind, id);
|
|
92
192
|
}
|
|
93
|
-
|
|
94
|
-
|
|
193
|
+
/** Workspace package name → its directory (relative), per inspection. */
|
|
194
|
+
const WORKSPACE_PACKAGES = new WeakMap();
|
|
195
|
+
function readPackageJson(abs) {
|
|
196
|
+
try {
|
|
197
|
+
return JSON.parse(readFileSync(abs, 'utf8'));
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
95
202
|
}
|
|
96
|
-
|
|
97
|
-
|
|
203
|
+
/** Directories (relative) matching one `workspaces` glob. */
|
|
204
|
+
function expandWorkspaceGlob(projectRoot, pattern) {
|
|
205
|
+
const norm = pattern.replace(/^\.\//, '').replace(/\/+$/, '');
|
|
206
|
+
const segs = norm.split('/');
|
|
207
|
+
const firstGlob = segs.findIndex((s) => /[*?[{]/.test(s));
|
|
208
|
+
if (firstGlob === -1)
|
|
209
|
+
return [norm];
|
|
210
|
+
const maxDepth = segs.includes('**') ? 4 : segs.length - firstGlob;
|
|
211
|
+
const out = [];
|
|
212
|
+
const walk = (rel, depth) => {
|
|
213
|
+
if (depth > maxDepth)
|
|
214
|
+
return;
|
|
215
|
+
let names;
|
|
216
|
+
try {
|
|
217
|
+
names = readdirSync(nodePath.join(projectRoot, rel));
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
for (const n of names) {
|
|
223
|
+
if (n === 'node_modules' || n.startsWith('.'))
|
|
224
|
+
continue;
|
|
225
|
+
const child = rel ? `${rel}/${n}` : n;
|
|
226
|
+
if (!dirExists(projectRoot, child))
|
|
227
|
+
continue;
|
|
228
|
+
// One `workspaces` pattern against one directory — not a gate-plane list.
|
|
229
|
+
if (globToRegex(norm).test(child))
|
|
230
|
+
out.push(child);
|
|
231
|
+
walk(child, depth + 1);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
walk(segs.slice(0, firstGlob).join('/'), 1);
|
|
235
|
+
return out;
|
|
98
236
|
}
|
|
99
|
-
|
|
100
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Every workspace package the root `package.json` declares — the packages its
|
|
239
|
+
* `workspaces` globs reach, by their `name`. This replaces a read of
|
|
240
|
+
* `inspection.packages`, a property the inspection never has (the phantom-cast
|
|
241
|
+
* bug class): it answered "not a package" for every id and fell through to a
|
|
242
|
+
* hard-coded `packages/<name>` layout.
|
|
243
|
+
*/
|
|
244
|
+
function workspacePackages(inspection) {
|
|
245
|
+
const cached = WORKSPACE_PACKAGES.get(inspection);
|
|
246
|
+
if (cached)
|
|
247
|
+
return cached;
|
|
248
|
+
const out = new Map();
|
|
249
|
+
const root = readPackageJson(nodePath.join(inspection.projectRoot, 'package.json'));
|
|
250
|
+
if (root) {
|
|
251
|
+
if (typeof root.name === 'string')
|
|
252
|
+
out.set(root.name, '.');
|
|
253
|
+
const ws = root.workspaces;
|
|
254
|
+
const patterns = Array.isArray(ws)
|
|
255
|
+
? ws
|
|
256
|
+
: Array.isArray(ws?.packages)
|
|
257
|
+
? (ws.packages)
|
|
258
|
+
: [];
|
|
259
|
+
for (const pattern of patterns) {
|
|
260
|
+
if (typeof pattern !== 'string' || pattern.startsWith('!'))
|
|
261
|
+
continue;
|
|
262
|
+
for (const dir of expandWorkspaceGlob(inspection.projectRoot, pattern)) {
|
|
263
|
+
const pkg = readPackageJson(nodePath.join(inspection.projectRoot, dir, 'package.json'));
|
|
264
|
+
if (pkg && typeof pkg.name === 'string' && !out.has(pkg.name))
|
|
265
|
+
out.set(pkg.name, dir);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
WORKSPACE_PACKAGES.set(inspection, out);
|
|
270
|
+
return out;
|
|
101
271
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
272
|
+
function checkPackageReference(inspection, id) {
|
|
273
|
+
const dir = workspacePackages(inspection).get(id);
|
|
274
|
+
if (dir) {
|
|
275
|
+
return { outcome: ReferenceCheckOutcome.Ok, message: `Package exists: ${id} (${dir})` };
|
|
276
|
+
}
|
|
277
|
+
// Last-resort backstop for repos without a `workspaces` field — reported as
|
|
278
|
+
// probable, because a directory name is not a package name.
|
|
279
|
+
const rel = id.startsWith('@') ? (id.split('/')[1] ?? '') : id;
|
|
280
|
+
if (rel && dirExists(inspection.projectRoot, `packages/${rel}`)) {
|
|
281
|
+
return {
|
|
282
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
283
|
+
message: `Package exists (probable): packages/${rel}/ is present, but no workspace package.json declares the name ${id}.`,
|
|
284
|
+
};
|
|
110
285
|
}
|
|
111
|
-
|
|
112
|
-
const rel = id.startsWith('@') ? id.split('/')[1] ?? '' : id;
|
|
113
|
-
return dirExists(inspection.projectRoot, `packages/${rel}`);
|
|
286
|
+
return staleId('package', id);
|
|
114
287
|
}
|
|
115
288
|
function normalizeRel(p) {
|
|
116
289
|
return p.split(/[\\/]/).join('/').replace(/^\.\//, '');
|
|
117
290
|
}
|
|
291
|
+
/** `Foo` for `Foo.bar` / `Foo.prototype.bar`; null for a bare name. */
|
|
292
|
+
function qualifiedOwner(sym) {
|
|
293
|
+
const cleaned = sym.replace(/\.prototype\./g, '.');
|
|
294
|
+
const dot = cleaned.indexOf('.');
|
|
295
|
+
return dot > 0 && dot < cleaned.length - 1 ? cleaned.slice(0, dot) : null;
|
|
296
|
+
}
|
|
118
297
|
function checkSymbolReference(projectRoot, ref, graph) {
|
|
119
298
|
const sym = ref.symbol ?? '';
|
|
120
299
|
if (!sym) {
|
|
121
300
|
return {
|
|
122
|
-
outcome: ReferenceCheckOutcome.
|
|
301
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
123
302
|
confidence: SymbolConfidence.Unknown,
|
|
124
|
-
|
|
303
|
+
failure: ReferenceFailure.Malformed,
|
|
304
|
+
message: 'malformed reference: symbol reference missing required field `symbol`.',
|
|
125
305
|
};
|
|
126
306
|
}
|
|
307
|
+
// `Owner.member`: the graph indexes top-level symbols only, so it answers for
|
|
308
|
+
// the OWNER (moved-detection); the member itself is verified by the AST on
|
|
309
|
+
// the owner's declaring file.
|
|
310
|
+
const owner = qualifiedOwner(sym);
|
|
311
|
+
const lookup = owner ?? sym;
|
|
127
312
|
// Graph-resolved, cross-file path (preferred when a graph is supplied).
|
|
128
313
|
// A single-file AST scan cannot tell a *moved* symbol from a deleted one;
|
|
129
314
|
// the graph's global symbol index can, so consult it first.
|
|
130
315
|
if (graph) {
|
|
131
316
|
const decl = graph
|
|
132
|
-
.findSymbol(
|
|
317
|
+
.findSymbol(lookup)
|
|
133
318
|
.map((n) => n.path)
|
|
134
319
|
.filter((p) => typeof p === 'string' && p.length > 0)
|
|
135
320
|
.map(normalizeRel);
|
|
136
321
|
if (decl.length > 0) {
|
|
137
322
|
if (ref.path) {
|
|
138
323
|
const target = normalizeRel(ref.path);
|
|
139
|
-
if (decl.includes(target)) {
|
|
324
|
+
if (!decl.includes(target)) {
|
|
325
|
+
// Symbol exists but no longer at the pinned file — it moved.
|
|
326
|
+
return {
|
|
327
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
328
|
+
confidence: SymbolConfidence.Missing,
|
|
329
|
+
failure: ReferenceFailure.AnchorMissing,
|
|
330
|
+
message: `Symbol \`${lookup}\` is no longer in ${ref.path}; the graph resolves it to ${decl
|
|
331
|
+
.slice(0, 3)
|
|
332
|
+
.join(', ')}.`,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (!owner && !hasContentAssertion(ref)) {
|
|
140
336
|
return {
|
|
141
337
|
outcome: ReferenceCheckOutcome.Ok,
|
|
142
338
|
confidence: SymbolConfidence.Exact,
|
|
143
339
|
message: `\`${sym}\` resolves to ${ref.path} (graph).`,
|
|
144
340
|
};
|
|
145
341
|
}
|
|
146
|
-
//
|
|
342
|
+
// The owner is at the pinned file; the member (and any content span)
|
|
343
|
+
// is verified by the AST below.
|
|
344
|
+
}
|
|
345
|
+
else if (!owner) {
|
|
346
|
+
// No file pin, but the graph resolved it cross-file.
|
|
147
347
|
return {
|
|
148
|
-
outcome: ReferenceCheckOutcome.
|
|
149
|
-
confidence: SymbolConfidence.
|
|
150
|
-
message:
|
|
151
|
-
|
|
152
|
-
|
|
348
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
349
|
+
confidence: SymbolConfidence.Probable,
|
|
350
|
+
message: `\`${sym}\` resolves via the code graph to ${decl.slice(0, 3).join(', ')}.`,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
else if (decl.length === 1) {
|
|
354
|
+
return resolveSymbolAtPath(projectRoot, decl[0], sym, false);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
return {
|
|
358
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
359
|
+
confidence: SymbolConfidence.Unknown,
|
|
360
|
+
failure: ReferenceFailure.Unverifiable,
|
|
361
|
+
message: `\`${owner}\` is declared in ${decl.length} files (${decl.slice(0, 3).join(', ')}) — pin the path to check \`${sym}\`.`,
|
|
153
362
|
};
|
|
154
363
|
}
|
|
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
364
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
365
|
+
else if (!ref.path) {
|
|
366
|
+
// Absent from the graph. The graph may be partial (locals, unindexed
|
|
367
|
+
// languages), so fall through to the AST/text backstop when a file is
|
|
368
|
+
// pinned; otherwise we cannot verify.
|
|
166
369
|
return {
|
|
167
370
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
168
371
|
confidence: SymbolConfidence.Unknown,
|
|
372
|
+
failure: ReferenceFailure.Unverifiable,
|
|
169
373
|
message: `Symbol reference \`${sym}\` has no file pin and is absent from the code graph; stale-check cannot verify.`,
|
|
170
374
|
};
|
|
171
375
|
}
|
|
172
376
|
}
|
|
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
|
-
}
|
|
377
|
+
if (ref.path)
|
|
378
|
+
return resolveSymbolAtPath(projectRoot, ref.path, sym, true);
|
|
379
|
+
// No file pinned — best-effort confidence is `unknown`.
|
|
380
|
+
return {
|
|
381
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
382
|
+
confidence: SymbolConfidence.Unknown,
|
|
383
|
+
failure: ReferenceFailure.Unverifiable,
|
|
384
|
+
message: `Symbol reference \`${sym}\` has no file pin; stale-check cannot verify (pin it as \`symbol:${sym}@<path>\`).`,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/** Resolve `sym` in one file through the AST index (text-scan backstop). */
|
|
388
|
+
function resolveSymbolAtPath(projectRoot, relPath, sym, pinned) {
|
|
389
|
+
const file = nodePath.join(projectRoot, relPath);
|
|
390
|
+
if (!existsSync(file)) {
|
|
391
|
+
return {
|
|
392
|
+
outcome: ReferenceCheckOutcome.Missing,
|
|
393
|
+
confidence: SymbolConfidence.Missing,
|
|
394
|
+
failure: ReferenceFailure.PathMissing,
|
|
395
|
+
message: `Referenced file does not exist: ${relPath}`,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const via = pinned ? '' : ` (declaring file ${relPath}, from the code graph)`;
|
|
399
|
+
// AST-backed resolution (falls back to text-scan if parse fails).
|
|
400
|
+
try {
|
|
401
|
+
const res = resolveSymbolInFile(file, sym);
|
|
402
|
+
switch (res.resolution) {
|
|
403
|
+
case SymbolResolution.ExactExport:
|
|
404
|
+
case SymbolResolution.ExactLocal:
|
|
405
|
+
case SymbolResolution.ExactReExport:
|
|
406
|
+
case SymbolResolution.ExactMember:
|
|
407
|
+
case SymbolResolution.ExactLocalMember:
|
|
408
|
+
return {
|
|
409
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
410
|
+
confidence: pinned ? SymbolConfidence.Exact : SymbolConfidence.Probable,
|
|
411
|
+
message: res.message + via,
|
|
412
|
+
...(res.span ? { span: res.span } : {}),
|
|
413
|
+
};
|
|
414
|
+
case SymbolResolution.ProbableText:
|
|
415
|
+
return {
|
|
416
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
417
|
+
confidence: SymbolConfidence.Probable,
|
|
418
|
+
message: res.message + via,
|
|
419
|
+
};
|
|
420
|
+
case SymbolResolution.Missing:
|
|
238
421
|
return {
|
|
239
422
|
outcome: ReferenceCheckOutcome.Stale,
|
|
240
423
|
confidence: SymbolConfidence.Missing,
|
|
241
|
-
|
|
424
|
+
failure: ReferenceFailure.AnchorMissing,
|
|
425
|
+
message: res.message + via,
|
|
426
|
+
...(res.suggestedSymbol ? { suggestedSymbol: res.suggestedSymbol } : {}),
|
|
242
427
|
};
|
|
243
|
-
|
|
244
|
-
catch {
|
|
428
|
+
default:
|
|
245
429
|
return {
|
|
246
430
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
247
431
|
confidence: SymbolConfidence.Unknown,
|
|
248
|
-
|
|
432
|
+
failure: ReferenceFailure.Unverifiable,
|
|
433
|
+
message: res.message + via,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
// Fallback to text scan.
|
|
439
|
+
const token = qualifiedOwner(sym) ? sym.slice(sym.lastIndexOf('.') + 1) : sym;
|
|
440
|
+
try {
|
|
441
|
+
const text = readFileSync(file, 'utf8');
|
|
442
|
+
const declRe = new RegExp(`(export\\s+(?:async\\s+)?(?:function|class|interface|enum|type|const|let|var)\\s+|class\\s+|function\\s+)${escapeRe(token)}\\b`);
|
|
443
|
+
if (declRe.test(text)) {
|
|
444
|
+
return {
|
|
445
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
446
|
+
confidence: SymbolConfidence.Exact,
|
|
447
|
+
message: `Found declaration of \`${sym}\` in ${relPath}.`,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (text.includes(token)) {
|
|
451
|
+
return {
|
|
452
|
+
outcome: ReferenceCheckOutcome.Ok,
|
|
453
|
+
confidence: SymbolConfidence.Probable,
|
|
454
|
+
message: `\`${sym}\` appears in ${relPath}, but not as an exported declaration.`,
|
|
249
455
|
};
|
|
250
456
|
}
|
|
457
|
+
return {
|
|
458
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
459
|
+
confidence: SymbolConfidence.Missing,
|
|
460
|
+
failure: ReferenceFailure.AnchorMissing,
|
|
461
|
+
message: `Symbol \`${sym}\` not found in ${relPath}.`,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return {
|
|
466
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
467
|
+
confidence: SymbolConfidence.Unknown,
|
|
468
|
+
failure: ReferenceFailure.Unverifiable,
|
|
469
|
+
message: `Failed to read ${relPath}.`,
|
|
470
|
+
};
|
|
251
471
|
}
|
|
252
472
|
}
|
|
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
473
|
}
|
|
260
474
|
function escapeRe(s) {
|
|
261
475
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
262
476
|
}
|
|
263
|
-
|
|
477
|
+
/**
|
|
478
|
+
* Check one reference: does its target exist — and, when it declares
|
|
479
|
+
* `contains` / `matches` / `count`, does the target still say what the asset
|
|
480
|
+
* claims. Every non-ok result carries a {@link ReferenceFailure}.
|
|
481
|
+
*/
|
|
482
|
+
function checkReference(inspection, ref, graph, excludeDirs = []) {
|
|
483
|
+
const base = checkReferenceTarget(inspection, ref, graph);
|
|
484
|
+
const result = base.outcome === ReferenceCheckOutcome.Ok
|
|
485
|
+
? applyAssertions(inspection.projectRoot, ref, base, excludeDirs)
|
|
486
|
+
: base;
|
|
487
|
+
if (result.outcome === ReferenceCheckOutcome.Ok || result.failure !== undefined)
|
|
488
|
+
return result;
|
|
489
|
+
return { ...result, failure: defaultFailure(ref.kind, result.outcome) };
|
|
490
|
+
}
|
|
491
|
+
/** The failure mode of a non-ok check that did not name one. */
|
|
492
|
+
function defaultFailure(kind, outcome) {
|
|
493
|
+
if (outcome === ReferenceCheckOutcome.Unknown)
|
|
494
|
+
return ReferenceFailure.Unverifiable;
|
|
495
|
+
if (outcome === ReferenceCheckOutcome.Invalid)
|
|
496
|
+
return ReferenceFailure.Malformed;
|
|
497
|
+
if (kind === 'file' || kind === 'directory')
|
|
498
|
+
return ReferenceFailure.PathMissing;
|
|
499
|
+
if (kind === 'symbol')
|
|
500
|
+
return ReferenceFailure.AnchorMissing;
|
|
501
|
+
return ReferenceFailure.IdUnregistered;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Content and count assertions, over a target that EXISTS — a mismatch on a
|
|
505
|
+
* missing file would report one break twice.
|
|
506
|
+
*/
|
|
507
|
+
function applyAssertions(projectRoot, ref, base, excludeDirs) {
|
|
508
|
+
const content = checkReferenceContent(projectRoot, ref, ref.kind === 'symbol' ? base.span : undefined);
|
|
509
|
+
if (content && !content.ok)
|
|
510
|
+
return assertionFailure(base, content, 'content');
|
|
511
|
+
const count = checkReferenceCount(projectRoot, ref, countExcludeDirsFor(ref, excludeDirs));
|
|
512
|
+
if (count && !count.ok)
|
|
513
|
+
return assertionFailure(base, count, 'count');
|
|
514
|
+
const held = [content?.message, count?.message].filter((m) => m !== undefined);
|
|
515
|
+
if (held.length === 0)
|
|
516
|
+
return base;
|
|
517
|
+
return {
|
|
518
|
+
...base,
|
|
519
|
+
message: `${base.message} ${held.join(' ')}`,
|
|
520
|
+
...(count ? { expected: count.expected, actual: count.actual } : {}),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function assertionFailure(base, a, what) {
|
|
524
|
+
const confidence = base.confidence ? { confidence: base.confidence } : {};
|
|
525
|
+
if (a.unverifiable) {
|
|
526
|
+
return {
|
|
527
|
+
outcome: ReferenceCheckOutcome.Unknown,
|
|
528
|
+
...confidence,
|
|
529
|
+
failure: ReferenceFailure.Unverifiable,
|
|
530
|
+
message: `${base.message} Its ${what} assertion could not be evaluated: ${a.message}`,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
535
|
+
...confidence,
|
|
536
|
+
failure: a.failure ?? (what === 'count' ? ReferenceFailure.CountMismatch : ReferenceFailure.ContentMismatch),
|
|
537
|
+
...(a.expected !== undefined ? { expected: a.expected } : {}),
|
|
538
|
+
...(a.actual !== undefined ? { actual: a.actual } : {}),
|
|
539
|
+
message: a.message,
|
|
540
|
+
suggestion: what === 'count'
|
|
541
|
+
? `Update the claim in the entry, then set count.expected: ${String(a.actual)}.`
|
|
542
|
+
: 'Update the entry (and the assertion) to what the code says now.',
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/** Does the reference's TARGET exist? (Assertions are layered on by {@link checkReference}.) */
|
|
546
|
+
function checkReferenceTarget(inspection, ref, graph) {
|
|
264
547
|
const projectRoot = inspection.projectRoot;
|
|
265
548
|
switch (ref.kind) {
|
|
266
549
|
case 'file': {
|
|
@@ -279,6 +562,7 @@ function checkReference(inspection, ref, graph) {
|
|
|
279
562
|
}
|
|
280
563
|
return {
|
|
281
564
|
outcome: ReferenceCheckOutcome.Stale,
|
|
565
|
+
failure: ReferenceFailure.PathMissing,
|
|
282
566
|
message: `Directory missing: ${ref.path}`,
|
|
283
567
|
suggestion: 'Move the directory or update the knowledge reference.',
|
|
284
568
|
};
|
|
@@ -291,98 +575,78 @@ function checkReference(inspection, ref, graph) {
|
|
|
291
575
|
const id = ref.id ?? ref.command ?? '';
|
|
292
576
|
if (!id)
|
|
293
577
|
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
|
-
};
|
|
578
|
+
return checkCommandString(inspection, id, 'Command');
|
|
302
579
|
}
|
|
303
580
|
case 'template': {
|
|
304
581
|
if (!ref.id)
|
|
305
582
|
return missingField('template', 'id');
|
|
306
|
-
|
|
307
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Template exists: ${ref.id}` };
|
|
308
|
-
}
|
|
309
|
-
return staleId('template', ref.id);
|
|
583
|
+
return checkRegisteredId(inspection, 'template', ref.id, 'Template');
|
|
310
584
|
}
|
|
311
585
|
case 'playbook': {
|
|
312
586
|
if (!ref.id)
|
|
313
587
|
return missingField('playbook', 'id');
|
|
314
|
-
|
|
315
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Playbook exists: ${ref.id}` };
|
|
316
|
-
}
|
|
317
|
-
return staleId('playbook', ref.id);
|
|
588
|
+
return checkRegisteredId(inspection, 'playbook', ref.id, 'Playbook');
|
|
318
589
|
}
|
|
319
590
|
case 'construct': {
|
|
320
591
|
if (!ref.id)
|
|
321
592
|
return missingField('construct', 'id');
|
|
322
|
-
|
|
323
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Construct exists: ${ref.id}` };
|
|
324
|
-
}
|
|
325
|
-
return staleId('construct', ref.id);
|
|
593
|
+
return checkRegisteredId(inspection, 'construct', ref.id, 'Construct');
|
|
326
594
|
}
|
|
327
595
|
case 'helper': {
|
|
328
596
|
if (!ref.id)
|
|
329
597
|
return missingField('helper', 'id');
|
|
330
|
-
|
|
331
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Helper exists: ${ref.id}` };
|
|
332
|
-
}
|
|
333
|
-
return staleId('helper', ref.id);
|
|
598
|
+
return checkRegisteredId(inspection, 'helper', ref.id, 'Helper');
|
|
334
599
|
}
|
|
335
600
|
case 'policy': {
|
|
336
601
|
if (!ref.id)
|
|
337
602
|
return missingField('policy', 'id');
|
|
338
|
-
|
|
339
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Policy exists: ${ref.id}` };
|
|
340
|
-
}
|
|
341
|
-
return staleId('policy', ref.id);
|
|
603
|
+
return checkRegisteredId(inspection, 'policy', ref.id, 'Policy');
|
|
342
604
|
}
|
|
343
605
|
case 'boundary-rule': {
|
|
344
606
|
if (!ref.id)
|
|
345
607
|
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);
|
|
608
|
+
return checkRegisteredId(inspection, 'boundary-rule', ref.id, 'Boundary rule');
|
|
350
609
|
}
|
|
351
610
|
case 'path-convention': {
|
|
352
611
|
if (!ref.id)
|
|
353
612
|
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);
|
|
613
|
+
return checkRegisteredId(inspection, 'path-convention', ref.id, 'Path convention');
|
|
358
614
|
}
|
|
359
615
|
case 'package': {
|
|
360
616
|
if (!ref.id)
|
|
361
617
|
return missingField('package', 'id');
|
|
362
|
-
|
|
363
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Package exists: ${ref.id}` };
|
|
364
|
-
}
|
|
365
|
-
return staleId('package', ref.id);
|
|
618
|
+
return checkPackageReference(inspection, ref.id);
|
|
366
619
|
}
|
|
367
620
|
case 'url': {
|
|
368
621
|
// We never fetch URLs. Mark them unknown unless we can resolve to a
|
|
369
622
|
// local docs file.
|
|
370
623
|
return {
|
|
371
624
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
625
|
+
failure: ReferenceFailure.Unverifiable,
|
|
372
626
|
message: 'URL references are not verified (no network).',
|
|
373
627
|
};
|
|
374
628
|
}
|
|
629
|
+
default:
|
|
630
|
+
// A kind outside the vocabulary still loads (the validator reports it as
|
|
631
|
+
// an error); here it is MALFORMED — never a crash, never a silent row.
|
|
632
|
+
return {
|
|
633
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
634
|
+
failure: ReferenceFailure.Malformed,
|
|
635
|
+
message: `malformed reference: unsupported kind "${String(ref.kind)}" — expected one of: ${KNOWLEDGE_REFERENCE_KINDS.join(', ')}.`,
|
|
636
|
+
};
|
|
375
637
|
}
|
|
376
638
|
}
|
|
377
639
|
function missingField(kind, field) {
|
|
378
640
|
return {
|
|
379
|
-
outcome: ReferenceCheckOutcome.
|
|
380
|
-
|
|
641
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
642
|
+
failure: ReferenceFailure.Malformed,
|
|
643
|
+
message: `malformed reference: ${kind} reference missing required field \`${field}\`.`,
|
|
381
644
|
};
|
|
382
645
|
}
|
|
383
646
|
function staleFile(rel) {
|
|
384
647
|
return {
|
|
385
648
|
outcome: ReferenceCheckOutcome.Stale,
|
|
649
|
+
failure: ReferenceFailure.PathMissing,
|
|
386
650
|
message: `File missing: ${rel}`,
|
|
387
651
|
suggestion: 'Restore the file or run `shrk knowledge rename-file <old> <new> --dry-run`.',
|
|
388
652
|
};
|
|
@@ -390,29 +654,237 @@ function staleFile(rel) {
|
|
|
390
654
|
function staleId(kind, id) {
|
|
391
655
|
return {
|
|
392
656
|
outcome: ReferenceCheckOutcome.Stale,
|
|
657
|
+
failure: ReferenceFailure.IdUnregistered,
|
|
393
658
|
message: `${kind} not found: ${id}`,
|
|
394
659
|
suggestion: 'Register the target or remove the reference.',
|
|
395
660
|
};
|
|
396
661
|
}
|
|
397
|
-
function
|
|
662
|
+
function normalizeSlashes(p) {
|
|
663
|
+
return p.split(/[\\/]/).join('/');
|
|
664
|
+
}
|
|
665
|
+
/** `./src/a.ts` / `src\a.ts` / `src/svc/` → `src/a.ts` / `src/svc`. */
|
|
666
|
+
function normalizeScopePath(p) {
|
|
667
|
+
return normalizeSlashes(p).replace(/^(?:\.\/)+/, '').replace(/\/+$/, '');
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* A changed path IS the target, or lies UNDER it — a `directory` reference
|
|
671
|
+
* whose files changed, or a directory deleted wholesale (git lists its files,
|
|
672
|
+
* never the directory itself).
|
|
673
|
+
*/
|
|
674
|
+
function pathTouched(target, changed) {
|
|
675
|
+
if (!target)
|
|
676
|
+
return false;
|
|
677
|
+
const p = normalizeScopePath(target);
|
|
678
|
+
if (p === '' || p === '.')
|
|
679
|
+
return false;
|
|
680
|
+
const under = `${p}/`;
|
|
681
|
+
return changed.some((c) => c === p || c.startsWith(under));
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Is a subject — a knowledge entry, a boundary rule, a policy check — in a
|
|
685
|
+
* changeset's scope? THE one scoping rule for `--changed-only` / `--files`:
|
|
686
|
+
*
|
|
687
|
+
* - the file that DECLARES it changed (its references were added or edited —
|
|
688
|
+
* a typo in a new reference must be checked by the run that introduced it);
|
|
689
|
+
* - a changed path equals a reference / anchor path, or lies under one;
|
|
690
|
+
* - a changed path matches a `count` source glob (the number re-derives from
|
|
691
|
+
* those globs, so a change there can move it with no pinned path changing).
|
|
692
|
+
*
|
|
693
|
+
* `source` is project-relative (`relativeSource`); `(unknown source)` matches
|
|
694
|
+
* nothing.
|
|
695
|
+
*/
|
|
696
|
+
function subjectTouchesChangedFiles(subject, changed) {
|
|
398
697
|
if (changed.length === 0)
|
|
399
698
|
return true;
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
699
|
+
const changedList = changed.map(normalizeScopePath);
|
|
700
|
+
if (changedList.includes(normalizeScopePath(subject.source)))
|
|
701
|
+
return true;
|
|
702
|
+
for (const ref of subject.references ?? []) {
|
|
703
|
+
if (pathTouched(ref.path, changedList))
|
|
704
|
+
return true;
|
|
705
|
+
// The scope the count is MEASURED over (`inspectSource` selects through
|
|
706
|
+
// `globListSelects`), so a change to a file its `!` excludes is no touch.
|
|
707
|
+
const globs = ref.count?.source?.files;
|
|
708
|
+
if (globs && globs.length > 0 && changedList.some((c) => globListSelects(c, globs)))
|
|
403
709
|
return true;
|
|
404
710
|
}
|
|
405
|
-
for (const anchor of
|
|
406
|
-
if (anchor.path
|
|
711
|
+
for (const anchor of subject.anchors ?? []) {
|
|
712
|
+
if (pathTouched(anchor.path, changedList))
|
|
407
713
|
return true;
|
|
408
714
|
}
|
|
409
715
|
return false;
|
|
410
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* The directories a `count` source never walks by default: the sharkcraft dir.
|
|
719
|
+
* Its `.ts` files hold the claims themselves — prose saying `registerService("<name>")`
|
|
720
|
+
* inside a count glob would count itself, and the mismatch hint would tell the
|
|
721
|
+
* author to bake that self-match into `expected` (policy-lint and finish prune
|
|
722
|
+
* the same dir for the same reason).
|
|
723
|
+
*/
|
|
724
|
+
function defaultCountExcludeDirs(inspection) {
|
|
725
|
+
const dir = inspection.sharkcraftDir;
|
|
726
|
+
if (!dir)
|
|
727
|
+
return [];
|
|
728
|
+
const rel = normalizeScopePath(nodePath.relative(inspection.projectRoot, dir));
|
|
729
|
+
return rel && rel !== '.' && !rel.startsWith('..') && !nodePath.isAbsolute(rel) ? [rel] : [];
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* The exclusions ONE count source walks under: an exclusion is dropped when the
|
|
733
|
+
* source's own globs target inside it (`sharkcraft/**` counts there on purpose).
|
|
734
|
+
*/
|
|
735
|
+
function countExcludeDirsFor(ref, excludeDirs) {
|
|
736
|
+
const globs = ref.count?.source?.files ?? [];
|
|
737
|
+
if (excludeDirs.length === 0 || globs.length === 0)
|
|
738
|
+
return excludeDirs;
|
|
739
|
+
const prefixes = globs.filter((g) => !g.startsWith('!')).map((g) => staticGlobPrefix(g));
|
|
740
|
+
return excludeDirs.filter((d) => !prefixes.some((p) => p === d || p.startsWith(`${d}/`)));
|
|
741
|
+
}
|
|
742
|
+
/** Every {@link ReferenceFailure} at 0 — the report always carries the full map. */
|
|
743
|
+
function emptyFailureCounts() {
|
|
744
|
+
const out = {};
|
|
745
|
+
for (const f of Object.values(ReferenceFailure))
|
|
746
|
+
out[f] = 0;
|
|
747
|
+
return out;
|
|
748
|
+
}
|
|
749
|
+
function emptyKindBucket() {
|
|
750
|
+
return { scanned: 0, zeroReferences: 0, referencesChecked: 0, verified: 0, stale: 0, unverifiable: 0 };
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* ok / stale / missing are real checks; unknown and invalid (malformed) proved
|
|
754
|
+
* nothing either way. THE predicate — the stale-check's entry buckets, its
|
|
755
|
+
* verdict fold (`declaredReferenceCoverage`) and the `shrk gate`
|
|
756
|
+
* knowledge-symbol gate all read it.
|
|
757
|
+
*/
|
|
758
|
+
export function isCheckableOutcome(o) {
|
|
759
|
+
return o !== ReferenceCheckOutcome.Unknown && o !== ReferenceCheckOutcome.Invalid;
|
|
760
|
+
}
|
|
761
|
+
function isFailingOutcome(o) {
|
|
762
|
+
return o === ReferenceCheckOutcome.Stale || o === ReferenceCheckOutcome.Missing;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* One subject's bucket from its checks: any failing check makes it stale; any
|
|
766
|
+
* passing one (and no failing) makes it verified; NOTHING checkable makes it
|
|
767
|
+
* unverifiable — never healthy.
|
|
768
|
+
*/
|
|
769
|
+
function classifyChecks(checkable, failing) {
|
|
770
|
+
if (failing > 0)
|
|
771
|
+
return KnowledgeEntryVerdict.Stale;
|
|
772
|
+
if (checkable > 0)
|
|
773
|
+
return KnowledgeEntryVerdict.Verified;
|
|
774
|
+
return KnowledgeEntryVerdict.Unverifiable;
|
|
775
|
+
}
|
|
776
|
+
function bumpBucket(b, declared, verdict) {
|
|
777
|
+
b.scanned += 1;
|
|
778
|
+
if (declared === 0)
|
|
779
|
+
b.zeroReferences += 1;
|
|
780
|
+
b.referencesChecked += declared;
|
|
781
|
+
if (verdict === KnowledgeEntryVerdict.Verified)
|
|
782
|
+
b.verified += 1;
|
|
783
|
+
else if (verdict === KnowledgeEntryVerdict.Stale)
|
|
784
|
+
b.stale += 1;
|
|
785
|
+
else
|
|
786
|
+
b.unverifiable += 1;
|
|
787
|
+
}
|
|
788
|
+
function tallyOutcome(b, outcome) {
|
|
789
|
+
b.checked += 1;
|
|
790
|
+
if (outcome === ReferenceCheckOutcome.Ok)
|
|
791
|
+
b.ok += 1;
|
|
792
|
+
else if (outcome === ReferenceCheckOutcome.Stale)
|
|
793
|
+
b.stale += 1;
|
|
794
|
+
else if (outcome === ReferenceCheckOutcome.Missing)
|
|
795
|
+
b.missing += 1;
|
|
796
|
+
else if (outcome === ReferenceCheckOutcome.Invalid)
|
|
797
|
+
b.invalid += 1;
|
|
798
|
+
else
|
|
799
|
+
b.unknown += 1;
|
|
800
|
+
}
|
|
801
|
+
function relativeSource(projectRoot, origin) {
|
|
802
|
+
if (!origin)
|
|
803
|
+
return '(unknown source)';
|
|
804
|
+
return normalizeSlashes(nodePath.isAbsolute(origin) ? nodePath.relative(projectRoot, origin) : origin);
|
|
805
|
+
}
|
|
806
|
+
function pct1(part, whole) {
|
|
807
|
+
return whole > 0 ? Math.round((part / whole) * 1000) / 10 : 0;
|
|
808
|
+
}
|
|
809
|
+
/** The static directory part of a glob (`apps/legacy/**` → `apps/legacy`); `''` when it starts with a wildcard. */
|
|
810
|
+
function staticGlobPrefix(glob) {
|
|
811
|
+
const segs = normalizeSlashes(glob).replace(/^\.\//, '').split('/');
|
|
812
|
+
const firstGlob = segs.findIndex((s) => /[*?[{]/.test(s));
|
|
813
|
+
return (firstGlob === -1 ? segs : segs.slice(0, firstGlob)).join('/');
|
|
814
|
+
}
|
|
815
|
+
const BACKTICKED_IDENTIFIER = /`([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?)(?:\(\))?`/g;
|
|
816
|
+
const TS_SOURCE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
817
|
+
const PATH_ONLY_ADVISORY_CAP = 5;
|
|
818
|
+
/**
|
|
819
|
+
* The path-only advisory: an entry whose references are all paths, whose prose
|
|
820
|
+
* names (in backticks) a symbol one of its referenced files DECLARES. Renaming
|
|
821
|
+
* that symbol would leave the entry wrong while every reference still resolves
|
|
822
|
+
* — the strong form (`symbol` pinned to the path) catches it. Never gating.
|
|
823
|
+
*/
|
|
824
|
+
function pathOnlyAdvisories(projectRoot, entry, refs, cache) {
|
|
825
|
+
if (refs.length === 0 || !refs.every((r) => r.kind === 'file' || r.kind === 'directory'))
|
|
826
|
+
return [];
|
|
827
|
+
const files = refs
|
|
828
|
+
.filter((r) => r.kind === 'file' && r.path !== undefined && TS_SOURCE.test(r.path))
|
|
829
|
+
.map((r) => r.path);
|
|
830
|
+
if (files.length === 0)
|
|
831
|
+
return [];
|
|
832
|
+
const names = new Set();
|
|
833
|
+
for (const text of [entry.summary ?? '', typeof entry.content === 'string' ? entry.content : '']) {
|
|
834
|
+
for (const m of text.matchAll(BACKTICKED_IDENTIFIER))
|
|
835
|
+
names.add(m[1]);
|
|
836
|
+
}
|
|
837
|
+
const out = [];
|
|
838
|
+
for (const name of names) {
|
|
839
|
+
if (out.length >= PATH_ONLY_ADVISORY_CAP)
|
|
840
|
+
break;
|
|
841
|
+
for (const rel of files) {
|
|
842
|
+
let idx = cache.get(rel);
|
|
843
|
+
if (idx === undefined) {
|
|
844
|
+
const abs = nodePath.join(projectRoot, rel);
|
|
845
|
+
idx = existsSync(abs) ? buildSymbolIndex(abs) : null;
|
|
846
|
+
cache.set(rel, idx);
|
|
847
|
+
}
|
|
848
|
+
if (!idx?.parsed)
|
|
849
|
+
continue;
|
|
850
|
+
const declared = name.includes('.')
|
|
851
|
+
? (idx.members ?? []).some((m) => `${m.owner}.${m.name}` === name)
|
|
852
|
+
: idx.exports.some((e) => e.name === name);
|
|
853
|
+
if (!declared)
|
|
854
|
+
continue;
|
|
855
|
+
out.push({
|
|
856
|
+
code: KnowledgeAdvisoryCode.PathOnlyReference,
|
|
857
|
+
subjectId: entry.id,
|
|
858
|
+
assetKind: ReferenceAssetKind.Knowledge,
|
|
859
|
+
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}' }.`,
|
|
860
|
+
suggestion: { kind: 'symbol', symbol: name, path: rel },
|
|
861
|
+
});
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return out;
|
|
866
|
+
}
|
|
411
867
|
export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
412
868
|
const strategy = options.renameStrategy ?? RenameStrategy.Strict;
|
|
869
|
+
const projectRoot = inspection.projectRoot;
|
|
870
|
+
// Default: never walk the sharkcraft dir — the claims live there and would
|
|
871
|
+
// count themselves (every caller gets this; none has to remember to pass it).
|
|
872
|
+
const excludeDirs = options.excludeDirs ?? defaultCountExcludeDirs(inspection);
|
|
413
873
|
const referenceChecks = [];
|
|
414
874
|
const anchorChecks = [];
|
|
415
|
-
const
|
|
875
|
+
const assetReferenceChecks = [];
|
|
876
|
+
const counts = { ok: 0, stale: 0, missing: 0, unknown: 0, invalid: 0 };
|
|
877
|
+
const failureCounts = emptyFailureCounts();
|
|
878
|
+
const byAssetKind = {
|
|
879
|
+
[ReferenceAssetKind.Knowledge]: emptyKindBucket(),
|
|
880
|
+
[ReferenceAssetKind.BoundaryRule]: emptyKindBucket(),
|
|
881
|
+
[ReferenceAssetKind.Policy]: emptyKindBucket(),
|
|
882
|
+
};
|
|
883
|
+
const byEntryType = {};
|
|
884
|
+
const byReferenceKind = {};
|
|
885
|
+
const entryVerdicts = [];
|
|
886
|
+
const advisories = [];
|
|
887
|
+
const symbolIndexCache = new Map();
|
|
416
888
|
let totalReferences = 0;
|
|
417
889
|
let totalAnchors = 0;
|
|
418
890
|
// Lazy symbol → file index, built on first stale-symbol need.
|
|
@@ -439,108 +911,180 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
439
911
|
dirBasenameIndex = buildBasenameDirIndex(inspection.projectRoot);
|
|
440
912
|
return dirBasenameIndex;
|
|
441
913
|
};
|
|
914
|
+
/**
|
|
915
|
+
* Check ONE declared reference and attach its rename signal — the same path
|
|
916
|
+
* for a knowledge entry, a boundary rule and a policy check, so every asset
|
|
917
|
+
* kind is verified by one checker.
|
|
918
|
+
*/
|
|
919
|
+
const checkOne = (subjectId, ref, assetKind) => {
|
|
920
|
+
const result = checkReference(inspection, ref, options.graph, excludeDirs);
|
|
921
|
+
const outcome = result.outcome;
|
|
922
|
+
const check = {
|
|
923
|
+
entryId: subjectId,
|
|
924
|
+
reference: ref,
|
|
925
|
+
outcome,
|
|
926
|
+
message: result.message,
|
|
927
|
+
...(result.confidence ? { symbolConfidence: result.confidence } : {}),
|
|
928
|
+
...(result.suggestion ? { suggestion: result.suggestion } : {}),
|
|
929
|
+
...(result.failure ? { failure: result.failure } : {}),
|
|
930
|
+
...(result.expected !== undefined ? { expected: result.expected } : {}),
|
|
931
|
+
...(result.actual !== undefined ? { actual: result.actual } : {}),
|
|
932
|
+
...(assetKind ? { assetKind } : {}),
|
|
933
|
+
};
|
|
934
|
+
tallyOutcome((byReferenceKind[ref.kind] ??= { checked: 0, ok: 0, stale: 0, missing: 0, unknown: 0, invalid: 0 }), outcome);
|
|
935
|
+
if (result.failure)
|
|
936
|
+
failureCounts[result.failure] += 1;
|
|
937
|
+
const isStaleOrMissing = outcome === ReferenceCheckOutcome.Stale || outcome === ReferenceCheckOutcome.Missing;
|
|
938
|
+
// A bare MEMBER name: the qualified spelling resolves in the same file, so
|
|
939
|
+
// it is the replacement (`shrk fix --knowledge-stale` applies it).
|
|
940
|
+
if (isStaleOrMissing && result.suggestedSymbol) {
|
|
941
|
+
check.replaceWith = {
|
|
942
|
+
symbol: result.suggestedSymbol,
|
|
943
|
+
rationale: `\`${ref.symbol}\` is a member, not a top-level declaration — \`${result.suggestedSymbol}\` resolves in ${ref.path ?? 'the pinned file'}.`,
|
|
944
|
+
strategy: RenameStrategy.Strict,
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
// Symbol rename detection. Strict mode: emit `replaceWith.path`
|
|
948
|
+
// only for the single unambiguous candidate. Wide mode also
|
|
949
|
+
// emits scored candidate lists for the multi-candidate cases that
|
|
950
|
+
// strict silently drops.
|
|
951
|
+
if (isStaleOrMissing && ref.kind === 'symbol' && ref.symbol && !check.replaceWith) {
|
|
952
|
+
const all = (getSymbolIndex().get(ref.symbol) ?? []).filter((p) => p !== ref.path);
|
|
953
|
+
if (all.length === 1) {
|
|
954
|
+
check.replaceWith = {
|
|
955
|
+
path: all[0],
|
|
956
|
+
rationale: `\`${ref.symbol}\` is exported from \`${all[0]}\` — sole candidate.`,
|
|
957
|
+
strategy: RenameStrategy.Strict,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
else if (strategy === RenameStrategy.Wide && all.length > 1) {
|
|
961
|
+
check.replaceWith = buildWideReplacement({
|
|
962
|
+
stalePath: ref.path ?? '',
|
|
963
|
+
paths: all,
|
|
964
|
+
kindLabel: `symbol \`${ref.symbol}\``,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
// File rename detection (directory move, basename match).
|
|
969
|
+
if (isStaleOrMissing && ref.kind === 'file' && ref.path && !check.replaceWith) {
|
|
970
|
+
const indexed = getFileBasenameIndex();
|
|
971
|
+
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
972
|
+
if (uniq) {
|
|
973
|
+
check.replaceWith = {
|
|
974
|
+
path: uniq,
|
|
975
|
+
rationale: `File basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\` (likely directory rename).`,
|
|
976
|
+
strategy: RenameStrategy.Strict,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
else if (strategy === RenameStrategy.Wide) {
|
|
980
|
+
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
981
|
+
if (all.length > 0) {
|
|
982
|
+
check.replaceWith = buildWideReplacement({
|
|
983
|
+
stalePath: ref.path,
|
|
984
|
+
paths: all,
|
|
985
|
+
kindLabel: `file \`${nodePath.basename(ref.path)}\``,
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
// Directory rename detection.
|
|
991
|
+
if (isStaleOrMissing && ref.kind === 'directory' && ref.path && !check.replaceWith) {
|
|
992
|
+
const indexed = getDirBasenameIndex();
|
|
993
|
+
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
994
|
+
if (uniq) {
|
|
995
|
+
check.replaceWith = {
|
|
996
|
+
path: uniq,
|
|
997
|
+
rationale: `Directory basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\`.`,
|
|
998
|
+
strategy: RenameStrategy.Strict,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
else if (strategy === RenameStrategy.Wide) {
|
|
1002
|
+
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
1003
|
+
if (all.length > 0) {
|
|
1004
|
+
check.replaceWith = buildWideReplacement({
|
|
1005
|
+
stalePath: ref.path,
|
|
1006
|
+
paths: all,
|
|
1007
|
+
kindLabel: `directory \`${nodePath.basename(ref.path)}\``,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return check;
|
|
1013
|
+
};
|
|
442
1014
|
for (const entry of inspection.knowledgeEntries) {
|
|
443
|
-
if (options.changedFiles &&
|
|
1015
|
+
if (options.changedFiles &&
|
|
1016
|
+
!subjectTouchesChangedFiles({
|
|
1017
|
+
source: relativeSource(projectRoot, entry.source?.origin),
|
|
1018
|
+
references: entry.references ?? [],
|
|
1019
|
+
anchors: entry.anchors ?? [],
|
|
1020
|
+
}, options.changedFiles)) {
|
|
444
1021
|
continue;
|
|
445
1022
|
}
|
|
446
|
-
|
|
1023
|
+
const refs = entry.references ?? [];
|
|
1024
|
+
const anchors = entry.anchors ?? [];
|
|
1025
|
+
let checkable = 0;
|
|
1026
|
+
let failing = 0;
|
|
1027
|
+
for (const ref of refs) {
|
|
447
1028
|
totalReferences += 1;
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
if (outcome === ReferenceCheckOutcome.Ok)
|
|
1029
|
+
const check = checkOne(entry.id, ref);
|
|
1030
|
+
if (check.outcome === ReferenceCheckOutcome.Ok)
|
|
451
1031
|
counts.ok += 1;
|
|
452
|
-
else if (outcome === ReferenceCheckOutcome.Stale)
|
|
1032
|
+
else if (check.outcome === ReferenceCheckOutcome.Stale)
|
|
453
1033
|
counts.stale += 1;
|
|
454
|
-
else if (outcome === ReferenceCheckOutcome.Missing)
|
|
1034
|
+
else if (check.outcome === ReferenceCheckOutcome.Missing)
|
|
455
1035
|
counts.missing += 1;
|
|
1036
|
+
else if (check.outcome === ReferenceCheckOutcome.Invalid)
|
|
1037
|
+
counts.invalid += 1;
|
|
456
1038
|
else
|
|
457
1039
|
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
|
-
}
|
|
1040
|
+
if (isCheckableOutcome(check.outcome)) {
|
|
1041
|
+
checkable += 1;
|
|
1042
|
+
if (isFailingOutcome(check.outcome))
|
|
1043
|
+
failing += 1;
|
|
531
1044
|
}
|
|
532
1045
|
referenceChecks.push(check);
|
|
533
1046
|
}
|
|
534
|
-
for (const anchor of
|
|
1047
|
+
for (const anchor of anchors) {
|
|
535
1048
|
totalAnchors += 1;
|
|
536
1049
|
const inspected = checkAnchor(inspection, anchor, options.graph);
|
|
1050
|
+
const failure = inspected.outcome === ReferenceCheckOutcome.Ok
|
|
1051
|
+
? undefined
|
|
1052
|
+
: (inspected.failure ?? defaultFailure(anchor.kind, inspected.outcome));
|
|
1053
|
+
if (failure)
|
|
1054
|
+
failureCounts[failure] += 1;
|
|
1055
|
+
if (isCheckableOutcome(inspected.outcome)) {
|
|
1056
|
+
checkable += 1;
|
|
1057
|
+
if (isFailingOutcome(inspected.outcome))
|
|
1058
|
+
failing += 1;
|
|
1059
|
+
}
|
|
537
1060
|
anchorChecks.push({
|
|
538
1061
|
entryId: entry.id,
|
|
539
1062
|
anchor,
|
|
540
1063
|
outcome: inspected.outcome,
|
|
541
1064
|
message: inspected.message,
|
|
1065
|
+
...(failure ? { failure } : {}),
|
|
542
1066
|
});
|
|
543
1067
|
}
|
|
1068
|
+
const verdict = classifyChecks(checkable, failing);
|
|
1069
|
+
const declared = refs.length + anchors.length;
|
|
1070
|
+
const reason = verdict !== KnowledgeEntryVerdict.Unverifiable
|
|
1071
|
+
? undefined
|
|
1072
|
+
: declared === 0
|
|
1073
|
+
? KnowledgeUnverifiableReason.NoReferences
|
|
1074
|
+
: KnowledgeUnverifiableReason.OnlyUnverifiableReferences;
|
|
1075
|
+
const type = String(entry.type);
|
|
1076
|
+
entryVerdicts.push({
|
|
1077
|
+
entryId: entry.id,
|
|
1078
|
+
verdict,
|
|
1079
|
+
...(reason ? { reason } : {}),
|
|
1080
|
+
source: relativeSource(projectRoot, entry.source?.origin),
|
|
1081
|
+
type,
|
|
1082
|
+
checkable,
|
|
1083
|
+
failing,
|
|
1084
|
+
});
|
|
1085
|
+
bumpBucket(byAssetKind[ReferenceAssetKind.Knowledge], declared, verdict);
|
|
1086
|
+
bumpBucket((byEntryType[type] ??= emptyKindBucket()), declared, verdict);
|
|
1087
|
+
advisories.push(...pathOnlyAdvisories(projectRoot, entry, refs, symbolIndexCache));
|
|
544
1088
|
}
|
|
545
1089
|
// Content-similarity boost: when multiple wide replacements in the
|
|
546
1090
|
// SAME entry name the same candidate path, raise that candidate's score
|
|
@@ -548,6 +1092,129 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
548
1092
|
// followed" case where a single per-reference signal is weak but the
|
|
549
1093
|
// aggregate is strong.
|
|
550
1094
|
applyEntryCorroborationBoost(referenceChecks);
|
|
1095
|
+
// Boundary rules and policy checks declare references too — swept by the
|
|
1096
|
+
// same checker, counted per kind, and NOT folded into the knowledge buckets
|
|
1097
|
+
// (their coverage is reported, not gated by default).
|
|
1098
|
+
const includeAssets = options.includeAssets !== false;
|
|
1099
|
+
const boundaryRules = includeAssets ? (inspection.boundaryRegistry?.list() ?? []) : [];
|
|
1100
|
+
const policyDecls = includeAssets ? listPolicyDeclarations(inspection) : [];
|
|
1101
|
+
const subjects = [
|
|
1102
|
+
...boundaryRules.map((rule) => ({
|
|
1103
|
+
assetKind: ReferenceAssetKind.BoundaryRule,
|
|
1104
|
+
id: rule.id,
|
|
1105
|
+
source: relativeSource(projectRoot, inspection.boundarySources?.get(rule.id)?.file),
|
|
1106
|
+
references: rule.references ?? [],
|
|
1107
|
+
})),
|
|
1108
|
+
...policyDecls.map((p) => ({
|
|
1109
|
+
assetKind: ReferenceAssetKind.Policy,
|
|
1110
|
+
id: p.qualifiedId,
|
|
1111
|
+
source: relativeSource(projectRoot, p.sourceFile),
|
|
1112
|
+
references: p.references ?? [],
|
|
1113
|
+
})),
|
|
1114
|
+
];
|
|
1115
|
+
for (const subject of subjects) {
|
|
1116
|
+
if (options.changedFiles &&
|
|
1117
|
+
!subjectTouchesChangedFiles({ source: subject.source ?? '(unknown source)', references: subject.references }, options.changedFiles)) {
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
let checkable = 0;
|
|
1121
|
+
let failing = 0;
|
|
1122
|
+
for (const ref of subject.references) {
|
|
1123
|
+
const check = checkOne(subject.id, ref, subject.assetKind);
|
|
1124
|
+
if (isCheckableOutcome(check.outcome)) {
|
|
1125
|
+
checkable += 1;
|
|
1126
|
+
if (isFailingOutcome(check.outcome))
|
|
1127
|
+
failing += 1;
|
|
1128
|
+
}
|
|
1129
|
+
assetReferenceChecks.push(check);
|
|
1130
|
+
}
|
|
1131
|
+
bumpBucket(byAssetKind[subject.assetKind], subject.references.length, classifyChecks(checkable, failing));
|
|
1132
|
+
}
|
|
1133
|
+
// IMPLICIT references (boundary rules only — their scope is data): a `from`
|
|
1134
|
+
// glob whose static prefix does not exist governs nothing there. Advisory
|
|
1135
|
+
// unless `--fail-on implicit`; never counted in `failureCounts`. Policy
|
|
1136
|
+
// checks keep their scope inside `evaluate()`, which cannot be read without
|
|
1137
|
+
// running it — that gap is reported, not guessed at.
|
|
1138
|
+
if (!options.changedFiles) {
|
|
1139
|
+
for (const rule of boundaryRules) {
|
|
1140
|
+
for (const glob of rule.from ?? []) {
|
|
1141
|
+
if (glob.startsWith('!'))
|
|
1142
|
+
continue;
|
|
1143
|
+
const prefix = staticGlobPrefix(glob);
|
|
1144
|
+
if (!prefix || fileExists(projectRoot, prefix))
|
|
1145
|
+
continue;
|
|
1146
|
+
const message = `boundary rule ${rule.id} scopes '${glob}' but ${prefix} does not exist — the rule governs nothing there.`;
|
|
1147
|
+
assetReferenceChecks.push({
|
|
1148
|
+
entryId: rule.id,
|
|
1149
|
+
reference: { kind: 'directory', path: prefix, note: `implicit: from '${glob}'` },
|
|
1150
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1151
|
+
failure: ReferenceFailure.PathMissing,
|
|
1152
|
+
implicit: true,
|
|
1153
|
+
assetKind: ReferenceAssetKind.BoundaryRule,
|
|
1154
|
+
message,
|
|
1155
|
+
suggestion: 'Fix or remove the glob (gate it with --fail-on implicit).',
|
|
1156
|
+
});
|
|
1157
|
+
advisories.push({
|
|
1158
|
+
code: KnowledgeAdvisoryCode.ImplicitPathMissing,
|
|
1159
|
+
subjectId: rule.id,
|
|
1160
|
+
assetKind: ReferenceAssetKind.BoundaryRule,
|
|
1161
|
+
message,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
const policySweep = {
|
|
1167
|
+
loaded: isReferenceCacheWarm(inspection),
|
|
1168
|
+
declared: policyDecls.length,
|
|
1169
|
+
withReferences: policyDecls.filter((p) => (p.references?.length ?? 0) > 0).length,
|
|
1170
|
+
};
|
|
1171
|
+
if (policySweep.declared > policySweep.withReferences) {
|
|
1172
|
+
advisories.push({
|
|
1173
|
+
code: KnowledgeAdvisoryCode.PolicyScopeUnverifiable,
|
|
1174
|
+
subjectId: 'policy',
|
|
1175
|
+
assetKind: ReferenceAssetKind.Policy,
|
|
1176
|
+
message: `policy: ${policySweep.declared} declared check(s), ${policySweep.withReferences} with references — the scope of the rest is unverifiable (it lives inside evaluate()).`,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
const verified = entryVerdicts.filter((v) => v.verdict === KnowledgeEntryVerdict.Verified).length;
|
|
1180
|
+
const stale = entryVerdicts.filter((v) => v.verdict === KnowledgeEntryVerdict.Stale).length;
|
|
1181
|
+
const unverifiableIds = entryVerdicts
|
|
1182
|
+
.filter((v) => v.verdict === KnowledgeEntryVerdict.Unverifiable)
|
|
1183
|
+
.map((v) => v.entryId);
|
|
1184
|
+
const entriesInScope = entryVerdicts.length;
|
|
1185
|
+
const coverage = {
|
|
1186
|
+
entriesInScope,
|
|
1187
|
+
verified,
|
|
1188
|
+
stale,
|
|
1189
|
+
unverifiable: unverifiableIds.length,
|
|
1190
|
+
unverifiablePct: pct1(unverifiableIds.length, entriesInScope),
|
|
1191
|
+
referencedRatio: entriesInScope > 0 ? (verified + stale) / entriesInScope : 0,
|
|
1192
|
+
};
|
|
1193
|
+
let age;
|
|
1194
|
+
if (options.staleAfterDays !== undefined) {
|
|
1195
|
+
const asOf = options.asOf ?? todayUtcIso();
|
|
1196
|
+
const inScope = new Set(entryVerdicts.map((v) => v.entryId));
|
|
1197
|
+
const aged = [];
|
|
1198
|
+
const neverVerified = [];
|
|
1199
|
+
for (const entry of inspection.knowledgeEntries) {
|
|
1200
|
+
if (!inScope.has(entry.id))
|
|
1201
|
+
continue;
|
|
1202
|
+
const days = entry.verifiedOn ? verifiedOnAgeDays(entry.verifiedOn, asOf) : undefined;
|
|
1203
|
+
if (days === undefined) {
|
|
1204
|
+
neverVerified.push(entry.id);
|
|
1205
|
+
}
|
|
1206
|
+
else if (days > options.staleAfterDays) {
|
|
1207
|
+
aged.push({
|
|
1208
|
+
entryId: entry.id,
|
|
1209
|
+
verifiedOn: entry.verifiedOn,
|
|
1210
|
+
ageDays: days,
|
|
1211
|
+
source: relativeSource(projectRoot, entry.source?.origin),
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
aged.sort((a, b) => b.ageDays - a.ageDays || a.entryId.localeCompare(b.entryId));
|
|
1216
|
+
age = { asOf, staleAfterDays: options.staleAfterDays, aged, neverVerified };
|
|
1217
|
+
}
|
|
551
1218
|
return {
|
|
552
1219
|
schema: KNOWLEDGE_STALE_SCHEMA,
|
|
553
1220
|
entries: inspection.knowledgeEntries.length,
|
|
@@ -556,6 +1223,18 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
556
1223
|
counts,
|
|
557
1224
|
referenceChecks,
|
|
558
1225
|
anchorChecks,
|
|
1226
|
+
entriesInScope,
|
|
1227
|
+
coverage,
|
|
1228
|
+
entryVerdicts,
|
|
1229
|
+
unverifiableIds,
|
|
1230
|
+
failureCounts,
|
|
1231
|
+
byAssetKind,
|
|
1232
|
+
byEntryType,
|
|
1233
|
+
byReferenceKind,
|
|
1234
|
+
assetReferenceChecks,
|
|
1235
|
+
advisories,
|
|
1236
|
+
policySweep,
|
|
1237
|
+
...(age ? { age } : {}),
|
|
559
1238
|
};
|
|
560
1239
|
}
|
|
561
1240
|
/**
|
|
@@ -872,51 +1551,77 @@ function checkAnchor(inspection, anchor, graph) {
|
|
|
872
1551
|
switch (anchor.kind) {
|
|
873
1552
|
case 'file':
|
|
874
1553
|
if (!anchor.path) {
|
|
875
|
-
return {
|
|
1554
|
+
return {
|
|
1555
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
1556
|
+
failure: ReferenceFailure.Malformed,
|
|
1557
|
+
message: 'malformed anchor: file anchor missing required field `path`.',
|
|
1558
|
+
};
|
|
876
1559
|
}
|
|
877
1560
|
if (fileExists(inspection.projectRoot, anchor.path)) {
|
|
878
1561
|
return { outcome: ReferenceCheckOutcome.Ok, message: `anchor file exists: ${anchor.path}` };
|
|
879
1562
|
}
|
|
880
|
-
return {
|
|
1563
|
+
return {
|
|
1564
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1565
|
+
failure: ReferenceFailure.PathMissing,
|
|
1566
|
+
message: `anchor file missing: ${anchor.path}`,
|
|
1567
|
+
};
|
|
881
1568
|
case 'symbol':
|
|
882
1569
|
return checkSymbolAnchor(inspection, anchor, graph);
|
|
883
|
-
case 'command':
|
|
884
|
-
if (anchor.targetId
|
|
885
|
-
return { outcome: ReferenceCheckOutcome.
|
|
1570
|
+
case 'command': {
|
|
1571
|
+
if (!anchor.targetId) {
|
|
1572
|
+
return { outcome: ReferenceCheckOutcome.Stale, message: 'command anchor unresolved: ?' };
|
|
886
1573
|
}
|
|
887
|
-
|
|
1574
|
+
const r = checkCommandString(inspection, anchor.targetId, 'command anchor');
|
|
1575
|
+
return { outcome: r.outcome, message: r.message };
|
|
1576
|
+
}
|
|
888
1577
|
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 ?? '?'}` };
|
|
1578
|
+
return checkAnchorId(inspection, 'construct', anchor.targetId);
|
|
893
1579
|
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 ?? '?'}` };
|
|
1580
|
+
return checkAnchorId(inspection, 'template', anchor.targetId);
|
|
898
1581
|
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 ?? '?'}` };
|
|
1582
|
+
return checkAnchorId(inspection, 'helper', anchor.targetId);
|
|
903
1583
|
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 ?? '?'}` };
|
|
1584
|
+
return checkAnchorId(inspection, 'playbook', anchor.targetId);
|
|
908
1585
|
case 'policy':
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1586
|
+
return checkAnchorId(inspection, 'policy', anchor.targetId);
|
|
1587
|
+
default:
|
|
1588
|
+
return {
|
|
1589
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
1590
|
+
failure: ReferenceFailure.Malformed,
|
|
1591
|
+
message: `malformed anchor: unsupported anchor kind "${String(anchor.kind)}".`,
|
|
1592
|
+
};
|
|
913
1593
|
}
|
|
914
1594
|
}
|
|
1595
|
+
/**
|
|
1596
|
+
* An id-keyed anchor, through the same guarded resolver as a reference — an
|
|
1597
|
+
* unloaded registry is NOT VERIFIED, never "unresolved".
|
|
1598
|
+
*/
|
|
1599
|
+
function checkAnchorId(inspection, kind, targetId) {
|
|
1600
|
+
if (!targetId) {
|
|
1601
|
+
return {
|
|
1602
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1603
|
+
failure: ReferenceFailure.IdUnregistered,
|
|
1604
|
+
message: `${kind} anchor unresolved: ?`,
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
const r = checkRegisteredId(inspection, kind, targetId, kind);
|
|
1608
|
+
if (r.outcome === ReferenceCheckOutcome.Ok) {
|
|
1609
|
+
return { outcome: ReferenceCheckOutcome.Ok, message: `${kind} exists: ${targetId}` };
|
|
1610
|
+
}
|
|
1611
|
+
if (r.outcome === ReferenceCheckOutcome.Unknown) {
|
|
1612
|
+
return { outcome: r.outcome, message: r.message, failure: ReferenceFailure.Unverifiable };
|
|
1613
|
+
}
|
|
1614
|
+
return {
|
|
1615
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1616
|
+
failure: ReferenceFailure.IdUnregistered,
|
|
1617
|
+
message: `${kind} anchor unresolved: ${targetId}`,
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
915
1620
|
function checkSymbolAnchor(inspection, anchor, graph) {
|
|
916
1621
|
const r = checkSymbolReference(inspection.projectRoot, {
|
|
917
1622
|
kind: 'symbol',
|
|
918
1623
|
symbol: anchor.symbol,
|
|
919
1624
|
...(anchor.path ? { path: anchor.path } : {}),
|
|
920
1625
|
}, graph);
|
|
921
|
-
return { outcome: r.outcome, message: r.message };
|
|
1626
|
+
return { outcome: r.outcome, message: r.message, ...(r.failure ? { failure: r.failure } : {}) };
|
|
922
1627
|
}
|