@event4u/agent-config 9.9.0 → 9.10.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +153 -194
- package/README.md +1 -1
- package/dist/agent-src/commands/agent-status.md +12 -11
- package/dist/agent-src/commands/agents/user/accept.md +115 -30
- package/dist/agent-src/commands/agents/user/delete.md +147 -0
- package/dist/agent-src/commands/agents/user/review.md +65 -28
- package/dist/agent-src/commands/agents/user/show.md +82 -29
- package/dist/agent-src/commands/agents/user/update.md +2 -2
- package/dist/agent-src/commands/agents/user.md +16 -3
- package/dist/agent-src/commands/check-current-md.md +0 -10
- package/dist/agent-src/commands/condense.md +28 -97
- package/dist/agent-src/commands/cost/report.md +7 -0
- package/dist/agent-src/commands/fix/portability.md +0 -8
- package/dist/agent-src/commands/fix/refs.md +0 -8
- package/dist/agent-src/commands/humanize.md +2 -2
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/memory/load.md +11 -0
- package/dist/agent-src/commands/optimize/skills.md +1 -1
- package/dist/agent-src/commands/pr/create.md +4 -4
- package/dist/agent-src/commands/profile/show.md +3 -3
- package/dist/agent-src/commands/rule-compliance-audit.md +5 -4
- package/dist/agent-src/commands/security-audit-config.md +1 -1
- package/dist/agent-src/commands/sync/gitignore/fix.md +1 -1
- package/dist/agent-src/commands/tdd.md +21 -0
- package/dist/agent-src/commands/team/delegate.md +2 -2
- package/dist/agent-src/commands/team-knowledge/bootstrap.md +9 -8
- package/dist/agent-src/commands/team-knowledge/consolidate.md +8 -7
- package/dist/agent-src/commands/team.md +4 -1
- package/dist/agent-src/commands/upstream-contribute.md +0 -1
- package/dist/agent-src/contexts/authority/commit-mechanics.md +7 -0
- package/dist/agent-src/contexts/authority/destructive-mechanics.md +16 -0
- package/dist/agent-src/contexts/authority/scope-mechanics.md +61 -0
- package/dist/agent-src/contexts/communication/rules-auto/reply-close-mechanics.md +20 -18
- package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/source-of-truth-mechanics.md +9 -10
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +11 -0
- package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +26 -15
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +67 -37
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
- package/dist/agent-src/contexts/execution/autonomy-examples.md +1 -1
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +7 -6
- package/dist/agent-src/contexts/execution/evidence-discipline.md +92 -77
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +22 -21
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +24 -0
- package/dist/agent-src/contexts/execution/project-intelligence.md +122 -103
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +23 -21
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +5 -5
- package/dist/agent-src/contexts/execution/subagent-routing.md +11 -0
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +13 -13
- package/dist/agent-src/contexts/execution/subagent-steering.md +36 -34
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +1 -1
- package/dist/agent-src/contexts/execution/user-memory-channels.md +189 -0
- package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -0
- package/dist/agent-src/contexts/model-recommendations.md +3 -2
- package/dist/agent-src/contexts/subagent-configuration.md +3 -3
- package/dist/agent-src/ghostwriter/README.md +2 -2
- package/dist/agent-src/packs/README.md +1 -1
- package/dist/agent-src/personas/README.md +5 -5
- package/dist/agent-src/personas/_template-specialist/persona.md +3 -3
- package/dist/agent-src/personas/backend-architect.md +24 -23
- package/dist/agent-src/personas/eloquent-tamer.md +30 -30
- package/dist/agent-src/personas/frontend-engineer.md +30 -30
- package/dist/agent-src/personas/product-owner.md +41 -38
- package/dist/agent-src/personas/qa.md +13 -13
- package/dist/agent-src/personas/security-engineer.md +22 -22
- package/dist/agent-src/presets/README.md +3 -3
- package/dist/agent-src/profiles/README.md +3 -3
- package/dist/agent-src/rules/active-remediation.md +2 -2
- package/dist/agent-src/rules/artifact-engagement-recording.md +4 -4
- package/dist/agent-src/rules/ask-when-uncertain.md +0 -4
- package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -4
- package/dist/agent-src/rules/code-comment-discipline.md +1 -1
- package/dist/agent-src/rules/code-provenance.md +2 -2
- package/dist/agent-src/rules/commit-policy.md +4 -9
- package/dist/agent-src/rules/communication-through-line.md +5 -5
- package/dist/agent-src/rules/decision-revisit-gate.md +1 -1
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -1
- package/dist/agent-src/rules/domain-adoption-policy.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +1 -1
- package/dist/agent-src/rules/external-code-graph-interop.md +1 -1
- package/dist/agent-src/rules/fast-path-marker-visibility.md +43 -7
- package/dist/agent-src/rules/finance-safety-floor.md +1 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +3 -3
- package/dist/agent-src/rules/history-discipline.md +1 -1
- package/dist/agent-src/rules/legal-safety-floor.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +5 -5
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +10 -8
- package/dist/agent-src/rules/media-governance-routing.md +13 -13
- package/dist/agent-src/rules/media-sync-ground-truth.md +29 -11
- package/dist/agent-src/rules/no-cheap-questions.md +7 -7
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +29 -18
- package/dist/agent-src/rules/non-destructive-by-default.md +5 -23
- package/dist/agent-src/rules/onboarding-gate.md +14 -4
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/prefer-enums-over-literals.md +9 -9
- package/dist/agent-src/rules/preservation-guard.md +10 -2
- package/dist/agent-src/rules/question-not-instruction.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +6 -6
- package/dist/agent-src/rules/rule-type-governance.md +1 -1
- package/dist/agent-src/rules/scale-discipline.md +1 -1
- package/dist/agent-src/rules/scope-control.md +1 -1
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -1
- package/dist/agent-src/rules/security-sensitive-stop.md +17 -11
- package/dist/agent-src/rules/senior-engineering-discipline.md +17 -17
- package/dist/agent-src/rules/session-canary.md +1 -1
- package/dist/agent-src/rules/skill-quality.md +1 -1
- package/dist/agent-src/rules/source-confidentiality.md +21 -19
- package/dist/agent-src/rules/source-of-truth.md +6 -5
- package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -2
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
- package/dist/agent-src/rules/think-before-action.md +5 -5
- package/dist/agent-src/rules/token-budget-discipline.md +5 -3
- package/dist/agent-src/rules/token-efficiency.md +2 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -4
- package/dist/agent-src/rules/user-interaction.md +12 -0
- package/dist/agent-src/rules/verify-before-complete.md +4 -24
- package/dist/agent-src/skills/adversarial-review/SKILL.md +2 -2
- package/dist/agent-src/skills/agent-security-review/SKILL.md +52 -45
- package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +2 -2
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +20 -20
- package/dist/agent-src/skills/ai-council/SKILL.md +185 -128
- package/dist/agent-src/skills/analysis-skill-router/SKILL.md +3 -3
- package/dist/agent-src/skills/api-endpoint/SKILL.md +2 -2
- package/dist/agent-src/skills/artisan-commands/SKILL.md +2 -2
- package/dist/agent-src/skills/async-python-patterns/SKILL.md +8 -7
- package/dist/agent-src/skills/authz-review/SKILL.md +16 -15
- package/dist/agent-src/skills/aws-infrastructure/SKILL.md +5 -5
- package/dist/agent-src/skills/blade-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +30 -12
- package/dist/agent-src/skills/code-intelligence/SKILL.md +0 -1
- package/dist/agent-src/skills/code-intelligence/evals/triggers.json +52 -0
- package/dist/agent-src/skills/code-review/SKILL.md +6 -6
- package/dist/agent-src/skills/command-routing/SKILL.md +5 -4
- package/dist/agent-src/skills/composer-packages/SKILL.md +2 -2
- package/dist/agent-src/skills/condense-memory/SKILL.md +43 -41
- package/dist/agent-src/skills/context-document/SKILL.md +23 -21
- package/dist/agent-src/skills/copilot-config/SKILL.md +6 -1
- package/dist/agent-src/skills/decision-record/SKILL.md +20 -18
- package/dist/agent-src/skills/design-intelligence/SKILL.md +4 -4
- package/dist/agent-src/skills/design-review/SKILL.md +15 -14
- package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +28 -25
- package/dist/agent-src/skills/devcontainer/SKILL.md +2 -2
- package/dist/agent-src/skills/docker/SKILL.md +1 -1
- package/dist/agent-src/skills/docx-authoring/evals/triggers.json +52 -0
- package/dist/agent-src/skills/emit-tickets/SKILL.md +2 -2
- package/dist/agent-src/skills/error-handling-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +2 -2
- package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +0 -1
- package/dist/agent-src/skills/evaluate-llm-feature/evals/triggers.json +52 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +6 -5
- package/dist/agent-src/skills/feature-planning/SKILL.md +2 -2
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +3 -3
- package/dist/agent-src/skills/flux/SKILL.md +1 -1
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +13 -13
- package/dist/agent-src/skills/git-workflow/SKILL.md +33 -33
- package/dist/agent-src/skills/grafana/SKILL.md +5 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +83 -66
- package/dist/agent-src/skills/humanizer/data/patterns.md +14 -14
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +4 -4
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +82 -37
- package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +17 -5
- package/dist/agent-src/skills/judge-injection-defense/SKILL.md +63 -25
- package/dist/agent-src/skills/judge-security-auditor/SKILL.md +2 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +72 -28
- package/dist/agent-src/skills/laravel-horizon/SKILL.md +5 -0
- package/dist/agent-src/skills/laravel-mail/SKILL.md +5 -4
- package/dist/agent-src/skills/laravel-middleware/SKILL.md +5 -4
- package/dist/agent-src/skills/laravel-notifications/SKILL.md +10 -9
- package/dist/agent-src/skills/laravel-pennant/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-pulse/SKILL.md +9 -8
- package/dist/agent-src/skills/laravel-reverb/SKILL.md +6 -5
- package/dist/agent-src/skills/laravel-scheduling/SKILL.md +6 -1
- package/dist/agent-src/skills/laravel-validation/SKILL.md +12 -0
- package/dist/agent-src/skills/launch-readiness/SKILL.md +1 -1
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +7 -7
- package/dist/agent-src/skills/learning-tutor/SKILL.md +44 -43
- package/dist/agent-src/skills/learning-tutor/evals/triggers.json +52 -0
- package/dist/agent-src/skills/license-compliance-audit/SKILL.md +7 -7
- package/dist/agent-src/skills/license-compliance-borrow-check/SKILL.md +3 -3
- package/dist/agent-src/skills/license-compliance-credits/SKILL.md +4 -4
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +0 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +69 -64
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +6 -6
- package/dist/agent-src/skills/pdf-tools/evals/triggers.json +52 -0
- package/dist/agent-src/skills/performance-analysis/SKILL.md +7 -3
- package/dist/agent-src/skills/persona-improvement/SKILL.md +0 -2
- package/dist/agent-src/skills/persona-improvement/evals/triggers.json +52 -0
- package/dist/agent-src/skills/php-coder/SKILL.md +10 -0
- package/dist/agent-src/skills/prediction-pool-optimizer/SKILL.md +129 -120
- package/dist/agent-src/skills/prediction-pool-optimizer/reference/ev-fixtures.md +36 -35
- package/dist/agent-src/skills/prediction-pool-optimizer/reference/odds-and-bonus.md +45 -41
- package/dist/agent-src/skills/project-analysis-core/SKILL.md +4 -4
- package/dist/agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
- package/dist/agent-src/skills/project-analysis-react/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
- package/dist/agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/prompt-validator/SKILL.md +39 -39
- package/dist/agent-src/skills/quality-tools/SKILL.md +15 -15
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +55 -19
- package/dist/agent-src/skills/react-shadcn-ui/reference/registry.md +29 -27
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -2
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +25 -25
- package/dist/agent-src/skills/recursive-verification/SKILL.md +6 -5
- package/dist/agent-src/skills/refine-prompt/SKILL.md +1 -1
- package/dist/agent-src/skills/refine-ticket/SKILL.md +20 -2
- package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
- package/dist/agent-src/skills/requesting-code-review/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-management/SKILL.md +80 -66
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +84 -77
- package/dist/agent-src/skills/rule-refactor/SKILL.md +1 -1
- package/dist/agent-src/skills/screenshot-hygiene/evals/triggers.json +52 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +26 -9
- package/dist/agent-src/skills/security-maturity-assessment/SKILL.md +38 -16
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +14 -11
- package/dist/agent-src/skills/skill-writing/SKILL.md +9 -8
- package/dist/agent-src/skills/song-to-script/SKILL.md +93 -85
- package/dist/agent-src/skills/sql-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +4 -3
- package/dist/agent-src/skills/standards-from-config/SKILL.md +61 -43
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +22 -8
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +28 -2
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +13 -13
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +22 -21
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/technical-specification/SKILL.md +5 -0
- package/dist/agent-src/skills/terraform/SKILL.md +2 -2
- package/dist/agent-src/skills/terragrunt/SKILL.md +8 -8
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +1 -1
- package/dist/agent-src/skills/threat-modeling/SKILL.md +19 -13
- package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +0 -1
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +9 -9
- package/dist/agent-src/skills/validate-feature-fit/SKILL.md +3 -1
- package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +4 -6
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +67 -61
- package/dist/agent-src/skills/video-director/SKILL.md +7 -7
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +3 -3
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/contexts/knowledge-pages.md +47 -45
- package/dist/agent-src/templates/contexts/lesson-card.md +2 -2
- package/dist/agent-src/templates/contexts/tenant-boundaries.md +2 -2
- package/dist/agent-src/templates/contexts.md +1 -1
- package/dist/agent-src/templates/features.md +1 -1
- package/dist/agent-src/templates/roadmaps.md +125 -116
- package/dist/agent-src/templates/rule.md +2 -1
- package/dist/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/dist/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/dist/agent-src/templates/scripts/tier_usage_report.ts +2 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +1 -1
- package/dist/agent-src/user-types/README.md +8 -8
- package/dist/agent-src/user-types/galabau-field-crew.md +34 -33
- package/dist/agent-src/user-types/metalworking-shop.md +35 -34
- package/dist/agent-src/user-types/truck-driver.md +22 -20
- package/dist/cli/python/workspace_roles.js +2 -2
- package/dist/cli/python/workspace_roles.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +88 -62
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +186 -12
- package/dist/install/install.mjs +8 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1199 -857
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/compile_time_toggles.js +48 -0
- package/dist/scripts/_lib/compile_time_toggles.js.map +1 -0
- package/dist/scripts/condense.js +234 -217
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/userMd/utils.js +30 -0
- package/dist/shared/userMd/utils.js.map +1 -1
- package/dist/ui/assets/index-BpSQt5vm.js.map +1 -1
- package/docs/guidelines/agent-infra/api-cost-levers.md +56 -5
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +93 -33
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/src/agent-src/templates/scripts/tier_usage_report.ts +2 -2
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -2
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +1 -1
- package/src/config/agent-settings.template.yml +32 -2
- package/src/config/ci-local-parity.yml +142 -0
- package/src/config/gate-coverage.yml +127 -0
- package/src/config/preamble-payload-budget.json +44 -0
- package/src/scripts/_cli/cmd_doctor.ts +78 -2
- package/src/scripts/_lib/agent_user_profile.ts +381 -0
- package/src/scripts/_lib/agents_overlay.ts +9 -0
- package/src/scripts/_lib/anchor_eval.ts +311 -0
- package/src/scripts/_lib/cc_transcript.ts +316 -0
- package/src/scripts/_lib/compile_time_toggles.ts +79 -0
- package/src/scripts/_lib/confusables.ts +101 -0
- package/src/scripts/_lib/duplicate_scope_census.ts +77 -0
- package/src/scripts/_lib/managed_agents_folder.ts +181 -0
- package/src/scripts/_lib/payload_hash_drift.ts +126 -0
- package/src/scripts/_lib/preservation_migration.ts +291 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +166 -2
- package/src/scripts/_lib/scan_scope.ts +115 -0
- package/src/scripts/_lib/user_global_memory_audit.ts +187 -0
- package/src/scripts/_lib/user_global_observations.ts +830 -0
- package/src/scripts/_lib/user_global_revocations.ts +110 -0
- package/src/scripts/_lib/user_memory_gate_counters.ts +291 -0
- package/src/scripts/ai_council/blind_review.ts +0 -0
- package/src/scripts/ai_council/budget_guard.ts +90 -11
- package/src/scripts/ai_council/bundler.ts +2 -2
- package/src/scripts/ai_council/clients.ts +59 -3
- package/src/scripts/ai_council/config.ts +38 -0
- package/src/scripts/ai_council/orchestrator.ts +54 -9
- package/src/scripts/ai_team/team_dispatch.ts +17 -6
- package/src/scripts/anchor_eval_falsify.ts +198 -0
- package/src/scripts/anchor_scoring_run.ts +239 -0
- package/src/scripts/annotate_discovery.ts +1 -65
- package/src/scripts/bench_quality_run.ts +168 -4
- package/src/scripts/cache_realization_report.ts +782 -0
- package/src/scripts/check_artefact_count_messaging.ts +5 -1
- package/src/scripts/check_augment_description_cap.ts +15 -4
- package/src/scripts/check_ci_local_parity.ts +317 -0
- package/src/scripts/check_condensation.ts +96 -191
- package/src/scripts/check_context_paths.ts +38 -9
- package/src/scripts/check_gate_coverage.ts +299 -0
- package/src/scripts/check_iron_law_prominence.ts +22 -1
- package/src/scripts/check_kernel_prefix_stability.ts +0 -0
- package/src/scripts/check_no_new_legacy_path.ts +16 -6
- package/src/scripts/check_no_roadmap_refs.ts +22 -9
- package/src/scripts/check_originality_freshness.ts +167 -0
- package/src/scripts/check_preamble_payload_budget.ts +130 -0
- package/src/scripts/check_proposal.ts +0 -0
- package/src/scripts/check_references.ts +0 -1
- package/src/scripts/check_safety_floor_untouched.ts +171 -24
- package/src/scripts/check_site_links.ts +62 -3
- package/src/scripts/ci_status.ts +0 -0
- package/src/scripts/cluster_near_miss_patterns.ts +0 -0
- package/src/scripts/code_graph/build.ts +0 -0
- package/src/scripts/command_suggester/types.ts +0 -0
- package/src/scripts/compile_router.ts +10 -14
- package/src/scripts/condense.sh +3 -4
- package/src/scripts/condense.ts +247 -228
- package/src/scripts/cost/track.mjs +168 -33
- package/src/scripts/cost_summary.ts +33 -1
- package/src/scripts/council_cli.ts +45 -3
- package/src/scripts/discovery_graph.ts +0 -0
- package/src/scripts/encoding_corpus.ts +500 -0
- package/src/scripts/encoding_corpus_report.ts +216 -0
- package/src/scripts/generate_index.ts +19 -2
- package/src/scripts/install-hooks.sh +2 -2
- package/src/scripts/learning_sidecar.ts +0 -0
- package/src/scripts/lint_confusables.ts +10 -72
- package/src/scripts/lint_featured_skills.ts +0 -0
- package/src/scripts/lint_hidden_unicode.ts +208 -0
- package/src/scripts/lint_load_context.ts +17 -5
- package/src/scripts/lint_new_skill_gate.ts +9 -3
- package/src/scripts/lint_no_python_twin_rationale.ts +17 -2
- package/src/scripts/lint_originality.ts +1 -1
- package/src/scripts/lint_skill_originality.ts +1 -1
- package/src/scripts/lint_ticket_buildable.ts +1 -1
- package/src/scripts/mcp_server/tools.ts +4 -4
- package/src/scripts/measure_scope_dedup.ts +224 -0
- package/src/scripts/memory_lookup.ts +0 -0
- package/src/scripts/mine_session.ts +98 -0
- package/src/scripts/orchestration_payload_hash_drift.ts +145 -0
- package/src/scripts/preamble_byte_census.ts +574 -0
- package/src/scripts/probe_skill_registration.ts +2 -2
- package/src/scripts/redact_hook_capture.ts +13 -0
- package/src/scripts/release.ts +65 -10
- package/src/scripts/router_telemetry.ts +1 -1
- package/src/scripts/second_brain_retrieval.ts +14 -4
- package/src/scripts/skill_linter.ts +5 -0
- package/src/scripts/surface-tiers.yml +2 -0
- package/src/scripts/validate_pack_yaml.ts +0 -0
- package/src/server/schemas/settings.ts +8 -0
- package/src/shared/userMd/utils.ts +31 -0
- package/dist/agent-src/rules/telegraph-speak.md +0 -87
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preservation-conformance verifier for safety-floor rule edits.
|
|
3
|
+
*
|
|
4
|
+
* `check_safety_floor_untouched` blocks every edit to the four safety-floor
|
|
5
|
+
* rules. That is right for substantive edits and wrong for a P4 migration, which
|
|
6
|
+
* moves lookup material into a `load_context:` target without losing anything.
|
|
7
|
+
* This module decides which one a diff is — by CHECKING, never by trusting a
|
|
8
|
+
* label, a commit message, or a contributor's word. A migration that cannot be
|
|
9
|
+
* proven conformant is treated as substantive and blocked (fail-closed).
|
|
10
|
+
*
|
|
11
|
+
* The contract it enforces is `preservation-guard`'s: every paragraph, list
|
|
12
|
+
* item, and fenced block from the source survives — either still in the rule
|
|
13
|
+
* (telegraph condensation is allowed, so matching is fuzzy) or verbatim in one
|
|
14
|
+
* of the rule's declared `load_context:` targets (a migration is a move, so
|
|
15
|
+
* matching there is near-exact). Iron Law sections are stricter still: their
|
|
16
|
+
* headings must survive at the same level and their fenced blocks byte-for-byte.
|
|
17
|
+
*
|
|
18
|
+
* Pure functions over strings — no git, no fs — so the whole decision is unit
|
|
19
|
+
* testable without manufacturing commits. That absence is why the guard it
|
|
20
|
+
* serves shipped inert for months.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** A structural unit of a markdown body: heading, paragraph, list item, or fence. */
|
|
24
|
+
export interface Unit {
|
|
25
|
+
kind: 'heading' | 'paragraph' | 'list-item' | 'fence';
|
|
26
|
+
/** Heading depth (1-6) for `heading`, else 0. */
|
|
27
|
+
level: number;
|
|
28
|
+
/** Raw text as it appeared, minus the trailing newline. */
|
|
29
|
+
raw: string;
|
|
30
|
+
/** Normalised form used for comparison. */
|
|
31
|
+
norm: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Strip frontmatter; return the body. */
|
|
35
|
+
export function strip_frontmatter(text: string): string {
|
|
36
|
+
const m = /^---\n[\s\S]*?\n---\n/.exec(text);
|
|
37
|
+
return m === null ? text : text.slice(m[0].length);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `load_context:` targets declared in a rule's frontmatter, in order. */
|
|
41
|
+
export function load_context_targets(text: string): string[] {
|
|
42
|
+
const fm = /^---\n([\s\S]*?)\n---\n/.exec(text);
|
|
43
|
+
if (fm === null) return [];
|
|
44
|
+
const out: string[] = [];
|
|
45
|
+
let inBlock = false;
|
|
46
|
+
for (const line of fm[1]!.split('\n')) {
|
|
47
|
+
if (/^load_context:\s*$/.test(line)) {
|
|
48
|
+
inBlock = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (inBlock) {
|
|
52
|
+
const item = /^\s*-\s+(.+?)\s*$/.exec(line);
|
|
53
|
+
if (item !== null) {
|
|
54
|
+
out.push(item[1]!.replace(/^["']|["']$/g, ''));
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (/^\S/.test(line)) inBlock = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Normalise a unit for comparison.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately blind to the three things a legitimate migration changes and
|
|
67
|
+
* nothing else: heading depth (an H2 becomes an H3 under a host section), link
|
|
68
|
+
* targets (relative paths are rewritten for the new location), and whitespace.
|
|
69
|
+
* Everything that carries obligation — words, emphasis, negations — is compared
|
|
70
|
+
* as written.
|
|
71
|
+
*/
|
|
72
|
+
export function normalise(raw: string): string {
|
|
73
|
+
return raw
|
|
74
|
+
.replace(/^#{1,6}\s+/gm, '') // heading depth
|
|
75
|
+
.replace(/\]\([^)]*\)/g, ']()') // link targets
|
|
76
|
+
.replace(/^\s*[-*+]\s+/gm, '') // list markers
|
|
77
|
+
.replace(/\s+/g, ' ')
|
|
78
|
+
.trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Split a markdown body into structural units. Fenced blocks stay intact. */
|
|
82
|
+
export function parse_units(body: string): Unit[] {
|
|
83
|
+
const lines = body.split('\n');
|
|
84
|
+
const units: Unit[] = [];
|
|
85
|
+
let para: string[] = [];
|
|
86
|
+
|
|
87
|
+
const flush = (): void => {
|
|
88
|
+
if (para.length === 0) return;
|
|
89
|
+
const raw = para.join('\n');
|
|
90
|
+
if (raw.trim() !== '') units.push({ kind: 'paragraph', level: 0, raw, norm: normalise(raw) });
|
|
91
|
+
para = [];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
95
|
+
const line = lines[i]!;
|
|
96
|
+
const fence = /^(\s*)(`{3,}|~{3,})/.exec(line);
|
|
97
|
+
if (fence !== null) {
|
|
98
|
+
flush();
|
|
99
|
+
const marker = fence[2]!;
|
|
100
|
+
const block = [line];
|
|
101
|
+
i += 1;
|
|
102
|
+
while (i < lines.length) {
|
|
103
|
+
block.push(lines[i]!);
|
|
104
|
+
if (lines[i]!.trimStart().startsWith(marker)) break;
|
|
105
|
+
i += 1;
|
|
106
|
+
}
|
|
107
|
+
const raw = block.join('\n');
|
|
108
|
+
units.push({ kind: 'fence', level: 0, raw, norm: raw.replace(/\s+$/gm, '') });
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
112
|
+
if (heading !== null) {
|
|
113
|
+
flush();
|
|
114
|
+
units.push({
|
|
115
|
+
kind: 'heading',
|
|
116
|
+
level: heading[1]!.length,
|
|
117
|
+
raw: line,
|
|
118
|
+
norm: normalise(line),
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const item = /^\s*[-*+]\s+/.test(line) || /^\s*\d+\.\s+/.test(line);
|
|
123
|
+
if (item) {
|
|
124
|
+
flush();
|
|
125
|
+
units.push({ kind: 'list-item', level: 0, raw: line, norm: normalise(line) });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (line.trim() === '') {
|
|
129
|
+
flush();
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
para.push(line);
|
|
133
|
+
}
|
|
134
|
+
flush();
|
|
135
|
+
return units;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Dice coefficient over character bigrams — cheap, order-sensitive enough. */
|
|
139
|
+
export function similarity(a: string, b: string): number {
|
|
140
|
+
if (a === b) return 1;
|
|
141
|
+
if (a.length < 2 || b.length < 2) return a === b ? 1 : 0;
|
|
142
|
+
const grams = (s: string): Map<string, number> => {
|
|
143
|
+
const m = new Map<string, number>();
|
|
144
|
+
for (let i = 0; i < s.length - 1; i += 1) {
|
|
145
|
+
const g = s.slice(i, i + 2);
|
|
146
|
+
m.set(g, (m.get(g) ?? 0) + 1);
|
|
147
|
+
}
|
|
148
|
+
return m;
|
|
149
|
+
};
|
|
150
|
+
const ga = grams(a);
|
|
151
|
+
const gb = grams(b);
|
|
152
|
+
let overlap = 0;
|
|
153
|
+
for (const [g, n] of ga) overlap += Math.min(n, gb.get(g) ?? 0);
|
|
154
|
+
return (2 * overlap) / (a.length - 1 + b.length - 1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** A unit still present in the rule after telegraph condensation. */
|
|
158
|
+
const KEPT_IN_RULE = 0.6;
|
|
159
|
+
/** A unit moved into a context file — a move, so near-verbatim. */
|
|
160
|
+
const MOVED_TO_CONTEXT = 0.9;
|
|
161
|
+
|
|
162
|
+
export interface Finding {
|
|
163
|
+
code:
|
|
164
|
+
| 'iron-law-heading-lost'
|
|
165
|
+
| 'iron-law-fence-changed'
|
|
166
|
+
| 'passage-lost'
|
|
167
|
+
| 'unexplained-addition';
|
|
168
|
+
detail: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Units belonging to an Iron Law section (heading matches, until the next same-or-shallower heading). */
|
|
172
|
+
export function iron_law_units(units: readonly Unit[]): Unit[] {
|
|
173
|
+
const out: Unit[] = [];
|
|
174
|
+
let depth: number | null = null;
|
|
175
|
+
for (const u of units) {
|
|
176
|
+
if (u.kind === 'heading') {
|
|
177
|
+
if (/^(the\s+)?iron\s+law/i.test(u.norm)) {
|
|
178
|
+
depth = u.level;
|
|
179
|
+
out.push(u);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (depth !== null && u.level <= depth) depth = null;
|
|
183
|
+
}
|
|
184
|
+
if (depth !== null) out.push(u);
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface VerifyInput {
|
|
190
|
+
/** Rule file content before the change. */
|
|
191
|
+
base_rule: string;
|
|
192
|
+
/** Rule file content after the change. */
|
|
193
|
+
head_rule: string;
|
|
194
|
+
/** Declared `load_context` target path → content AFTER the change. */
|
|
195
|
+
head_contexts: ReadonlyMap<string, string>;
|
|
196
|
+
/** Same targets, content BEFORE — so only ADDED context text counts as a landing site. */
|
|
197
|
+
base_contexts: ReadonlyMap<string, string>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Decide whether a safety-floor rule edit is a preservation-conforming
|
|
202
|
+
* migration. Returns [] when it is; otherwise every reason it is not.
|
|
203
|
+
*/
|
|
204
|
+
export function verify_migration(input: VerifyInput): Finding[] {
|
|
205
|
+
const findings: Finding[] = [];
|
|
206
|
+
const baseUnits = parse_units(strip_frontmatter(input.base_rule));
|
|
207
|
+
const headUnits = parse_units(strip_frontmatter(input.head_rule));
|
|
208
|
+
|
|
209
|
+
// ── Iron Law: headings survive at depth, fences survive byte-for-byte ──
|
|
210
|
+
const baseIron = iron_law_units(baseUnits);
|
|
211
|
+
const headIron = iron_law_units(headUnits);
|
|
212
|
+
for (const u of baseIron) {
|
|
213
|
+
if (u.kind === 'heading') {
|
|
214
|
+
const hit = headIron.some((h) => h.kind === 'heading' && h.norm === u.norm && h.level === u.level);
|
|
215
|
+
if (!hit) {
|
|
216
|
+
findings.push({
|
|
217
|
+
code: 'iron-law-heading-lost',
|
|
218
|
+
detail: `${u.raw.trim()} — must survive verbatim at the same level`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
} else if (u.kind === 'fence') {
|
|
222
|
+
const hit = headIron.some((h) => h.kind === 'fence' && h.raw === u.raw);
|
|
223
|
+
if (!hit) {
|
|
224
|
+
findings.push({
|
|
225
|
+
code: 'iron-law-fence-changed',
|
|
226
|
+
detail: `fenced block under ${u.raw.split('\n')[0] ?? ''} changed — Iron Law fences are byte-exact`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Landing sites: only text ADDED to a DECLARED context counts ──
|
|
233
|
+
//
|
|
234
|
+
// The declared set is re-derived here from the rule's own frontmatter rather
|
|
235
|
+
// than trusted from the caller. A migration is only legitimate into a file
|
|
236
|
+
// the rule actually points readers at; dropping a passage into some other
|
|
237
|
+
// file leaves it unreachable from the rule and is a loss, not a move.
|
|
238
|
+
const declared = load_context_targets(input.head_rule);
|
|
239
|
+
const isDeclared = (p: string): boolean =>
|
|
240
|
+
declared.some((rel) => p === rel || p.endsWith(`/${rel}`));
|
|
241
|
+
const landing: string[] = [];
|
|
242
|
+
for (const [path, head] of input.head_contexts) {
|
|
243
|
+
if (!isDeclared(path)) continue;
|
|
244
|
+
const before = new Set(parse_units(strip_frontmatter(input.base_contexts.get(path) ?? '')).map((u) => u.norm));
|
|
245
|
+
for (const u of parse_units(strip_frontmatter(head))) {
|
|
246
|
+
if (!before.has(u.norm)) landing.push(u.norm);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Every base unit survives: in the rule (fuzzy) or in a context (near-exact) ──
|
|
251
|
+
const headNorms = headUnits.map((u) => u.norm);
|
|
252
|
+
const usedHead = new Set<number>();
|
|
253
|
+
for (const u of baseUnits) {
|
|
254
|
+
let bestIdx = -1;
|
|
255
|
+
let best = 0;
|
|
256
|
+
for (let i = 0; i < headNorms.length; i += 1) {
|
|
257
|
+
if (usedHead.has(i)) continue;
|
|
258
|
+
const s = similarity(u.norm, headNorms[i]!);
|
|
259
|
+
if (s > best) {
|
|
260
|
+
best = s;
|
|
261
|
+
bestIdx = i;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (best >= KEPT_IN_RULE) {
|
|
265
|
+
usedHead.add(bestIdx);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const moved = landing.some((l) => similarity(u.norm, l) >= MOVED_TO_CONTEXT);
|
|
269
|
+
if (!moved) {
|
|
270
|
+
findings.push({
|
|
271
|
+
code: 'passage-lost',
|
|
272
|
+
detail: `${u.kind}: ${u.raw.trim().slice(0, 90)}… — not kept in the rule and not found in any declared load_context target`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ── Nothing genuinely new in the rule: a migration only removes ──
|
|
278
|
+
for (let i = 0; i < headUnits.length; i += 1) {
|
|
279
|
+
if (usedHead.has(i)) continue;
|
|
280
|
+
const u = headUnits[i]!;
|
|
281
|
+
const back = baseUnits.some((b) => similarity(b.norm, u.norm) >= KEPT_IN_RULE);
|
|
282
|
+
if (!back) {
|
|
283
|
+
findings.push({
|
|
284
|
+
code: 'unexplained-addition',
|
|
285
|
+
detail: `${u.kind}: ${u.raw.trim().slice(0, 90)}… — new content in a safety-floor rule is a substantive edit, not a migration`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return findings;
|
|
291
|
+
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* (road-to-retrieval-substrate-hardening B6).
|
|
4
4
|
*
|
|
5
5
|
* Corpus content — especially user-ingested knowledge chunks under
|
|
6
|
-
* `agents/memory/knowledge/` — flows into the agent context
|
|
7
|
-
*
|
|
6
|
+
* `agents/memory/knowledge/` — flows into the agent context on the retrieval
|
|
7
|
+
* read surfaces. An attacker (or a compromised ingested source) can embed
|
|
8
8
|
* hidden-instruction vectors (bidi controls, zero-width chars, Unicode Tag
|
|
9
9
|
* block) or control-char noise that the model may act on — the untrusted-input
|
|
10
10
|
* / lethal-trifecta injection surface. This floor strips those vectors from
|
|
@@ -15,12 +15,49 @@
|
|
|
15
15
|
* layer + unbounded length; visible content is the agent's to read as data.
|
|
16
16
|
* Codepoint classes are shared with `lint_hidden_unicode` (one source of truth
|
|
17
17
|
* for what counts as a hidden-instruction vector).
|
|
18
|
+
*
|
|
19
|
+
* WHERE IT ACTUALLY RUNS — measured end-to-end, not asserted. Keep this list
|
|
20
|
+
* honest: an earlier version of this header named surfaces by intent, and the
|
|
21
|
+
* legacy-envelope gap below went unnoticed for exactly that reason. Every
|
|
22
|
+
* claim here is backed by a probe row in
|
|
23
|
+
* `agents/evidence/reports/sanitize-floor-wiring.md`:
|
|
24
|
+
*
|
|
25
|
+
* - `retrieve_v1()` — yes (`memory_lookup.ts` calls `sanitize_entry`)
|
|
26
|
+
* - `memory_get_v1()` — yes (same)
|
|
27
|
+
* - MCP `memory_lookup` / `memory_get` tools — inherited from the two above
|
|
28
|
+
* - `retrieve()` / `retrieve_with_meta()` — yes, since the S0.0 wiring fix;
|
|
29
|
+
* before it they emitted every vector intact, and so did the CLI default
|
|
30
|
+
* (`--envelope legacy`), which is the path the rules document
|
|
31
|
+
* - inter-agent channels (`ai_team` / `ai_council` model replies) — the
|
|
32
|
+
* inbound parse choke point only; the subagent boundary itself is a HOST
|
|
33
|
+
* primitive this package cannot reach (see the report, § S0.0b)
|
|
34
|
+
*
|
|
35
|
+
* Anything not on that list is uncovered. Do not widen the list without a
|
|
36
|
+
* probe row to back it.
|
|
18
37
|
*/
|
|
19
38
|
import { _classify } from '../lint_hidden_unicode.js';
|
|
39
|
+
import { TOKEN_RE, classifyToken } from './confusables.js';
|
|
20
40
|
|
|
21
41
|
/** Hard per-field length cap — bounds a runaway/adversarial body. */
|
|
22
42
|
export const MAX_FIELD_CHARS = 8192;
|
|
23
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Invisible fillers — render as blank but are not whitespace, so they sit in
|
|
46
|
+
* neither the zero-width nor the bidi set and survived the original floor
|
|
47
|
+
* (measured: `agents/evidence/reports/encoding-channel-coverage.md` row 18).
|
|
48
|
+
*
|
|
49
|
+
* These are the ONLY channel Phase 1 chose to strip rather than flag: invisible
|
|
50
|
+
* by nature, with no legitimate role in this corpus, so folding them into the
|
|
51
|
+
* existing invisible set carries no corruption risk. Every other uncovered
|
|
52
|
+
* channel is a flag, because rewriting visible text can corrupt legitimate
|
|
53
|
+
* content.
|
|
54
|
+
*/
|
|
55
|
+
const _INVISIBLE_FILLERS: ReadonlySet<number> = new Set([
|
|
56
|
+
0x3164, // HANGUL FILLER
|
|
57
|
+
0x115f, // HANGUL CHOSEONG FILLER
|
|
58
|
+
0x1160, // HANGUL JUNGSEONG FILLER
|
|
59
|
+
]);
|
|
60
|
+
|
|
24
61
|
/** True for a C0/C1 control char that is NOT a benign whitespace (\t, \n). */
|
|
25
62
|
function _isStrippableControl(cp: number): boolean {
|
|
26
63
|
if (cp === 0x09 || cp === 0x0a) return false; // keep tab + newline
|
|
@@ -42,11 +79,138 @@ export function sanitize_text(s: string): string {
|
|
|
42
79
|
if (cp === undefined) continue;
|
|
43
80
|
if (_classify(cp) !== null) continue; // hidden-instruction vector → drop
|
|
44
81
|
if (_isStrippableControl(cp)) continue; // control-char noise → drop
|
|
82
|
+
if (_INVISIBLE_FILLERS.has(cp)) continue; // blank-but-not-whitespace → drop
|
|
45
83
|
out += ch;
|
|
46
84
|
}
|
|
47
85
|
return out.length > MAX_FIELD_CHARS ? out.slice(0, MAX_FIELD_CHARS) : out;
|
|
48
86
|
}
|
|
49
87
|
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Visible-layer scanner — FLAG ONLY, never rewrites
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
//
|
|
92
|
+
// `sanitize_text` strips the invisible layer. This reports the VISIBLE layer,
|
|
93
|
+
// which the floor deliberately does not touch: folding a confusable to a Latin
|
|
94
|
+
// skeleton, or NFKC-normalising a math-alphanumeric run, corrupts any body that
|
|
95
|
+
// legitimately contains mixed script, CJK punctuation, or a ligature. Phase 1
|
|
96
|
+
// measured each channel and chose flag-not-rewrite for every one of these:
|
|
97
|
+
// `agents/evidence/reports/encoding-channel-coverage.md` § Disposition.
|
|
98
|
+
//
|
|
99
|
+
// Deterministic and pure: no model call, no network, no I/O. Safe to run in CI
|
|
100
|
+
// and on a request path.
|
|
101
|
+
//
|
|
102
|
+
// Not scanned, deliberately, with the reason recorded so it is not mistaken for
|
|
103
|
+
// an oversight:
|
|
104
|
+
// - confusable whitespace (NBSP, figure space) — pervasive in real prose
|
|
105
|
+
// - HTML/XML entities — decoding them would CREATE the payload
|
|
106
|
+
// - nested multibase (base64) — legitimate throughout this repo; semantic,
|
|
107
|
+
// not structural
|
|
108
|
+
// - structured-data key ordering — not a codepoint channel at all, and
|
|
109
|
+
// reordering keys would break the byte-identical v1 envelope contract
|
|
110
|
+
// - word-order permutation — carrier text with legitimate word order is
|
|
111
|
+
// indistinguishable from permuted text without semantics
|
|
112
|
+
|
|
113
|
+
/** Codepoint thresholds — a run is the signal; a single occurrence is not. */
|
|
114
|
+
const _VS_RUN_MIN = 3;
|
|
115
|
+
const _COMBINING_RUN_MIN = 5;
|
|
116
|
+
|
|
117
|
+
export interface EncodingFinding {
|
|
118
|
+
/** Stable channel id, matching the frozen corpus labels. */
|
|
119
|
+
readonly channel: string;
|
|
120
|
+
/** Why it fired, with the concrete evidence (codepoint or token). */
|
|
121
|
+
readonly detail: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function _isVariationSelector(cp: number): boolean {
|
|
125
|
+
return (cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0xe0100 && cp <= 0xe01ef);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function _isCombiningMark(cp: number): boolean {
|
|
129
|
+
return cp >= 0x0300 && cp <= 0x036f;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Report visible-layer encoding signals in a string. Never mutates it.
|
|
134
|
+
*
|
|
135
|
+
* The confusable signature is NOT reimplemented here — it is the shared
|
|
136
|
+
* `classifyToken` from `_lib/confusables.ts`, the single definition this
|
|
137
|
+
* package's authoring-time linter also uses, so the two surfaces cannot drift
|
|
138
|
+
* apart on what counts as a homoglyph.
|
|
139
|
+
*/
|
|
140
|
+
export function scan_encoding_findings(s: string): EncodingFinding[] {
|
|
141
|
+
const out: EncodingFinding[] = [];
|
|
142
|
+
|
|
143
|
+
// Longest run of variation selectors / combining marks anywhere in the
|
|
144
|
+
// string. A run is what distinguishes steganography from legitimate use:
|
|
145
|
+
// U+FE0F is the emoji presentation selector and diacritics are normal in
|
|
146
|
+
// many languages, so a single occurrence is never the signal.
|
|
147
|
+
let vsRun = 0;
|
|
148
|
+
let vsMax = 0;
|
|
149
|
+
let cmRun = 0;
|
|
150
|
+
let cmMax = 0;
|
|
151
|
+
let hasMathAlnum = false;
|
|
152
|
+
let hasFullwidth = false;
|
|
153
|
+
for (const ch of s) {
|
|
154
|
+
const cp = ch.codePointAt(0);
|
|
155
|
+
if (cp === undefined) continue;
|
|
156
|
+
if (_isVariationSelector(cp)) {
|
|
157
|
+
vsRun += 1;
|
|
158
|
+
vsMax = Math.max(vsMax, vsRun);
|
|
159
|
+
} else {
|
|
160
|
+
vsRun = 0;
|
|
161
|
+
}
|
|
162
|
+
if (_isCombiningMark(cp)) {
|
|
163
|
+
cmRun += 1;
|
|
164
|
+
cmMax = Math.max(cmMax, cmRun);
|
|
165
|
+
} else {
|
|
166
|
+
cmRun = 0;
|
|
167
|
+
}
|
|
168
|
+
if (cp >= 0x1d400 && cp <= 0x1d7ff) hasMathAlnum = true;
|
|
169
|
+
if (cp >= 0xff01 && cp <= 0xff5e) hasFullwidth = true;
|
|
170
|
+
}
|
|
171
|
+
if (vsMax >= _VS_RUN_MIN) {
|
|
172
|
+
out.push({
|
|
173
|
+
channel: 'variation-selector-run',
|
|
174
|
+
detail: `variation-selector run x${vsMax} (steganography signature)`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (cmMax >= _COMBINING_RUN_MIN) {
|
|
178
|
+
out.push({
|
|
179
|
+
channel: 'combining-mark-run',
|
|
180
|
+
detail: `combining-mark run x${cmMax} on one base character`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (hasMathAlnum) {
|
|
184
|
+
out.push({
|
|
185
|
+
channel: 'math-alphanumeric',
|
|
186
|
+
detail: 'Mathematical Alphanumeric Symbols used as letters (U+1D400–1D7FF)',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (hasFullwidth) {
|
|
190
|
+
out.push({
|
|
191
|
+
channel: 'fullwidth-forms',
|
|
192
|
+
detail: 'Halfwidth/Fullwidth Forms used as ASCII letters (U+FF01–FF5E)',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Mixed-script confusable tokens — shared signature, one table in the tree.
|
|
197
|
+
for (const m of s.matchAll(TOKEN_RE)) {
|
|
198
|
+
const verdict = classifyToken(m[0]);
|
|
199
|
+
if (verdict !== null) {
|
|
200
|
+
const script = verdict.startsWith('latin+greek') ? 'greek' : 'cyrillic';
|
|
201
|
+
out.push({ channel: `confusable-${script}`, detail: `${m[0]} — ${verdict}` });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Punycode / IDN in a text-expected field. Flagged, never rewritten:
|
|
206
|
+
// `xn--` labels are valid DNS and rewriting one would break a real link.
|
|
207
|
+
for (const m of s.matchAll(/\bxn--[a-z0-9-]+/gi)) {
|
|
208
|
+
out.push({ channel: 'punycode-idn', detail: `punycode label ${m[0]}` });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
50
214
|
/**
|
|
51
215
|
* Return a shallow copy of a retrieval entry with every string value (and
|
|
52
216
|
* one level of string-array values) sanitized. Non-string values pass through
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan-scope assertions — "a gate that read nothing did not pass".
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS (audit 2026-07-29, `agents/settings/contexts/gates-that-cannot-fail.md`):
|
|
5
|
+
* ADR-051 moved the source container and a later commit deleted `packages/`.
|
|
6
|
+
* Fourteen `lint_*`/`check_*` gates kept a hardcoded literal path, and every
|
|
7
|
+
* one of them treats a missing directory as "nothing to check" — so they exit 0
|
|
8
|
+
* and print a green checkmark while scanning zero files. Their own output said
|
|
9
|
+
* so (`0 file(s) scanned`, `0 name(s) checked`, `0 declarer(s)`); nothing ever
|
|
10
|
+
* asserted on the number.
|
|
11
|
+
*
|
|
12
|
+
* Two shapes cover the whole class:
|
|
13
|
+
*
|
|
14
|
+
* - **Corpus gates** walk a root and check N units → {@link assertScanned}.
|
|
15
|
+
* Zero units is a failure unless the gate declares a justified `allowEmpty`.
|
|
16
|
+
* - **Watch-list gates** (diff-based, no corpus to count) name specific files
|
|
17
|
+
* they guard → {@link assertWatchlistResolves}. A watch list that resolves to
|
|
18
|
+
* nothing on disk means the guard is watching phantoms.
|
|
19
|
+
*
|
|
20
|
+
* Both throw {@link DeadScopeError}; callers map it to a non-zero exit. The
|
|
21
|
+
* point is that the failure is LOUD and names the root, so the next path
|
|
22
|
+
* migration cannot silently disarm a gate again.
|
|
23
|
+
*/
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
|
|
27
|
+
/** Raised when a gate's scope is empty — it cannot have checked anything. */
|
|
28
|
+
export class DeadScopeError extends Error {
|
|
29
|
+
readonly gate: string;
|
|
30
|
+
|
|
31
|
+
constructor(gate: string, message: string) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'DeadScopeError';
|
|
34
|
+
this.gate = gate;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ScannedOptions {
|
|
39
|
+
/** Gate name, for the error message (e.g. `lint_namespace`). */
|
|
40
|
+
gate: string;
|
|
41
|
+
/** How many units the gate actually examined. */
|
|
42
|
+
scanned: number;
|
|
43
|
+
/** Plural noun for the unit — `file(s)`, `name(s)`, `declarer(s)`. */
|
|
44
|
+
units: string;
|
|
45
|
+
/** The root(s) the gate walked, repo-relative, for the error message. */
|
|
46
|
+
roots: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* Justification for legitimately scanning zero. Supply ONLY when an empty
|
|
49
|
+
* corpus is a real, expected state (an optional consumer surface that may
|
|
50
|
+
* genuinely be absent) — never to silence a moved root. The string is the
|
|
51
|
+
* reason and is printed, so it is reviewable in a diff.
|
|
52
|
+
*/
|
|
53
|
+
allowEmpty?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Assert a corpus gate examined at least one unit.
|
|
58
|
+
*
|
|
59
|
+
* @throws {DeadScopeError} when `scanned === 0` and no `allowEmpty` reason is given.
|
|
60
|
+
*/
|
|
61
|
+
export function assertScanned(opts: ScannedOptions): void {
|
|
62
|
+
if (opts.scanned > 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (opts.allowEmpty !== undefined && opts.allowEmpty.trim() !== '') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const roots = opts.roots.length > 0 ? opts.roots.join(', ') : '(no root declared)';
|
|
69
|
+
throw new DeadScopeError(
|
|
70
|
+
opts.gate,
|
|
71
|
+
`${opts.gate}: scanned 0 ${opts.units} under ${roots} — the scan scope is ` +
|
|
72
|
+
'dead or the root moved. A gate that read nothing has not passed. ' +
|
|
73
|
+
'Repoint the root (prefer the shared resolver in _lib/agent_src.ts), ' +
|
|
74
|
+
'or declare a justified `allowEmpty` reason if an empty corpus is genuinely expected.',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface WatchlistOptions {
|
|
79
|
+
/** Gate name, for the error message. */
|
|
80
|
+
gate: string;
|
|
81
|
+
/** Repo-relative candidate paths the gate guards. */
|
|
82
|
+
candidates: readonly string[];
|
|
83
|
+
/** Absolute repo root the candidates resolve against. */
|
|
84
|
+
repoRoot: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Assert a watch-list gate's targets exist on disk, and return the ones that do.
|
|
89
|
+
*
|
|
90
|
+
* A diff-based guard has no corpus to count: it compares changed paths against
|
|
91
|
+
* a fixed list. If that list resolves to nothing, the guard reports "clean"
|
|
92
|
+
* forever — the exact shape of the `check_safety_floor_untouched` defect, which
|
|
93
|
+
* announced `4 rules guarded` while guarding none.
|
|
94
|
+
*
|
|
95
|
+
* @returns the subset of `candidates` that exist.
|
|
96
|
+
* @throws {DeadScopeError} when none of them exist.
|
|
97
|
+
*/
|
|
98
|
+
export function assertWatchlistResolves(opts: WatchlistOptions): string[] {
|
|
99
|
+
const resolved = opts.candidates.filter((rel) => {
|
|
100
|
+
try {
|
|
101
|
+
return fs.statSync(path.join(opts.repoRoot, rel)).isFile();
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
if (resolved.length === 0) {
|
|
107
|
+
throw new DeadScopeError(
|
|
108
|
+
opts.gate,
|
|
109
|
+
`${opts.gate}: none of its ${opts.candidates.length} guarded path(s) exist ` +
|
|
110
|
+
`under ${opts.repoRoot} — the guard is watching phantoms and can never fire. ` +
|
|
111
|
+
`Checked: ${opts.candidates.join(', ')}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return resolved;
|
|
115
|
+
}
|