@event4u/agent-config 14.12.0 → 14.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +118 -155
- package/CONTRIBUTING.md +38 -0
- package/CREDITS.md +135 -0
- package/NOTICE +13 -0
- package/README.md +8 -6
- package/dist/agent-src/commands/analyze/inbox.md +145 -2
- package/dist/agent-src/commands/feature/dev.md +7 -2
- package/dist/agent-src/commands/ghostwriter/write.md +5 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/mission/upgrade.md +1 -1
- package/dist/agent-src/commands/orchestrate.md +2 -2
- package/dist/agent-src/commands/post-as/me.md +5 -1
- package/dist/agent-src/commands/pr/merge.md +19 -6
- package/dist/agent-src/commands/roadmap/process-full.md +19 -5
- package/dist/agent-src/commands/tdd/red.md +12 -2
- package/dist/agent-src/commands/work.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- package/dist/agent-src/rules/code-provenance.md +4 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +4 -0
- package/dist/agent-src/rules/domain-safety-retention.md +4 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
- package/dist/agent-src/rules/improve-before-implement.md +6 -8
- package/dist/agent-src/rules/laravel-routing.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +12 -34
- package/dist/agent-src/rules/symfony-routing.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +14 -0
- package/dist/agent-src/rules/tool-safety.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/dist/agent-src/scripts/guarded_baseline.ts +343 -0
- package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/dist/agent-src/scripts/stub_queue.ts +82 -0
- package/dist/agent-src/scripts/stubs_due.ts +516 -0
- package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
- package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
- package/dist/agent-src/skills/database/SKILL.md +8 -0
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
- package/dist/agent-src/skills/laravel/SKILL.md +35 -0
- package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
- package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
- package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
- package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +97 -3
- package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
- package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/dist/cli/commands/uiAudit.js +124 -5
- package/dist/cli/registry.js +2 -0
- package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
- package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
- package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
- package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
- package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
- package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
- package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
- package/dist/cli-delegate/cmd_adr_effective.js +102 -0
- package/dist/cli-delegate/cmd_conformance.js +80 -11
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +3 -2
- package/dist/cli-delegate/cmd_fleet.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +3 -2
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +3 -2
- package/dist/cli-delegate/cmd_uninstall.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +3 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +58 -58
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +2900 -1049
- package/dist/install/claudeRuleRewrite.js +254 -0
- package/dist/install/detect.js +29 -16
- package/dist/install/detect_php_shape.js +159 -0
- package/dist/install/install.mjs +634 -428
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +3 -3
- package/dist/mcp/server.mjs +4895 -2627
- package/dist/server/schemas/settings.js +3 -0
- package/dist/ui/assets/index-D0jaCRPM.js +29 -0
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/THIRD-PARTY-NOTICES.md +25 -0
- package/docs/contracts/provider-lifecycle.md +1 -1
- package/docs/contracts/settings-classes.md +10 -4
- package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
- package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
- package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
- package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
- package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
- package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
- package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/docs/guidelines/agent-infra/first-principles.md +2 -2
- package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
- package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +10 -10
- package/docs/guidelines/agent-infra/memory-access.md +1 -1
- package/docs/guidelines/agent-infra/mental-models.md +3 -3
- package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
- package/docs/guidelines/agent-infra/role-contracts.md +2 -2
- package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
- package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
- package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
- package/docs/guidelines/agent-infra/six-hats.md +3 -3
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
- package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
- package/docs/guidelines/augment-portability-patterns.md +6 -4
- package/docs/guidelines/code-clarity.md +18 -0
- package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
- package/docs/guidelines/cross-role-handoff.md +2 -2
- package/docs/guidelines/design-fidelity-mechanics.md +1 -1
- package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
- package/docs/guidelines/php/database.md +90 -7
- package/docs/guidelines/prompt-templates.md +7 -7
- package/llms.txt +2 -2
- package/package.json +5 -2
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/src/agent-src/scripts/guarded_baseline.ts +343 -0
- package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/src/agent-src/scripts/stub_queue.ts +82 -0
- package/src/agent-src/scripts/stubs_due.ts +516 -0
- package/src/agent-src/scripts/ticket_bundles.ts +84 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/src/agent-src/templates/scripts/check_memory.ts +258 -17
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/src/config/agent-settings.template.yml +32 -3
- package/src/config/assurance-capability-registry.json +21 -9
- package/src/config/canonical-terms.yml +119 -0
- package/src/config/capability-native-outcome-bars.json +81 -0
- package/src/config/ci-local-parity.yml +80 -0
- package/src/config/drafting-channels.yml +43 -0
- package/src/config/estate-count-budget.json +57 -36
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +729 -63
- package/src/config/gate-reachability-exemptions.json +21 -0
- package/src/config/gate-violation-baselines.json +42 -19
- package/src/config/harness-evolution-budget.json +25 -0
- package/src/config/lapsed-beta-baseline.json +96 -0
- package/src/config/memory-twin-verdicts.yml +129 -0
- package/src/config/metric-registry.yml +124 -0
- package/src/config/pack-size-budget.json +16 -15
- package/src/config/preamble-payload-budget.json +12 -4
- package/src/config/profiles/balanced.ini +27 -0
- package/src/config/profiles/full.ini +27 -0
- package/src/config/profiles/minimal.ini +27 -0
- package/src/config/publish-surface.json +9 -2
- package/src/config/routing-coverage-seed.json +16 -2
- package/src/config/rule-stub-ceilings.json +133 -112
- package/src/config/turnaround-budget.json +66 -0
- package/src/install/claudeRuleRewrite.ts +288 -0
- package/src/install/detect.ts +60 -17
- package/src/install/detect_php_shape.ts +191 -0
- package/src/scripts/_cli/cmd_doctor.ts +27 -112
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/activation_ladder.ts +255 -0
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/activation_states.ts +88 -0
- package/src/scripts/_lib/adherence_detectors.ts +143 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/arith_claims.ts +181 -0
- package/src/scripts/_lib/arm_ranking.ts +62 -0
- package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
- package/src/scripts/_lib/audit_field_provenance.ts +54 -0
- package/src/scripts/_lib/auto_dispatch.ts +32 -3
- package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
- package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/candidate_proposer.ts +402 -0
- package/src/scripts/_lib/candidate_record.ts +601 -0
- package/src/scripts/_lib/capture_rate.ts +319 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/collector_denominator.ts +771 -0
- package/src/scripts/_lib/collector_record.ts +340 -0
- package/src/scripts/_lib/collector_store.ts +664 -0
- package/src/scripts/_lib/collector_supervision.ts +827 -0
- package/src/scripts/_lib/concern_estate.ts +77 -0
- package/src/scripts/_lib/config_chain.ts +477 -0
- package/src/scripts/_lib/config_cost.ts +209 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/council_settings_block.ts +6 -0
- package/src/scripts/_lib/curator_ops.ts +172 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +282 -0
- package/src/scripts/_lib/delivery_criticality.ts +126 -0
- package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
- package/src/scripts/_lib/empty_cycles.ts +88 -0
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/eval_publication.ts +354 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evaluation_vector.ts +283 -0
- package/src/scripts/_lib/evaluator_promotion.ts +225 -0
- package/src/scripts/_lib/evidence_basis.ts +118 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/exec_evidence.ts +8 -0
- package/src/scripts/_lib/experience_card.ts +272 -0
- package/src/scripts/_lib/experience_report.ts +151 -0
- package/src/scripts/_lib/experiment_freeze.ts +112 -0
- package/src/scripts/_lib/generated_by.ts +66 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +239 -0
- package/src/scripts/_lib/hook_effect_probe.ts +141 -0
- package/src/scripts/_lib/host_denominator.ts +687 -0
- package/src/scripts/_lib/host_projection_reach.ts +157 -0
- package/src/scripts/_lib/ignored_blocker.ts +148 -0
- package/src/scripts/_lib/install_reach_checks.ts +495 -0
- package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
- package/src/scripts/_lib/judge_hygiene.ts +228 -0
- package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/loop_guards.ts +299 -0
- package/src/scripts/_lib/loss_class.ts +129 -0
- package/src/scripts/_lib/machine_wake.ts +51 -0
- package/src/scripts/_lib/md_prose_lines.ts +177 -0
- package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
- package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
- package/src/scripts/_lib/orchestration_record.ts +253 -5
- package/src/scripts/_lib/outcome_envelope.ts +227 -0
- package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
- package/src/scripts/_lib/paired_stats.ts +245 -0
- package/src/scripts/_lib/paired_verdict.ts +226 -0
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
- package/src/scripts/_lib/privacy_class.ts +47 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
- package/src/scripts/_lib/release_highlights.ts +70 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/repeated_failure.ts +105 -0
- package/src/scripts/_lib/repo_root.ts +111 -0
- package/src/scripts/_lib/review_skipped_record.ts +37 -0
- package/src/scripts/_lib/role_split.ts +338 -0
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/runtime_journal.ts +1499 -0
- package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/_lib/skill_catalogue.ts +11 -1
- package/src/scripts/_lib/source_digest.ts +200 -0
- package/src/scripts/_lib/source_redact.ts +260 -0
- package/src/scripts/_lib/source_shape.ts +502 -0
- package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
- package/src/scripts/_lib/strip_comments.d.mts +8 -0
- package/src/scripts/_lib/strip_comments.mjs +65 -0
- package/src/scripts/_lib/test_red_state.ts +154 -0
- package/src/scripts/_lib/trigger_shift.ts +144 -0
- package/src/scripts/_lib/untrusted_content.ts +82 -7
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/adoption_report.ts +1 -1
- package/src/scripts/adr/evidence_census.ts +1 -1
- package/src/scripts/adr/regenerate_index.ts +26 -2
- package/src/scripts/adr_cite_check.ts +164 -1
- package/src/scripts/ai_council/blind_review.ts +11 -1
- package/src/scripts/ai_council/clients.ts +4 -0
- package/src/scripts/ai_council/config.ts +3 -0
- package/src/scripts/ai_council/consensus.ts +28 -8
- package/src/scripts/ai_council/consensus_round.ts +226 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/disagreement_signal.ts +338 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/inline_findings.ts +340 -0
- package/src/scripts/ai_council/judge_position_bias.ts +197 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/orchestrator.ts +16 -203
- package/src/scripts/ai_council/orchestrator_results.ts +23 -3
- package/src/scripts/ai_council/pricing.ts +106 -0
- package/src/scripts/ai_council/prompts.ts +74 -7
- package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
- package/src/scripts/ai_council/py_format.ts +14 -3
- package/src/scripts/ai_council/recouncil_guard.ts +293 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/response_record.ts +50 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
- package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
- package/src/scripts/asset_delivery_ledger.ts +157 -0
- package/src/scripts/attest_artifact.ts +193 -0
- package/src/scripts/audit_auto_rules.ts +2 -1
- package/src/scripts/audit_command_surface.ts +1 -1
- package/src/scripts/audit_mcp_tools.ts +2 -2
- package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
- package/src/scripts/audit_user_type_axis.ts +2 -1
- package/src/scripts/bench_ab_clone.ts +215 -10
- package/src/scripts/bench_ab_integrity.ts +119 -5
- package/src/scripts/bench_ab_v2_stats.ts +40 -213
- package/src/scripts/build_archive_index.ts +1 -1
- package/src/scripts/build_linear_digest.ts +1 -1
- package/src/scripts/build_proof.ts +11 -4
- package/src/scripts/build_rule_trigger_matrix.ts +1 -1
- package/src/scripts/build_source_digests.ts +157 -0
- package/src/scripts/cache_realization_report.ts +103 -0
- package/src/scripts/check_adr_frontmatter.ts +131 -10
- package/src/scripts/check_benchmark_report_fields.ts +207 -0
- package/src/scripts/check_beta_review_markers.ts +235 -6
- package/src/scripts/check_bundle_path_leakage.ts +34 -1
- package/src/scripts/check_claims.ts +576 -6
- package/src/scripts/check_composite_arming.ts +35 -0
- package/src/scripts/check_concern_admissions.ts +365 -0
- package/src/scripts/check_enforcement_coverage.ts +28 -0
- package/src/scripts/check_estate_count.ts +347 -25
- package/src/scripts/check_gate_reachability.ts +383 -0
- package/src/scripts/check_host_loadability.ts +104 -0
- package/src/scripts/check_loss_class_declared.ts +308 -0
- package/src/scripts/check_md_language.ts +15 -98
- package/src/scripts/check_memory_proposal.ts +18 -11
- package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
- package/src/scripts/check_no_external_sources.ts +281 -6
- package/src/scripts/check_pack_conformance_fixture.ts +11 -1
- package/src/scripts/check_pack_size.ts +59 -2
- package/src/scripts/check_preamble_payload_budget.ts +114 -3
- package/src/scripts/check_prefix_stable_mutation.ts +372 -0
- package/src/scripts/check_publish_surface.ts +10 -1
- package/src/scripts/check_references.ts +24 -1
- package/src/scripts/check_release_highlights.ts +96 -13
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/check_rule_evidence_declaration.ts +316 -0
- package/src/scripts/check_rule_projection_integrity.ts +14 -4
- package/src/scripts/check_score_contract.ts +26 -3
- package/src/scripts/check_static_parity.ts +285 -0
- package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
- package/src/scripts/check_suppression_hygiene.ts +15 -0
- package/src/scripts/ci_settle.ts +39 -2
- package/src/scripts/code_graph/build.ts +60 -3
- package/src/scripts/code_graph/extract.ts +44 -0
- package/src/scripts/code_graph/types.ts +24 -0
- package/src/scripts/collector_daemon.ts +714 -0
- package/src/scripts/config_cost_report.ts +302 -0
- package/src/scripts/consumer_matrix.ts +74 -1
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/cost/budget.mjs +10 -3
- package/src/scripts/cost/track.mjs +10 -3
- package/src/scripts/council_cli.ts +37 -54
- package/src/scripts/dispatch_r2_reviewer.ts +46 -5
- package/src/scripts/evolution_lab.ts +1153 -0
- package/src/scripts/external_sources_denylist.json +5 -18
- package/src/scripts/extract_audit_patterns.ts +21 -0
- package/src/scripts/fold_intake.ts +12 -1
- package/src/scripts/gen_discovery_baseline.ts +1 -1
- package/src/scripts/generate_capabilities_index.ts +12 -5
- package/src/scripts/generate_capability_matrix.ts +3 -3
- package/src/scripts/generate_command_flows.ts +2 -2
- package/src/scripts/generate_cookbook.ts +2 -2
- package/src/scripts/generate_index.ts +8 -1
- package/src/scripts/generate_knowledge_index.ts +2 -1
- package/src/scripts/generate_ownership_matrix.ts +2 -1
- package/src/scripts/generate_pack_manifests.ts +10 -2
- package/src/scripts/generate_role_experiences_catalog.ts +2 -2
- package/src/scripts/generate_settings_reference.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +22 -1
- package/src/scripts/grade_target_readiness.ts +48 -5
- package/src/scripts/hook_effect_doctor.ts +261 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +125 -16
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
- package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +32 -0
- package/src/scripts/hooks/journal_record_hook.ts +345 -0
- package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
- package/src/scripts/hooks/run_continuation_hook.ts +44 -9
- package/src/scripts/hooks/shims/install.sh +61 -0
- package/src/scripts/hooks/shims/php +101 -0
- package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/hot_context_hook.ts +16 -0
- package/src/scripts/install-hooks.sh +20 -5
- package/src/scripts/install.ts +17 -35
- package/src/scripts/inventory_frontmatter.ts +2 -1
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_adr_sweep_routing.ts +12 -0
- package/src/scripts/lint_canonical_terms.ts +604 -0
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +643 -0
- package/src/scripts/lint_composition_review.ts +619 -0
- package/src/scripts/lint_consolidation_lineage.ts +666 -0
- package/src/scripts/lint_eval_specs.ts +593 -0
- package/src/scripts/lint_hook_manifest.ts +19 -0
- package/src/scripts/lint_host_portability.ts +373 -0
- package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
- package/src/scripts/lint_memory_twin_parity.ts +335 -0
- package/src/scripts/lint_metric_consumers.ts +355 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/lint_skill_descriptions.ts +18 -1
- package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
- package/src/scripts/lint_skill_link_reach.ts +252 -0
- package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
- package/src/scripts/lint_ticket_buildable.ts +17 -1
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
- package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_docs_dead_links.ts +348 -0
- package/src/scripts/measure_host_capture.ts +461 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/migration_status.ts +3 -3
- package/src/scripts/model_rule_injection.ts +76 -0
- package/src/scripts/orchestration_record.ts +14 -0
- package/src/scripts/preamble_byte_census.ts +139 -10
- package/src/scripts/prepack-check.mjs +4 -1
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/probe_turnaround.ts +384 -0
- package/src/scripts/probe_unblocked_ask.ts +279 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +143 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +84 -0
- package/src/scripts/render_benchmark_md.ts +1 -1
- package/src/scripts/rule_activation_census.ts +70 -9
- package/src/scripts/rule_backlinks.ts +1 -1
- package/src/scripts/run_lifecycle_suite.ts +208 -0
- package/src/scripts/run_skill_evals.ts +40 -0
- package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
- package/src/scripts/schemas/drafting-channels.schema.json +43 -0
- package/src/scripts/schemas/evals.schema.json +24 -1
- package/src/scripts/schemas/rule.schema.json +52 -1
- package/src/scripts/schemas/skill.schema.json +17 -2
- package/src/scripts/skill_overlap.ts +7 -1
- package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
- package/src/scripts/skill_usage_report.ts +2 -1
- package/src/scripts/stubs_due.ts +211 -0
- package/src/scripts/sweep_source_surfaces.ts +678 -0
- package/src/scripts/sync_pr_branch.ts +3 -3
- package/src/scripts/trigger_eval_grandfather.json +202 -222
- package/src/scripts/utilization_report.ts +22 -2
- package/src/server/schemas/settings.ts +5 -0
- package/src/templates/marketing-copy.yml +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +0 -26
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install-reach checks for `agent-config doctor`.
|
|
3
|
+
*
|
|
4
|
+
* `road-to-consumer-repo-reality` Phase 1. The existing doctor family answers
|
|
5
|
+
* "is the mechanism I configured working" — hooks resolve, the router parses,
|
|
6
|
+
* the settings cascade returns something, the manifest matches the filesystem.
|
|
7
|
+
* **None of them asks whether a path a root INSTRUCTION FILE names resolves at
|
|
8
|
+
* all**, which is the one question this module adds.
|
|
9
|
+
*
|
|
10
|
+
* The distinction is not academic. `doctor`'s existing `missing` drift category
|
|
11
|
+
* compares MANIFEST entries against disk: it knows what the installer claims it
|
|
12
|
+
* wrote. A root instruction file is a different kind of claim — prose, written
|
|
13
|
+
* by a human or a previous install, describing an agent layer to the model that
|
|
14
|
+
* reads it. When that prose over-reports, every routing decision downstream is
|
|
15
|
+
* made against a layer that is not there and nothing surfaces the fault.
|
|
16
|
+
*
|
|
17
|
+
* THREE OUTCOMES, NEVER TWO. Risk-register rank 4 of the roadmap: "a checker
|
|
18
|
+
* that parses instruction files can misparse a path and report a healthy
|
|
19
|
+
* install as broken, which is worse than not checking". So a path is `present`,
|
|
20
|
+
* `dangling`, or `unresolvable` — and an `unresolvable` path is attributed to
|
|
21
|
+
* THIS CHECKER in its own message, never reported as absent.
|
|
22
|
+
*
|
|
23
|
+
* ALL THREE ARE REPORTS. `doctor` writes nothing and always exits zero on a
|
|
24
|
+
* clean tree; a check here returns a verdict and never a refusal. A gate that
|
|
25
|
+
* wanted to refuse would belong in the gate estate.
|
|
26
|
+
*
|
|
27
|
+
* Extracted into `_lib/` rather than added to `cmd_doctor.ts` for the reason
|
|
28
|
+
* `runtime_wiring_checks.ts` states for itself: that file is ~3,700 lines and
|
|
29
|
+
* `check_source_size_budget` counts every line above 1,500, so checks written
|
|
30
|
+
* inline are charged against a budget while the same code in a new module is
|
|
31
|
+
* free. The seam is also what makes them unit-testable without booting the CLI.
|
|
32
|
+
*/
|
|
33
|
+
import * as fs from 'node:fs';
|
|
34
|
+
import * as path from 'node:path';
|
|
35
|
+
|
|
36
|
+
import type { WiringCheck } from './runtime_wiring_checks.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Root agent-instruction files, as a multi-vendor ecosystem convention rather
|
|
40
|
+
* than this package's own arrangement — which is the roadmap's Phase 1 anchor.
|
|
41
|
+
* `AGENTS.md` is the cross-vendor one; the rest are per-tool root files.
|
|
42
|
+
*
|
|
43
|
+
* Order is stable so a report is diffable. A consumer carrying none of them is
|
|
44
|
+
* not a fault: it is a tree with no root instruction surface, and the check
|
|
45
|
+
* reports `skipped` rather than inventing a finding.
|
|
46
|
+
*/
|
|
47
|
+
export const ROOT_INSTRUCTION_FILES: readonly string[] = [
|
|
48
|
+
'AGENTS.md',
|
|
49
|
+
'CLAUDE.md',
|
|
50
|
+
'GEMINI.md',
|
|
51
|
+
'.github/copilot-instructions.md',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Deliberately NOT read: `.windsurfrules`, `.cursorrules`, `.clinerules`.
|
|
56
|
+
*
|
|
57
|
+
* They are single-file CONCATENATIONS of many rule bodies rather than authored
|
|
58
|
+
* root documents, and that difference decides whether their paths are claims at
|
|
59
|
+
* all. A path inside a concatenated body is relative to the rule's ORIGINAL
|
|
60
|
+
* location, not to the repository root, so resolving it from the root is a
|
|
61
|
+
* category error. Many are not even claims: a rule that enumerates the four
|
|
62
|
+
* candidate locations a resolver searches (`assets/tokens.json`,
|
|
63
|
+
* `resources/tokens.json`, …) is listing possibilities, and at most one exists
|
|
64
|
+
* by design.
|
|
65
|
+
*
|
|
66
|
+
* Measured on this repository before the exclusion: `.windsurfrules` produced 49
|
|
67
|
+
* of 57 "dangling" paths, every one a false positive, while the four authored
|
|
68
|
+
* files above produced 8 — all real. Since these three are ALSO this package's
|
|
69
|
+
* own generated projections, shipped into consumer trees, reading them would
|
|
70
|
+
* have made `doctor` fail for every consumer that installs them.
|
|
71
|
+
*
|
|
72
|
+
* Risk-register rank 4, in its sharpest form: a checker that reports a healthy
|
|
73
|
+
* install as broken is worse than not checking.
|
|
74
|
+
*/
|
|
75
|
+
export const EXCLUDED_CONCATENATIONS: readonly string[] = ['.windsurfrules', '.cursorrules', '.clinerules'];
|
|
76
|
+
|
|
77
|
+
/** One path a root instruction file named, and what resolving it found. */
|
|
78
|
+
export interface NamedPath {
|
|
79
|
+
/** The instruction file the path was read from, repo-relative. */
|
|
80
|
+
source: string;
|
|
81
|
+
/** The path as written in that file. */
|
|
82
|
+
raw: string;
|
|
83
|
+
/** `raw` normalised for filesystem resolution, or null when unresolvable. */
|
|
84
|
+
normalised: string | null;
|
|
85
|
+
outcome: 'present' | 'dangling' | 'unresolvable';
|
|
86
|
+
/** Why the parser could not interpret it. Set only for `unresolvable`. */
|
|
87
|
+
reason?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Paths this checker deliberately does NOT claim to resolve, with the reason
|
|
92
|
+
* stated per class rather than lumped into one bucket. Each is reported as
|
|
93
|
+
* `unresolvable` with its reason, which is the honest answer: the checker
|
|
94
|
+
* cannot tell whether an absolute path or a URL exists in the consumer's world.
|
|
95
|
+
*/
|
|
96
|
+
const UNRESOLVABLE_REASONS: readonly { test: (s: string) => boolean; reason: string }[] = [
|
|
97
|
+
{ test: (s) => /^[a-z][a-z0-9+.-]*:\/\//i.test(s), reason: 'a URL, not a repository path' },
|
|
98
|
+
{ test: (s) => s.startsWith('/'), reason: 'absolute — outside the repository root this checker resolves against' },
|
|
99
|
+
{ test: (s) => s.startsWith('~'), reason: 'home-relative — resolves per machine, not per repository' },
|
|
100
|
+
{ test: (s) => s.includes('*') || s.includes('?') || s.includes('['), reason: 'a glob, which names a set rather than a path' },
|
|
101
|
+
{ test: (s) => s.includes('<') || s.includes('>') || s.includes('{'), reason: 'carries a placeholder, so no single path is named' },
|
|
102
|
+
{ test: (s) => s.startsWith('..'), reason: 'escapes the repository root' },
|
|
103
|
+
{
|
|
104
|
+
// `archive/`, `skipped/` — written inside prose that already established
|
|
105
|
+
// a parent directory. Read from the repository root they are wrong, and
|
|
106
|
+
// reporting them dangling would blame the tree for the checker's missing
|
|
107
|
+
// context. Their parent is knowable to a reader and not to this parser.
|
|
108
|
+
test: (s) => /^[^/]+\/$/.test(s),
|
|
109
|
+
reason: 'a bare directory reference — the prose around it names the parent, this parser does not',
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A repo-relative path as written in markdown prose or a link target.
|
|
115
|
+
*
|
|
116
|
+
* Deliberately conservative. It matches a path only when it carries a directory
|
|
117
|
+
* separator or a known agent-layer directory name, because a bare word in prose
|
|
118
|
+
* is not a path and treating it as one is exactly the false-positive rank 4
|
|
119
|
+
* warns about. Backtick-quoted spans and markdown link targets are the two
|
|
120
|
+
* shapes that actually carry paths in these files.
|
|
121
|
+
*/
|
|
122
|
+
const BACKTICKED = /`([^`\n]+)`/g;
|
|
123
|
+
const LINK_TARGET = /\]\(([^)\s]+)\)/g;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A path claim must be ANCHORED. This is the single most load-bearing decision
|
|
127
|
+
* in the module, and it is the answer to risk-register rank 4: a checker that
|
|
128
|
+
* reports a healthy install as broken is worse than not checking.
|
|
129
|
+
*
|
|
130
|
+
* A first attempt treated a bare agent-layer word (`skills`, `rules`) as a path
|
|
131
|
+
* claim and any token containing a slash as a path. Measured against this
|
|
132
|
+
* repository's own root files, that produced **38 "dangling" paths, and the
|
|
133
|
+
* majority were not paths at all**: `skills` in prose means "the skills layer",
|
|
134
|
+
* not `./skills`, and `event4u/agent-config` is a package name that happens to
|
|
135
|
+
* contain a slash. Both would have been reported as a broken install.
|
|
136
|
+
*
|
|
137
|
+
* Anchored means one of: it starts with `./`, `../`, `/` or `.`; it ends with
|
|
138
|
+
* `/`; it carries a file extension; or its FIRST SEGMENT is a directory that
|
|
139
|
+
* exists in the tree. The last clause is what separates `src/skills` (the
|
|
140
|
+
* segment `src` is there, so a claim about `src/skills` is a claim about this
|
|
141
|
+
* tree) from `event4u/agent-config` (no `event4u` directory, so the token reads
|
|
142
|
+
* as a name). It deliberately does NOT require the whole path to exist — a
|
|
143
|
+
* wholly absent layer under an existing parent, which is the defect this check
|
|
144
|
+
* is for, stays anchored and is reported dangling.
|
|
145
|
+
*/
|
|
146
|
+
const EXTENSION = /\.[A-Za-z0-9]{1,8}$/;
|
|
147
|
+
|
|
148
|
+
function _isAnchored(projectRoot: string, token: string): boolean {
|
|
149
|
+
if (token.startsWith('./') || token.startsWith('../') || token.startsWith('/') || token.startsWith('.')) return true;
|
|
150
|
+
if (token.endsWith('/')) return true;
|
|
151
|
+
if (EXTENSION.test(token)) return true;
|
|
152
|
+
const first = token.split('/')[0];
|
|
153
|
+
if (first === undefined || first === '' || first === token) return false;
|
|
154
|
+
try {
|
|
155
|
+
return fs.statSync(path.join(projectRoot, first)).isDirectory();
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function _looksLikePath(projectRoot: string, token: string): boolean {
|
|
162
|
+
const t = token.trim();
|
|
163
|
+
if (t === '') return false;
|
|
164
|
+
// A shell command, a flag, or prose with spaces is not a path claim.
|
|
165
|
+
if (/\s/.test(t)) return false;
|
|
166
|
+
if (t.startsWith('-')) return false;
|
|
167
|
+
// A token with NO separator is a filename or an identifier, never a
|
|
168
|
+
// repository-relative path — `link_crypto.ts`, `size-enforcement.md` and
|
|
169
|
+
// `pathlib.Path` all reached this function during development and all three
|
|
170
|
+
// would have been reported dangling. A bare name states no location, so
|
|
171
|
+
// there is nothing to resolve and nothing to report.
|
|
172
|
+
if (!t.includes('/')) return false;
|
|
173
|
+
return _isAnchored(projectRoot, t);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Extract every path-shaped token from one instruction file's text. */
|
|
177
|
+
export function extractNamedPaths(projectRoot: string, source: string, text: string): NamedPath[] {
|
|
178
|
+
const seen = new Set<string>();
|
|
179
|
+
const out: NamedPath[] = [];
|
|
180
|
+
for (const re of [BACKTICKED, LINK_TARGET]) {
|
|
181
|
+
re.lastIndex = 0;
|
|
182
|
+
let m: RegExpExecArray | null;
|
|
183
|
+
while ((m = re.exec(text)) !== null) {
|
|
184
|
+
// A backticked span immediately followed by `](` is a link LABEL, and
|
|
185
|
+
// the real path is the target that follows. Reading the label as a
|
|
186
|
+
// claim was the module's largest source of false "dangling" verdicts:
|
|
187
|
+
// the dominant link style in this package's own rule bodies is
|
|
188
|
+
// [`contexts/execution/x.md`](../contexts/execution/x.md), whose label
|
|
189
|
+
// carries an extension, resolves from the repository root, and is not
|
|
190
|
+
// there — while the target, being `../`-relative, is separately and
|
|
191
|
+
// correctly reported unresolvable. 49 of 57 dangling paths in the
|
|
192
|
+
// generated `.windsurfrules` concatenation were this one bug.
|
|
193
|
+
if (re === BACKTICKED && text.slice(m.index + m[0].length, m.index + m[0].length + 2) === '](') continue;
|
|
194
|
+
const raw = (m[1] ?? '').trim();
|
|
195
|
+
if (!_looksLikePath(projectRoot, raw) || seen.has(raw)) continue;
|
|
196
|
+
seen.add(raw);
|
|
197
|
+
out.push({ source, raw, normalised: null, outcome: 'unresolvable' });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Classify one extracted path against the filesystem. */
|
|
204
|
+
export function classifyNamedPath(projectRoot: string, entry: NamedPath): NamedPath {
|
|
205
|
+
for (const { test, reason } of UNRESOLVABLE_REASONS) {
|
|
206
|
+
if (test(entry.raw)) return { ...entry, normalised: null, outcome: 'unresolvable', reason };
|
|
207
|
+
}
|
|
208
|
+
const rel = entry.raw.replace(/^\.\//, '').replace(/\/$/, '');
|
|
209
|
+
if (rel === '') {
|
|
210
|
+
return { ...entry, normalised: null, outcome: 'unresolvable', reason: 'empty after normalisation' };
|
|
211
|
+
}
|
|
212
|
+
const abs = path.join(projectRoot, rel);
|
|
213
|
+
// A path that escapes the root after joining is unresolvable, not absent.
|
|
214
|
+
if (!path.resolve(abs).startsWith(path.resolve(projectRoot))) {
|
|
215
|
+
return { ...entry, normalised: rel, outcome: 'unresolvable', reason: 'escapes the repository root' };
|
|
216
|
+
}
|
|
217
|
+
return { ...entry, normalised: rel, outcome: fs.existsSync(abs) ? 'present' : 'dangling' };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Every path every present root instruction file names, classified. */
|
|
221
|
+
export function resolveInstructionPaths(projectRoot: string): NamedPath[] {
|
|
222
|
+
const out: NamedPath[] = [];
|
|
223
|
+
for (const rel of ROOT_INSTRUCTION_FILES) {
|
|
224
|
+
const abs = path.join(projectRoot, rel);
|
|
225
|
+
let text: string;
|
|
226
|
+
try {
|
|
227
|
+
text = fs.readFileSync(abs, 'utf8');
|
|
228
|
+
} catch {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
for (const entry of extractNamedPaths(projectRoot, rel, text)) {
|
|
232
|
+
out.push(classifyNamedPath(projectRoot, entry));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Step 1.2 — the check that would have caught all three observed trees.
|
|
240
|
+
*
|
|
241
|
+
* `fail` on a dangling path, because a root file describing a layer that is not
|
|
242
|
+
* there is a real defect the consumer can act on. `unresolvable` alone is never
|
|
243
|
+
* a fail: it is this checker declining to guess, and reporting it as a fault
|
|
244
|
+
* would be the checker blaming the tree for its own limit.
|
|
245
|
+
*/
|
|
246
|
+
export function checkInstructionPaths(projectRoot: string): WiringCheck {
|
|
247
|
+
const id = 'instruction-path-reach';
|
|
248
|
+
const named = resolveInstructionPaths(projectRoot);
|
|
249
|
+
if (named.length === 0) {
|
|
250
|
+
return {
|
|
251
|
+
id,
|
|
252
|
+
status: 'skipped',
|
|
253
|
+
message: 'no root instruction file names a repository path — nothing to resolve',
|
|
254
|
+
remedy: '',
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const dangling = named.filter((n) => n.outcome === 'dangling');
|
|
258
|
+
const unresolvable = named.filter((n) => n.outcome === 'unresolvable');
|
|
259
|
+
const present = named.length - dangling.length - unresolvable.length;
|
|
260
|
+
const tail =
|
|
261
|
+
unresolvable.length === 0
|
|
262
|
+
? ''
|
|
263
|
+
: ` ${unresolvable.length} path(s) THIS CHECKER could not interpret, which is not the same as absent: ` +
|
|
264
|
+
unresolvable.map((n) => `${n.source} → ${n.raw} (${n.reason ?? 'no reason recorded'})`).join('; ');
|
|
265
|
+
if (dangling.length === 0) {
|
|
266
|
+
return {
|
|
267
|
+
id,
|
|
268
|
+
status: 'ok',
|
|
269
|
+
message: `${present} instruction-file path(s) resolve.${tail}`,
|
|
270
|
+
remedy: '',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
id,
|
|
275
|
+
status: 'fail',
|
|
276
|
+
message:
|
|
277
|
+
`${dangling.length} path(s) named by a root instruction file do NOT exist: ` +
|
|
278
|
+
dangling.map((n) => `${n.source} → ${n.raw}`).join('; ') +
|
|
279
|
+
`. ${present} resolved.${tail} An instruction file that over-reports is read as fact: ` +
|
|
280
|
+
'every routing decision downstream of a dangling path is made against a layer that is not there. ' +
|
|
281
|
+
'See rule `instruction-path-verification`.',
|
|
282
|
+
remedy: 'install the missing layer, or delete the claim from the instruction file — a path named is a promise',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Step 1.3 — the version axis, as a three-way comparison
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Project settings files that may carry a pin, canonical first.
|
|
292
|
+
*
|
|
293
|
+
* `agent_settings.ts` reads the first two (`project_settings_path` falls back
|
|
294
|
+
* from the canonical `agents/settings/` location to the repo root). The rest are
|
|
295
|
+
* carried here as LEGACY: a consumer tree may still hold one, and the roadmap's
|
|
296
|
+
* requirement is that a legacy filename is "named as legacy rather than
|
|
297
|
+
* ignored". Reading one is not the same as honouring it — the label says which.
|
|
298
|
+
*/
|
|
299
|
+
export const SETTINGS_PIN_FILES: readonly { rel: string; legacy: boolean }[] = [
|
|
300
|
+
{ rel: path.join('agents', 'settings', '.agent-settings.yml'), legacy: false },
|
|
301
|
+
{ rel: '.agent-settings.yml', legacy: true },
|
|
302
|
+
{ rel: '.agent-project-settings.yml', legacy: true },
|
|
303
|
+
{ rel: 'agent-settings.yml', legacy: true },
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
/** A version pin found in a settings file. */
|
|
307
|
+
export interface VersionPin {
|
|
308
|
+
/** Repo-relative file it came from. */
|
|
309
|
+
file: string;
|
|
310
|
+
legacy: boolean;
|
|
311
|
+
version: string;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const PIN_RE = /^\s*(?:agent_config_version|version)\s*:\s*"?([^"'\s]+)"?\s*$/m;
|
|
315
|
+
const LOCK_VERSION_RE = /^\s*agent_config_version\s*:\s*"?([^"\s]+)"?\s*$/m;
|
|
316
|
+
|
|
317
|
+
/** Every pin the tree actually carries, canonical first. */
|
|
318
|
+
export function readVersionPins(projectRoot: string): VersionPin[] {
|
|
319
|
+
const out: VersionPin[] = [];
|
|
320
|
+
for (const { rel, legacy } of SETTINGS_PIN_FILES) {
|
|
321
|
+
let text: string;
|
|
322
|
+
try {
|
|
323
|
+
text = fs.readFileSync(path.join(projectRoot, rel), 'utf8');
|
|
324
|
+
} catch {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const m = PIN_RE.exec(text);
|
|
328
|
+
if (m?.[1] !== undefined) out.push({ file: rel, legacy, version: m[1] });
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** The version recorded by the install that wrote the lockfile. */
|
|
334
|
+
export function readInstalledVersion(projectRoot: string): string | null {
|
|
335
|
+
try {
|
|
336
|
+
const text = fs.readFileSync(path.join(projectRoot, 'agents', 'installed-tools.lock'), 'utf8');
|
|
337
|
+
return LOCK_VERSION_RE.exec(text)?.[1] ?? null;
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Step 1.3 — pinned, installed, and resolvable-now, printed TOGETHER.
|
|
345
|
+
*
|
|
346
|
+
* Printing them together is the whole point: each number alone reads as
|
|
347
|
+
* correct, and only the comparison shows the drift. A pin under a legacy
|
|
348
|
+
* filename is reported WITH the legacy label, never dropped — the observed tree
|
|
349
|
+
* pinned a version several majors behind the installed projection under exactly
|
|
350
|
+
* such a filename, and a checker that skipped the file would have called that
|
|
351
|
+
* tree clean.
|
|
352
|
+
*/
|
|
353
|
+
export function checkVersionAxis(projectRoot: string, resolvableNow: string | null): WiringCheck {
|
|
354
|
+
const id = 'version-axis';
|
|
355
|
+
const pins = readVersionPins(projectRoot);
|
|
356
|
+
const installed = readInstalledVersion(projectRoot);
|
|
357
|
+
if (pins.length === 0 && installed === null) {
|
|
358
|
+
return { id, status: 'skipped', message: 'no version pin and no install lockfile in this tree', remedy: '' };
|
|
359
|
+
}
|
|
360
|
+
const pinText =
|
|
361
|
+
pins.length === 0
|
|
362
|
+
? 'pinned: none'
|
|
363
|
+
: `pinned: ${pins.map((p) => `${p.version} (${p.file}${p.legacy ? ', LEGACY filename' : ''})`).join(' · ')}`;
|
|
364
|
+
const line = `${pinText} · installed: ${installed ?? 'unknown'} · resolvable now: ${resolvableNow ?? 'unknown'}`;
|
|
365
|
+
const divergent = pins.filter((p) => installed !== null && p.version !== installed);
|
|
366
|
+
if (divergent.length === 0) {
|
|
367
|
+
return { id, status: 'ok', message: line, remedy: '' };
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
id,
|
|
371
|
+
status: 'warn',
|
|
372
|
+
message:
|
|
373
|
+
`${line} — ${divergent.length} pin(s) disagree with the installed projection. ` +
|
|
374
|
+
'A pin behind the installed tree is ordinary manifest-versus-lockfile drift, ' +
|
|
375
|
+
'and it is only visible as a comparison: each number alone reads as correct.',
|
|
376
|
+
remedy: 'reconcile the pin with the installed version, or migrate a legacy settings file into the canonical location',
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// Step 5.2 — the local override set, as identity and a count only
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* One overridden shipped artifact: its identity and how many layers carry it.
|
|
386
|
+
*
|
|
387
|
+
* PII-EXCLUSION BY CONSTRUCTION. This type has NO field capable of holding a
|
|
388
|
+
* path, a diff, or any consumer content — the shape `artifact-engagement-recording`
|
|
389
|
+
* § "PII-exclusion-by-construction" requires, where privacy is a property of
|
|
390
|
+
* the schema rather than of a scrubbing pass that could fail. `kind` and `name`
|
|
391
|
+
* are the artifact's identity in THIS package's own namespace, so they carry
|
|
392
|
+
* nothing about the consumer; `layers` is a small integer. Never widen this
|
|
393
|
+
* with a `path`, `source`, `diff`, `notes`, or `extra: unknown` field.
|
|
394
|
+
*/
|
|
395
|
+
export interface OverrideSignal {
|
|
396
|
+
kind: 'rule' | 'skill' | 'command';
|
|
397
|
+
/** The shipped artifact's own name — this package's identifier, not a path. */
|
|
398
|
+
name: string;
|
|
399
|
+
/** How many override layers carry an artifact of this identity. */
|
|
400
|
+
layers: number;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Override layers a consumer install may carry, shallowest first. */
|
|
404
|
+
const OVERRIDE_LAYERS: readonly string[] = [
|
|
405
|
+
path.join('agents', 'overrides'),
|
|
406
|
+
path.join('.agent', 'overrides'),
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
const KIND_DIRS: readonly { dir: string; kind: OverrideSignal['kind'] }[] = [
|
|
410
|
+
{ dir: 'rules', kind: 'rule' },
|
|
411
|
+
{ dir: 'skills', kind: 'skill' },
|
|
412
|
+
{ dir: 'commands', kind: 'command' },
|
|
413
|
+
];
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* The local override set: which shipped artifacts this tree overrides, and in
|
|
417
|
+
* how many layers. A count of 1 is an ordinary local customisation. A count
|
|
418
|
+
* above 1 is the signal — the same artifact overridden independently is the
|
|
419
|
+
* cheapest available evidence that a shipped default is wrong.
|
|
420
|
+
*
|
|
421
|
+
* The signal is READ here and acted on nowhere: turning an aggregate into a
|
|
422
|
+
* proposal is the upstream-contribution path's job, and a single tree cannot
|
|
423
|
+
* clear the generality bar on its own.
|
|
424
|
+
*/
|
|
425
|
+
export function collectOverrideSet(projectRoot: string): OverrideSignal[] {
|
|
426
|
+
const counts = new Map<string, OverrideSignal>();
|
|
427
|
+
for (const layer of OVERRIDE_LAYERS) {
|
|
428
|
+
for (const { dir, kind } of KIND_DIRS) {
|
|
429
|
+
let entries: string[];
|
|
430
|
+
try {
|
|
431
|
+
entries = fs.readdirSync(path.join(projectRoot, layer, dir));
|
|
432
|
+
} catch {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
for (const entry of entries) {
|
|
436
|
+
const name = entry.replace(/\.md$/, '');
|
|
437
|
+
if (name === '' || name.startsWith('.')) continue;
|
|
438
|
+
const key = `${kind}:${name}`;
|
|
439
|
+
const prev = counts.get(key);
|
|
440
|
+
counts.set(key, { kind, name, layers: (prev?.layers ?? 0) + 1 });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return [...counts.values()].sort((a, b) => `${a.kind}:${a.name}`.localeCompare(`${b.kind}:${b.name}`));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Only the repeated ones — a single override is not a signal about a default. */
|
|
448
|
+
export function repeatedOverrides(set: readonly OverrideSignal[]): OverrideSignal[] {
|
|
449
|
+
return set.filter((s) => s.layers > 1);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Step 5.2 — report the local override set inside a flow a consumer already runs. */
|
|
453
|
+
export function checkOverrideSet(projectRoot: string): WiringCheck {
|
|
454
|
+
const id = 'override-set';
|
|
455
|
+
const set = collectOverrideSet(projectRoot);
|
|
456
|
+
if (set.length === 0) {
|
|
457
|
+
return { id, status: 'skipped', message: 'no shipped artifact is overridden in this tree', remedy: '' };
|
|
458
|
+
}
|
|
459
|
+
const repeated = repeatedOverrides(set);
|
|
460
|
+
const names = set.map((s) => `${s.kind}:${s.name}×${s.layers}`).join(' · ');
|
|
461
|
+
if (repeated.length === 0) {
|
|
462
|
+
return {
|
|
463
|
+
id,
|
|
464
|
+
status: 'ok',
|
|
465
|
+
message: `${set.length} shipped artifact(s) overridden locally, each once: ${names}`,
|
|
466
|
+
remedy: '',
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
id,
|
|
471
|
+
status: 'warn',
|
|
472
|
+
message:
|
|
473
|
+
`${repeated.length} shipped artifact(s) overridden in more than one layer: ${names}. ` +
|
|
474
|
+
'A repeatedly overridden default is a candidate for an upstream proposal — but ONE tree is not ' +
|
|
475
|
+
'evidence about a default. It is one input to an aggregate across independent consumers.',
|
|
476
|
+
remedy: 'route it through the upstream-contribution path, never by changing a shipped default on one install',
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ---------------------------------------------------------------------------
|
|
481
|
+
// Wiring — the shape `cmd_doctor` consumes
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
|
|
484
|
+
export const REACH_CHECK_IDS = ['instruction-path-reach', 'version-axis', 'override-set'] as const;
|
|
485
|
+
|
|
486
|
+
export function reachRunners(opts: {
|
|
487
|
+
projectRoot: string;
|
|
488
|
+
resolvableVersion: string | null;
|
|
489
|
+
}): Record<string, () => WiringCheck> {
|
|
490
|
+
return {
|
|
491
|
+
'instruction-path-reach': () => checkInstructionPaths(opts.projectRoot),
|
|
492
|
+
'version-axis': () => checkVersionAxis(opts.projectRoot, opts.resolvableVersion),
|
|
493
|
+
'override-set': () => checkOverrideSet(opts.projectRoot),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find duplicate object keys in a JSON document, with both line numbers.
|
|
3
|
+
*
|
|
4
|
+
* `JSON.parse` silently keeps the LAST value for a repeated key. In an eval
|
|
5
|
+
* specification that is not a cosmetic problem: one case becomes a
|
|
6
|
+
* byte-identical clone of another and the intended fixture is never loaded, so
|
|
7
|
+
* the suite reports the same coverage while measuring less. The file parses,
|
|
8
|
+
* every schema check passes, and nothing downstream can tell.
|
|
9
|
+
*
|
|
10
|
+
* Reporting BOTH positions is the point rather than a nicety. A single
|
|
11
|
+
* "duplicate key `id`" line sends the reader hunting through a file where the
|
|
12
|
+
* word appears fifty times; the pair says which two entries collided.
|
|
13
|
+
*
|
|
14
|
+
* A hand-written scanner rather than a parser dependency: the input is
|
|
15
|
+
* machine-generated-shaped JSON, the property needed is positional, and a
|
|
16
|
+
* dependency that must be installed before a gate can run is a gate that does
|
|
17
|
+
* not run in the environment that matters.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** One collision: the key, and the 1-based lines of the first and later occurrence. */
|
|
21
|
+
export interface DuplicateKey {
|
|
22
|
+
key: string;
|
|
23
|
+
/** Line of the occurrence that JSON.parse discards. */
|
|
24
|
+
firstLine: number;
|
|
25
|
+
/** Line of the occurrence that wins. */
|
|
26
|
+
laterLine: number;
|
|
27
|
+
/** Dotted path of the containing object, `""` for the document root. */
|
|
28
|
+
container: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Frame {
|
|
32
|
+
kind: 'object' | 'array';
|
|
33
|
+
/** Keys seen so far in this object, mapped to the line they appeared on. */
|
|
34
|
+
seen: Map<string, number>;
|
|
35
|
+
path: string;
|
|
36
|
+
/** Index of the next array element, for path construction. */
|
|
37
|
+
index: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Scan a JSON document for repeated keys within the same object.
|
|
42
|
+
*
|
|
43
|
+
* Returns every collision, in document order. An unparseable document returns
|
|
44
|
+
* an empty list rather than throwing — the caller runs `JSON.parse` for that
|
|
45
|
+
* verdict, and a scanner that also reports syntax errors would give two
|
|
46
|
+
* different messages for one defect.
|
|
47
|
+
*/
|
|
48
|
+
export function findDuplicateKeys(source: string): DuplicateKey[] {
|
|
49
|
+
const out: DuplicateKey[] = [];
|
|
50
|
+
const stack: Frame[] = [];
|
|
51
|
+
let line = 1;
|
|
52
|
+
let i = 0;
|
|
53
|
+
// The key most recently read at the current object level, awaiting its `:`.
|
|
54
|
+
let pendingKey: { name: string; line: number } | null = null;
|
|
55
|
+
|
|
56
|
+
const readString = (): { value: string; startLine: number } | null => {
|
|
57
|
+
const startLine = line;
|
|
58
|
+
let value = '';
|
|
59
|
+
i += 1; // opening quote
|
|
60
|
+
while (i < source.length) {
|
|
61
|
+
const ch = source[i];
|
|
62
|
+
if (ch === '\\') {
|
|
63
|
+
// Copy the escape verbatim; the KEY text only needs to be
|
|
64
|
+
// stable, not decoded — two keys that collide do so as written.
|
|
65
|
+
value += ch + (source[i + 1] ?? '');
|
|
66
|
+
if (source[i + 1] === '\n') line += 1;
|
|
67
|
+
i += 2;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === '"') {
|
|
71
|
+
i += 1;
|
|
72
|
+
return { value, startLine };
|
|
73
|
+
}
|
|
74
|
+
if (ch === '\n') line += 1;
|
|
75
|
+
value += ch;
|
|
76
|
+
i += 1;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
while (i < source.length) {
|
|
82
|
+
const ch = source[i];
|
|
83
|
+
if (ch === '\n') {
|
|
84
|
+
line += 1;
|
|
85
|
+
i += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === '"') {
|
|
89
|
+
const str = readString();
|
|
90
|
+
if (str === null) return out; // unterminated — let JSON.parse report it
|
|
91
|
+
const top = stack[stack.length - 1];
|
|
92
|
+
if (top?.kind === 'object' && pendingKey === null) {
|
|
93
|
+
pendingKey = { name: str.value, line: str.startLine };
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (ch === ':') {
|
|
98
|
+
const top = stack[stack.length - 1];
|
|
99
|
+
if (top !== undefined && pendingKey !== null) {
|
|
100
|
+
const prior = top.seen.get(pendingKey.name);
|
|
101
|
+
if (prior !== undefined) {
|
|
102
|
+
out.push({
|
|
103
|
+
key: pendingKey.name,
|
|
104
|
+
firstLine: prior,
|
|
105
|
+
laterLine: pendingKey.line,
|
|
106
|
+
container: top.path,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
top.seen.set(pendingKey.name, pendingKey.line);
|
|
110
|
+
}
|
|
111
|
+
i += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (ch === '{' || ch === '[') {
|
|
115
|
+
const parent = stack[stack.length - 1];
|
|
116
|
+
let path = '';
|
|
117
|
+
if (parent === undefined) {
|
|
118
|
+
path = '';
|
|
119
|
+
} else if (parent.kind === 'array') {
|
|
120
|
+
path = `${parent.path}[${String(parent.index)}]`;
|
|
121
|
+
parent.index += 1;
|
|
122
|
+
} else {
|
|
123
|
+
path = pendingKey === null ? parent.path : joinPath(parent.path, pendingKey.name);
|
|
124
|
+
}
|
|
125
|
+
stack.push({ kind: ch === '{' ? 'object' : 'array', seen: new Map(), path, index: 0 });
|
|
126
|
+
pendingKey = null;
|
|
127
|
+
i += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (ch === '}' || ch === ']') {
|
|
131
|
+
stack.pop();
|
|
132
|
+
pendingKey = null;
|
|
133
|
+
i += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (ch === ',') {
|
|
137
|
+
const top = stack[stack.length - 1];
|
|
138
|
+
if (top?.kind === 'array') top.index += 1;
|
|
139
|
+
pendingKey = null;
|
|
140
|
+
i += 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
i += 1;
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function joinPath(parent: string, key: string): string {
|
|
149
|
+
return parent === '' ? key : `${parent}.${key}`;
|
|
150
|
+
}
|