@event4u/agent-config 14.12.0 → 14.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +118 -155
- package/CONTRIBUTING.md +38 -0
- package/CREDITS.md +135 -0
- package/NOTICE +13 -0
- package/README.md +8 -6
- package/dist/agent-src/commands/analyze/inbox.md +145 -2
- package/dist/agent-src/commands/feature/dev.md +7 -2
- package/dist/agent-src/commands/ghostwriter/write.md +5 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/mission/upgrade.md +1 -1
- package/dist/agent-src/commands/orchestrate.md +2 -2
- package/dist/agent-src/commands/post-as/me.md +5 -1
- package/dist/agent-src/commands/pr/merge.md +19 -6
- package/dist/agent-src/commands/roadmap/process-full.md +19 -5
- package/dist/agent-src/commands/tdd/red.md +12 -2
- package/dist/agent-src/commands/work.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- package/dist/agent-src/rules/code-provenance.md +4 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +4 -0
- package/dist/agent-src/rules/domain-safety-retention.md +4 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
- package/dist/agent-src/rules/improve-before-implement.md +6 -8
- package/dist/agent-src/rules/laravel-routing.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +12 -34
- package/dist/agent-src/rules/symfony-routing.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +14 -0
- package/dist/agent-src/rules/tool-safety.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/dist/agent-src/scripts/guarded_baseline.ts +343 -0
- package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/dist/agent-src/scripts/stub_queue.ts +82 -0
- package/dist/agent-src/scripts/stubs_due.ts +516 -0
- package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
- package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
- package/dist/agent-src/skills/database/SKILL.md +8 -0
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
- package/dist/agent-src/skills/laravel/SKILL.md +35 -0
- package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
- package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
- package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
- package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +97 -3
- package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
- package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/dist/cli/commands/uiAudit.js +124 -5
- package/dist/cli/registry.js +2 -0
- package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
- package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
- package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
- package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
- package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
- package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
- package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
- package/dist/cli-delegate/cmd_adr_effective.js +102 -0
- package/dist/cli-delegate/cmd_conformance.js +80 -11
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +3 -2
- package/dist/cli-delegate/cmd_fleet.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +3 -2
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +3 -2
- package/dist/cli-delegate/cmd_uninstall.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +3 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +58 -58
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +2900 -1049
- package/dist/install/claudeRuleRewrite.js +254 -0
- package/dist/install/detect.js +29 -16
- package/dist/install/detect_php_shape.js +159 -0
- package/dist/install/install.mjs +634 -428
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +3 -3
- package/dist/mcp/server.mjs +4895 -2627
- package/dist/server/schemas/settings.js +3 -0
- package/dist/ui/assets/index-D0jaCRPM.js +29 -0
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/THIRD-PARTY-NOTICES.md +25 -0
- package/docs/contracts/provider-lifecycle.md +1 -1
- package/docs/contracts/settings-classes.md +10 -4
- package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
- package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
- package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
- package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
- package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
- package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
- package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/docs/guidelines/agent-infra/first-principles.md +2 -2
- package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
- package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +10 -10
- package/docs/guidelines/agent-infra/memory-access.md +1 -1
- package/docs/guidelines/agent-infra/mental-models.md +3 -3
- package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
- package/docs/guidelines/agent-infra/role-contracts.md +2 -2
- package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
- package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
- package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
- package/docs/guidelines/agent-infra/six-hats.md +3 -3
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
- package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
- package/docs/guidelines/augment-portability-patterns.md +6 -4
- package/docs/guidelines/code-clarity.md +18 -0
- package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
- package/docs/guidelines/cross-role-handoff.md +2 -2
- package/docs/guidelines/design-fidelity-mechanics.md +1 -1
- package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
- package/docs/guidelines/php/database.md +90 -7
- package/docs/guidelines/prompt-templates.md +7 -7
- package/llms.txt +2 -2
- package/package.json +5 -2
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/src/agent-src/scripts/guarded_baseline.ts +343 -0
- package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/src/agent-src/scripts/stub_queue.ts +82 -0
- package/src/agent-src/scripts/stubs_due.ts +516 -0
- package/src/agent-src/scripts/ticket_bundles.ts +84 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/src/agent-src/templates/scripts/check_memory.ts +258 -17
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/src/config/agent-settings.template.yml +32 -3
- package/src/config/assurance-capability-registry.json +21 -9
- package/src/config/canonical-terms.yml +119 -0
- package/src/config/capability-native-outcome-bars.json +81 -0
- package/src/config/ci-local-parity.yml +80 -0
- package/src/config/drafting-channels.yml +43 -0
- package/src/config/estate-count-budget.json +57 -36
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +729 -63
- package/src/config/gate-reachability-exemptions.json +21 -0
- package/src/config/gate-violation-baselines.json +42 -19
- package/src/config/harness-evolution-budget.json +25 -0
- package/src/config/lapsed-beta-baseline.json +96 -0
- package/src/config/memory-twin-verdicts.yml +129 -0
- package/src/config/metric-registry.yml +124 -0
- package/src/config/pack-size-budget.json +16 -15
- package/src/config/preamble-payload-budget.json +12 -4
- package/src/config/profiles/balanced.ini +27 -0
- package/src/config/profiles/full.ini +27 -0
- package/src/config/profiles/minimal.ini +27 -0
- package/src/config/publish-surface.json +9 -2
- package/src/config/routing-coverage-seed.json +16 -2
- package/src/config/rule-stub-ceilings.json +133 -112
- package/src/config/turnaround-budget.json +66 -0
- package/src/install/claudeRuleRewrite.ts +288 -0
- package/src/install/detect.ts +60 -17
- package/src/install/detect_php_shape.ts +191 -0
- package/src/scripts/_cli/cmd_doctor.ts +27 -112
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/activation_ladder.ts +255 -0
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/activation_states.ts +88 -0
- package/src/scripts/_lib/adherence_detectors.ts +143 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/arith_claims.ts +181 -0
- package/src/scripts/_lib/arm_ranking.ts +62 -0
- package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
- package/src/scripts/_lib/audit_field_provenance.ts +54 -0
- package/src/scripts/_lib/auto_dispatch.ts +32 -3
- package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
- package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/candidate_proposer.ts +402 -0
- package/src/scripts/_lib/candidate_record.ts +601 -0
- package/src/scripts/_lib/capture_rate.ts +319 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/collector_denominator.ts +771 -0
- package/src/scripts/_lib/collector_record.ts +340 -0
- package/src/scripts/_lib/collector_store.ts +664 -0
- package/src/scripts/_lib/collector_supervision.ts +827 -0
- package/src/scripts/_lib/concern_estate.ts +77 -0
- package/src/scripts/_lib/config_chain.ts +477 -0
- package/src/scripts/_lib/config_cost.ts +209 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/council_settings_block.ts +6 -0
- package/src/scripts/_lib/curator_ops.ts +172 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +282 -0
- package/src/scripts/_lib/delivery_criticality.ts +126 -0
- package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
- package/src/scripts/_lib/empty_cycles.ts +88 -0
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/eval_publication.ts +354 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evaluation_vector.ts +283 -0
- package/src/scripts/_lib/evaluator_promotion.ts +225 -0
- package/src/scripts/_lib/evidence_basis.ts +118 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/exec_evidence.ts +8 -0
- package/src/scripts/_lib/experience_card.ts +272 -0
- package/src/scripts/_lib/experience_report.ts +151 -0
- package/src/scripts/_lib/experiment_freeze.ts +112 -0
- package/src/scripts/_lib/generated_by.ts +66 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +239 -0
- package/src/scripts/_lib/hook_effect_probe.ts +141 -0
- package/src/scripts/_lib/host_denominator.ts +687 -0
- package/src/scripts/_lib/host_projection_reach.ts +157 -0
- package/src/scripts/_lib/ignored_blocker.ts +148 -0
- package/src/scripts/_lib/install_reach_checks.ts +495 -0
- package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
- package/src/scripts/_lib/judge_hygiene.ts +228 -0
- package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/loop_guards.ts +299 -0
- package/src/scripts/_lib/loss_class.ts +129 -0
- package/src/scripts/_lib/machine_wake.ts +51 -0
- package/src/scripts/_lib/md_prose_lines.ts +177 -0
- package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
- package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
- package/src/scripts/_lib/orchestration_record.ts +253 -5
- package/src/scripts/_lib/outcome_envelope.ts +227 -0
- package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
- package/src/scripts/_lib/paired_stats.ts +245 -0
- package/src/scripts/_lib/paired_verdict.ts +226 -0
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
- package/src/scripts/_lib/privacy_class.ts +47 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
- package/src/scripts/_lib/release_highlights.ts +70 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/repeated_failure.ts +105 -0
- package/src/scripts/_lib/repo_root.ts +111 -0
- package/src/scripts/_lib/review_skipped_record.ts +37 -0
- package/src/scripts/_lib/role_split.ts +338 -0
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/runtime_journal.ts +1499 -0
- package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/_lib/skill_catalogue.ts +11 -1
- package/src/scripts/_lib/source_digest.ts +200 -0
- package/src/scripts/_lib/source_redact.ts +260 -0
- package/src/scripts/_lib/source_shape.ts +502 -0
- package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
- package/src/scripts/_lib/strip_comments.d.mts +8 -0
- package/src/scripts/_lib/strip_comments.mjs +65 -0
- package/src/scripts/_lib/test_red_state.ts +154 -0
- package/src/scripts/_lib/trigger_shift.ts +144 -0
- package/src/scripts/_lib/untrusted_content.ts +82 -7
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/adoption_report.ts +1 -1
- package/src/scripts/adr/evidence_census.ts +1 -1
- package/src/scripts/adr/regenerate_index.ts +26 -2
- package/src/scripts/adr_cite_check.ts +164 -1
- package/src/scripts/ai_council/blind_review.ts +11 -1
- package/src/scripts/ai_council/clients.ts +4 -0
- package/src/scripts/ai_council/config.ts +3 -0
- package/src/scripts/ai_council/consensus.ts +28 -8
- package/src/scripts/ai_council/consensus_round.ts +226 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/disagreement_signal.ts +338 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/inline_findings.ts +340 -0
- package/src/scripts/ai_council/judge_position_bias.ts +197 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/orchestrator.ts +16 -203
- package/src/scripts/ai_council/orchestrator_results.ts +23 -3
- package/src/scripts/ai_council/pricing.ts +106 -0
- package/src/scripts/ai_council/prompts.ts +74 -7
- package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
- package/src/scripts/ai_council/py_format.ts +14 -3
- package/src/scripts/ai_council/recouncil_guard.ts +293 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/response_record.ts +50 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
- package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
- package/src/scripts/asset_delivery_ledger.ts +157 -0
- package/src/scripts/attest_artifact.ts +193 -0
- package/src/scripts/audit_auto_rules.ts +2 -1
- package/src/scripts/audit_command_surface.ts +1 -1
- package/src/scripts/audit_mcp_tools.ts +2 -2
- package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
- package/src/scripts/audit_user_type_axis.ts +2 -1
- package/src/scripts/bench_ab_clone.ts +215 -10
- package/src/scripts/bench_ab_integrity.ts +119 -5
- package/src/scripts/bench_ab_v2_stats.ts +40 -213
- package/src/scripts/build_archive_index.ts +1 -1
- package/src/scripts/build_linear_digest.ts +1 -1
- package/src/scripts/build_proof.ts +11 -4
- package/src/scripts/build_rule_trigger_matrix.ts +1 -1
- package/src/scripts/build_source_digests.ts +157 -0
- package/src/scripts/cache_realization_report.ts +103 -0
- package/src/scripts/check_adr_frontmatter.ts +131 -10
- package/src/scripts/check_benchmark_report_fields.ts +207 -0
- package/src/scripts/check_beta_review_markers.ts +235 -6
- package/src/scripts/check_bundle_path_leakage.ts +34 -1
- package/src/scripts/check_claims.ts +576 -6
- package/src/scripts/check_composite_arming.ts +35 -0
- package/src/scripts/check_concern_admissions.ts +365 -0
- package/src/scripts/check_enforcement_coverage.ts +28 -0
- package/src/scripts/check_estate_count.ts +347 -25
- package/src/scripts/check_gate_reachability.ts +383 -0
- package/src/scripts/check_host_loadability.ts +104 -0
- package/src/scripts/check_loss_class_declared.ts +308 -0
- package/src/scripts/check_md_language.ts +15 -98
- package/src/scripts/check_memory_proposal.ts +18 -11
- package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
- package/src/scripts/check_no_external_sources.ts +281 -6
- package/src/scripts/check_pack_conformance_fixture.ts +11 -1
- package/src/scripts/check_pack_size.ts +59 -2
- package/src/scripts/check_preamble_payload_budget.ts +114 -3
- package/src/scripts/check_prefix_stable_mutation.ts +372 -0
- package/src/scripts/check_publish_surface.ts +10 -1
- package/src/scripts/check_references.ts +24 -1
- package/src/scripts/check_release_highlights.ts +96 -13
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/check_rule_evidence_declaration.ts +316 -0
- package/src/scripts/check_rule_projection_integrity.ts +14 -4
- package/src/scripts/check_score_contract.ts +26 -3
- package/src/scripts/check_static_parity.ts +285 -0
- package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
- package/src/scripts/check_suppression_hygiene.ts +15 -0
- package/src/scripts/ci_settle.ts +39 -2
- package/src/scripts/code_graph/build.ts +60 -3
- package/src/scripts/code_graph/extract.ts +44 -0
- package/src/scripts/code_graph/types.ts +24 -0
- package/src/scripts/collector_daemon.ts +714 -0
- package/src/scripts/config_cost_report.ts +302 -0
- package/src/scripts/consumer_matrix.ts +74 -1
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/cost/budget.mjs +10 -3
- package/src/scripts/cost/track.mjs +10 -3
- package/src/scripts/council_cli.ts +37 -54
- package/src/scripts/dispatch_r2_reviewer.ts +46 -5
- package/src/scripts/evolution_lab.ts +1153 -0
- package/src/scripts/external_sources_denylist.json +5 -18
- package/src/scripts/extract_audit_patterns.ts +21 -0
- package/src/scripts/fold_intake.ts +12 -1
- package/src/scripts/gen_discovery_baseline.ts +1 -1
- package/src/scripts/generate_capabilities_index.ts +12 -5
- package/src/scripts/generate_capability_matrix.ts +3 -3
- package/src/scripts/generate_command_flows.ts +2 -2
- package/src/scripts/generate_cookbook.ts +2 -2
- package/src/scripts/generate_index.ts +8 -1
- package/src/scripts/generate_knowledge_index.ts +2 -1
- package/src/scripts/generate_ownership_matrix.ts +2 -1
- package/src/scripts/generate_pack_manifests.ts +10 -2
- package/src/scripts/generate_role_experiences_catalog.ts +2 -2
- package/src/scripts/generate_settings_reference.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +22 -1
- package/src/scripts/grade_target_readiness.ts +48 -5
- package/src/scripts/hook_effect_doctor.ts +261 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +125 -16
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
- package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +32 -0
- package/src/scripts/hooks/journal_record_hook.ts +345 -0
- package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
- package/src/scripts/hooks/run_continuation_hook.ts +44 -9
- package/src/scripts/hooks/shims/install.sh +61 -0
- package/src/scripts/hooks/shims/php +101 -0
- package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/hot_context_hook.ts +16 -0
- package/src/scripts/install-hooks.sh +20 -5
- package/src/scripts/install.ts +17 -35
- package/src/scripts/inventory_frontmatter.ts +2 -1
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_adr_sweep_routing.ts +12 -0
- package/src/scripts/lint_canonical_terms.ts +604 -0
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +643 -0
- package/src/scripts/lint_composition_review.ts +619 -0
- package/src/scripts/lint_consolidation_lineage.ts +666 -0
- package/src/scripts/lint_eval_specs.ts +593 -0
- package/src/scripts/lint_hook_manifest.ts +19 -0
- package/src/scripts/lint_host_portability.ts +373 -0
- package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
- package/src/scripts/lint_memory_twin_parity.ts +335 -0
- package/src/scripts/lint_metric_consumers.ts +355 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/lint_skill_descriptions.ts +18 -1
- package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
- package/src/scripts/lint_skill_link_reach.ts +252 -0
- package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
- package/src/scripts/lint_ticket_buildable.ts +17 -1
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
- package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_docs_dead_links.ts +348 -0
- package/src/scripts/measure_host_capture.ts +461 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/migration_status.ts +3 -3
- package/src/scripts/model_rule_injection.ts +76 -0
- package/src/scripts/orchestration_record.ts +14 -0
- package/src/scripts/preamble_byte_census.ts +139 -10
- package/src/scripts/prepack-check.mjs +4 -1
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/probe_turnaround.ts +384 -0
- package/src/scripts/probe_unblocked_ask.ts +279 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +143 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +84 -0
- package/src/scripts/render_benchmark_md.ts +1 -1
- package/src/scripts/rule_activation_census.ts +70 -9
- package/src/scripts/rule_backlinks.ts +1 -1
- package/src/scripts/run_lifecycle_suite.ts +208 -0
- package/src/scripts/run_skill_evals.ts +40 -0
- package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
- package/src/scripts/schemas/drafting-channels.schema.json +43 -0
- package/src/scripts/schemas/evals.schema.json +24 -1
- package/src/scripts/schemas/rule.schema.json +52 -1
- package/src/scripts/schemas/skill.schema.json +17 -2
- package/src/scripts/skill_overlap.ts +7 -1
- package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
- package/src/scripts/skill_usage_report.ts +2 -1
- package/src/scripts/stubs_due.ts +211 -0
- package/src/scripts/sweep_source_surfaces.ts +678 -0
- package/src/scripts/sync_pr_branch.ts +3 -3
- package/src/scripts/trigger_eval_grandfather.json +202 -222
- package/src/scripts/utilization_report.ts +22 -2
- package/src/server/schemas/settings.ts +5 -0
- package/src/templates/marketing-copy.yml +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +0 -26
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* sweep_source_surfaces — the full-surface source-attribution census
|
|
4
|
+
* (`road-to-source-silence` Phase 0.1).
|
|
5
|
+
*
|
|
6
|
+
* `check_no_external_sources` scans one surface: the CONTENT of tracked files.
|
|
7
|
+
* The measurement that motivated this roadmap found leaks on four more —
|
|
8
|
+
* tracked file PATHS, commit messages on the trunk, branch refs, and PR titles
|
|
9
|
+
* and bodies — none of which any check has ever looked at. This sweep is the
|
|
10
|
+
* inventory over all five.
|
|
11
|
+
*
|
|
12
|
+
* ## Why the output is encrypted and not printed
|
|
13
|
+
*
|
|
14
|
+
* The findings list is, by construction, the most concentrated source
|
|
15
|
+
* disclosure this repository has ever held — it is every name the programme
|
|
16
|
+
* exists to hide, gathered into one file. So the tracked artefact carries
|
|
17
|
+
* **per-surface counts plus an `ENC1:` ciphertext** and nothing else
|
|
18
|
+
* (Phase 0.2). `--json` prints plaintext and is for a maintainer at a terminal;
|
|
19
|
+
* it must never be redirected into a tracked file.
|
|
20
|
+
*
|
|
21
|
+
* ## Surfaces
|
|
22
|
+
*
|
|
23
|
+
* | id | source | needs |
|
|
24
|
+
* |---|---|---|
|
|
25
|
+
* | `content` | tracked file bodies | git |
|
|
26
|
+
* | `path` | tracked file paths | git |
|
|
27
|
+
* | `commit` | `<base>` commit subjects + bodies | git, a resolvable base ref |
|
|
28
|
+
* | `branch` | local + remote branch refs | git |
|
|
29
|
+
* | `pr` | PR titles + bodies | `gh`, network |
|
|
30
|
+
*
|
|
31
|
+
* A surface that cannot be read is reported as `unavailable` with its reason —
|
|
32
|
+
* never as zero. A zero that means "nobody looked" is the failure this
|
|
33
|
+
* repository's own scan-scope discipline exists to prevent.
|
|
34
|
+
*
|
|
35
|
+
* ## Matchers
|
|
36
|
+
*
|
|
37
|
+
* - `denylist` — the `deny` patterns from `external_sources_denylist.json`.
|
|
38
|
+
* - `shape` — the name-list-independent heuristic in `_lib/source_shape.ts`.
|
|
39
|
+
*
|
|
40
|
+
* Usage:
|
|
41
|
+
* sweep_source_surfaces [--json] [--census <path>] [--decrypt <path>]
|
|
42
|
+
* [--base <ref>] [--no-remote] [--limit <n>]
|
|
43
|
+
* sweep_source_surfaces --gate-metadata [--branch <name>] [--title <s>]
|
|
44
|
+
* [--body-file <path>] [--commit-range <a>..<b>]
|
|
45
|
+
*
|
|
46
|
+
* `--gate-metadata` is the Phase 4.3 surface: the OFF-TREE metadata of ONE
|
|
47
|
+
* change — branch name, PR title, PR body, and the commit subjects the change
|
|
48
|
+
* adds — checked against the deny set plus the shape heuristic, exiting 1 on a
|
|
49
|
+
* hit. It is the same matcher the tracked-tree gate uses, pointed at the four
|
|
50
|
+
* surfaces no content gate can see. Used by `.github/workflows/pr-metadata-
|
|
51
|
+
* sources.yml` on `pull_request` and by the pre-push hook
|
|
52
|
+
* `src/scripts/hooks/prepush_metadata_sources.sh`, so an author sees locally
|
|
53
|
+
* what CI will say once the metadata is already public.
|
|
54
|
+
*
|
|
55
|
+
* Exit codes: 0 = the sweep ran (findings are data, not a failure), 2 = usage
|
|
56
|
+
* or config error. This is a census, not a gate: it never fails a build.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
import { spawnSync } from 'node:child_process';
|
|
60
|
+
import * as fs from 'node:fs';
|
|
61
|
+
import * as path from 'node:path';
|
|
62
|
+
import process from 'node:process';
|
|
63
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
64
|
+
|
|
65
|
+
import { decrypt, encrypt, resolve_keys } from './_lib/link_crypto.js';
|
|
66
|
+
import { shapeHits, shapePathHits, tierFor } from './_lib/source_shape.js';
|
|
67
|
+
|
|
68
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
69
|
+
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
70
|
+
const CONFIG = path.join(path.dirname(_HERE), 'external_sources_denylist.json');
|
|
71
|
+
|
|
72
|
+
const SKIP_EXT = new Set([
|
|
73
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.pdf', '.zip', '.gz',
|
|
74
|
+
'.woff', '.woff2', '.ttf', '.mp3', '.mp4', '.wav', '.lock',
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
export const SURFACES = ['content', 'path', 'commit', 'branch', 'pr'] as const;
|
|
78
|
+
export type Surface = (typeof SURFACES)[number];
|
|
79
|
+
|
|
80
|
+
export interface Finding {
|
|
81
|
+
surface: Surface;
|
|
82
|
+
/** `file:line`, a ref name, or `PR #<n>`. */
|
|
83
|
+
anchor: string;
|
|
84
|
+
matcher: 'denylist' | 'shape';
|
|
85
|
+
/** Deny pattern, or the shape class. */
|
|
86
|
+
kind: string;
|
|
87
|
+
tier: 'block' | 'warn';
|
|
88
|
+
/** The offending text, truncated. Present only in the plaintext output. */
|
|
89
|
+
excerpt: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface SurfaceReport {
|
|
93
|
+
surface: Surface;
|
|
94
|
+
status: 'scanned' | 'unavailable';
|
|
95
|
+
/** Units actually read — never omitted, so a zero can be told from a blind run. */
|
|
96
|
+
scanned: number;
|
|
97
|
+
unit: string;
|
|
98
|
+
reason?: string;
|
|
99
|
+
counts: { denylist: number; shape: number; block: number; warn: number; total: number };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function sh(cmd: string, args: string[], cwd = ROOT): { ok: boolean; out: string; err: string } {
|
|
103
|
+
const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8', maxBuffer: 512 * 1024 * 1024 });
|
|
104
|
+
return { ok: r.status === 0, out: r.stdout ?? '', err: (r.stderr ?? '').trim() };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function loadDeny(): Array<[string, RegExp]> {
|
|
108
|
+
const cfg = JSON.parse(fs.readFileSync(CONFIG, 'utf-8')) as { deny?: string[]; deny_digests?: string[] };
|
|
109
|
+
if (!cfg.deny || cfg.deny.length === 0) {
|
|
110
|
+
// After the atomic digest cutover (road-to-source-silence-cutover Phase
|
|
111
|
+
// 1.3) the tracked config carries `deny_digests` and NO `deny` array.
|
|
112
|
+
// This used to throw, and the CLI entry wraps `main` in no try/catch, so
|
|
113
|
+
// the cutover would have turned every sweep into an unhandled crash —
|
|
114
|
+
// caught by the R2 review of the branch that WROTE that step, before it
|
|
115
|
+
// could bite. Degrade loudly and keep the shape half working: it is
|
|
116
|
+
// name-list-independent by construction, so it is exactly the half that
|
|
117
|
+
// still has something to say without a deny set.
|
|
118
|
+
if ((cfg.deny_digests ?? []).length > 0) {
|
|
119
|
+
process.stderr.write(
|
|
120
|
+
'⚠️ no plaintext `deny` array — the digest cutover has happened.\n' +
|
|
121
|
+
' This sweep does NOT match hashed digests: it is a discovery/census tool\n' +
|
|
122
|
+
' and matching them needs the key. The SHAPE half still runs and the\n' +
|
|
123
|
+
' counts below are shape-only for the denylist column.\n',
|
|
124
|
+
);
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
throw new Error('config error: empty deny list');
|
|
128
|
+
}
|
|
129
|
+
return cfg.deny.map((p) => [p, new RegExp(p, 'i')] as [string, RegExp]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Deny + shape hits on one text unit, tagged with the tier its location implies. */
|
|
133
|
+
function scanText(
|
|
134
|
+
text: string,
|
|
135
|
+
anchor: string,
|
|
136
|
+
surface: Surface,
|
|
137
|
+
tier: 'block' | 'warn',
|
|
138
|
+
deny: ReadonlyArray<[string, RegExp]>,
|
|
139
|
+
pathLike = false,
|
|
140
|
+
): Finding[] {
|
|
141
|
+
const out: Finding[] = [];
|
|
142
|
+
for (const [raw, rx] of deny) {
|
|
143
|
+
if (rx.test(text)) {
|
|
144
|
+
out.push({ surface, anchor, matcher: 'denylist', kind: raw, tier: 'block', excerpt: text.slice(0, 160) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const h of pathLike ? shapePathHits(text) : shapeHits(text)) {
|
|
148
|
+
out.push({ surface, anchor, matcher: 'shape', kind: h.cls, tier, excerpt: h.value });
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --------------------------------------------------------------------------- //
|
|
154
|
+
// Surfaces
|
|
155
|
+
// --------------------------------------------------------------------------- //
|
|
156
|
+
|
|
157
|
+
function sweepTracked(
|
|
158
|
+
deny: ReadonlyArray<[string, RegExp]>,
|
|
159
|
+
): [SurfaceReport, SurfaceReport, Finding[], Array<{ anchor: string; text: string }>] {
|
|
160
|
+
const ls = sh('git', ['ls-files']);
|
|
161
|
+
const empty = (s: Surface, unit: string, reason: string): SurfaceReport => ({
|
|
162
|
+
surface: s, status: 'unavailable', scanned: 0, unit, reason, counts: zero(),
|
|
163
|
+
});
|
|
164
|
+
if (!ls.ok) {
|
|
165
|
+
return [empty('content', 'file', 'git ls-files failed'), empty('path', 'file', 'git ls-files failed'), [], []];
|
|
166
|
+
}
|
|
167
|
+
const tracked = ls.out.split('\n').filter(Boolean);
|
|
168
|
+
const findings: Finding[] = [];
|
|
169
|
+
const cueTexts: Array<{ anchor: string; text: string }> = [];
|
|
170
|
+
let read = 0;
|
|
171
|
+
for (const rel of tracked) {
|
|
172
|
+
const tier = tierFor(rel);
|
|
173
|
+
findings.push(...scanText(rel, rel, 'path', tier, deny, true));
|
|
174
|
+
const dot = (rel.split('/').pop() as string).lastIndexOf('.');
|
|
175
|
+
const ext = dot > 0 ? (rel.split('/').pop() as string).slice(dot).toLowerCase() : '';
|
|
176
|
+
if (SKIP_EXT.has(ext)) continue;
|
|
177
|
+
let body: string;
|
|
178
|
+
try {
|
|
179
|
+
const abs = path.join(ROOT, rel);
|
|
180
|
+
if (!fs.statSync(abs).isFile()) continue;
|
|
181
|
+
body = fs.readFileSync(abs, 'utf-8');
|
|
182
|
+
} catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
read += 1;
|
|
186
|
+
const lines = body.split('\n');
|
|
187
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
188
|
+
const ln = lines[i] as string;
|
|
189
|
+
const anchor = `${rel}:${String(i + 1)}`;
|
|
190
|
+
findings.push(...scanText(ln, anchor, 'content', tier, deny));
|
|
191
|
+
if (CUE_RE.test(ln)) cueTexts.push({ anchor, text: ln });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const content = findings.filter((f) => f.surface === 'content');
|
|
195
|
+
const paths = findings.filter((f) => f.surface === 'path');
|
|
196
|
+
return [
|
|
197
|
+
{ surface: 'content', status: 'scanned', scanned: read, unit: 'text file', counts: tally(content) },
|
|
198
|
+
{ surface: 'path', status: 'scanned', scanned: tracked.length, unit: 'tracked path', counts: tally(paths) },
|
|
199
|
+
findings,
|
|
200
|
+
cueTexts,
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function sweepCommits(deny: ReadonlyArray<[string, RegExp]>, base: string): [SurfaceReport, Finding[]] {
|
|
205
|
+
const probe = sh('git', ['rev-parse', '--verify', '--quiet', base]);
|
|
206
|
+
if (!probe.ok) {
|
|
207
|
+
return [{ surface: 'commit', status: 'unavailable', scanned: 0, unit: 'commit', reason: `base ref ${base} not resolvable`, counts: zero() }, []];
|
|
208
|
+
}
|
|
209
|
+
const log = sh('git', ['log', '--format=%H%x1f%s%x1f%b%x1e', base]);
|
|
210
|
+
if (!log.ok) {
|
|
211
|
+
return [{ surface: 'commit', status: 'unavailable', scanned: 0, unit: 'commit', reason: 'git log failed', counts: zero() }, []];
|
|
212
|
+
}
|
|
213
|
+
const recs = log.out.split('\x1e').map((r) => r.trim()).filter(Boolean);
|
|
214
|
+
const findings: Finding[] = [];
|
|
215
|
+
for (const rec of recs) {
|
|
216
|
+
const [sha, subject, body] = rec.split('\x1f');
|
|
217
|
+
const anchor = `commit ${(sha ?? '').slice(0, 9)}`;
|
|
218
|
+
// A commit message is not a tracked path, so `agents/**` tiering cannot
|
|
219
|
+
// apply; the trunk's public record is treated as block-tier throughout.
|
|
220
|
+
findings.push(...scanText(`${subject ?? ''}\n${body ?? ''}`, anchor, 'commit', 'block', deny));
|
|
221
|
+
}
|
|
222
|
+
return [{ surface: 'commit', status: 'scanned', scanned: recs.length, unit: 'commit', counts: tally(findings) }, findings];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function sweepBranches(deny: ReadonlyArray<[string, RegExp]>): [SurfaceReport, Finding[]] {
|
|
226
|
+
const r = sh('git', ['for-each-ref', '--format=%(refname)', 'refs/heads', 'refs/remotes']);
|
|
227
|
+
if (!r.ok) {
|
|
228
|
+
return [{ surface: 'branch', status: 'unavailable', scanned: 0, unit: 'ref', reason: 'git for-each-ref failed', counts: zero() }, []];
|
|
229
|
+
}
|
|
230
|
+
const refs = r.out.split('\n').filter(Boolean);
|
|
231
|
+
const findings: Finding[] = [];
|
|
232
|
+
for (const ref of refs) findings.push(...scanText(ref, ref, 'branch', 'block', deny, true));
|
|
233
|
+
return [{ surface: 'branch', status: 'scanned', scanned: refs.length, unit: 'ref', counts: tally(findings) }, findings];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function sweepPulls(deny: ReadonlyArray<[string, RegExp]>, limit: number): [SurfaceReport, Finding[]] {
|
|
237
|
+
const r = sh('gh', ['pr', 'list', '--state', 'all', '--limit', String(limit), '--json', 'number,title,body,headRefName']);
|
|
238
|
+
if (!r.ok) {
|
|
239
|
+
return [{ surface: 'pr', status: 'unavailable', scanned: 0, unit: 'pull request', reason: `gh pr list failed: ${r.err.slice(0, 120)}`, counts: zero() }, []];
|
|
240
|
+
}
|
|
241
|
+
let prs: Array<{ number: number; title: string; body: string; headRefName: string }>;
|
|
242
|
+
try {
|
|
243
|
+
prs = JSON.parse(r.out) as typeof prs;
|
|
244
|
+
} catch {
|
|
245
|
+
return [{ surface: 'pr', status: 'unavailable', scanned: 0, unit: 'pull request', reason: 'gh returned unparseable JSON', counts: zero() }, []];
|
|
246
|
+
}
|
|
247
|
+
const findings: Finding[] = [];
|
|
248
|
+
// Dependency-bot PRs are excluded, and the reason is the one the config's
|
|
249
|
+
// own `r2_review_diff_capture` skip already states: a bot's body is a
|
|
250
|
+
// MECHANICAL CAPTURE of an upstream changelog, not authored attribution.
|
|
251
|
+
// Measured before the carve-out: 2,434 of the PR surface's 2,449 shape hits
|
|
252
|
+
// came from bot bodies linking the release notes of the packages they bump
|
|
253
|
+
// (top owners 856 / 213 / 203 / 150 …), which is dependency provenance —
|
|
254
|
+
// a class `provenance/borrows.jsonl` REQUIRES to name its upstream. Leaving
|
|
255
|
+
// them in would have buried the 15 real hits under a 99.4 % noise floor.
|
|
256
|
+
const bot = (ref: string): boolean => /^(?:dependabot|renovate)\//.test(ref);
|
|
257
|
+
const authored = prs.filter((p) => !bot(p.headRefName ?? ''));
|
|
258
|
+
for (const pr of authored) {
|
|
259
|
+
const anchor = `PR #${String(pr.number)}`;
|
|
260
|
+
findings.push(...scanText(`${pr.title}\n${pr.body ?? ''}\n${pr.headRefName}`, anchor, 'pr', 'block', deny));
|
|
261
|
+
}
|
|
262
|
+
return [{ surface: 'pr', status: 'scanned', scanned: authored.length, unit: 'authored pull request', counts: tally(findings) }, findings];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// --------------------------------------------------------------------------- //
|
|
266
|
+
// Candidate discovery — the half the deny set structurally cannot do
|
|
267
|
+
// --------------------------------------------------------------------------- //
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The deny set finds a source somebody already wrote down; the shape heuristic
|
|
271
|
+
* finds three specific FORMS. Neither can find a family named as a bare word in
|
|
272
|
+
* prose — which is exactly the class the roadmap's measurement table counts
|
|
273
|
+
* (twelve token families, 0 of 12 in the deny array). Something has to look, or
|
|
274
|
+
* Phase 0.3 ("extend the deny set from the census") has no input.
|
|
275
|
+
*
|
|
276
|
+
* This is that something, and it is a **review aid, not a matcher**: it lists
|
|
277
|
+
* identifier-shaped tokens that co-occur with an attribution cue on the same
|
|
278
|
+
* line and are not already denied. A human reads the ranked list and decides.
|
|
279
|
+
* It over-reports on purpose — a candidate list that needs pruning is cheap; a
|
|
280
|
+
* family nobody surfaced is the defect this roadmap exists for.
|
|
281
|
+
*/
|
|
282
|
+
const CUE_RE =
|
|
283
|
+
/\b(?:inspired\s+by|harvested\s+from|harvest\s+source|borrowed\s+from|ported\s+from|adapted\s+from|derived\s+from|copied\s+from|forked\s+from|taken\s+from|lifted\s+from|modelled\s+after|modeled\s+after|reference\s+repo(?:sitory)?|external\s+reference|upstream\s+(?:is|repo|project|source))\b/i;
|
|
284
|
+
|
|
285
|
+
/** Identifier-shaped: a hyphenated lowercase token, or an `owner/repo` pair. */
|
|
286
|
+
const CANDIDATE_RE = /\b([a-z][a-z0-9]*(?:-[a-z0-9]+){1,4})\b/g;
|
|
287
|
+
|
|
288
|
+
/** Tokens that are this suite's own vocabulary, not a candidate source. */
|
|
289
|
+
const OWN_VOCAB =
|
|
290
|
+
/^(?:agent-|road-to-|check-|lint-|audit-|skill-|rule-|dist-|src-|non-|pre-|post-|self-|cross-|multi-|sub-|re-|co-|user-|host-|token-|source-|third-party|read-only|write-only|end-to-end|up-to-date|opt-in|opt-out|fail-|well-|so-called)/;
|
|
291
|
+
|
|
292
|
+
export interface Candidate {
|
|
293
|
+
token: string;
|
|
294
|
+
hits: number;
|
|
295
|
+
/** First anchor the token was seen at, for a human to open. */
|
|
296
|
+
first: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function discoverCandidates(
|
|
300
|
+
findings: readonly Finding[],
|
|
301
|
+
texts: ReadonlyArray<{ anchor: string; text: string }>,
|
|
302
|
+
deny: ReadonlyArray<[string, RegExp]>,
|
|
303
|
+
): Candidate[] {
|
|
304
|
+
const seen = new Map<string, Candidate>();
|
|
305
|
+
for (const { anchor, text } of texts) {
|
|
306
|
+
if (!CUE_RE.test(text)) continue;
|
|
307
|
+
const cue = CUE_RE.exec(text);
|
|
308
|
+
const cueAt = cue ? cue.index : 0;
|
|
309
|
+
CANDIDATE_RE.lastIndex = 0;
|
|
310
|
+
let m: RegExpExecArray | null;
|
|
311
|
+
while ((m = CANDIDATE_RE.exec(text)) !== null) {
|
|
312
|
+
const tok = m[1] as string;
|
|
313
|
+
// Proximity: a hyphenated token 200 characters away from the cue is
|
|
314
|
+
// a coincidence, not the object of the attribution.
|
|
315
|
+
if (Math.abs(m.index - cueAt) > 60) continue;
|
|
316
|
+
if (OWN_VOCAB.test(tok)) continue;
|
|
317
|
+
if (deny.some(([, rx]) => rx.test(tok))) continue;
|
|
318
|
+
const prev = seen.get(tok);
|
|
319
|
+
if (prev) prev.hits += 1;
|
|
320
|
+
else seen.set(tok, { token: tok, hits: 1, first: anchor });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
void findings;
|
|
324
|
+
return [...seen.values()].sort((a, b) => b.hits - a.hits || a.token.localeCompare(b.token));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --------------------------------------------------------------------------- //
|
|
328
|
+
// Reporting
|
|
329
|
+
// --------------------------------------------------------------------------- //
|
|
330
|
+
|
|
331
|
+
function zero(): SurfaceReport['counts'] {
|
|
332
|
+
return { denylist: 0, shape: 0, block: 0, warn: 0, total: 0 };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function tally(f: readonly Finding[]): SurfaceReport['counts'] {
|
|
336
|
+
return {
|
|
337
|
+
denylist: f.filter((x) => x.matcher === 'denylist').length,
|
|
338
|
+
shape: f.filter((x) => x.matcher === 'shape').length,
|
|
339
|
+
block: f.filter((x) => x.tier === 'block').length,
|
|
340
|
+
warn: f.filter((x) => x.tier === 'warn').length,
|
|
341
|
+
total: f.length,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface SweepResult {
|
|
346
|
+
reports: SurfaceReport[];
|
|
347
|
+
findings: Finding[];
|
|
348
|
+
/** Ranked un-denied tokens on attribution-cue lines — Phase 0.3's input. */
|
|
349
|
+
candidates: Candidate[];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function runSweep(opts: { base: string; remote: boolean; limit: number }): SweepResult {
|
|
353
|
+
const deny = loadDeny();
|
|
354
|
+
const [content, paths, trackedFindings, cueTexts] = sweepTracked(deny);
|
|
355
|
+
const [commit, commitFindings] = sweepCommits(deny, opts.base);
|
|
356
|
+
const [branch, branchFindings] = sweepBranches(deny);
|
|
357
|
+
const [pr, prFindings] = opts.remote
|
|
358
|
+
? sweepPulls(deny, opts.limit)
|
|
359
|
+
: ([{ surface: 'pr' as const, status: 'unavailable' as const, scanned: 0, unit: 'pull request', reason: '--no-remote', counts: zero() }, [] as Finding[]] as [SurfaceReport, Finding[]]);
|
|
360
|
+
const findings = [...trackedFindings, ...commitFindings, ...branchFindings, ...prFindings];
|
|
361
|
+
return {
|
|
362
|
+
reports: [content, paths, commit, branch, pr],
|
|
363
|
+
findings,
|
|
364
|
+
candidates: discoverCandidates(findings, cueTexts, deny),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Per-class breakdown, names excluded — safe for a tracked artefact. */
|
|
369
|
+
export function classBreakdown(f: readonly Finding[]): Record<string, number> {
|
|
370
|
+
const out: Record<string, number> = {};
|
|
371
|
+
for (const x of f) {
|
|
372
|
+
const key = x.matcher === 'denylist' ? 'denylist' : `shape:${x.kind}`;
|
|
373
|
+
out[key] = (out[key] ?? 0) + 1;
|
|
374
|
+
}
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function renderCounts(res: SweepResult): string {
|
|
379
|
+
const lines: string[] = [];
|
|
380
|
+
lines.push('| surface | status | scanned | denylist | shape | block | warn | total |');
|
|
381
|
+
lines.push('|---|---|---:|---:|---:|---:|---:|---:|');
|
|
382
|
+
for (const r of res.reports) {
|
|
383
|
+
const c = r.counts;
|
|
384
|
+
const st = r.status === 'scanned' ? 'scanned' : `unavailable (${r.reason ?? '?'})`;
|
|
385
|
+
lines.push(
|
|
386
|
+
`| \`${r.surface}\` | ${st} | ${String(r.scanned)} ${r.unit}(s) | ${String(c.denylist)} | ` +
|
|
387
|
+
`${String(c.shape)} | ${String(c.block)} | ${String(c.warn)} | ${String(c.total)} |`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return lines.join('\n');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Phase 4.3 — gate the OFF-TREE metadata of one change.
|
|
396
|
+
*
|
|
397
|
+
* Four surfaces, none of which any content gate can reach: the branch name, the
|
|
398
|
+
* PR title, the PR body, and the commit subjects/bodies the change adds. All
|
|
399
|
+
* four become public the moment the PR opens, and unlike a tracked file none of
|
|
400
|
+
* them can be quietly fixed afterwards — a merged PR body and a trunk commit
|
|
401
|
+
* message are the immutable residual the `whether-history-gets-rewritten`
|
|
402
|
+
* blocker resolved to accept. So this runs BEFORE they are public: locally on
|
|
403
|
+
* pre-push, and on `pull_request` as the backstop.
|
|
404
|
+
*
|
|
405
|
+
* Block tier throughout. The `agents/**` tiering that softens the tracked-tree
|
|
406
|
+
* scan is a statement about where attribution lives in the REPO; a branch name
|
|
407
|
+
* has no path to tier on, and the public record has no warn level.
|
|
408
|
+
*/
|
|
409
|
+
export function gateMetadata(argv: readonly string[]): number {
|
|
410
|
+
const deny = loadDeny();
|
|
411
|
+
const arg = (flag: string): string => {
|
|
412
|
+
const i = argv.indexOf(flag);
|
|
413
|
+
return i >= 0 && argv[i + 1] !== undefined ? (argv[i + 1] as string) : '';
|
|
414
|
+
};
|
|
415
|
+
const findings: Finding[] = [];
|
|
416
|
+
let scanned = 0;
|
|
417
|
+
|
|
418
|
+
const branch = arg('--branch');
|
|
419
|
+
if (branch !== '') {
|
|
420
|
+
scanned += 1;
|
|
421
|
+
// The `true` flag scans the branch name as a PATH, so only the two
|
|
422
|
+
// path-shaped classes can fire on it: a literal `agents/tmp(.old)/<name>/`
|
|
423
|
+
// segment or a `github.com/<owner>/<repo>` URL. An ordinary speaking
|
|
424
|
+
// branch name (`feat/some-project-parity`) matches NEITHER — verified by
|
|
425
|
+
// the R2 review. So the branch surface is covered by the DENY SET, and
|
|
426
|
+
// by the shape half only in those two literal forms. Stated here rather
|
|
427
|
+
// than implied, because "branch names are shape-checked" would be an
|
|
428
|
+
// over-claim; closing it needs a branch-name class the heuristic does
|
|
429
|
+
// not have, and inventing one to match a doc sentence is how a
|
|
430
|
+
// false-positive-heavy class gets shipped.
|
|
431
|
+
findings.push(...scanText(branch, `branch ${branch}`, 'branch', 'block', deny, true));
|
|
432
|
+
}
|
|
433
|
+
const title = arg('--title');
|
|
434
|
+
if (title !== '') {
|
|
435
|
+
scanned += 1;
|
|
436
|
+
findings.push(...scanText(title, 'PR title', 'pr', 'block', deny));
|
|
437
|
+
}
|
|
438
|
+
const bodyFile = arg('--body-file');
|
|
439
|
+
if (bodyFile !== '') {
|
|
440
|
+
try {
|
|
441
|
+
const body = fs.readFileSync(path.resolve(ROOT, bodyFile), 'utf-8');
|
|
442
|
+
scanned += 1;
|
|
443
|
+
for (const [i, ln] of body.split('\n').entries()) {
|
|
444
|
+
findings.push(...scanText(ln, `PR body:${String(i + 1)}`, 'pr', 'block', deny));
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
process.stderr.write(`❌ --body-file ${bodyFile} is not readable\n`);
|
|
448
|
+
return 2;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const range = arg('--commit-range');
|
|
452
|
+
if (range !== '') {
|
|
453
|
+
const log = sh('git', ['log', '--format=%H%x1f%s%x1f%b%x1e', range]);
|
|
454
|
+
if (!log.ok) {
|
|
455
|
+
process.stderr.write(`❌ git log ${range} failed — cannot check the change's commit messages\n`);
|
|
456
|
+
return 2;
|
|
457
|
+
}
|
|
458
|
+
const recs = log.out.split('\x1e').map((r) => r.trim()).filter(Boolean);
|
|
459
|
+
scanned += recs.length;
|
|
460
|
+
for (const rec of recs) {
|
|
461
|
+
const [sha, subject, body] = rec.split('\x1f');
|
|
462
|
+
const anchor = `commit ${(sha ?? '').slice(0, 9)}`;
|
|
463
|
+
// PER LINE, not as one blob. `SOURCE_HEADER_RE` is anchored `^…$`
|
|
464
|
+
// with no `m` flag, so `sourceHeaderHits` returns nothing for any
|
|
465
|
+
// multi-line input — a commit body carrying a speaking `Source:`
|
|
466
|
+
// line was therefore invisible to the shape half while the PR body,
|
|
467
|
+
// scanned per line 14 lines above, was not. Found by the R2 review.
|
|
468
|
+
for (const ln of `${subject ?? ''}\n${body ?? ''}`.split('\n')) {
|
|
469
|
+
findings.push(...scanText(ln, anchor, 'commit', 'block', deny));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (scanned === 0) {
|
|
475
|
+
// A gate that scanned nothing exits green and checks nothing — this
|
|
476
|
+
// repo's own recorded failure class. Say so and fail as a usage error.
|
|
477
|
+
process.stderr.write(
|
|
478
|
+
'usage: --gate-metadata needs at least one of --branch / --title / --body-file / --commit-range\n',
|
|
479
|
+
);
|
|
480
|
+
return 2;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
process.stdout.write(`scanned: ${String(scanned)} metadata surface unit(s)\n`);
|
|
484
|
+
if (findings.length === 0) {
|
|
485
|
+
process.stdout.write('✅ no source attribution in this change\'s branch name, PR text or commit messages.\n');
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
process.stdout.write(`❌ ${String(findings.length)} source-attribution hit(s) in off-tree metadata:\n\n`);
|
|
489
|
+
for (const f of findings) {
|
|
490
|
+
// The CLASS only, never `kind`. For a `denylist` finding `kind` IS the
|
|
491
|
+
// deny PATTERN — i.e. the readable source name this gate exists to keep
|
|
492
|
+
// out of public surfaces — and this line runs in a PUBLIC GitHub Actions
|
|
493
|
+
// job log. Printing it disclosed the protected name at exactly the
|
|
494
|
+
// moment the gate fired, which is worse than the leak it was catching.
|
|
495
|
+
// Found by the R2 completion review of this branch, verified
|
|
496
|
+
// empirically. The shape classes are safe to name (`tmp-quote`,
|
|
497
|
+
// `source-header`, `repo-slug` describe a FORM), so they are printed —
|
|
498
|
+
// but the discriminator is the matcher, not the author's care.
|
|
499
|
+
const cls = f.matcher === 'denylist' ? 'denylist' : `shape:${f.kind}`;
|
|
500
|
+
process.stdout.write(` ${f.anchor} [${cls}]\n`);
|
|
501
|
+
}
|
|
502
|
+
process.stdout.write(
|
|
503
|
+
'\nBranch names, PR titles, PR bodies and commit messages are PUBLIC and\n' +
|
|
504
|
+
'effectively immutable once merged — no content gate can see them and no\n' +
|
|
505
|
+
'later edit un-publishes them. Rename the branch, rewrite the title/body, or\n' +
|
|
506
|
+
'amend the commit message to an opaque round identifier before pushing.\n' +
|
|
507
|
+
'The value itself is deliberately NOT printed. Rule: source-confidentiality.\n',
|
|
508
|
+
);
|
|
509
|
+
return 1;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function main(argv: readonly string[]): number {
|
|
513
|
+
if (argv.includes('--gate-metadata')) {
|
|
514
|
+
return gateMetadata(argv);
|
|
515
|
+
}
|
|
516
|
+
const asJson = argv.includes('--json');
|
|
517
|
+
const remote = !argv.includes('--no-remote');
|
|
518
|
+
const bi = argv.indexOf('--base');
|
|
519
|
+
const base = bi >= 0 && argv[bi + 1] !== undefined ? (argv[bi + 1] as string) : 'origin/main';
|
|
520
|
+
const li = argv.indexOf('--limit');
|
|
521
|
+
const limit = li >= 0 && argv[li + 1] !== undefined ? Number(argv[li + 1]) : 3000;
|
|
522
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
523
|
+
process.stderr.write('usage: --limit takes a positive integer\n');
|
|
524
|
+
return 2;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const dIdx = argv.indexOf('--decrypt');
|
|
528
|
+
if (dIdx >= 0) {
|
|
529
|
+
if (argv[dIdx + 1] === undefined) {
|
|
530
|
+
process.stderr.write('usage: --decrypt <census-file>\n');
|
|
531
|
+
return 2;
|
|
532
|
+
}
|
|
533
|
+
return decryptCensus(path.resolve(ROOT, argv[dIdx + 1] as string));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let res: SweepResult;
|
|
537
|
+
try {
|
|
538
|
+
res = runSweep({ base, remote, limit });
|
|
539
|
+
} catch (exc) {
|
|
540
|
+
process.stderr.write(`${(exc as Error).message}\n`);
|
|
541
|
+
return 2;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (asJson) {
|
|
545
|
+
// PLAINTEXT — every name the programme hides. Terminal only.
|
|
546
|
+
process.stdout.write(
|
|
547
|
+
JSON.stringify({ reports: res.reports, findings: res.findings, candidates: res.candidates }, null, 2) + '\n',
|
|
548
|
+
);
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
process.stdout.write(renderCounts(res) + '\n\n');
|
|
553
|
+
process.stdout.write(`classes: ${JSON.stringify(classBreakdown(res.findings))}\n`);
|
|
554
|
+
process.stdout.write(`candidates: ${String(res.candidates.length)} un-denied token(s) on attribution-cue lines\n`);
|
|
555
|
+
process.stdout.write(`scanned: ${String(res.reports.reduce((a, r) => a + r.scanned, 0))}\n`);
|
|
556
|
+
|
|
557
|
+
const ci = argv.indexOf('--census');
|
|
558
|
+
if (ci >= 0 && argv[ci + 1] !== undefined) {
|
|
559
|
+
const out = path.resolve(ROOT, argv[ci + 1] as string);
|
|
560
|
+
const keys = resolve_keys(ROOT);
|
|
561
|
+
if (keys.length === 0) {
|
|
562
|
+
process.stderr.write(
|
|
563
|
+
'❌ no link-encryption key resolvable — refusing to write a census.\n' +
|
|
564
|
+
' The findings list is plaintext source attribution; writing it unencrypted\n' +
|
|
565
|
+
' would publish exactly what this sweep exists to inventory. Set\n' +
|
|
566
|
+
' secrets.link_encryption_key in .agent-settings.yml (project or user-global)\n' +
|
|
567
|
+
' or EVENT4U_LINK_KEY, then re-run.\n',
|
|
568
|
+
);
|
|
569
|
+
return 2;
|
|
570
|
+
}
|
|
571
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
572
|
+
fs.writeFileSync(out, renderCensus(res, keys[0] as string), 'utf-8');
|
|
573
|
+
process.stdout.write(`census: ${path.relative(ROOT, out)}\n`);
|
|
574
|
+
}
|
|
575
|
+
return 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** Print the decrypted payload of a census file. Terminal only — plaintext names. */
|
|
579
|
+
export function decryptCensus(file: string): number {
|
|
580
|
+
let text: string;
|
|
581
|
+
try {
|
|
582
|
+
text = fs.readFileSync(file, 'utf-8');
|
|
583
|
+
} catch {
|
|
584
|
+
process.stderr.write(`cannot read ${file}\n`);
|
|
585
|
+
return 2;
|
|
586
|
+
}
|
|
587
|
+
const m = /ENC1:[A-Za-z0-9+/=]+/.exec(text);
|
|
588
|
+
if (!m) {
|
|
589
|
+
process.stderr.write('no ENC1 token in that file\n');
|
|
590
|
+
return 2;
|
|
591
|
+
}
|
|
592
|
+
const keys = resolve_keys(ROOT);
|
|
593
|
+
if (keys.length === 0) {
|
|
594
|
+
process.stderr.write('no link-encryption key resolvable\n');
|
|
595
|
+
return 2;
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
process.stdout.write(decrypt(m[0], keys) + '\n');
|
|
599
|
+
} catch (exc) {
|
|
600
|
+
process.stderr.write(`${(exc as Error).message}\n`);
|
|
601
|
+
return 2;
|
|
602
|
+
}
|
|
603
|
+
return 0;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** The tracked artefact: counts in the clear, findings as one `ENC1:` line. */
|
|
607
|
+
export function renderCensus(res: SweepResult, key: string, now = new Date()): string {
|
|
608
|
+
const cipher = encrypt(JSON.stringify({ findings: res.findings, candidates: res.candidates }), key);
|
|
609
|
+
const stamp = now.toISOString().slice(0, 10);
|
|
610
|
+
const residual = res.findings.filter((f) => f.surface === 'commit' || f.surface === 'pr').length;
|
|
611
|
+
return `<!-- evidence-type: analysis -->
|
|
612
|
+
# Source-attribution census — every surface, counts only
|
|
613
|
+
|
|
614
|
+
> Generated by \`./scripts-run src/scripts/sweep_source_surfaces --census <path>\`
|
|
615
|
+
> on ${stamp}. Regenerate rather than hand-edit.
|
|
616
|
+
|
|
617
|
+
**This file deliberately contains no source name.** The findings list is the
|
|
618
|
+
most concentrated source disclosure this repository could hold, so it lives
|
|
619
|
+
below as a single \`ENC1:\` ciphertext (\`src/scripts/_lib/link_crypto.ts\`) and
|
|
620
|
+
the readable half is counts. Decrypt with:
|
|
621
|
+
|
|
622
|
+
\`\`\`bash
|
|
623
|
+
./scripts-run src/scripts/sweep_source_surfaces --decrypt <this file>
|
|
624
|
+
\`\`\`
|
|
625
|
+
|
|
626
|
+
Read it from the FILE, never by pasting the token into
|
|
627
|
+
\`link_crypto decrypt --value\`: the ciphertext is ~320 kB on one line and a
|
|
628
|
+
shell argument that size is mangled before the CLI ever sees it (reproduced —
|
|
629
|
+
the paste path reports \`authentication failed\`, the file path round-trips).
|
|
630
|
+
|
|
631
|
+
## Per-surface counts
|
|
632
|
+
|
|
633
|
+
${renderCounts(res)}
|
|
634
|
+
|
|
635
|
+
## Per-class counts
|
|
636
|
+
|
|
637
|
+
\`\`\`json
|
|
638
|
+
${JSON.stringify(classBreakdown(res.findings), null, 2)}
|
|
639
|
+
\`\`\`
|
|
640
|
+
|
|
641
|
+
candidates: ${String(res.candidates.length)}
|
|
642
|
+
|
|
643
|
+
The \`candidates:\` field counts un-denied identifier-shaped tokens found on
|
|
644
|
+
attribution-cue lines. It is Phase 0.3's **input**, not its result: a human
|
|
645
|
+
reads the decrypted list and decides which are real source families. It
|
|
646
|
+
over-reports by design — the deny set structurally cannot find a family nobody
|
|
647
|
+
listed, so something has to over-report or nothing looks at all.
|
|
648
|
+
|
|
649
|
+
residual: ${String(residual)}
|
|
650
|
+
|
|
651
|
+
The \`residual:\` field is the count on the two **immutable** surfaces — commit
|
|
652
|
+
messages already on the trunk, and PR bodies of merged pull requests. The
|
|
653
|
+
\`whether-history-gets-rewritten\` blocker resolved **(a) — no rewrite**, so this
|
|
654
|
+
number is accepted debt that is counted rather than removed. It does not shrink
|
|
655
|
+
to zero and is not meant to: anyone with repository access can still recover
|
|
656
|
+
those names from history, and no phase of this programme changes that.
|
|
657
|
+
|
|
658
|
+
## Findings (encrypted)
|
|
659
|
+
|
|
660
|
+
${cipher}
|
|
661
|
+
`;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function _isCliEntry(): boolean {
|
|
665
|
+
if (process.argv[1] === undefined) return false;
|
|
666
|
+
if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) return true;
|
|
667
|
+
try {
|
|
668
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
669
|
+
} catch {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (_isCliEntry()) {
|
|
675
|
+
process.exit(main(process.argv.slice(2)));
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export { main };
|