@shrkcrft/inspector 0.1.0-alpha.29 → 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 +40 -0
- package/dist/code-intelligence-doctor.d.ts.map +1 -1
- package/dist/code-intelligence-doctor.js +86 -12
- 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 +80 -0
- package/dist/doc-references.d.ts.map +1 -0
- package/dist/doc-references.js +269 -0
- 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.d.ts.map +1 -1
- package/dist/fuzzy-impact.js +12 -21
- 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 +145 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +176 -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 +1002 -316
- 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 +14 -0
- package/dist/nearest-id.d.ts.map +1 -0
- package/dist/nearest-id.js +12 -0
- 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 +42 -0
- package/dist/policy-registry.d.ts.map +1 -0
- package/dist/policy-registry.js +131 -0
- 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 +15 -12
- 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 +210 -0
- package/dist/reference-registry.d.ts.map +1 -0
- package/dist/reference-registry.js +395 -0
- 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 +258 -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 +1329 -255
- 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 +65 -309
- 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 +47 -1
- package/dist/sharkcraft-inspector.d.ts.map +1 -1
- package/dist/sharkcraft-inspector.js +278 -29
- 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 -318
- 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,18 +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 {
|
|
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";
|
|
16
40
|
import { resolveSymbolInFile, SymbolResolution } from "./symbol-index.js";
|
|
17
41
|
export const KNOWLEDGE_STALE_SCHEMA = 'sharkcraft.knowledge-stale/v1';
|
|
18
42
|
export var ReferenceCheckOutcome;
|
|
@@ -20,7 +44,16 @@ export var ReferenceCheckOutcome;
|
|
|
20
44
|
ReferenceCheckOutcome["Ok"] = "ok";
|
|
21
45
|
ReferenceCheckOutcome["Stale"] = "stale";
|
|
22
46
|
ReferenceCheckOutcome["Missing"] = "missing";
|
|
47
|
+
/** Well-formed, but the check cannot evaluate it (a `url`, an unwarmed registry). */
|
|
23
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";
|
|
24
57
|
})(ReferenceCheckOutcome || (ReferenceCheckOutcome = {}));
|
|
25
58
|
export var SymbolConfidence;
|
|
26
59
|
(function (SymbolConfidence) {
|
|
@@ -64,222 +97,453 @@ function dirExists(projectRoot, rel) {
|
|
|
64
97
|
return false;
|
|
65
98
|
}
|
|
66
99
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
100
|
+
/*
|
|
101
|
+
* Existence checks delegate to the SHARED reference registry.
|
|
102
|
+
*
|
|
103
|
+
* They used to be private copies here, each reading a differently-shaped
|
|
104
|
+
* structural cast off the inspection (`playbookRegistry`, `constructRegistry`,
|
|
105
|
+
* `policyChecks`) — properties nothing ever attached, so those kinds answered
|
|
106
|
+
* "does not exist" for every id including correct ones. An unchecked cast to a
|
|
107
|
+
* hoped-for shape compiles perfectly and fails silently forever; the registry
|
|
108
|
+
* imports the real accessors so the compiler can see a rename.
|
|
109
|
+
*/
|
|
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
|
+
}
|
|
75
150
|
}
|
|
76
|
-
// Fallback — accept anything that looks like a valid `shrk` command.
|
|
77
|
-
return id.startsWith('shrk ') || id.startsWith('bun ');
|
|
78
|
-
}
|
|
79
|
-
function templateExists(inspection, id) {
|
|
80
|
-
return inspection.templates.some((t) => t.id === id);
|
|
81
151
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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)`;
|
|
87
167
|
}
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
function constructExists(inspection, id) {
|
|
91
|
-
const inspectionAny = inspection;
|
|
92
|
-
const reg = inspectionAny.constructRegistry;
|
|
93
|
-
if (reg && typeof reg.list === 'function') {
|
|
94
|
-
return (reg.list() ?? []).some((c) => c.id === id);
|
|
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)`;
|
|
95
170
|
}
|
|
96
|
-
return
|
|
171
|
+
return undefined;
|
|
97
172
|
}
|
|
98
|
-
|
|
99
|
-
|
|
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);
|
|
100
192
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return
|
|
106
|
-
|
|
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
|
+
}
|
|
107
202
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
113
236
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
}
|
|
118
268
|
}
|
|
119
|
-
|
|
269
|
+
WORKSPACE_PACKAGES.set(inspection, out);
|
|
270
|
+
return out;
|
|
120
271
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
};
|
|
129
285
|
}
|
|
130
|
-
|
|
131
|
-
const rel = id.startsWith('@') ? id.split('/')[1] ?? '' : id;
|
|
132
|
-
return dirExists(inspection.projectRoot, `packages/${rel}`);
|
|
286
|
+
return staleId('package', id);
|
|
133
287
|
}
|
|
134
288
|
function normalizeRel(p) {
|
|
135
289
|
return p.split(/[\\/]/).join('/').replace(/^\.\//, '');
|
|
136
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
|
+
}
|
|
137
297
|
function checkSymbolReference(projectRoot, ref, graph) {
|
|
138
298
|
const sym = ref.symbol ?? '';
|
|
139
299
|
if (!sym) {
|
|
140
300
|
return {
|
|
141
|
-
outcome: ReferenceCheckOutcome.
|
|
301
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
142
302
|
confidence: SymbolConfidence.Unknown,
|
|
143
|
-
|
|
303
|
+
failure: ReferenceFailure.Malformed,
|
|
304
|
+
message: 'malformed reference: symbol reference missing required field `symbol`.',
|
|
144
305
|
};
|
|
145
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;
|
|
146
312
|
// Graph-resolved, cross-file path (preferred when a graph is supplied).
|
|
147
313
|
// A single-file AST scan cannot tell a *moved* symbol from a deleted one;
|
|
148
314
|
// the graph's global symbol index can, so consult it first.
|
|
149
315
|
if (graph) {
|
|
150
316
|
const decl = graph
|
|
151
|
-
.findSymbol(
|
|
317
|
+
.findSymbol(lookup)
|
|
152
318
|
.map((n) => n.path)
|
|
153
319
|
.filter((p) => typeof p === 'string' && p.length > 0)
|
|
154
320
|
.map(normalizeRel);
|
|
155
321
|
if (decl.length > 0) {
|
|
156
322
|
if (ref.path) {
|
|
157
323
|
const target = normalizeRel(ref.path);
|
|
158
|
-
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)) {
|
|
159
336
|
return {
|
|
160
337
|
outcome: ReferenceCheckOutcome.Ok,
|
|
161
338
|
confidence: SymbolConfidence.Exact,
|
|
162
339
|
message: `\`${sym}\` resolves to ${ref.path} (graph).`,
|
|
163
340
|
};
|
|
164
341
|
}
|
|
165
|
-
//
|
|
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.
|
|
166
347
|
return {
|
|
167
|
-
outcome: ReferenceCheckOutcome.
|
|
168
|
-
confidence: SymbolConfidence.
|
|
169
|
-
message:
|
|
170
|
-
|
|
171
|
-
|
|
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}\`.`,
|
|
172
362
|
};
|
|
173
363
|
}
|
|
174
|
-
// No file pin, but the graph resolved it cross-file.
|
|
175
|
-
return {
|
|
176
|
-
outcome: ReferenceCheckOutcome.Ok,
|
|
177
|
-
confidence: SymbolConfidence.Probable,
|
|
178
|
-
message: `\`${sym}\` resolves via the code graph to ${decl.slice(0, 3).join(', ')}.`,
|
|
179
|
-
};
|
|
180
364
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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.
|
|
185
369
|
return {
|
|
186
370
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
187
371
|
confidence: SymbolConfidence.Unknown,
|
|
372
|
+
failure: ReferenceFailure.Unverifiable,
|
|
188
373
|
message: `Symbol reference \`${sym}\` has no file pin and is absent from the code graph; stale-check cannot verify.`,
|
|
189
374
|
};
|
|
190
375
|
}
|
|
191
376
|
}
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
// Fallback to text scan.
|
|
240
|
-
try {
|
|
241
|
-
const text = readFileSync(file, 'utf8');
|
|
242
|
-
const declRe = new RegExp(`(export\\s+(?:async\\s+)?(?:function|class|interface|enum|type|const|let|var)\\s+|class\\s+|function\\s+)${escapeRe(sym)}\\b`);
|
|
243
|
-
if (declRe.test(text)) {
|
|
244
|
-
return {
|
|
245
|
-
outcome: ReferenceCheckOutcome.Ok,
|
|
246
|
-
confidence: SymbolConfidence.Exact,
|
|
247
|
-
message: `Found declaration of \`${sym}\` in ${ref.path}.`,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
if (text.includes(sym)) {
|
|
251
|
-
return {
|
|
252
|
-
outcome: ReferenceCheckOutcome.Ok,
|
|
253
|
-
confidence: SymbolConfidence.Probable,
|
|
254
|
-
message: `\`${sym}\` appears in ${ref.path}, but not as an exported declaration.`,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
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:
|
|
257
421
|
return {
|
|
258
422
|
outcome: ReferenceCheckOutcome.Stale,
|
|
259
423
|
confidence: SymbolConfidence.Missing,
|
|
260
|
-
|
|
424
|
+
failure: ReferenceFailure.AnchorMissing,
|
|
425
|
+
message: res.message + via,
|
|
426
|
+
...(res.suggestedSymbol ? { suggestedSymbol: res.suggestedSymbol } : {}),
|
|
261
427
|
};
|
|
262
|
-
|
|
263
|
-
catch {
|
|
428
|
+
default:
|
|
264
429
|
return {
|
|
265
430
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
266
431
|
confidence: SymbolConfidence.Unknown,
|
|
267
|
-
|
|
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}.`,
|
|
268
448
|
};
|
|
269
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.`,
|
|
455
|
+
};
|
|
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
|
+
};
|
|
270
471
|
}
|
|
271
472
|
}
|
|
272
|
-
// No file pinned — best-effort confidence is `unknown`.
|
|
273
|
-
return {
|
|
274
|
-
outcome: ReferenceCheckOutcome.Unknown,
|
|
275
|
-
confidence: SymbolConfidence.Unknown,
|
|
276
|
-
message: `Symbol reference \`${sym}\` has no file pin; stale-check cannot verify.`,
|
|
277
|
-
};
|
|
278
473
|
}
|
|
279
474
|
function escapeRe(s) {
|
|
280
475
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
281
476
|
}
|
|
282
|
-
|
|
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) {
|
|
283
547
|
const projectRoot = inspection.projectRoot;
|
|
284
548
|
switch (ref.kind) {
|
|
285
549
|
case 'file': {
|
|
@@ -298,6 +562,7 @@ function checkReference(inspection, ref, graph) {
|
|
|
298
562
|
}
|
|
299
563
|
return {
|
|
300
564
|
outcome: ReferenceCheckOutcome.Stale,
|
|
565
|
+
failure: ReferenceFailure.PathMissing,
|
|
301
566
|
message: `Directory missing: ${ref.path}`,
|
|
302
567
|
suggestion: 'Move the directory or update the knowledge reference.',
|
|
303
568
|
};
|
|
@@ -310,98 +575,78 @@ function checkReference(inspection, ref, graph) {
|
|
|
310
575
|
const id = ref.id ?? ref.command ?? '';
|
|
311
576
|
if (!id)
|
|
312
577
|
return missingField('command', 'id or command');
|
|
313
|
-
|
|
314
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Command available: ${id}` };
|
|
315
|
-
}
|
|
316
|
-
return {
|
|
317
|
-
outcome: ReferenceCheckOutcome.Stale,
|
|
318
|
-
message: `Command not registered: ${id}`,
|
|
319
|
-
suggestion: 'Register the command in the command catalog or update the reference.',
|
|
320
|
-
};
|
|
578
|
+
return checkCommandString(inspection, id, 'Command');
|
|
321
579
|
}
|
|
322
580
|
case 'template': {
|
|
323
581
|
if (!ref.id)
|
|
324
582
|
return missingField('template', 'id');
|
|
325
|
-
|
|
326
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Template exists: ${ref.id}` };
|
|
327
|
-
}
|
|
328
|
-
return staleId('template', ref.id);
|
|
583
|
+
return checkRegisteredId(inspection, 'template', ref.id, 'Template');
|
|
329
584
|
}
|
|
330
585
|
case 'playbook': {
|
|
331
586
|
if (!ref.id)
|
|
332
587
|
return missingField('playbook', 'id');
|
|
333
|
-
|
|
334
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Playbook exists: ${ref.id}` };
|
|
335
|
-
}
|
|
336
|
-
return staleId('playbook', ref.id);
|
|
588
|
+
return checkRegisteredId(inspection, 'playbook', ref.id, 'Playbook');
|
|
337
589
|
}
|
|
338
590
|
case 'construct': {
|
|
339
591
|
if (!ref.id)
|
|
340
592
|
return missingField('construct', 'id');
|
|
341
|
-
|
|
342
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Construct exists: ${ref.id}` };
|
|
343
|
-
}
|
|
344
|
-
return staleId('construct', ref.id);
|
|
593
|
+
return checkRegisteredId(inspection, 'construct', ref.id, 'Construct');
|
|
345
594
|
}
|
|
346
595
|
case 'helper': {
|
|
347
596
|
if (!ref.id)
|
|
348
597
|
return missingField('helper', 'id');
|
|
349
|
-
|
|
350
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Helper exists: ${ref.id}` };
|
|
351
|
-
}
|
|
352
|
-
return staleId('helper', ref.id);
|
|
598
|
+
return checkRegisteredId(inspection, 'helper', ref.id, 'Helper');
|
|
353
599
|
}
|
|
354
600
|
case 'policy': {
|
|
355
601
|
if (!ref.id)
|
|
356
602
|
return missingField('policy', 'id');
|
|
357
|
-
|
|
358
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Policy exists: ${ref.id}` };
|
|
359
|
-
}
|
|
360
|
-
return staleId('policy', ref.id);
|
|
603
|
+
return checkRegisteredId(inspection, 'policy', ref.id, 'Policy');
|
|
361
604
|
}
|
|
362
605
|
case 'boundary-rule': {
|
|
363
606
|
if (!ref.id)
|
|
364
607
|
return missingField('boundary-rule', 'id');
|
|
365
|
-
|
|
366
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Boundary rule exists: ${ref.id}` };
|
|
367
|
-
}
|
|
368
|
-
return staleId('boundary-rule', ref.id);
|
|
608
|
+
return checkRegisteredId(inspection, 'boundary-rule', ref.id, 'Boundary rule');
|
|
369
609
|
}
|
|
370
610
|
case 'path-convention': {
|
|
371
611
|
if (!ref.id)
|
|
372
612
|
return missingField('path-convention', 'id');
|
|
373
|
-
|
|
374
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Path convention exists: ${ref.id}` };
|
|
375
|
-
}
|
|
376
|
-
return staleId('path-convention', ref.id);
|
|
613
|
+
return checkRegisteredId(inspection, 'path-convention', ref.id, 'Path convention');
|
|
377
614
|
}
|
|
378
615
|
case 'package': {
|
|
379
616
|
if (!ref.id)
|
|
380
617
|
return missingField('package', 'id');
|
|
381
|
-
|
|
382
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `Package exists: ${ref.id}` };
|
|
383
|
-
}
|
|
384
|
-
return staleId('package', ref.id);
|
|
618
|
+
return checkPackageReference(inspection, ref.id);
|
|
385
619
|
}
|
|
386
620
|
case 'url': {
|
|
387
621
|
// We never fetch URLs. Mark them unknown unless we can resolve to a
|
|
388
622
|
// local docs file.
|
|
389
623
|
return {
|
|
390
624
|
outcome: ReferenceCheckOutcome.Unknown,
|
|
625
|
+
failure: ReferenceFailure.Unverifiable,
|
|
391
626
|
message: 'URL references are not verified (no network).',
|
|
392
627
|
};
|
|
393
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
|
+
};
|
|
394
637
|
}
|
|
395
638
|
}
|
|
396
639
|
function missingField(kind, field) {
|
|
397
640
|
return {
|
|
398
|
-
outcome: ReferenceCheckOutcome.
|
|
399
|
-
|
|
641
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
642
|
+
failure: ReferenceFailure.Malformed,
|
|
643
|
+
message: `malformed reference: ${kind} reference missing required field \`${field}\`.`,
|
|
400
644
|
};
|
|
401
645
|
}
|
|
402
646
|
function staleFile(rel) {
|
|
403
647
|
return {
|
|
404
648
|
outcome: ReferenceCheckOutcome.Stale,
|
|
649
|
+
failure: ReferenceFailure.PathMissing,
|
|
405
650
|
message: `File missing: ${rel}`,
|
|
406
651
|
suggestion: 'Restore the file or run `shrk knowledge rename-file <old> <new> --dry-run`.',
|
|
407
652
|
};
|
|
@@ -409,29 +654,237 @@ function staleFile(rel) {
|
|
|
409
654
|
function staleId(kind, id) {
|
|
410
655
|
return {
|
|
411
656
|
outcome: ReferenceCheckOutcome.Stale,
|
|
657
|
+
failure: ReferenceFailure.IdUnregistered,
|
|
412
658
|
message: `${kind} not found: ${id}`,
|
|
413
659
|
suggestion: 'Register the target or remove the reference.',
|
|
414
660
|
};
|
|
415
661
|
}
|
|
416
|
-
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) {
|
|
417
697
|
if (changed.length === 0)
|
|
418
698
|
return true;
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
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)))
|
|
422
709
|
return true;
|
|
423
710
|
}
|
|
424
|
-
for (const anchor of
|
|
425
|
-
if (anchor.path
|
|
711
|
+
for (const anchor of subject.anchors ?? []) {
|
|
712
|
+
if (pathTouched(anchor.path, changedList))
|
|
426
713
|
return true;
|
|
427
714
|
}
|
|
428
715
|
return false;
|
|
429
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
|
+
}
|
|
430
867
|
export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
431
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);
|
|
432
873
|
const referenceChecks = [];
|
|
433
874
|
const anchorChecks = [];
|
|
434
|
-
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();
|
|
435
888
|
let totalReferences = 0;
|
|
436
889
|
let totalAnchors = 0;
|
|
437
890
|
// Lazy symbol → file index, built on first stale-symbol need.
|
|
@@ -458,108 +911,180 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
458
911
|
dirBasenameIndex = buildBasenameDirIndex(inspection.projectRoot);
|
|
459
912
|
return dirBasenameIndex;
|
|
460
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
|
+
};
|
|
461
1014
|
for (const entry of inspection.knowledgeEntries) {
|
|
462
|
-
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)) {
|
|
463
1021
|
continue;
|
|
464
1022
|
}
|
|
465
|
-
|
|
1023
|
+
const refs = entry.references ?? [];
|
|
1024
|
+
const anchors = entry.anchors ?? [];
|
|
1025
|
+
let checkable = 0;
|
|
1026
|
+
let failing = 0;
|
|
1027
|
+
for (const ref of refs) {
|
|
466
1028
|
totalReferences += 1;
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
if (outcome === ReferenceCheckOutcome.Ok)
|
|
1029
|
+
const check = checkOne(entry.id, ref);
|
|
1030
|
+
if (check.outcome === ReferenceCheckOutcome.Ok)
|
|
470
1031
|
counts.ok += 1;
|
|
471
|
-
else if (outcome === ReferenceCheckOutcome.Stale)
|
|
1032
|
+
else if (check.outcome === ReferenceCheckOutcome.Stale)
|
|
472
1033
|
counts.stale += 1;
|
|
473
|
-
else if (outcome === ReferenceCheckOutcome.Missing)
|
|
1034
|
+
else if (check.outcome === ReferenceCheckOutcome.Missing)
|
|
474
1035
|
counts.missing += 1;
|
|
1036
|
+
else if (check.outcome === ReferenceCheckOutcome.Invalid)
|
|
1037
|
+
counts.invalid += 1;
|
|
475
1038
|
else
|
|
476
1039
|
counts.unknown += 1;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
message: result.message,
|
|
482
|
-
...(result.confidence ? { symbolConfidence: result.confidence } : {}),
|
|
483
|
-
...(result.suggestion ? { suggestion: result.suggestion } : {}),
|
|
484
|
-
};
|
|
485
|
-
const isStaleOrMissing = outcome === ReferenceCheckOutcome.Stale || outcome === ReferenceCheckOutcome.Missing;
|
|
486
|
-
// Symbol rename detection. Strict mode: emit `replaceWith.path`
|
|
487
|
-
// only for the single unambiguous candidate. Wide mode also
|
|
488
|
-
// emits scored candidate lists for the multi-candidate cases that
|
|
489
|
-
// strict silently drops.
|
|
490
|
-
if (isStaleOrMissing && ref.kind === 'symbol' && ref.symbol) {
|
|
491
|
-
const all = (getSymbolIndex().get(ref.symbol) ?? []).filter((p) => p !== ref.path);
|
|
492
|
-
if (all.length === 1) {
|
|
493
|
-
check.replaceWith = {
|
|
494
|
-
path: all[0],
|
|
495
|
-
rationale: `\`${ref.symbol}\` is exported from \`${all[0]}\` — sole candidate.`,
|
|
496
|
-
strategy: RenameStrategy.Strict,
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
else if (strategy === RenameStrategy.Wide && all.length > 1) {
|
|
500
|
-
check.replaceWith = buildWideReplacement({
|
|
501
|
-
stalePath: ref.path ?? '',
|
|
502
|
-
paths: all,
|
|
503
|
-
kindLabel: `symbol \`${ref.symbol}\``,
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
// File rename detection (directory move, basename match).
|
|
508
|
-
if (isStaleOrMissing && ref.kind === 'file' && ref.path && !check.replaceWith) {
|
|
509
|
-
const indexed = getFileBasenameIndex();
|
|
510
|
-
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
511
|
-
if (uniq) {
|
|
512
|
-
check.replaceWith = {
|
|
513
|
-
path: uniq,
|
|
514
|
-
rationale: `File basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\` (likely directory rename).`,
|
|
515
|
-
strategy: RenameStrategy.Strict,
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
else if (strategy === RenameStrategy.Wide) {
|
|
519
|
-
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
520
|
-
if (all.length > 0) {
|
|
521
|
-
check.replaceWith = buildWideReplacement({
|
|
522
|
-
stalePath: ref.path,
|
|
523
|
-
paths: all,
|
|
524
|
-
kindLabel: `file \`${nodePath.basename(ref.path)}\``,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
// Directory rename detection.
|
|
530
|
-
if (isStaleOrMissing && ref.kind === 'directory' && ref.path && !check.replaceWith) {
|
|
531
|
-
const indexed = getDirBasenameIndex();
|
|
532
|
-
const uniq = pickUniqueRenameCandidate(ref.path, indexed);
|
|
533
|
-
if (uniq) {
|
|
534
|
-
check.replaceWith = {
|
|
535
|
-
path: uniq,
|
|
536
|
-
rationale: `Directory basename \`${nodePath.basename(ref.path)}\` resolves uniquely to \`${uniq}\`.`,
|
|
537
|
-
strategy: RenameStrategy.Strict,
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
else if (strategy === RenameStrategy.Wide) {
|
|
541
|
-
const all = (indexed.get(nodePath.basename(ref.path)) ?? []).filter((p) => p !== ref.path);
|
|
542
|
-
if (all.length > 0) {
|
|
543
|
-
check.replaceWith = buildWideReplacement({
|
|
544
|
-
stalePath: ref.path,
|
|
545
|
-
paths: all,
|
|
546
|
-
kindLabel: `directory \`${nodePath.basename(ref.path)}\``,
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
}
|
|
1040
|
+
if (isCheckableOutcome(check.outcome)) {
|
|
1041
|
+
checkable += 1;
|
|
1042
|
+
if (isFailingOutcome(check.outcome))
|
|
1043
|
+
failing += 1;
|
|
550
1044
|
}
|
|
551
1045
|
referenceChecks.push(check);
|
|
552
1046
|
}
|
|
553
|
-
for (const anchor of
|
|
1047
|
+
for (const anchor of anchors) {
|
|
554
1048
|
totalAnchors += 1;
|
|
555
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
|
+
}
|
|
556
1060
|
anchorChecks.push({
|
|
557
1061
|
entryId: entry.id,
|
|
558
1062
|
anchor,
|
|
559
1063
|
outcome: inspected.outcome,
|
|
560
1064
|
message: inspected.message,
|
|
1065
|
+
...(failure ? { failure } : {}),
|
|
561
1066
|
});
|
|
562
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));
|
|
563
1088
|
}
|
|
564
1089
|
// Content-similarity boost: when multiple wide replacements in the
|
|
565
1090
|
// SAME entry name the same candidate path, raise that candidate's score
|
|
@@ -567,6 +1092,129 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
567
1092
|
// followed" case where a single per-reference signal is weak but the
|
|
568
1093
|
// aggregate is strong.
|
|
569
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
|
+
}
|
|
570
1218
|
return {
|
|
571
1219
|
schema: KNOWLEDGE_STALE_SCHEMA,
|
|
572
1220
|
entries: inspection.knowledgeEntries.length,
|
|
@@ -575,6 +1223,18 @@ export function buildKnowledgeStaleReport(inspection, options = {}) {
|
|
|
575
1223
|
counts,
|
|
576
1224
|
referenceChecks,
|
|
577
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 } : {}),
|
|
578
1238
|
};
|
|
579
1239
|
}
|
|
580
1240
|
/**
|
|
@@ -891,51 +1551,77 @@ function checkAnchor(inspection, anchor, graph) {
|
|
|
891
1551
|
switch (anchor.kind) {
|
|
892
1552
|
case 'file':
|
|
893
1553
|
if (!anchor.path) {
|
|
894
|
-
return {
|
|
1554
|
+
return {
|
|
1555
|
+
outcome: ReferenceCheckOutcome.Invalid,
|
|
1556
|
+
failure: ReferenceFailure.Malformed,
|
|
1557
|
+
message: 'malformed anchor: file anchor missing required field `path`.',
|
|
1558
|
+
};
|
|
895
1559
|
}
|
|
896
1560
|
if (fileExists(inspection.projectRoot, anchor.path)) {
|
|
897
1561
|
return { outcome: ReferenceCheckOutcome.Ok, message: `anchor file exists: ${anchor.path}` };
|
|
898
1562
|
}
|
|
899
|
-
return {
|
|
1563
|
+
return {
|
|
1564
|
+
outcome: ReferenceCheckOutcome.Stale,
|
|
1565
|
+
failure: ReferenceFailure.PathMissing,
|
|
1566
|
+
message: `anchor file missing: ${anchor.path}`,
|
|
1567
|
+
};
|
|
900
1568
|
case 'symbol':
|
|
901
1569
|
return checkSymbolAnchor(inspection, anchor, graph);
|
|
902
|
-
case 'command':
|
|
903
|
-
if (anchor.targetId
|
|
904
|
-
return { outcome: ReferenceCheckOutcome.
|
|
1570
|
+
case 'command': {
|
|
1571
|
+
if (!anchor.targetId) {
|
|
1572
|
+
return { outcome: ReferenceCheckOutcome.Stale, message: 'command anchor unresolved: ?' };
|
|
905
1573
|
}
|
|
906
|
-
|
|
1574
|
+
const r = checkCommandString(inspection, anchor.targetId, 'command anchor');
|
|
1575
|
+
return { outcome: r.outcome, message: r.message };
|
|
1576
|
+
}
|
|
907
1577
|
case 'construct':
|
|
908
|
-
|
|
909
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `construct exists: ${anchor.targetId}` };
|
|
910
|
-
}
|
|
911
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `construct anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1578
|
+
return checkAnchorId(inspection, 'construct', anchor.targetId);
|
|
912
1579
|
case 'template':
|
|
913
|
-
|
|
914
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `template exists: ${anchor.targetId}` };
|
|
915
|
-
}
|
|
916
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `template anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1580
|
+
return checkAnchorId(inspection, 'template', anchor.targetId);
|
|
917
1581
|
case 'helper':
|
|
918
|
-
|
|
919
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `helper exists: ${anchor.targetId}` };
|
|
920
|
-
}
|
|
921
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `helper anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1582
|
+
return checkAnchorId(inspection, 'helper', anchor.targetId);
|
|
922
1583
|
case 'playbook':
|
|
923
|
-
|
|
924
|
-
return { outcome: ReferenceCheckOutcome.Ok, message: `playbook exists: ${anchor.targetId}` };
|
|
925
|
-
}
|
|
926
|
-
return { outcome: ReferenceCheckOutcome.Stale, message: `playbook anchor unresolved: ${anchor.targetId ?? '?'}` };
|
|
1584
|
+
return checkAnchorId(inspection, 'playbook', anchor.targetId);
|
|
927
1585
|
case 'policy':
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
+
};
|
|
932
1593
|
}
|
|
933
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
|
+
}
|
|
934
1620
|
function checkSymbolAnchor(inspection, anchor, graph) {
|
|
935
1621
|
const r = checkSymbolReference(inspection.projectRoot, {
|
|
936
1622
|
kind: 'symbol',
|
|
937
1623
|
symbol: anchor.symbol,
|
|
938
1624
|
...(anchor.path ? { path: anchor.path } : {}),
|
|
939
1625
|
}, graph);
|
|
940
|
-
return { outcome: r.outcome, message: r.message };
|
|
1626
|
+
return { outcome: r.outcome, message: r.message, ...(r.failure ? { failure: r.failure } : {}) };
|
|
941
1627
|
}
|