@event4u/agent-config 14.22.0 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +333 -356
- package/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +8 -7
- package/dist/agent-src/commands/agents/init.md +1 -1
- package/dist/agent-src/commands/agents/user/accept.md +4 -4
- package/dist/agent-src/commands/agents/user/delete.md +2 -2
- package/dist/agent-src/commands/agents/user/init.md +3 -3
- package/dist/agent-src/commands/agents/user/review.md +3 -3
- package/dist/agent-src/commands/agents/user/show.md +3 -3
- package/dist/agent-src/commands/agents/user/update.md +2 -2
- package/dist/agent-src/commands/agents/user.md +6 -6
- package/dist/agent-src/commands/analytics/prune.md +1 -1
- package/dist/agent-src/commands/analytics/show.md +2 -2
- package/dist/agent-src/commands/analyze/decision.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +5 -5
- package/dist/agent-src/commands/analyze/incident.md +3 -3
- package/dist/agent-src/commands/analyze/near-miss.md +3 -3
- package/dist/agent-src/commands/analyze/postmortem.md +4 -4
- package/dist/agent-src/commands/analyze/premortem.md +2 -2
- package/dist/agent-src/commands/analyze.md +2 -2
- package/dist/agent-src/commands/bug/fix.md +1 -1
- package/dist/agent-src/commands/bug/investigate.md +2 -2
- package/dist/agent-src/commands/cost/profile.md +3 -3
- package/dist/agent-src/commands/design.md +1 -1
- package/dist/agent-src/commands/feature/explore.md +1 -1
- package/dist/agent-src/commands/feature/plan.md +2 -2
- package/dist/agent-src/commands/feature/roadmap.md +2 -2
- package/dist/agent-src/commands/ghostwriter/fetch.md +4 -4
- package/dist/agent-src/commands/ghostwriter/list.md +1 -1
- package/dist/agent-src/commands/ghostwriter/show.md +1 -1
- package/dist/agent-src/commands/ghostwriter/write.md +9 -9
- package/dist/agent-src/commands/humanize.md +6 -6
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/judge/solo.md +1 -1
- package/dist/agent-src/commands/judge.md +1 -1
- package/dist/agent-src/commands/knowledge/cross-repo.md +1 -1
- package/dist/agent-src/commands/knowledge/forget.md +1 -1
- package/dist/agent-src/commands/knowledge/ingest.md +2 -2
- package/dist/agent-src/commands/knowledge/list.md +1 -1
- package/dist/agent-src/commands/memory/add.md +3 -3
- package/dist/agent-src/commands/memory/load.md +4 -4
- package/dist/agent-src/commands/memory/mine-session.md +1 -1
- package/dist/agent-src/commands/memory/promote.md +4 -4
- package/dist/agent-src/commands/memory/propose.md +2 -2
- package/dist/agent-src/commands/mode.md +5 -5
- package/dist/agent-src/commands/post-as/ghostwriter.md +3 -3
- package/dist/agent-src/commands/post-as/me.md +7 -7
- package/dist/agent-src/commands/pr/create.md +1 -1
- package/dist/agent-src/commands/pr/merge.md +48 -39
- package/dist/agent-src/commands/profile/activate.md +1 -1
- package/dist/agent-src/commands/profile/show.md +1 -1
- package/dist/agent-src/commands/research/deep.md +2 -2
- package/dist/agent-src/commands/research/report.md +2 -2
- package/dist/agent-src/commands/review/changes.md +1 -1
- package/dist/agent-src/commands/review/routing.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +2 -2
- package/dist/agent-src/commands/roadmap/next.md +9 -1
- package/dist/agent-src/commands/roadmap/process-full.md +2 -2
- package/dist/agent-src/commands/security-audit-config.md +3 -3
- package/dist/agent-src/commands/skill/preview.md +1 -1
- package/dist/agent-src/commands/skills/discover.md +1 -1
- package/dist/agent-src/commands/sync/agent-settings.md +2 -2
- package/dist/agent-src/commands/sync/gitignore/fix.md +1 -1
- package/dist/agent-src/commands/tests/create.md +1 -1
- package/dist/agent-src/commands/tests/e2e-heal.md +1 -1
- package/dist/agent-src/commands/tests/e2e-plan.md +1 -1
- package/dist/agent-src/contexts/authority/kernel-rule-edits.md +6 -6
- package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -2
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +2 -2
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +88 -1
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +1 -1
- package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +1 -1
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +13 -1
- package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +1 -1
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +3 -3
- package/dist/agent-src/contexts/execution/mandated-lines.md +1 -1
- package/dist/agent-src/contexts/execution/non-interactive-contract.md +1 -1
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -1
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +5 -5
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +1 -1
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +1 -1
- package/dist/agent-src/contexts/execution/subagent-routing.md +2 -2
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +1 -1
- package/dist/agent-src/contexts/execution/user-memory-channels.md +3 -3
- package/dist/agent-src/contexts/execution/verification-mechanics.md +1 -1
- package/dist/agent-src/contexts/judges/persona-voice-rubric.md +2 -2
- package/dist/agent-src/guidelines/abstraction-thresholds.md +88 -0
- package/dist/agent-src/guidelines/agent-infra/5w2h-analysis.md +260 -0
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +99 -0
- package/dist/agent-src/guidelines/agent-infra/agent-interaction-and-decision-quality.md +325 -0
- package/dist/agent-src/guidelines/agent-infra/api-cost-levers.md +177 -0
- package/dist/agent-src/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +222 -0
- package/dist/agent-src/guidelines/agent-infra/ask-when-uncertain-demos.md +134 -0
- package/dist/agent-src/guidelines/agent-infra/asking-and-brevity-examples.md +198 -0
- package/dist/agent-src/guidelines/agent-infra/break-glass-usage.md +113 -0
- package/dist/agent-src/guidelines/agent-infra/carve-out-predicates.md +17 -0
- package/dist/agent-src/guidelines/agent-infra/comparison-matrix.md +179 -0
- package/dist/agent-src/guidelines/agent-infra/context-hygiene-mechanics.md +165 -0
- package/dist/agent-src/guidelines/agent-infra/corpus-grounding-authoring.md +91 -0
- package/dist/agent-src/guidelines/agent-infra/critical-thinking.md +156 -0
- package/dist/agent-src/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
- package/dist/agent-src/guidelines/agent-infra/developer-judgment.md +82 -0
- package/dist/agent-src/guidelines/agent-infra/direct-answers-demos.md +142 -0
- package/dist/agent-src/guidelines/agent-infra/domain-adoption-gates.md +112 -0
- package/dist/agent-src/guidelines/agent-infra/domain-eval-anti-pattern.md +53 -0
- package/dist/agent-src/guidelines/agent-infra/domain-pack-architecture.md +56 -0
- package/dist/agent-src/guidelines/agent-infra/downstream-changes-mechanics.md +91 -0
- package/dist/agent-src/guidelines/agent-infra/emphasis-budget.md +45 -0
- package/dist/agent-src/guidelines/agent-infra/engineering-memory-data-format.md +210 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
- package/dist/agent-src/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/dist/agent-src/guidelines/agent-infra/failure-signatures.md +70 -0
- package/dist/agent-src/guidelines/agent-infra/false-green.md +233 -0
- package/dist/agent-src/guidelines/agent-infra/first-principles.md +192 -0
- package/dist/agent-src/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
- package/dist/agent-src/guidelines/agent-infra/frontier-reasoning-operating-profile.md +169 -0
- package/dist/agent-src/guidelines/agent-infra/gate-authoring.md +177 -0
- package/dist/agent-src/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/dist/agent-src/guidelines/agent-infra/installed-tools-manifest.md +142 -0
- package/dist/agent-src/guidelines/agent-infra/inversion-thinking.md +388 -0
- package/dist/agent-src/guidelines/agent-infra/ios-simulator-guide.md +378 -0
- package/dist/agent-src/guidelines/agent-infra/language-and-tone-examples.md +141 -0
- package/dist/agent-src/guidelines/agent-infra/layered-settings.md +317 -0
- package/dist/agent-src/guidelines/agent-infra/linked-projects-onboarding-gate.md +80 -0
- package/dist/agent-src/guidelines/agent-infra/mcp-request-signing.md +197 -0
- package/dist/agent-src/guidelines/agent-infra/memory-access.md +149 -0
- package/dist/agent-src/guidelines/agent-infra/mental-models.md +314 -0
- package/dist/agent-src/guidelines/agent-infra/minimal-safe-diff-mechanics.md +241 -0
- package/dist/agent-src/guidelines/agent-infra/missing-tool-handling.md +66 -0
- package/dist/agent-src/guidelines/agent-infra/model-recommendation.md +101 -0
- package/dist/agent-src/guidelines/agent-infra/naming.md +69 -0
- package/dist/agent-src/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/dist/agent-src/guidelines/agent-infra/output-patterns.md +117 -0
- package/dist/agent-src/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/dist/agent-src/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/dist/agent-src/guidelines/agent-infra/review-routing-data-format.md +143 -0
- package/dist/agent-src/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
- package/dist/agent-src/guidelines/agent-infra/roadmap-progress-mechanics.md +274 -0
- package/dist/agent-src/guidelines/agent-infra/role-contracts.md +209 -0
- package/dist/agent-src/guidelines/agent-infra/role-mode-router.md +89 -0
- package/dist/agent-src/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
- package/dist/agent-src/guidelines/agent-infra/rule-type-governance.md +73 -0
- package/dist/agent-src/guidelines/agent-infra/runtime-layer.md +89 -0
- package/dist/agent-src/guidelines/agent-infra/scqa-framework.md +526 -0
- package/dist/agent-src/guidelines/agent-infra/security-lint-containment.md +121 -0
- package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +134 -0
- package/dist/agent-src/guidelines/agent-infra/simplicity-and-goal-demos.md +211 -0
- package/dist/agent-src/guidelines/agent-infra/six-hats.md +353 -0
- package/dist/agent-src/guidelines/agent-infra/size-and-scope.md +229 -0
- package/dist/agent-src/guidelines/agent-infra/skill-quality-checklist.md +157 -0
- package/dist/agent-src/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/dist/agent-src/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
- package/dist/agent-src/guidelines/agent-infra/systems-thinking.md +220 -0
- package/dist/agent-src/guidelines/agent-infra/tool-description-as-policy.md +35 -0
- package/dist/agent-src/guidelines/agent-infra/tool-integration.md +73 -0
- package/dist/agent-src/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
- package/dist/agent-src/guidelines/agent-infra/untrusted-input-spotlighting.md +166 -0
- package/dist/agent-src/guidelines/agent-infra/verify-before-complete-demos.md +128 -0
- package/dist/agent-src/guidelines/augment-portability-patterns.md +70 -0
- package/dist/agent-src/guidelines/code-clarity.md +350 -0
- package/dist/agent-src/guidelines/component-oriented-and-oop-development.md +189 -0
- package/dist/agent-src/guidelines/cross-role-handoff.md +127 -0
- package/dist/agent-src/guidelines/design-antipatterns-triggers.json +18 -0
- package/dist/agent-src/guidelines/design-antipatterns.md +327 -0
- package/dist/agent-src/guidelines/design-asset-discipline.md +38 -0
- package/dist/agent-src/guidelines/design-canon.md +63 -0
- package/dist/agent-src/guidelines/design-fidelity-mechanics.md +293 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +78 -0
- package/dist/agent-src/guidelines/design-handover-extraction.md +117 -0
- package/dist/agent-src/guidelines/design-modes.md +143 -0
- package/dist/agent-src/guidelines/design-reconciliation.md +172 -0
- package/dist/agent-src/guidelines/docs/readme-size-and-splitting.md +209 -0
- package/dist/agent-src/guidelines/e2e/playwright.md +363 -0
- package/dist/agent-src/guidelines/gtm-handoff.md +114 -0
- package/dist/agent-src/guidelines/monorepo-antipatterns.md +141 -0
- package/dist/agent-src/guidelines/php/api-design.md +115 -0
- package/dist/agent-src/guidelines/php/artisan-commands.md +81 -0
- package/dist/agent-src/guidelines/php/blade-ui.md +78 -0
- package/dist/agent-src/guidelines/php/controllers.md +90 -0
- package/dist/agent-src/guidelines/php/database.md +194 -0
- package/dist/agent-src/guidelines/php/eloquent.md +208 -0
- package/dist/agent-src/guidelines/php/flux.md +80 -0
- package/dist/agent-src/guidelines/php/general.md +207 -0
- package/dist/agent-src/guidelines/php/git.md +164 -0
- package/dist/agent-src/guidelines/php/jobs.md +111 -0
- package/dist/agent-src/guidelines/php/livewire.md +71 -0
- package/dist/agent-src/guidelines/php/logging.md +79 -0
- package/dist/agent-src/guidelines/php/naming.md +89 -0
- package/dist/agent-src/guidelines/php/patterns/dependency-injection.md +57 -0
- package/dist/agent-src/guidelines/php/patterns/dtos.md +199 -0
- package/dist/agent-src/guidelines/php/patterns/events.md +67 -0
- package/dist/agent-src/guidelines/php/patterns/factory.md +53 -0
- package/dist/agent-src/guidelines/php/patterns/pipelines.md +66 -0
- package/dist/agent-src/guidelines/php/patterns/policies.md +66 -0
- package/dist/agent-src/guidelines/php/patterns/repositories.md +122 -0
- package/dist/agent-src/guidelines/php/patterns/service-layer.md +64 -0
- package/dist/agent-src/guidelines/php/patterns/strategy.md +247 -0
- package/dist/agent-src/guidelines/php/patterns.md +28 -0
- package/dist/agent-src/guidelines/php/performance.md +92 -0
- package/dist/agent-src/guidelines/php/php-coding-patterns.md +80 -0
- package/dist/agent-src/guidelines/php/resources.md +100 -0
- package/dist/agent-src/guidelines/php/security.md +110 -0
- package/dist/agent-src/guidelines/php/sql.md +97 -0
- package/dist/agent-src/guidelines/php/validations.md +119 -0
- package/dist/agent-src/guidelines/php/websocket.md +100 -0
- package/dist/agent-src/guidelines/prompt-templates.md +189 -0
- package/dist/agent-src/guidelines/redundancy-taxonomy.md +208 -0
- package/dist/agent-src/guidelines/wing4-handoff.md +127 -0
- package/dist/agent-src/personas/README.md +1 -1
- package/dist/agent-src/personas/_template-specialist/persona.md +1 -1
- package/dist/agent-src/personas/product-owner.md +1 -1
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/architecture.md +2 -2
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/ask-when-uncertain.md +2 -2
- package/dist/agent-src/rules/brand-source-of-truth.md +9 -7
- package/dist/agent-src/rules/code-comment-discipline.md +2 -2
- package/dist/agent-src/rules/code-provenance.md +1 -1
- package/dist/agent-src/rules/content-quoting-floor.md +1 -1
- package/dist/agent-src/rules/context-hygiene.md +5 -5
- package/dist/agent-src/rules/cross-source-consistency.md +1 -1
- package/dist/agent-src/rules/design-fidelity.md +36 -46
- package/dist/agent-src/rules/direct-answers.md +2 -2
- package/dist/agent-src/rules/domain-adoption-policy.md +1 -1
- package/dist/agent-src/rules/evaluator-independence.md +1 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
- package/dist/agent-src/rules/icon-consistency.md +5 -1
- package/dist/agent-src/rules/improve-before-implement.md +1 -1
- package/dist/agent-src/rules/language-and-tone.md +3 -3
- package/dist/agent-src/rules/minimal-safe-diff.md +1 -1
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- package/dist/agent-src/rules/recurring-criticism.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/role-mode-adherence.md +2 -2
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
- package/dist/agent-src/rules/size-enforcement.md +1 -1
- package/dist/agent-src/rules/source-confidentiality.md +2 -2
- package/dist/agent-src/rules/think-before-action.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +7 -5
- package/dist/agent-src/rules/ui-audit-gate.md +3 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +3 -3
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/dist/agent-src/skills/activation-design/SKILL.md +2 -2
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -5
- package/dist/agent-src/skills/ai-council/SKILL.md +1 -1
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +1 -1
- package/dist/agent-src/skills/blade-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +24 -3
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
- package/dist/agent-src/skills/churn-prevention/SKILL.md +2 -2
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -1
- package/dist/agent-src/skills/code-refactoring/SKILL.md +3 -3
- package/dist/agent-src/skills/code-review/SKILL.md +3 -3
- package/dist/agent-src/skills/command-writing/SKILL.md +3 -3
- package/dist/agent-src/skills/comp-banding/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-moat-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -2
- package/dist/agent-src/skills/contracts-cognition/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +2 -2
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +2 -2
- package/dist/agent-src/skills/customer-research/SKILL.md +2 -2
- package/dist/agent-src/skills/dashboard-design/SKILL.md +1 -1
- package/dist/agent-src/skills/data-handling-judgment/SKILL.md +1 -1
- package/dist/agent-src/skills/deal-qualification-meddic/SKILL.md +2 -2
- package/dist/agent-src/skills/decision-record/SKILL.md +3 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +8 -8
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +22 -22
- package/dist/agent-src/skills/design-intelligence/SKILL.md +4 -4
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +4 -4
- package/dist/agent-src/skills/design-system-capture/references/design-system-json.md +20 -1
- package/dist/agent-src/skills/design-tokens/SKILL.md +17 -2
- package/dist/agent-src/skills/design-variations/SKILL.md +2 -2
- package/dist/agent-src/skills/developer-like-execution/SKILL.md +2 -2
- package/dist/agent-src/skills/discovery-interview/SKILL.md +2 -2
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +3 -3
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -2
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +44 -1
- package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +2 -2
- package/dist/agent-src/skills/expansion-playbook/SKILL.md +2 -2
- package/dist/agent-src/skills/experiment-loop/SKILL.md +1 -1
- package/dist/agent-src/skills/experiment-loop/references/protocol.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +3 -3
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +1 -1
- package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +3 -3
- package/dist/agent-src/skills/fe-design/references/source-led-port.md +1 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/flux/SKILL.md +1 -1
- package/dist/agent-src/skills/forecast-accuracy/SKILL.md +2 -2
- package/dist/agent-src/skills/forecasting/SKILL.md +2 -2
- package/dist/agent-src/skills/fundraising-narrative/SKILL.md +2 -2
- package/dist/agent-src/skills/funnel-analysis/SKILL.md +2 -2
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/gtm-launch/SKILL.md +2 -2
- package/dist/agent-src/skills/hiring-loop-design/SKILL.md +1 -1
- package/dist/agent-src/skills/html-deck/SKILL.md +5 -5
- package/dist/agent-src/skills/humanizer/SKILL.md +1 -1
- package/dist/agent-src/skills/humanizer/data/patterns.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +3 -3
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-api-endpoint/SKILL.md +2 -2
- package/dist/agent-src/skills/launch-readiness/SKILL.md +3 -3
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +6 -6
- package/dist/agent-src/skills/livewire/SKILL.md +1 -1
- package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/mcp-builder/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +3 -3
- package/dist/agent-src/skills/messaging-architecture/SKILL.md +2 -2
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/onboarding-design/SKILL.md +2 -2
- package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
- package/dist/agent-src/skills/one-on-one-cadence/SKILL.md +1 -1
- package/dist/agent-src/skills/org-design/SKILL.md +1 -1
- package/dist/agent-src/skills/overbuild-review-lens/SKILL.md +3 -3
- package/dist/agent-src/skills/override-management/SKILL.md +1 -1
- package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/php-coder/SKILL.md +1 -1
- package/dist/agent-src/skills/pipeline-strategy/SKILL.md +2 -2
- package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -3
- package/dist/agent-src/skills/po-discovery/SKILL.md +1 -1
- package/dist/agent-src/skills/positioning-strategy/SKILL.md +2 -2
- package/dist/agent-src/skills/privacy-review/SKILL.md +1 -1
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +3 -3
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -2
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
- package/dist/agent-src/skills/refine-ticket/SKILL.md +2 -2
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -2
- package/dist/agent-src/skills/retention-loops/SKILL.md +2 -2
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-management/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/references/archival.md +1 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +5 -5
- package/dist/agent-src/skills/rule-writing/SKILL.md +6 -6
- package/dist/agent-src/skills/runway-cognition/SKILL.md +2 -2
- package/dist/agent-src/skills/scenario-modeling/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-writing/references/procedure.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +3 -3
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +3 -3
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +1 -1
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +2 -2
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +60 -4
- package/dist/agent-src/skills/throughput-vs-morale-tradeoff/SKILL.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/typography-system/SKILL.md +2 -2
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +28 -8
- package/dist/agent-src/skills/unit-economics-modeling/SKILL.md +2 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +1 -1
- package/dist/agent-src/skills/validate-feature-fit/SKILL.md +2 -2
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
- package/dist/agent-src/skills/vision-articulation/SKILL.md +1 -1
- package/dist/agent-src/skills/voc-extract/SKILL.md +2 -2
- package/dist/agent-src/skills/voice-and-tone-design/SKILL.md +2 -2
- package/dist/agent-src/skills/wireframe/SKILL.md +1 -1
- package/dist/agent-src/skills/workspace-link/SKILL.md +1 -1
- package/dist/agent-src/templates/agent-settings.md +6 -6
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/contexts/knowledge-pages.md +2 -3
- package/dist/agent-src/templates/copilot-instructions.md +1 -1
- package/dist/agent-src/templates/roadmaps.md +1 -1
- package/dist/agent-src/templates/scripts/README.md +2 -2
- package/dist/agent-src/user-types/_template/user-type.md +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +52 -4
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/{chunk-TGIPZBCW.js → chunk-2KKXJW25.js} +22 -8
- package/dist/cli-delegate/{chunk-NLP54U3J.js → chunk-2XY7ILBT.js} +13 -1
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-3FKR64R5.js} +34 -6
- package/dist/cli-delegate/{chunk-J3RFNKG4.js → chunk-55ACLDUE.js} +2 -2
- package/dist/cli-delegate/{chunk-PKOBQADP.js → chunk-57PN4GYJ.js} +1 -1
- package/dist/cli-delegate/{chunk-BIXEDQPZ.js → chunk-AK6HODNQ.js} +4 -4
- package/dist/cli-delegate/{chunk-2FTMENJL.js → chunk-GYC3VRYN.js} +1 -1
- package/dist/cli-delegate/{chunk-LWTYTX6F.js → chunk-HZ52BRGG.js} +24 -18
- package/dist/cli-delegate/{chunk-6RJA2F5A.js → chunk-IVBPRCQO.js} +1 -1
- package/dist/cli-delegate/{chunk-I6RHFQEG.js → chunk-YUM5KURG.js} +17 -1
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +24 -16
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +3 -3
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +4 -14
- package/dist/cli-delegate/cmd_settings_get.js +15 -9
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +41 -11
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +27 -80
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +5 -7
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +4 -6
- package/dist/hooks/dispatch.js +9249 -8981
- package/dist/install/install.mjs +47 -8
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +5496 -2400
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +73 -0
- package/dist/scripts/_lib/agent_src.js +20 -0
- package/dist/scripts/_lib/guidelines_lane.js +158 -0
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +22 -25
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/io/yamlIO.js +125 -17
- package/dist/server/schemas/settings.js +32 -5
- package/dist/shared/settingsSurface.js +6 -1
- package/docs/MIGRATION.md +56 -0
- package/docs/contracts/settings-classes.md +48 -7
- package/docs/guidelines/abstraction-thresholds.md +35 -0
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +3 -3
- package/docs/guidelines/agent-infra/false-green.md +2 -2
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +1 -1
- package/docs/guidelines/design-fidelity-mechanics.md +4 -0
- package/docs/guidelines/design-fidelity-routing.md +78 -0
- package/docs/guidelines/design-reconciliation.md +172 -0
- package/docs/guidelines/gtm-handoff.md +2 -2
- package/docs/guidelines/wing4-handoff.md +2 -2
- package/llms.txt +1 -1
- package/package.json +7 -3
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/src/config/agent-settings.template.yml +94 -1
- package/src/config/ci-local-parity.yml +34 -0
- package/src/config/continuity-surface.json +8 -29
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +31 -8
- package/src/config/gate-reachability-exemptions.json +1 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/hook-token-budget.json +8 -7
- package/src/config/host-injection-effect.json +2 -2
- package/src/config/pack-size-budget.json +55 -4
- package/src/config/packed-binary-manifest.json +1 -1
- package/src/config/platform-anchor.json +47 -0
- package/src/config/preamble-payload-budget.json +2 -3
- package/src/config/publish-surface.json +4 -2
- package/src/config/ratification-policy.json +7 -0
- package/src/config/routing-coverage-seed.json +9 -2
- package/src/config/rule-stub-ceilings.json +16 -9
- package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/cmd_settings_get.ts +22 -7
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- package/src/scripts/_lib/agent_settings.ts +85 -0
- package/src/scripts/_lib/agent_src.ts +21 -0
- package/src/scripts/_lib/artifact_maturity.ts +242 -0
- package/src/scripts/_lib/conformance_report.ts +403 -0
- package/src/scripts/_lib/continuity_slot.ts +295 -0
- package/src/scripts/_lib/continuity_writer.ts +312 -0
- package/src/scripts/_lib/design_system_import.ts +20 -0
- package/src/scripts/_lib/design_tolerance.ts +281 -0
- package/src/scripts/_lib/guidelines_lane.ts +164 -0
- package/src/scripts/_lib/hook_settings.ts +85 -0
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/obligation_frequency.ts +2 -3
- package/src/scripts/_lib/packed_binary_predicate.ts +37 -0
- package/src/scripts/_lib/platform_anchor.ts +1053 -0
- package/src/scripts/_lib/ratification_artifact.ts +278 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/security_lint.ts +23 -2
- package/src/scripts/_lib/self_repair.ts +21 -0
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +387 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +220 -0
- package/src/scripts/_lib/tolerance_shadow.ts +177 -0
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- package/src/scripts/ai_council/redact_low_impact_entry.ts +8 -0
- package/src/scripts/audit_mcp_tools.ts +69 -25
- package/src/scripts/autonomy_friction_traffic.ts +379 -0
- package/src/scripts/build_mcp_catalog.ts +24 -1
- package/src/scripts/check_host_tree_parity.ts +291 -0
- package/src/scripts/check_kernel_edit_ratified.ts +462 -0
- package/src/scripts/check_loss_class_declared.ts +793 -46
- package/src/scripts/check_platform_anchor.ts +390 -0
- package/src/scripts/check_preamble_payload_budget.ts +424 -7
- package/src/scripts/check_projected_rule_routes.ts +395 -0
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +79 -0
- package/src/scripts/code_graph/cli.ts +264 -7
- package/src/scripts/code_graph/detect.ts +38 -0
- package/src/scripts/code_graph/query.ts +25 -2
- package/src/scripts/code_graph/sqlite_store.ts +16 -1
- package/src/scripts/code_graph/types.ts +52 -3
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +22 -26
- package/src/scripts/generate_host_cost_table.ts +354 -0
- package/src/scripts/handoff_context_hook.ts +39 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +59 -28
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/category_a.ts +1 -1
- package/src/scripts/hooks/chain_nudge_hook.ts +311 -25
- package/src/scripts/hooks/code_graph_context_hook.ts +7 -18
- package/src/scripts/hooks/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +107 -20
- package/src/scripts/hooks/session_eol_hook.ts +177 -145
- package/src/scripts/hooks/source_first_gate_hook.ts +12 -0
- package/src/scripts/hooks/ui_route_nudge_hook.ts +52 -15
- package/src/scripts/hot_context_hook.ts +103 -408
- package/src/scripts/lint_evidence_artifacts.ts +1 -0
- package/src/scripts/lint_knowledge_scale.ts +0 -26
- package/src/scripts/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +3 -13
- package/src/scripts/memory_lookup.ts +9 -9
- package/src/scripts/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- package/src/scripts/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +86 -8
- package/src/scripts/sync_agent_settings.ts +270 -10
- package/src/server/io/yamlIO.ts +127 -17
- package/src/server/schemas/settings.ts +45 -6
- package/src/shared/settingsSurface.ts +13 -2
- package/src/templates/compact-instructions.md +8 -3
- package/src/vendor/grammars/README.md +27 -2
- package/dist/agent-src/commands/chat-history/import.md +0 -170
- package/dist/agent-src/commands/chat-history.md +0 -63
|
@@ -19,17 +19,93 @@
|
|
|
19
19
|
*
|
|
20
20
|
* WHAT COUNTS AS MODEL-FACING
|
|
21
21
|
* ---------------------------
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* An EMITTER is a hook concern script whose code emits a context payload. A
|
|
23
|
+
* TARGET is any module that emitter reaches through relative imports —
|
|
24
|
+
* transitively, the emitter itself included — whose code applies a lossy
|
|
25
|
+
* operation to content. The emitter half and the lossy half are read from
|
|
26
|
+
* different files on purpose: a `_lib` module does not emit, the concern does,
|
|
27
|
+
* so requiring both halves of one file is what kept the corpus at one and then
|
|
28
|
+
* at zero.
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* Both halves are read from code with comments stripped — ALL comments, a
|
|
31
|
+
* trailing `// truncate the rows` included, since one scan classifies every
|
|
32
|
+
* character rather than two regexes guessing at each other's boundaries (see
|
|
33
|
+
* `scanSource`). The first cut of this detector matched `truncat` inside three
|
|
34
|
+
* docblocks that describe truncation without performing any. The LOSSY half
|
|
35
|
+
* additionally blanks string, template and regex literals, and widening the
|
|
36
|
+
* walk is what made that necessary — measured over the reached set, the raw
|
|
37
|
+
* patterns matched a SQLite `PRAGMA wal_checkpoint(TRUNCATE)`, the settings key
|
|
38
|
+
* `'knowledge.global_sharing.redaction.enabled'`, and a destructive-command
|
|
39
|
+
* detector's own `/truncate\s+table/` regex. None of the three shortens
|
|
40
|
+
* anything. A gate that fires on data ABOUT an operation rather than on the
|
|
41
|
+
* operation is how a real corpus becomes a pro-forma one, each member carrying
|
|
42
|
+
* a declaration nobody meant.
|
|
43
|
+
*
|
|
44
|
+
* The EMIT half deliberately keeps reading literals: a concern that writes its
|
|
45
|
+
* payload key as `'additionalContext'` is emitting, and blanking that string
|
|
46
|
+
* would drop a real emitter — measured, it dropped six of seven.
|
|
47
|
+
*
|
|
48
|
+
* WHAT IT MEASURES, AND HOW THAT NUMBER MOVED
|
|
49
|
+
* -------------------------------------------
|
|
50
|
+
* MATCHED IS NOT LOSSY, and the headline used to say otherwise. The detector
|
|
51
|
+
* establishes a MATCH; the module's own DECLARATION says whether anything is
|
|
52
|
+
* lost, and `exact` says nothing is. Of the five modules below, four declare a
|
|
53
|
+
* lossy class and one declares `exact`, so "5 lossy modules" overstated the
|
|
54
|
+
* real lossy corpus by one. Every count here now reads "matched".
|
|
55
|
+
*
|
|
56
|
+
* Measured at landing (emitter-only): **1** module — `hot_context_hook`, the
|
|
57
|
+
* `ephemeral-lossy` exemplar the contract classifies. Measured 2026-09-09 after
|
|
58
|
+
* road-to-continuity-writer-activation step 3.1 retired that module's cache
|
|
59
|
+
* half: **0**. The scanned corpus was unchanged at 58 hook scripts; the matched
|
|
60
|
+
* subset was empty, so the gate passed over nothing — a hole, not a clean bill,
|
|
61
|
+
* tracked as blocker `loss-class-corpus-is-empty-after-hot-context`.
|
|
62
|
+
*
|
|
63
|
+
* Measured 2026-09-10 with the walk widened past concern scripts: **58 hook
|
|
64
|
+
* scripts scanned, 7 of them emitters, 73 modules reached, 5 of those
|
|
65
|
+
* matched** — four lossy plus one `exact`:
|
|
66
|
+
* - `_lib/session_index_trust.ts` — the surviving 30-row cap the blocker named
|
|
67
|
+
* - `session_memory_index.ts` — which applies it
|
|
68
|
+
* - `memory_lookup.ts` — the token-budget cut
|
|
69
|
+
* - `_lib/self_repair.ts` — evidence sanitization (but see below)
|
|
70
|
+
* - `ai_council/redact_low_impact_entry.ts` — `exact`: it refuses rather than
|
|
71
|
+
* rewrites, so no byte of its input is dropped or replaced
|
|
72
|
+
* The retired exemplar is not among them and does not come back.
|
|
73
|
+
*
|
|
74
|
+
* `exact` IS A SILENCER, AND IT IS UNVERIFIABLE HERE. `parseLossDeclaration`
|
|
75
|
+
* accepts it and `isProblem` is false, so declaring `exact` turns any future
|
|
76
|
+
* over-match green without the detector learning anything. Nothing in this gate
|
|
77
|
+
* checks that an `exact` module is byte-preserving — that is a reviewer's
|
|
78
|
+
* judgement, and this one was reviewed: the redactor returns a verdict, never a
|
|
79
|
+
* rewritten string. Treat a NEW `exact` declaration as a detector defect to fix
|
|
80
|
+
* rather than a declaration to accept.
|
|
81
|
+
*
|
|
82
|
+
* ONE CORPUS MEMBER IS MATCHED FOR THE WRONG REASON. `_lib/self_repair.ts` is
|
|
83
|
+
* declared `ephemeral-lossy` for `sanitizeEvidence`, which hard-cuts at
|
|
84
|
+
* `MAX_EVIDENCE` — and the cap pattern CANNOT SEE that constant: `MAX_EVIDENCE`
|
|
85
|
+
* fails `(?:WORD|CHAR|ROW|MAX)_(?:CAP|CHARS|LEN|WORDS)` because the unit word
|
|
86
|
+
* must be followed by a size noun. What actually matches is `\bredact`, against
|
|
87
|
+
* the `redact_low_impact_entry` identifier the module imports for its EGRESS
|
|
88
|
+
* gate. Delete or rename that import and a module that still cuts content
|
|
89
|
+
* leaves the corpus silently.
|
|
90
|
+
*
|
|
91
|
+
* Widening the pattern to any `MAX_<WORD>` was MEASURED before it was refused,
|
|
92
|
+
* 2026-09-10 over the same 73-module reach: it takes the corpus from 5 to
|
|
93
|
+
* **12**, admitting `MAX_RETRIES` (a retry budget), `DEFAULT_MAX_TOKENS` (an
|
|
94
|
+
* API parameter), `MAX_PATHS`, `MAX_ENTRIES` and three `*_MAX_BYTES` retention
|
|
95
|
+
* settings — seven constants, not one of them evidence that content is cut.
|
|
96
|
+
* Seven pro-forma declarations to catch one real cap is the trade this gate's
|
|
97
|
+
* own docstring argues against two paragraphs up, so the gap is disclosed
|
|
98
|
+
* rather than closed. The honest position: this member's declaration is right
|
|
99
|
+
* and the detector's reason for it is not.
|
|
100
|
+
*
|
|
101
|
+
* One pattern moved with the walk, and only one. `\b(?:WORD|CHAR|MAX)_…`
|
|
102
|
+
* required the cap constant to BEGIN with a unit word, so it saw `WORD_CAP` and
|
|
103
|
+
* could not see `SESSION_INDEX_ROW_CAP` — the exact constant the blocker names.
|
|
104
|
+
* The prefix is now optional and `ROW` joined the unit list; everything the old
|
|
105
|
+
* form matched, the new form still matches. Deliberately NOT generalized to any
|
|
106
|
+
* `_CAP` at all: that also matches `security_lint.PRAGMA_CAP`, a lint budget
|
|
107
|
+
* that shortens no content, which is the pro-forma member this paragraph exists
|
|
108
|
+
* to keep out.
|
|
33
109
|
*
|
|
34
110
|
* Exit codes: 0 clean (warnings allowed) · 1 an undeclared model-facing
|
|
35
111
|
* transform · 2 misuse / unreadable manifest.
|
|
@@ -64,27 +140,265 @@ const MODEL_FACING_SLOTS = [
|
|
|
64
140
|
/** Emitting a context payload — the "reaches the model" half. */
|
|
65
141
|
const EMIT_PATTERNS = [/\bcontext\s*:/, /additionalContext/, /hookSpecificOutput/];
|
|
66
142
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Shortening, redacting or capping content — the "lossy" half.
|
|
145
|
+
*
|
|
146
|
+
* The third pattern names a CAP CONSTANT. Its unit vocabulary is the load-bearing
|
|
147
|
+
* part, not the leading position: an optional qualifying prefix lets it see
|
|
148
|
+
* `SESSION_INDEX_ROW_CAP` while `PRAGMA_CAP` — a count of lint pragmas, not of
|
|
149
|
+
* content — stays out.
|
|
150
|
+
*/
|
|
151
|
+
const LOSSY_PATTERNS = [
|
|
152
|
+
/\bredact/i,
|
|
153
|
+
/\btruncat/i,
|
|
154
|
+
/\b(?:[A-Z0-9]+_)*(?:WORD|CHAR|ROW|MAX)_(?:CAP|CHARS|LEN|WORDS)\b/,
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Relative module specifiers, in every form this tree actually writes them.
|
|
159
|
+
*
|
|
160
|
+
* The extension is no longer part of the pattern — `resolveSpecifier` decides
|
|
161
|
+
* what a specifier resolves to, and it accepts only code files, so an
|
|
162
|
+
* extensionless import is a reach and `'./notes.md'` still is not.
|
|
163
|
+
*/
|
|
164
|
+
// Anchored on an import or call position rather than matching any quoted
|
|
165
|
+
// `./`-shaped string anywhere. Unanchored, a path in a prose message could pull
|
|
166
|
+
// an unimported module into the gated population — over-match, so it costs a
|
|
167
|
+
// declaration rather than a silent green, but a corpus member nobody imports is
|
|
168
|
+
// the pro-forma shape this gate refuses. Measured: the corpus is the same 5
|
|
169
|
+
// either way, so the narrowing loses no real edge, `from`/`(`/`,` still covers
|
|
170
|
+
// static imports and every `createRequire` alias this tree writes.
|
|
171
|
+
const IMPORT_SPEC = /(?:from\s*|[(,]\s*)['"](\.\.?\/[^'"\n]*)['"]/g;
|
|
69
172
|
|
|
70
173
|
export interface LossFinding {
|
|
71
174
|
concern: string;
|
|
175
|
+
/** The concern's own script — the emitter the module was reached from. */
|
|
72
176
|
script: string;
|
|
177
|
+
/** The reached module that applies the lossy operation. */
|
|
178
|
+
module: string;
|
|
73
179
|
tier: 'fail' | 'warn';
|
|
74
180
|
problem: DeclarationProblem;
|
|
75
181
|
}
|
|
76
182
|
|
|
77
183
|
export interface LossVerdict {
|
|
184
|
+
/** Concern scripts read from the manifest. */
|
|
78
185
|
scanned: number;
|
|
186
|
+
/** Concern scripts that emit a context payload — the walk's entry points. */
|
|
187
|
+
emitters: number;
|
|
188
|
+
/** Distinct modules the detector MATCHED on a model-facing path. */
|
|
79
189
|
modelFacing: number;
|
|
190
|
+
/**
|
|
191
|
+
* Of those, how many declare `exact` — matched, and losing nothing.
|
|
192
|
+
*
|
|
193
|
+
* Reported separately because the headline used to call every matched
|
|
194
|
+
* module lossy, and one of them is a redactor that refuses rather than
|
|
195
|
+
* rewrites. "Matched" is what the detector establishes; "lossy" is what
|
|
196
|
+
* the declaration says, and on `exact` it says the opposite.
|
|
197
|
+
*/
|
|
198
|
+
declaredExact: number;
|
|
199
|
+
/**
|
|
200
|
+
* Those modules by repo-relative path, sorted.
|
|
201
|
+
*
|
|
202
|
+
* Exposed so a test can assert WHICH module the detector matched, not only
|
|
203
|
+
* how many — the blocker this walk closed was satisfiable by a count.
|
|
204
|
+
*/
|
|
205
|
+
modules: string[];
|
|
80
206
|
findings: LossFinding[];
|
|
81
207
|
}
|
|
82
208
|
|
|
83
|
-
/**
|
|
209
|
+
/** The two views of a source file the detector reads. */
|
|
210
|
+
export interface SourceViews {
|
|
211
|
+
/** Comments removed, literals blanked — code, and nothing about code. */
|
|
212
|
+
code: string;
|
|
213
|
+
/** Comments removed, literals kept — code plus the data it writes. */
|
|
214
|
+
withLiterals: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Characters after which a `/` opens a regex rather than dividing. */
|
|
218
|
+
const REGEX_PREFIX = new Set(['(', ',', '=', ':', '[', '!', '&', '|', '?', '{', ';']);
|
|
219
|
+
/** Keywords after which the same is true. */
|
|
220
|
+
const REGEX_KEYWORDS = new Set(['return', 'typeof', 'case', 'in', 'of', 'new', 'delete', 'void', 'yield', 'await', 'throw']);
|
|
221
|
+
|
|
222
|
+
/** The standard division-vs-regex heuristic, read off what the scan has emitted. */
|
|
223
|
+
function regexAllowed(code: string): boolean {
|
|
224
|
+
let j = code.length - 1;
|
|
225
|
+
while (j >= 0 && /\s/.test(code[j] as string)) j -= 1;
|
|
226
|
+
if (j < 0) return true;
|
|
227
|
+
const ch = code[j] as string;
|
|
228
|
+
if (REGEX_PREFIX.has(ch)) return true;
|
|
229
|
+
if (!/[A-Za-z0-9_$]/.test(ch)) return false;
|
|
230
|
+
let k = j;
|
|
231
|
+
while (k >= 0 && /[A-Za-z0-9_$]/.test(code[k] as string)) k -= 1;
|
|
232
|
+
return REGEX_KEYWORDS.has(code.slice(k + 1, j + 1));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* ONE left-to-right scan that classifies every character as code, literal or
|
|
237
|
+
* comment, and returns both views from that single pass.
|
|
238
|
+
*
|
|
239
|
+
* Independent regexes for comments and for literals cannot see each other, and
|
|
240
|
+
* a completion review found the resulting hole from both sides:
|
|
241
|
+
*
|
|
242
|
+
* - a trailing `// truncate the rows` survived comment stripping (which
|
|
243
|
+
* removed WHOLE-LINE `//` only) and read as a real truncation — an
|
|
244
|
+
* over-match that manufactures a declaration nobody meant;
|
|
245
|
+
* - a `/*` inside a string opened a phantom block comment that deleted every
|
|
246
|
+
* byte up to the next real `*` + `/` — an under-match, the silent-green
|
|
247
|
+
* direction.
|
|
248
|
+
*
|
|
249
|
+
* The second finding's ATTRIBUTION was corrected by reproduction before this
|
|
250
|
+
* fix was written: it blamed the composition order in `stripLiterals(
|
|
251
|
+
* stripComments(src))`, and the old comment pass ALONE already ate that input,
|
|
252
|
+
* so the order was never the cause. The phantom-comment half was pre-existing
|
|
253
|
+
* in comment stripping, not introduced by composing the two. Both halves are
|
|
254
|
+
* the same root cause seen from two sides, and both are gone here for the same
|
|
255
|
+
* reason: a scanner that knows which state it is in cannot open a comment
|
|
256
|
+
* inside a literal, cannot open a literal inside a comment, and does not care
|
|
257
|
+
* whether a `//` starts its line.
|
|
258
|
+
*
|
|
259
|
+
* Newlines inside a removed comment are preserved so the two views stay
|
|
260
|
+
* line-aligned with the source.
|
|
261
|
+
*/
|
|
262
|
+
export function scanSource(src: string): SourceViews {
|
|
263
|
+
let code = '';
|
|
264
|
+
let withLiterals = '';
|
|
265
|
+
const n = src.length;
|
|
266
|
+
// `code` mode reads code; `template` mode reads template-literal text. A
|
|
267
|
+
// `${…}` pushes `code` back on, so a substitution's expression is code —
|
|
268
|
+
// which is what it is.
|
|
269
|
+
const modes: ('code' | 'template')[] = ['code'];
|
|
270
|
+
/** Brace depth outside each open `${…}`, innermost last. */
|
|
271
|
+
const subst: number[] = [];
|
|
272
|
+
let braces = 0;
|
|
273
|
+
let i = 0;
|
|
274
|
+
|
|
275
|
+
while (i < n) {
|
|
276
|
+
const ch = src[i] as string;
|
|
277
|
+
|
|
278
|
+
if (modes[modes.length - 1] === 'template') {
|
|
279
|
+
if (ch === '\\') {
|
|
280
|
+
withLiterals += src.slice(i, i + 2);
|
|
281
|
+
i += 2;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (ch === '`') {
|
|
285
|
+
modes.pop();
|
|
286
|
+
code += '`';
|
|
287
|
+
withLiterals += '`';
|
|
288
|
+
i += 1;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (ch === '$' && src[i + 1] === '{') {
|
|
292
|
+
modes.push('code');
|
|
293
|
+
subst.push(braces);
|
|
294
|
+
braces += 1;
|
|
295
|
+
code += '${';
|
|
296
|
+
withLiterals += '${';
|
|
297
|
+
i += 2;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (ch === '\n') code += '\n';
|
|
301
|
+
withLiterals += ch;
|
|
302
|
+
i += 1;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const next = src[i + 1];
|
|
307
|
+
if (ch === '/' && next === '/') {
|
|
308
|
+
let j = i + 2;
|
|
309
|
+
while (j < n && src[j] !== '\n') j += 1;
|
|
310
|
+
i = j; // the newline itself is emitted on the next turn
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (ch === '/' && next === '*') {
|
|
314
|
+
let j = i + 2;
|
|
315
|
+
while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j += 1;
|
|
316
|
+
const end = Math.min(j + 2, n);
|
|
317
|
+
const kept = src.slice(i, end).replace(/[^\n]/g, '');
|
|
318
|
+
code += kept;
|
|
319
|
+
withLiterals += kept;
|
|
320
|
+
i = end;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (ch === "'" || ch === '"') {
|
|
324
|
+
let j = i + 1;
|
|
325
|
+
while (j < n && src[j] !== ch && src[j] !== '\n') j += src[j] === '\\' ? 2 : 1;
|
|
326
|
+
const end = src[j] === ch ? j + 1 : j;
|
|
327
|
+
code += ch + ch;
|
|
328
|
+
withLiterals += src.slice(i, end);
|
|
329
|
+
i = end;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (ch === '`') {
|
|
333
|
+
modes.push('template');
|
|
334
|
+
code += '`';
|
|
335
|
+
withLiterals += '`';
|
|
336
|
+
i += 1;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (ch === '/' && regexAllowed(code)) {
|
|
340
|
+
let j = i + 1;
|
|
341
|
+
let inClass = false;
|
|
342
|
+
let closed = false;
|
|
343
|
+
while (j < n && src[j] !== '\n') {
|
|
344
|
+
const c = src[j] as string;
|
|
345
|
+
if (c === '\\') {
|
|
346
|
+
j += 2;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (c === '[') inClass = true;
|
|
350
|
+
else if (c === ']') inClass = false;
|
|
351
|
+
else if (c === '/' && !inClass) {
|
|
352
|
+
closed = true;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
j += 1;
|
|
356
|
+
}
|
|
357
|
+
if (closed) {
|
|
358
|
+
let k = j + 1;
|
|
359
|
+
while (k < n && /[dgimsuvy]/.test(src[k] as string)) k += 1;
|
|
360
|
+
code += '/RE/';
|
|
361
|
+
withLiterals += src.slice(i, k);
|
|
362
|
+
i = k;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (ch === '{') {
|
|
367
|
+
braces += 1;
|
|
368
|
+
} else if (ch === '}') {
|
|
369
|
+
const open = subst[subst.length - 1];
|
|
370
|
+
if (open !== undefined && braces === open + 1) {
|
|
371
|
+
subst.pop();
|
|
372
|
+
braces = open;
|
|
373
|
+
modes.pop();
|
|
374
|
+
code += '}';
|
|
375
|
+
withLiterals += '}';
|
|
376
|
+
i += 1;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
braces -= 1;
|
|
380
|
+
}
|
|
381
|
+
code += ch;
|
|
382
|
+
withLiterals += ch;
|
|
383
|
+
i += 1;
|
|
384
|
+
}
|
|
385
|
+
return { code, withLiterals };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Comments removed, literals KEPT — the emit half, and the marker-substitution
|
|
390
|
+
* half of the lossy test.
|
|
391
|
+
*
|
|
392
|
+
* Name and callers unchanged; the behaviour is the scan's, so a trailing `//`
|
|
393
|
+
* is now removed like any other comment.
|
|
394
|
+
*/
|
|
84
395
|
export function stripComments(src: string): string {
|
|
85
|
-
return src
|
|
86
|
-
|
|
87
|
-
|
|
396
|
+
return scanSource(src).withLiterals;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** Comments removed and literals blanked — the pattern half of the lossy test. */
|
|
400
|
+
export function stripNonCode(src: string): string {
|
|
401
|
+
return scanSource(src).code;
|
|
88
402
|
}
|
|
89
403
|
|
|
90
404
|
export function emitsContext(codeOnly: string): boolean {
|
|
@@ -95,6 +409,169 @@ export function isLossy(codeOnly: string): boolean {
|
|
|
95
409
|
return LOSSY_PATTERNS.some((p) => p.test(codeOnly));
|
|
96
410
|
}
|
|
97
411
|
|
|
412
|
+
/**
|
|
413
|
+
* A substitution whose ARGUMENTS carry the lossy marker — `s.replace(RE,
|
|
414
|
+
* '[REDACTED]')`. Deliberately a proximity test and not a file-level one: a
|
|
415
|
+
* module that merely mentions `redaction` somewhere AND calls `.slice` somewhere
|
|
416
|
+
* else is not performing a redaction, and admitting it was measured to pull in
|
|
417
|
+
* three modules that lose nothing (settings KEY NAMES containing `redaction`, a
|
|
418
|
+
* SQLite `PRAGMA wal_checkpoint(TRUNCATE)`, and length constants used only to
|
|
419
|
+
* VALIDATE). Three pro-forma declarations is the corpus this gate's own
|
|
420
|
+
* docstring warns against, so the co-presence rule was measured and dropped.
|
|
421
|
+
*/
|
|
422
|
+
// `(?:[^()]|\([^()]*\))*?` and not `[^)]*?`: the first form stopped at the
|
|
423
|
+
// FIRST `)`, so `s.replace(buildPattern(), '[REDACTED]')` — marker after a
|
|
424
|
+
// nested call — was invisible, which is a silent-green gap in the one mechanism
|
|
425
|
+
// this pattern exists to close. One level of nesting covers every shape in this
|
|
426
|
+
// tree; deeper nesting is a bounded under-match rather than an unbounded scan
|
|
427
|
+
// that would run into the next statement.
|
|
428
|
+
const MARKER_SUBSTITUTION = /\.(?:replace|replaceAll)\s*\((?:[^()]|\([^()]*\))*?(?:redact|truncat)/i;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Is this module lossy, judged on BOTH the blanked and the unblanked form?
|
|
432
|
+
*
|
|
433
|
+
* Blanking literals keeps the gate off data ABOUT operations — a SQLite
|
|
434
|
+
* `PRAGMA wal_checkpoint(TRUNCATE)`, a settings key containing `redaction`, a
|
|
435
|
+
* destructive-command detector's own `/truncate\s+table/`. It also deletes a
|
|
436
|
+
* real class of true positives, which a completion review caught and this
|
|
437
|
+
* function is the answer to: a redaction whose only code-level evidence is its
|
|
438
|
+
* replacement marker — `s.replace(RE, '[REDACTED]')` — is lossy, and blanking
|
|
439
|
+
* alone made it invisible.
|
|
440
|
+
*
|
|
441
|
+
* The discriminator is measured, not guessed, and the first version of it was
|
|
442
|
+
* wrong. Asking only whether the module ALSO contains a shortening call
|
|
443
|
+
* anywhere admitted three modules that lose nothing — settings key names
|
|
444
|
+
* containing `redaction`, a SQLite `PRAGMA wal_checkpoint(TRUNCATE)`, and
|
|
445
|
+
* length constants used to validate rather than cut — taking the corpus from 5
|
|
446
|
+
* to 8. Co-presence in a file is not evidence. The marker has to sit in the
|
|
447
|
+
* substitution's own arguments, which is what separates `s.replace(RE,
|
|
448
|
+
* '[REDACTED]')` from `db.exec(q)`, `read(k)` and `RE.test(cmd)`.
|
|
449
|
+
*
|
|
450
|
+
* Residual error, stated rather than implied: a substitution whose replacement
|
|
451
|
+
* merely NAMES redaction without performing one still counts. That direction is
|
|
452
|
+
* the affordable one — it costs a declaration, where the other direction is a
|
|
453
|
+
* module cutting content with nothing watching.
|
|
454
|
+
*/
|
|
455
|
+
export function isLossyModule(source: string): boolean {
|
|
456
|
+
return isLossyViews(scanSource(source));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** The same test over an already-scanned file, so the scan is paid once. */
|
|
460
|
+
export function isLossyViews(views: SourceViews): boolean {
|
|
461
|
+
if (isLossy(views.code)) return true;
|
|
462
|
+
return MARKER_SUBSTITUTION.test(views.withLiterals);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Resolve a relative specifier to a real file, preferring the `.ts` twin of a `.js` import. */
|
|
466
|
+
export function resolveSpecifier(spec: string, fromFile: string, root: string): string | null {
|
|
467
|
+
const base = path.resolve(path.dirname(fromFile), spec);
|
|
468
|
+
// A specifier that already names a code file keeps the original two
|
|
469
|
+
// candidates. One that does not gets the extensionless and directory forms
|
|
470
|
+
// in Node resolution order: the pattern used to REQUIRE an extension, so
|
|
471
|
+
// `require('./_lib/x')` and `from './helpers'` were not reaches at all — a
|
|
472
|
+
// static gap the contract's disclosure list did not carry. A specifier
|
|
473
|
+
// naming some other extension (`./x.md`) falls here too and resolves to
|
|
474
|
+
// nothing, which keeps non-code files out of the walk.
|
|
475
|
+
//
|
|
476
|
+
// Measured 2026-09-10, and the honest number is zero: the reached set is
|
|
477
|
+
// 73 modules with the extension required and 73 without, and the matched
|
|
478
|
+
// corpus is the same 5 either way — this tree writes no extensionless
|
|
479
|
+
// relative specifier on any reached path today. The gap is closed
|
|
480
|
+
// prospectively, not because it was leaking. A self-test case pins that
|
|
481
|
+
// the new candidates resolve at all, so the closure is not untested.
|
|
482
|
+
// `.mts` / `.cts` / `.tsx` / `.jsx` are in the list although this tree
|
|
483
|
+
// writes none of them on a reached path today. Leaving them out is a
|
|
484
|
+
// latent under-match that would surface as a silently smaller corpus the
|
|
485
|
+
// day one appears, which is the failure direction this whole gate exists
|
|
486
|
+
// to refuse; carrying them costs nothing.
|
|
487
|
+
const candidates = /\.(?:js|ts|mjs|cjs|mts|cts|tsx|jsx)$/.test(base)
|
|
488
|
+
? [base.replace(/\.(?:js|mjs|cjs|jsx)$/, '.ts'), base]
|
|
489
|
+
: [`${base}.ts`, `${base}.js`, path.join(base, 'index.ts'), path.join(base, 'index.js')];
|
|
490
|
+
for (const candidate of candidates) {
|
|
491
|
+
if (!candidate.startsWith(root + path.sep)) continue;
|
|
492
|
+
try {
|
|
493
|
+
if (fs.statSync(candidate).isFile()) return candidate;
|
|
494
|
+
} catch {
|
|
495
|
+
/* not a file — try the next candidate */
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Read each file once per run and keep both views plus its declaration.
|
|
503
|
+
*
|
|
504
|
+
* The walk, the lossy test and the declaration parse all want the same file,
|
|
505
|
+
* and `reachedModules` re-walked a heavily shared `_lib` graph once per
|
|
506
|
+
* emitter. With 7 emitters that was O(emitters × graph) reads for a result
|
|
507
|
+
* that is per-file and per-entry and does not change during a run.
|
|
508
|
+
*/
|
|
509
|
+
export class SourceCache {
|
|
510
|
+
private readonly files = new Map<string, { raw: string; views: SourceViews } | null>();
|
|
511
|
+
private readonly reach = new Map<string, string[]>();
|
|
512
|
+
|
|
513
|
+
get(file: string): { raw: string; views: SourceViews } | null {
|
|
514
|
+
const hit = this.files.get(file);
|
|
515
|
+
if (hit !== undefined) return hit;
|
|
516
|
+
let entry: { raw: string; views: SourceViews } | null = null;
|
|
517
|
+
try {
|
|
518
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
519
|
+
entry = { raw, views: scanSource(raw) };
|
|
520
|
+
} catch {
|
|
521
|
+
entry = null;
|
|
522
|
+
}
|
|
523
|
+
this.files.set(file, entry);
|
|
524
|
+
return entry;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Memoized per entry point, which is what the per-emitter loop asks for. */
|
|
528
|
+
reachedFrom(entry: string, root: string): string[] {
|
|
529
|
+
const hit = this.reach.get(entry);
|
|
530
|
+
if (hit !== undefined) return hit;
|
|
531
|
+
const out = reachedModules(entry, root, this);
|
|
532
|
+
this.reach.set(entry, out);
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Every module an emitter reaches through relative imports, transitively, the
|
|
539
|
+
* emitter itself included.
|
|
540
|
+
*
|
|
541
|
+
* `createRequire` is how the lazy hook paths in this tree load their heavy
|
|
542
|
+
* dependencies, so the walk reads relative SPECIFIERS rather than `import`
|
|
543
|
+
* statements — a `req('./_lib/session_index_trust.js')` is a reach and a static
|
|
544
|
+
* import is a reach, and the detector must not be able to tell them apart.
|
|
545
|
+
*
|
|
546
|
+
* There is no depth cap, and that is a fix rather than an omission. The cap
|
|
547
|
+
* was tested AFTER `seen.add`, and the stack is LIFO, so `depth` was the depth
|
|
548
|
+
* of whichever path happened to push a node first — a module first popped deep
|
|
549
|
+
* was marked reached and its children were never explored, even when a shallow
|
|
550
|
+
* path to it existed and was popped later. That made the reach set depend on
|
|
551
|
+
* import order rather than on the graph. `seen` is what terminates a cycle, and
|
|
552
|
+
* it terminated every cycle already, so removing the cap removes the
|
|
553
|
+
* order-dependence and nothing else.
|
|
554
|
+
*/
|
|
555
|
+
export function reachedModules(entry: string, root: string, cache: SourceCache = new SourceCache()): string[] {
|
|
556
|
+
const seen = new Set<string>();
|
|
557
|
+
const stack: string[] = [entry];
|
|
558
|
+
while (stack.length > 0) {
|
|
559
|
+
const file = stack.pop();
|
|
560
|
+
if (file === undefined) break;
|
|
561
|
+
if (seen.has(file)) continue;
|
|
562
|
+
seen.add(file);
|
|
563
|
+
const hit = cache.get(file);
|
|
564
|
+
if (hit === null) continue;
|
|
565
|
+
for (const m of hit.views.withLiterals.matchAll(IMPORT_SPEC)) {
|
|
566
|
+
const spec = m[1];
|
|
567
|
+
if (spec === undefined) continue;
|
|
568
|
+
const target = resolveSpecifier(spec, file, root);
|
|
569
|
+
if (target !== null && !seen.has(target)) stack.push(target);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return [...seen];
|
|
573
|
+
}
|
|
574
|
+
|
|
98
575
|
function loadManifest(root: string): { concerns: Record<string, { script?: unknown }>; platforms: Record<string, Record<string, string[]>> } {
|
|
99
576
|
const file = path.join(root, 'src', 'scripts', 'hook_manifest.yaml');
|
|
100
577
|
const raw = fs.readFileSync(file, 'utf-8');
|
|
@@ -136,8 +613,11 @@ export function evaluate(root: string = REPO_ROOT, ledger?: GateLedger): LossVer
|
|
|
136
613
|
}
|
|
137
614
|
|
|
138
615
|
const findings: LossFinding[] = [];
|
|
616
|
+
const lossyModules = new Map<string, boolean>();
|
|
617
|
+
const exactModules = new Set<string>();
|
|
618
|
+
const cache = new SourceCache();
|
|
139
619
|
let scanned = 0;
|
|
140
|
-
let
|
|
620
|
+
let emitters = 0;
|
|
141
621
|
|
|
142
622
|
ledger?.plan(Object.keys(concerns));
|
|
143
623
|
|
|
@@ -148,34 +628,66 @@ export function evaluate(root: string = REPO_ROOT, ledger?: GateLedger): LossVer
|
|
|
148
628
|
continue;
|
|
149
629
|
}
|
|
150
630
|
const abs = path.join(root, rel);
|
|
151
|
-
|
|
631
|
+
const entry = cache.get(abs);
|
|
632
|
+
if (entry === null) {
|
|
152
633
|
ledger?.skip(name, 'no_applicable_files');
|
|
153
634
|
continue;
|
|
154
635
|
}
|
|
155
636
|
scanned += 1;
|
|
156
637
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (!
|
|
160
|
-
// Not a lossy model-facing transform: the check does not apply to it.
|
|
638
|
+
// The emitter half is a property of the CONCERN script. A `_lib` module
|
|
639
|
+
// never emits; requiring it to is what emptied this corpus.
|
|
640
|
+
if (!emitsContext(entry.views.withLiterals)) {
|
|
161
641
|
ledger?.outOfScope(name, 'not_applicable_kind');
|
|
162
642
|
continue;
|
|
163
643
|
}
|
|
644
|
+
emitters += 1;
|
|
164
645
|
|
|
165
646
|
// Unknown reachability fails closed: a bound concern this gate cannot
|
|
166
647
|
// place on a known slot is treated as model-reaching.
|
|
167
648
|
const reaches = facing.has(name) || !bound.has(name);
|
|
168
|
-
if (reaches) modelFacing += 1;
|
|
169
649
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
650
|
+
let anyLossy = false;
|
|
651
|
+
let anyProblem = false;
|
|
652
|
+
for (const file of cache.reachedFrom(abs, root)) {
|
|
653
|
+
const hit = cache.get(file);
|
|
654
|
+
if (hit === null) continue;
|
|
655
|
+
if (!isLossyViews(hit.views)) continue;
|
|
656
|
+
anyLossy = true;
|
|
657
|
+
const moduleRel = path.relative(root, file);
|
|
658
|
+
if (reaches) lossyModules.set(moduleRel, true);
|
|
659
|
+
|
|
660
|
+
// The declaration lives in a docblock, so it is read from the RAW
|
|
661
|
+
// source — the one place the comment-stripped views are wrong.
|
|
662
|
+
const decl = parseLossDeclaration(hit.raw);
|
|
663
|
+
if (!isProblem(decl)) {
|
|
664
|
+
if (reaches && decl.lossClass === 'exact') exactModules.add(moduleRel);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
anyProblem = true;
|
|
668
|
+
findings.push({ concern: name, script: rel, module: moduleRel, tier: reaches ? 'fail' : 'warn', problem: decl });
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!anyLossy) {
|
|
672
|
+
// Nothing this emitter reaches shortens content: the check does not
|
|
673
|
+
// apply to it.
|
|
674
|
+
ledger?.outOfScope(name, 'not_applicable_kind');
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (anyProblem) {
|
|
678
|
+
ledger?.fail(name, 'reaches a lossy module with no usable loss_class');
|
|
173
679
|
continue;
|
|
174
680
|
}
|
|
175
|
-
|
|
176
|
-
ledger?.fail(name, `undeclared or malformed loss_class (${decl.kind})`);
|
|
681
|
+
ledger?.complete(name);
|
|
177
682
|
}
|
|
178
|
-
return {
|
|
683
|
+
return {
|
|
684
|
+
scanned,
|
|
685
|
+
emitters,
|
|
686
|
+
modelFacing: lossyModules.size,
|
|
687
|
+
declaredExact: exactModules.size,
|
|
688
|
+
modules: [...lossyModules.keys()].sort(),
|
|
689
|
+
findings,
|
|
690
|
+
};
|
|
179
691
|
}
|
|
180
692
|
|
|
181
693
|
// ---------------------------------------------------------------- self-test
|
|
@@ -188,12 +700,154 @@ export function run() {
|
|
|
188
700
|
}
|
|
189
701
|
`;
|
|
190
702
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
703
|
+
/**
|
|
704
|
+
* The specifier the self-test fixtures import, assembled rather than written.
|
|
705
|
+
*
|
|
706
|
+
* `prepack-check` scans every SHIPPED source for `from '<relative>'` and fails
|
|
707
|
+
* on one that does not resolve. These fixtures are template literals describing
|
|
708
|
+
* a file the self-test writes to a temp directory, so the path resolves there
|
|
709
|
+
* and nowhere here — and interpolating it keeps the literal form out of this
|
|
710
|
+
* file while leaving it intact in the fixture that gets written. The same shape
|
|
711
|
+
* this gate's own IMPORT_SPEC was narrowed for: a path in a string is not an
|
|
712
|
+
* import, and two different tools read it as one.
|
|
713
|
+
*/
|
|
714
|
+
const FIXTURE_LIB = './_lib/fixture_lib.js';
|
|
715
|
+
const FIXTURE_LIB_BARE = './_lib/fixture_lib';
|
|
716
|
+
|
|
717
|
+
/** An emitter that is clean itself and reaches a lossy `_lib` module. */
|
|
718
|
+
const IMPORTING_EMITTER = `
|
|
719
|
+
import { capRows } from '${FIXTURE_LIB}';
|
|
720
|
+
export function run() {
|
|
721
|
+
return { context: capRows(rows).join('\\n') };
|
|
722
|
+
}
|
|
723
|
+
`;
|
|
724
|
+
|
|
725
|
+
/** The same emitter, importing a module that shortens nothing. */
|
|
726
|
+
const IMPORTING_CLEAN_EMITTER = `
|
|
727
|
+
import { passthrough } from '${FIXTURE_LIB}';
|
|
728
|
+
export function run() {
|
|
729
|
+
return { context: passthrough(rows).join('\\n') };
|
|
730
|
+
}
|
|
731
|
+
`;
|
|
732
|
+
|
|
733
|
+
const LOSSY_LIB = `
|
|
734
|
+
export const SESSION_INDEX_ROW_CAP = 30;
|
|
735
|
+
export function capRows(rows) { return rows.slice(0, SESSION_INDEX_ROW_CAP); }
|
|
736
|
+
`;
|
|
737
|
+
|
|
738
|
+
const CLEAN_LIB = `
|
|
739
|
+
export function passthrough(rows) { return rows; }
|
|
740
|
+
`;
|
|
741
|
+
|
|
742
|
+
/** The same emitter again, reaching its lib through an EXTENSIONLESS specifier. */
|
|
743
|
+
const EXTENSIONLESS_EMITTER = `
|
|
744
|
+
import { capRows } from '${FIXTURE_LIB_BARE}';
|
|
745
|
+
export function run() {
|
|
746
|
+
return { context: capRows(rows).join('\\n') };
|
|
747
|
+
}
|
|
748
|
+
`;
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* The cap-widening pair. These two differ in ONE token — the unit word — so
|
|
752
|
+
* either one failing localises the pattern change. The reject half used to be
|
|
753
|
+
* a byte-identical copy of the "reached module" case above and could not fail
|
|
754
|
+
* independently of it.
|
|
755
|
+
*/
|
|
756
|
+
const PREFIXED_UNIT_CAP_LIB = `
|
|
757
|
+
export const SESSION_INDEX_ROW_CAP = 30;
|
|
758
|
+
export function rows(r) { return r.slice(0, SESSION_INDEX_ROW_CAP); }
|
|
759
|
+
`;
|
|
760
|
+
const PREFIXED_NON_UNIT_CAP_LIB = `
|
|
761
|
+
export const SESSION_INDEX_PRAGMA_CAP = 30;
|
|
762
|
+
export function rows(r) { return r; }
|
|
763
|
+
`;
|
|
764
|
+
const ROWS_EMITTER = IMPORTING_EMITTER.replaceAll('capRows', 'rows');
|
|
765
|
+
|
|
766
|
+
// ---- the four shapes that must stay OUT, one fixture each -----------------
|
|
767
|
+
//
|
|
768
|
+
// One per shape on purpose. They used to be two shapes in one fixture, so a
|
|
769
|
+
// regression in either was reported as one failure and neither was localised.
|
|
770
|
+
|
|
771
|
+
/** A SQLite pragma that names TRUNCATE and shortens nothing. */
|
|
772
|
+
const PRAGMA_LITERAL_EMITTER = `
|
|
773
|
+
const stmt = 'PRAGMA wal_checkpoint(TRUNCATE)';
|
|
774
|
+
export function run() { return { context: stmt }; }
|
|
775
|
+
`;
|
|
776
|
+
|
|
777
|
+
/** A destructive-command detector's OWN pattern — data about truncation. */
|
|
778
|
+
const DETECTOR_REGEX_EMITTER = `
|
|
779
|
+
const destructive = /\\btruncate\\s+table\\b/i;
|
|
780
|
+
export function run() { return { context: String(destructive) }; }
|
|
781
|
+
`;
|
|
782
|
+
|
|
783
|
+
/** A settings KEY NAME containing `redaction`. Reading a flag is not redacting. */
|
|
784
|
+
const SETTINGS_KEY_EMITTER = `
|
|
785
|
+
const KEY = 'knowledge.global_sharing.redaction.enabled';
|
|
786
|
+
export function run() { return { context: read(KEY) }; }
|
|
787
|
+
`;
|
|
788
|
+
|
|
789
|
+
/** A TRAILING comment — the half whole-line comment stripping could not see. */
|
|
790
|
+
const TRAILING_COMMENT_EMITTER = `
|
|
791
|
+
export function run() { return { context: rows.join('\\n') }; } // truncate the rows here
|
|
792
|
+
`;
|
|
793
|
+
|
|
794
|
+
// ---- the three shapes that must stay IN ------------------------------------
|
|
795
|
+
|
|
796
|
+
/** A redaction whose ONLY code-level evidence is its replacement literal. */
|
|
797
|
+
const MARKER_REPLACEMENT_LIB = `
|
|
798
|
+
export function scrub(s) { return s.replace(SECRET_RE, '[REDACTED]'); }
|
|
799
|
+
`;
|
|
800
|
+
// The same redaction with the marker AFTER a nested call. `[^)]*?` stopped at
|
|
801
|
+
// the first `)` and made this shape invisible — a silent-green gap in the one
|
|
802
|
+
// mechanism that pattern exists to close, and unpinned until this case.
|
|
803
|
+
const NESTED_MARKER_LIB = `
|
|
804
|
+
export function scrub(s) { return s.replace(buildPattern(kind), '[REDACTED]'); }
|
|
805
|
+
`;
|
|
806
|
+
const SCRUB_EMITTER = `
|
|
807
|
+
import { scrub } from '${FIXTURE_LIB}';
|
|
808
|
+
export function run() { return { context: scrub(body) }; }
|
|
809
|
+
`;
|
|
810
|
+
|
|
811
|
+
/** A contraction inside a template literal, with real truncating code after it. */
|
|
812
|
+
const TEMPLATE_APOSTROPHE_EMITTER = `
|
|
813
|
+
export function run(body) {
|
|
814
|
+
const msg = \`it's fine\`; const kept = body.truncate('x');
|
|
815
|
+
return { context: msg + kept };
|
|
816
|
+
}
|
|
817
|
+
`;
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* An UNBALANCED `/*` inside a string literal, followed by a real comment.
|
|
821
|
+
*
|
|
822
|
+
* The unbalanced form is the one that bites: whole-line comment stripping ran
|
|
823
|
+
* from the phantom opener to the next real closer and deleted the truncation
|
|
824
|
+
* between them, so the module read clean. A balanced `'src/` + `**` + `/*.ts'`
|
|
825
|
+
* glob only mangles the string and leaves the code, which is why this fixture
|
|
826
|
+
* does not use one.
|
|
827
|
+
*/
|
|
828
|
+
const PHANTOM_COMMENT_EMITTER = `
|
|
829
|
+
const open = '/*';
|
|
830
|
+
export function run(body) { return { context: open + truncateBody(body) }; }
|
|
831
|
+
/* a real comment, after the phantom one */
|
|
832
|
+
`;
|
|
833
|
+
|
|
834
|
+
interface Plant {
|
|
835
|
+
header?: string;
|
|
836
|
+
body: string;
|
|
837
|
+
libHeader?: string;
|
|
838
|
+
lib?: string;
|
|
839
|
+
slot?: string;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function plant(dir: string, p: Plant): void {
|
|
843
|
+
fs.mkdirSync(path.join(dir, 'src', 'scripts', '_lib'), { recursive: true });
|
|
844
|
+
fs.writeFileSync(path.join(dir, 'src', 'scripts', 'fixture_hook.ts'), (p.header ?? '') + p.body);
|
|
845
|
+
if (p.lib !== undefined) {
|
|
846
|
+
fs.writeFileSync(path.join(dir, 'src', 'scripts', '_lib', 'fixture_lib.ts'), (p.libHeader ?? '') + p.lib);
|
|
847
|
+
}
|
|
194
848
|
fs.writeFileSync(
|
|
195
849
|
path.join(dir, 'src', 'scripts', 'hook_manifest.yaml'),
|
|
196
|
-
`schema_version: 1\nconcerns:\n fixture:\n script: src/scripts/fixture_hook.ts\nplatforms:\n claude:\n ${slot}:\n - fixture\n`,
|
|
850
|
+
`schema_version: 1\nconcerns:\n fixture:\n script: src/scripts/fixture_hook.ts\nplatforms:\n claude:\n ${p.slot ?? 'session_start'}:\n - fixture\n`,
|
|
197
851
|
);
|
|
198
852
|
}
|
|
199
853
|
|
|
@@ -204,13 +858,13 @@ const DECL_EPHEMERAL = '/**\n * loss_class: ephemeral-lossy\n */\n';
|
|
|
204
858
|
const DECL_TYPO = '/**\n * loss_class: recoverable_lossy\n */\n';
|
|
205
859
|
|
|
206
860
|
function selfTestCases(): SelfTestCase[] {
|
|
207
|
-
const mk = (name: string, expect: 'reject' | 'accept',
|
|
861
|
+
const mk = (name: string, expect: 'reject' | 'accept', p: Plant): SelfTestCase => ({
|
|
208
862
|
name,
|
|
209
863
|
expect,
|
|
210
864
|
run: () => {
|
|
211
865
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lcd-'));
|
|
212
866
|
try {
|
|
213
|
-
plant(dir,
|
|
867
|
+
plant(dir, p);
|
|
214
868
|
return runGateCli(REPO_ROOT, 'src/scripts/check_loss_class_declared.ts', ['--root', dir, '--quiet'], REPO_ROOT);
|
|
215
869
|
} finally {
|
|
216
870
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
@@ -218,14 +872,72 @@ function selfTestCases(): SelfTestCase[] {
|
|
|
218
872
|
},
|
|
219
873
|
});
|
|
220
874
|
return [
|
|
221
|
-
mk('model-facing lossy transform with no declaration → reject', 'reject', NO_DECL, LOSSY_EMITTER),
|
|
222
|
-
mk('same transform with recoverable-lossy + locator → accept', 'accept', DECL_RECOVERABLE, LOSSY_EMITTER),
|
|
223
|
-
mk('recoverable-lossy WITHOUT a locator → reject', 'reject', DECL_RECOVERABLE_NO_LOCATOR, LOSSY_EMITTER),
|
|
224
|
-
mk('ephemeral-lossy (owes no locator) → accept', 'accept', DECL_EPHEMERAL, LOSSY_EMITTER),
|
|
225
|
-
mk('a misspelled class is not honoured → reject', 'reject', DECL_TYPO, LOSSY_EMITTER),
|
|
226
|
-
mk('non-lossy emitter needs no declaration → accept', 'accept', NO_DECL, `export function run(){return {context:'hi'};}`),
|
|
227
|
-
mk('lossy but emits no context → accept', 'accept', NO_DECL, `const WORD_CAP=5;export function run(){return body.slice(0,WORD_CAP);}`),
|
|
228
|
-
mk('prose about truncation is not truncation → accept', 'accept', NO_DECL, `/* we deliberately never truncate or redact here */\nexport function run(){return {context:'hi'};}`),
|
|
875
|
+
mk('model-facing lossy transform with no declaration → reject', 'reject', { header: NO_DECL, body: LOSSY_EMITTER }),
|
|
876
|
+
mk('same transform with recoverable-lossy + locator → accept', 'accept', { header: DECL_RECOVERABLE, body: LOSSY_EMITTER }),
|
|
877
|
+
mk('recoverable-lossy WITHOUT a locator → reject', 'reject', { header: DECL_RECOVERABLE_NO_LOCATOR, body: LOSSY_EMITTER }),
|
|
878
|
+
mk('ephemeral-lossy (owes no locator) → accept', 'accept', { header: DECL_EPHEMERAL, body: LOSSY_EMITTER }),
|
|
879
|
+
mk('a misspelled class is not honoured → reject', 'reject', { header: DECL_TYPO, body: LOSSY_EMITTER }),
|
|
880
|
+
mk('non-lossy emitter needs no declaration → accept', 'accept', { header: NO_DECL, body: `export function run(){return {context:'hi'};}` }),
|
|
881
|
+
mk('lossy but emits no context → accept', 'accept', { header: NO_DECL, body: `const WORD_CAP=5;export function run(){return body.slice(0,WORD_CAP);}` }),
|
|
882
|
+
mk('prose about truncation is not truncation → accept', 'accept', { header: NO_DECL, body: `/* we deliberately never truncate or redact here */\nexport function run(){return {context:'hi'};}` }),
|
|
883
|
+
// The widening, in both directions.
|
|
884
|
+
mk('a lossy _lib module an emitter REACHES, undeclared → reject', 'reject', {
|
|
885
|
+
header: NO_DECL, body: IMPORTING_EMITTER, libHeader: NO_DECL, lib: LOSSY_LIB,
|
|
886
|
+
}),
|
|
887
|
+
mk('the same reached module, declared on the module itself → accept', 'accept', {
|
|
888
|
+
header: NO_DECL, body: IMPORTING_EMITTER, libHeader: DECL_EPHEMERAL, lib: LOSSY_LIB,
|
|
889
|
+
}),
|
|
890
|
+
mk('a declaration on the EMITTER does not cover the reached module → reject', 'reject', {
|
|
891
|
+
header: DECL_EPHEMERAL, body: IMPORTING_EMITTER, libHeader: NO_DECL, lib: LOSSY_LIB,
|
|
892
|
+
}),
|
|
893
|
+
mk('a reached module that shortens nothing → accept', 'accept', {
|
|
894
|
+
header: NO_DECL, body: IMPORTING_CLEAN_EMITTER, libHeader: NO_DECL, lib: CLEAN_LIB,
|
|
895
|
+
}),
|
|
896
|
+
// The cap widening, isolated: these two differ in ONE token.
|
|
897
|
+
mk('a prefixed UNIT cap is a cap — SESSION_INDEX_ROW_CAP → reject', 'reject', {
|
|
898
|
+
header: NO_DECL, body: ROWS_EMITTER, libHeader: NO_DECL, lib: PREFIXED_UNIT_CAP_LIB,
|
|
899
|
+
}),
|
|
900
|
+
mk('the same shape with a NON-unit word — SESSION_INDEX_PRAGMA_CAP → accept', 'accept', {
|
|
901
|
+
header: NO_DECL, body: ROWS_EMITTER, libHeader: NO_DECL, lib: PREFIXED_NON_UNIT_CAP_LIB,
|
|
902
|
+
}),
|
|
903
|
+
mk('a lint budget is not a content cap — PRAGMA_CAP → accept', 'accept', {
|
|
904
|
+
// `replaceAll`, not `replace`: the first-match form renamed the
|
|
905
|
+
// import and left the call site reading `capRows`, so the fixture
|
|
906
|
+
// no longer meant what its name said.
|
|
907
|
+
header: NO_DECL, body: IMPORTING_EMITTER.replaceAll('capRows', 'pragmas'), libHeader: NO_DECL,
|
|
908
|
+
lib: `export const PRAGMA_CAP = 20;\nexport function pragmas(rows) { return rows; }\n`,
|
|
909
|
+
}),
|
|
910
|
+
// The four DATA shapes, one case each, so a regression localises.
|
|
911
|
+
mk('a SQLite PRAGMA naming TRUNCATE shortens nothing → accept', 'accept', {
|
|
912
|
+
header: NO_DECL, body: PRAGMA_LITERAL_EMITTER,
|
|
913
|
+
}),
|
|
914
|
+
mk("a detector's own /truncate table/ pattern is data → accept", 'accept', {
|
|
915
|
+
header: NO_DECL, body: DETECTOR_REGEX_EMITTER,
|
|
916
|
+
}),
|
|
917
|
+
mk('a settings key containing `redaction` is not a redaction → accept', 'accept', {
|
|
918
|
+
header: NO_DECL, body: SETTINGS_KEY_EMITTER,
|
|
919
|
+
}),
|
|
920
|
+
mk('a TRAILING // comment about truncation is not truncation → accept', 'accept', {
|
|
921
|
+
header: NO_DECL, body: TRAILING_COMMENT_EMITTER,
|
|
922
|
+
}),
|
|
923
|
+
// The three shapes that must stay IN — the true positives blanking and
|
|
924
|
+
// regex-composed stripping each cost, one case per direction.
|
|
925
|
+
mk('a redaction whose only evidence is its replacement literal → reject', 'reject', {
|
|
926
|
+
header: NO_DECL, body: SCRUB_EMITTER, libHeader: NO_DECL, lib: MARKER_REPLACEMENT_LIB,
|
|
927
|
+
}),
|
|
928
|
+
mk('a marker after a nested call is still a redaction → reject', 'reject', {
|
|
929
|
+
header: NO_DECL, body: SCRUB_EMITTER, libHeader: NO_DECL, lib: NESTED_MARKER_LIB,
|
|
930
|
+
}),
|
|
931
|
+
mk('an apostrophe in a template literal does not blank the code after it → reject', 'reject', {
|
|
932
|
+
header: NO_DECL, body: TEMPLATE_APOSTROPHE_EMITTER,
|
|
933
|
+
}),
|
|
934
|
+
mk('a /* inside a string literal does not open a comment → reject', 'reject', {
|
|
935
|
+
header: NO_DECL, body: PHANTOM_COMMENT_EMITTER,
|
|
936
|
+
}),
|
|
937
|
+
// The reach widening: an extensionless specifier is a reach.
|
|
938
|
+
mk('an extensionless relative import is a reach → reject', 'reject', {
|
|
939
|
+
header: NO_DECL, body: EXTENSIONLESS_EMITTER, libHeader: NO_DECL, lib: LOSSY_LIB,
|
|
940
|
+
}),
|
|
229
941
|
];
|
|
230
942
|
}
|
|
231
943
|
|
|
@@ -233,7 +945,12 @@ function selfTestCases(): SelfTestCase[] {
|
|
|
233
945
|
|
|
234
946
|
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
235
947
|
if (argv.includes('--self-test')) {
|
|
236
|
-
|
|
948
|
+
// Floors sit ON the actual counts, not below them. At 13/5 against
|
|
949
|
+
// 15/6 the reject floor tolerated deleting a rejecting case in
|
|
950
|
+
// silence, so it no longer defended the number `gate-coverage.yml`
|
|
951
|
+
// documents. Raising a floor is a deliberate edit; letting one rot
|
|
952
|
+
// below the corpus is not.
|
|
953
|
+
return runSelfTest({ gate: 'check_loss_class_declared', cases: selfTestCases(), minCases: 24, minRejectCases: 11 });
|
|
237
954
|
}
|
|
238
955
|
const quiet = argv.includes('--quiet');
|
|
239
956
|
const json = argv.includes('--json');
|
|
@@ -278,9 +995,33 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
278
995
|
}
|
|
279
996
|
};
|
|
280
997
|
|
|
998
|
+
// One line per MODULE, not per (concern, module) pair. A shared `_lib`
|
|
999
|
+
// module reached from several emitters is one undeclared module and one
|
|
1000
|
+
// edit; printing it once per reaching emitter made the output disagree
|
|
1001
|
+
// with `modules` / `modelFacing`, which already deduplicate. The ledger
|
|
1002
|
+
// keeps its per-concern `fail` — that count answers a different question
|
|
1003
|
+
// (which concerns are affected), and the reaching set is named on the line
|
|
1004
|
+
// so nothing is lost by collapsing it.
|
|
1005
|
+
const byModule = new Map<string, { first: LossFinding; concerns: string[] }>();
|
|
281
1006
|
for (const f of v.findings) {
|
|
1007
|
+
const hit = byModule.get(f.module);
|
|
1008
|
+
if (hit === undefined) byModule.set(f.module, { first: f, concerns: [f.concern] });
|
|
1009
|
+
else {
|
|
1010
|
+
hit.concerns.push(f.concern);
|
|
1011
|
+
if (f.tier === 'fail') hit.first = { ...hit.first, tier: 'fail' };
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
for (const { first: f, concerns } of byModule.values()) {
|
|
282
1016
|
const stream = f.tier === 'fail' ? process.stderr : process.stdout;
|
|
283
|
-
|
|
1017
|
+
const reached =
|
|
1018
|
+
concerns.length === 1
|
|
1019
|
+
? `reached from concern ${f.concern} → ${f.script}`
|
|
1020
|
+
: `reached from ${String(concerns.length)} concerns: ${concerns.join(', ')}`;
|
|
1021
|
+
stream.write(
|
|
1022
|
+
`${f.tier === 'fail' ? '❌' : '⚠️ '} ${f.module} shortens content on a model-facing path ` +
|
|
1023
|
+
`(${reached}) and ${say(f)}\n`,
|
|
1024
|
+
);
|
|
284
1025
|
}
|
|
285
1026
|
|
|
286
1027
|
if (v.findings.some((f) => f.tier === 'fail')) {
|
|
@@ -293,9 +1034,15 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
293
1034
|
return 1;
|
|
294
1035
|
}
|
|
295
1036
|
if (!quiet) {
|
|
1037
|
+
// "matched", not "lossy". The detector establishes a match; the
|
|
1038
|
+
// DECLARATION says whether anything is lost, and on `exact` it says
|
|
1039
|
+
// nothing is. Calling every matched module lossy overstated the real
|
|
1040
|
+
// lossy corpus by however many `exact` members it held.
|
|
1041
|
+
const exact = v.declaredExact > 0 ? `, ${String(v.declaredExact)} of them declaring exact` : '';
|
|
296
1042
|
process.stdout.write(
|
|
297
1043
|
`✅ every model-facing lossy transform declares its loss class ` +
|
|
298
|
-
`(${String(v.modelFacing)}
|
|
1044
|
+
`(${String(v.modelFacing)} matched module(s)${exact}, reached from ${String(v.emitters)} emitter(s) ` +
|
|
1045
|
+
`of ${String(v.scanned)} hook script(s)).\n`,
|
|
299
1046
|
);
|
|
300
1047
|
}
|
|
301
1048
|
return 0;
|