@event4u/agent-config 14.11.0 → 14.12.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 +156 -538
- package/README.md +1 -1
- package/dist/agent-src/contexts/execution/evidence-discipline.md +26 -0
- package/dist/agent-src/skills/code-refactoring/SKILL.md +45 -1
- package/dist/agent-src/skills/code-review/SKILL.md +65 -6
- package/dist/agent-src/skills/command-writing/SKILL.md +35 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -1
- package/dist/agent-src/skills/project-analysis-core/SKILL.md +23 -0
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +29 -12
- package/dist/agent-src/skills/skill-writing/references/procedure.md +24 -2
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +9 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +8 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-L43NCYPB.js → chunk-JCW7ZLHK.js} +5 -0
- package/dist/cli-delegate/{chunk-236PNQZK.js → chunk-JJA2QEWA.js} +1 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +10 -10
- 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 +1081 -879
- package/dist/install/install.mjs +5 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/server/schemas/settings.js +3 -0
- package/docs/contracts/settings-classes.md +24 -4
- package/docs/guidelines/agent-infra/downstream-changes-mechanics.md +91 -0
- package/docs/guidelines/code-clarity.md +21 -0
- package/docs/guidelines/redundancy-taxonomy.md +180 -0
- package/package.json +6 -2
- package/src/config/agent-settings.template.yml +16 -0
- package/src/config/budgets.yml +19 -0
- package/src/config/ci-local-parity.yml +15 -0
- package/src/config/discovery/prompt-patterns.yml +56 -0
- package/src/config/estate-count-budget.json +8 -3
- package/src/config/gate-coverage.yml +191 -6
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/pack-size-budget.json +39 -12
- package/src/config/preamble-payload-budget.json +97 -71
- package/src/config/routing-coverage-seed.json +29 -0
- package/src/config/suggestion-capture.json +28 -0
- package/src/config/surface-matrix.yml +21 -2
- package/src/config/web-launch-readiness.json +188 -0
- package/src/scripts/_lib/gate_baseline.ts +150 -1
- package/src/scripts/_lib/skill_estate.ts +115 -0
- package/src/scripts/ai_council/argument_exhaustion.ts +130 -0
- package/src/scripts/check_bundle_path_leakage.ts +162 -7
- package/src/scripts/check_command_examples.ts +557 -0
- package/src/scripts/check_estate_count.ts +122 -3
- package/src/scripts/check_preamble_payload_budget.ts +30 -3
- package/src/scripts/check_routing_coverage.ts +352 -0
- package/src/scripts/check_score_contract.ts +505 -0
- package/src/scripts/check_skill_admissions.ts +514 -0
- package/src/scripts/check_web_launch_readiness.ts +705 -0
- package/src/scripts/description_route_check.ts +494 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +29 -2
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/suggestion_capture_hook.ts +368 -0
- package/src/scripts/lint_budget_ownership.ts +54 -4
- package/src/scripts/lint_skill_trigger_corpus.ts +349 -0
- package/src/scripts/pack_install_smoke.ts +363 -0
- package/src/scripts/pr_conflict_census.ts +187 -0
- package/src/scripts/report_pack_delta.ts +204 -0
- package/src/scripts/select_analysis_mode.ts +112 -0
- package/src/scripts/sync_pr_branch.ts +40 -1
- package/src/server/schemas/settings.ts +5 -0
- package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +0 -15
- package/dist/agent-src/skills/agent-security-review/evals/triggers.json +0 -52
- package/dist/agent-src/skills/ai-code-blindspots/evals/triggers.json +0 -52
- package/dist/agent-src/skills/alerting-doctrine/evals/triggers.json +0 -17
- package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +0 -13
- package/dist/agent-src/skills/brand/evals/triggers.json +0 -17
- package/dist/agent-src/skills/brand-asset-generation/evals/triggers.json +0 -17
- package/dist/agent-src/skills/brand-audit/evals/triggers.json +0 -17
- package/dist/agent-src/skills/brand-identity/evals/triggers.json +0 -17
- package/dist/agent-src/skills/brand-strategy/evals/triggers.json +0 -17
- package/dist/agent-src/skills/brand-to-tokens/evals/triggers.json +0 -17
- package/dist/agent-src/skills/bug-analyzer/evals/evals.json +0 -13
- package/dist/agent-src/skills/canvas-design/evals/triggers.json +0 -17
- package/dist/agent-src/skills/character-consistency/evals/evals.json +0 -16
- package/dist/agent-src/skills/code-intelligence/evals/triggers.json +0 -52
- package/dist/agent-src/skills/code-review/evals/evals.json +0 -100
- package/dist/agent-src/skills/command-routing/evals/evals.json +0 -14
- package/dist/agent-src/skills/complexity-first-planning/evals/triggers.json +0 -17
- package/dist/agent-src/skills/contract-review/evals/triggers.json +0 -17
- package/dist/agent-src/skills/conventional-commits-writing/evals/evals.json +0 -15
- package/dist/agent-src/skills/corpus-grounding/evals/triggers.json +0 -17
- package/dist/agent-src/skills/dashboard-design/evals/evals.json +0 -16
- package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +0 -29
- package/dist/agent-src/skills/dcf-modeling/evals/evals.json +0 -20
- package/dist/agent-src/skills/decision-record/evals/evals.json +0 -20
- package/dist/agent-src/skills/decision-record/evals/triggers.json +0 -87
- package/dist/agent-src/skills/design-intelligence/evals/evals.json +0 -14
- package/dist/agent-src/skills/design-intelligence/evals/triggers.json +0 -17
- package/dist/agent-src/skills/design-system-capture/evals/evals.json +0 -15
- package/dist/agent-src/skills/design-tokens/evals/triggers.json +0 -17
- package/dist/agent-src/skills/design-variations/evals/triggers.json +0 -17
- package/dist/agent-src/skills/developer-like-execution/evals/evals.json +0 -16
- package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +0 -16
- package/dist/agent-src/skills/doc-coauthoring/evals/triggers.json +0 -17
- package/dist/agent-src/skills/docx-authoring/evals/triggers.json +0 -52
- package/dist/agent-src/skills/dpa-review/evals/triggers.json +0 -17
- package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +0 -16
- package/dist/agent-src/skills/editorial-calendar/evals/evals.json +0 -16
- package/dist/agent-src/skills/eloquent/evals/triggers.json +0 -17
- package/dist/agent-src/skills/estimate-ticket/evals/evals.json +0 -16
- package/dist/agent-src/skills/estimate-ticket/evals/output-schema.yml +0 -20
- package/dist/agent-src/skills/estimate-ticket/evals/triggers.json +0 -19
- package/dist/agent-src/skills/evaluate-llm-feature/evals/triggers.json +0 -52
- package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +0 -47
- package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +0 -27
- package/dist/agent-src/skills/experiment-loop/evals/triggers.json +0 -17
- package/dist/agent-src/skills/fe-design/evals/triggers.json +0 -67
- package/dist/agent-src/skills/forecasting/evals/domain-truth.json +0 -29
- package/dist/agent-src/skills/forecasting/evals/evals.json +0 -20
- package/dist/agent-src/skills/forensics-report/evals/triggers.json +0 -17
- package/dist/agent-src/skills/frontend-render-security/evals/triggers.json +0 -52
- package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +0 -16
- package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +0 -16
- package/dist/agent-src/skills/gated-reach/evals/triggers.json +0 -72
- package/dist/agent-src/skills/github-ci/evals/evals.json +0 -25
- package/dist/agent-src/skills/history-design/evals/triggers.json +0 -17
- package/dist/agent-src/skills/html-deck/evals/triggers.json +0 -17
- package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +0 -57
- package/dist/agent-src/skills/humanizer/evals/triggers.json +0 -17
- package/dist/agent-src/skills/iconography/evals/triggers.json +0 -17
- package/dist/agent-src/skills/image-analyser/evals/triggers.json +0 -17
- package/dist/agent-src/skills/image-creator/evals/triggers.json +0 -17
- package/dist/agent-src/skills/image-editing/evals/triggers.json +0 -17
- package/dist/agent-src/skills/image-generation/evals/triggers.json +0 -17
- package/dist/agent-src/skills/image-provider-routing/evals/triggers.json +0 -17
- package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +0 -16
- package/dist/agent-src/skills/incident-commander/evals/evals.json +0 -20
- package/dist/agent-src/skills/js-library-packaging/evals/triggers.json +0 -17
- package/dist/agent-src/skills/judge-artifact-completeness/evals/triggers.json +0 -17
- package/dist/agent-src/skills/judge-injection-defense/evals/triggers.json +0 -17
- package/dist/agent-src/skills/judge-synthesis/evals/triggers.json +0 -17
- package/dist/agent-src/skills/launch-readiness/evals/evals.json +0 -21
- package/dist/agent-src/skills/learning-tutor/evals/triggers.json +0 -52
- package/dist/agent-src/skills/legal-intake-triage/evals/triggers.json +0 -17
- package/dist/agent-src/skills/legal-practice-profile/evals/triggers.json +0 -17
- package/dist/agent-src/skills/license-compliance-audit/evals/triggers.json +0 -52
- package/dist/agent-src/skills/license-compliance-borrow-check/evals/triggers.json +0 -52
- package/dist/agent-src/skills/license-compliance-credits/evals/triggers.json +0 -52
- package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +0 -20
- package/dist/agent-src/skills/logging-monitoring/evals/evals.json +0 -16
- package/dist/agent-src/skills/logo-generation/evals/triggers.json +0 -17
- package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +0 -16
- package/dist/agent-src/skills/messaging-architecture/evals/evals.json +0 -16
- package/dist/agent-src/skills/monorepo-workspace/evals/triggers.json +0 -17
- package/dist/agent-src/skills/nda-triage/evals/triggers.json +0 -17
- package/dist/agent-src/skills/operational-readiness/evals/triggers.json +0 -17
- package/dist/agent-src/skills/pdf-tools/evals/triggers.json +0 -52
- package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +0 -16
- package/dist/agent-src/skills/persona-improvement/evals/triggers.json +0 -52
- package/dist/agent-src/skills/php-coder/evals/triggers.json +0 -17
- package/dist/agent-src/skills/prediction-pool-optimizer/evals/triggers.json +0 -21
- package/dist/agent-src/skills/prompt-engineering-image/evals/triggers.json +0 -17
- package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +0 -16
- package/dist/agent-src/skills/prompt-validator/evals/triggers.json +0 -17
- package/dist/agent-src/skills/quality-tools/evals/evals.json +0 -15
- package/dist/agent-src/skills/reasoning-orchestrator/evals/triggers.json +0 -17
- package/dist/agent-src/skills/refine-prompt/evals/evals.json +0 -16
- package/dist/agent-src/skills/refine-ticket/evals/evals.json +0 -16
- package/dist/agent-src/skills/refine-ticket/evals/output-schema.yml +0 -16
- package/dist/agent-src/skills/refine-ticket/evals/triggers.json +0 -17
- package/dist/agent-src/skills/release-comms/evals/evals.json +0 -16
- package/dist/agent-src/skills/rice-prioritization/evals/evals.json +0 -20
- package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +0 -29
- package/dist/agent-src/skills/runway-cognition/evals/evals.json +0 -20
- package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +0 -29
- package/dist/agent-src/skills/scenario-modeling/evals/evals.json +0 -20
- package/dist/agent-src/skills/schema-review/evals/triggers.json +0 -17
- package/dist/agent-src/skills/screenshot-hygiene/evals/triggers.json +0 -52
- package/dist/agent-src/skills/security-maturity-assessment/evals/triggers.json +0 -52
- package/dist/agent-src/skills/server-hardening/evals/triggers.json +0 -17
- package/dist/agent-src/skills/skill-writing/evals/triggers.json +0 -17
- package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +0 -47
- package/dist/agent-src/skills/storybook-workshop/evals/triggers.json +0 -17
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +0 -60
- package/dist/agent-src/skills/supply-chain-intake/evals/triggers.json +0 -52
- package/dist/agent-src/skills/systematic-debugging/evals/evals.json +0 -20
- package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +0 -52
- package/dist/agent-src/skills/test-driven-development/evals/evals.json +0 -16
- package/dist/agent-src/skills/threat-modeling/evals/evals.json +0 -21
- package/dist/agent-src/skills/typography-system/evals/evals.json +0 -14
- package/dist/agent-src/skills/typography-system/evals/triggers.json +0 -17
- package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +0 -40
- package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +0 -24
- package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +0 -16
- package/dist/agent-src/skills/verify-repair-loop/evals/output-schema.yml +0 -20
- package/dist/agent-src/skills/verify-repair-loop/evals/triggers.json +0 -17
- package/dist/agent-src/skills/vision-articulation/evals/evals.json +0 -16
- package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +0 -16
- package/dist/agent-src/skills/wireframe/evals/triggers.json +0 -17
- package/dist/agent-src/skills/workspace-link/evals/triggers.json +0 -17
- package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +0 -38
- package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +0 -52
- package/dist/cli/agent-config.js.map +0 -1
- package/dist/cli/bash/runBash.js.map +0 -1
- package/dist/cli/commands/commands.js.map +0 -1
- package/dist/cli/commands/config.js.map +0 -1
- package/dist/cli/commands/doctorShell.js.map +0 -1
- package/dist/cli/commands/mcpServer.js.map +0 -1
- package/dist/cli/commands/packs.js.map +0 -1
- package/dist/cli/commands/profiles.js.map +0 -1
- package/dist/cli/commands/recordTriggerEval.js.map +0 -1
- package/dist/cli/commands/rtkDetect.js.map +0 -1
- package/dist/cli/commands/settings.js.map +0 -1
- package/dist/cli/commands/uiAudit.js.map +0 -1
- package/dist/cli/commands/uiRender.js.map +0 -1
- package/dist/cli/commands/uiServe.js.map +0 -1
- package/dist/cli/commands/versions.js.map +0 -1
- package/dist/cli/commands/workspaces.js.map +0 -1
- package/dist/cli/configRoot.js.map +0 -1
- package/dist/cli/discovery/loadManifest.js.map +0 -1
- package/dist/cli/firstRunNotice.js.map +0 -1
- package/dist/cli/help.js.map +0 -1
- package/dist/cli/initRouting.js.map +0 -1
- package/dist/cli/log/logger.js.map +0 -1
- package/dist/cli/main.js.map +0 -1
- package/dist/cli/mcp/content.js.map +0 -1
- package/dist/cli/mcp/dispatch.js.map +0 -1
- package/dist/cli/mcp/stdio.js.map +0 -1
- package/dist/cli/paths.js.map +0 -1
- package/dist/cli/python/knowledge_ingest.js.map +0 -1
- package/dist/cli/python/workspace_analytics.js.map +0 -1
- package/dist/cli/python/workspace_crypto.js.map +0 -1
- package/dist/cli/python/workspace_documents.js.map +0 -1
- package/dist/cli/python/workspace_drive.js.map +0 -1
- package/dist/cli/python/workspace_drive_health.js.map +0 -1
- package/dist/cli/python/workspace_explain.js.map +0 -1
- package/dist/cli/python/workspace_hosts.js.map +0 -1
- package/dist/cli/python/workspace_inbox.js.map +0 -1
- package/dist/cli/python/workspace_render.js.map +0 -1
- package/dist/cli/python/workspace_roles.js.map +0 -1
- package/dist/cli/python/workspace_secrets.js.map +0 -1
- package/dist/cli/python/workspace_sessions.js.map +0 -1
- package/dist/cli/python/workspace_skills.js.map +0 -1
- package/dist/cli/registry.js.map +0 -1
- package/dist/install/agentSwitchDetection.js.map +0 -1
- package/dist/install/agentSwitchProfile.js.map +0 -1
- package/dist/install/atomic.js.map +0 -1
- package/dist/install/claudePathsPlan.js.map +0 -1
- package/dist/install/conflict.js.map +0 -1
- package/dist/install/detect.js.map +0 -1
- package/dist/install/emit_host_rules_cli.js.map +0 -1
- package/dist/install/globalRuleLayers.js.map +0 -1
- package/dist/install/hostLayerFingerprint.js.map +0 -1
- package/dist/install/partitionEligibility.js.map +0 -1
- package/dist/install/paths.js.map +0 -1
- package/dist/install/plan.js.map +0 -1
- package/dist/install/preflight.js.map +0 -1
- package/dist/install/rtkDetection.js.map +0 -1
- package/dist/install/ruleInScope.js.map +0 -1
- package/dist/install/ruleLayerPartition.js.map +0 -1
- package/dist/install/rule_scope.js.map +0 -1
- package/dist/install/rule_scope_cli.js.map +0 -1
- package/dist/install/selectedTools.js.map +0 -1
- package/dist/install/toolDetection.js.map +0 -1
- package/dist/install/txlog.js.map +0 -1
- package/dist/install/types.js.map +0 -1
- package/dist/install/wizard-plan.js.map +0 -1
- package/dist/install/wizardDismissals.js.map +0 -1
- package/dist/scripts/_lib/agent_settings.js.map +0 -1
- package/dist/scripts/_lib/agent_src.js.map +0 -1
- package/dist/scripts/_lib/claude_builtin_names.js.map +0 -1
- package/dist/scripts/_lib/claude_plugin.js.map +0 -1
- package/dist/scripts/_lib/claude_settings_hooks.js.map +0 -1
- package/dist/scripts/_lib/compile_time_toggles.js.map +0 -1
- package/dist/scripts/_lib/environment_detector.js.map +0 -1
- package/dist/scripts/_lib/host_launch.js.map +0 -1
- package/dist/scripts/_lib/install_layout.js.map +0 -1
- package/dist/scripts/_lib/installed_lock.js.map +0 -1
- package/dist/scripts/_lib/lean_projection_mode.js.map +0 -1
- package/dist/scripts/_lib/model_tier.js.map +0 -1
- package/dist/scripts/_lib/prune_empty_dirs.js.map +0 -1
- package/dist/scripts/_lib/scoped_projection.js.map +0 -1
- package/dist/scripts/_lib/script_output.js.map +0 -1
- package/dist/scripts/_lib/spawn_env.js.map +0 -1
- package/dist/scripts/_lib/surface_tiers.js.map +0 -1
- package/dist/scripts/_lib/token_count.js.map +0 -1
- package/dist/scripts/_lib/trigger_eval_floors.js.map +0 -1
- package/dist/scripts/_lib/ui_surface.js.map +0 -1
- package/dist/scripts/_lib/user_global_paths.js.map +0 -1
- package/dist/scripts/ai_council/modes.js.map +0 -1
- package/dist/scripts/ai_council/transport_resolver.js.map +0 -1
- package/dist/scripts/condense.js.map +0 -1
- package/dist/scripts/project_thin_rules.js.map +0 -1
- package/dist/server/app.js.map +0 -1
- package/dist/server/io/atomicMultiWrite.js.map +0 -1
- package/dist/server/io/atomicWrite.js.map +0 -1
- package/dist/server/io/sharedWriteCheck.js.map +0 -1
- package/dist/server/io/substituteTemplate.js.map +0 -1
- package/dist/server/io/yamlIO.js.map +0 -1
- package/dist/server/nicknameResolver.js.map +0 -1
- package/dist/server/port.js.map +0 -1
- package/dist/server/routes/discovery.js.map +0 -1
- package/dist/server/routes/install.js.map +0 -1
- package/dist/server/routes/ping.js.map +0 -1
- package/dist/server/routes/schema.js.map +0 -1
- package/dist/server/routes/settings.js.map +0 -1
- package/dist/server/routes/settingsChanges.js.map +0 -1
- package/dist/server/routes/userMd.js.map +0 -1
- package/dist/server/routes/wizard.js.map +0 -1
- package/dist/server/routes/workspace.js.map +0 -1
- package/dist/server/schemas/settings.js.map +0 -1
- package/dist/server/serverInfo.js.map +0 -1
- package/dist/server/token.js.map +0 -1
- package/dist/server/writeRoot.js.map +0 -1
- package/dist/shared/capabilities.js.map +0 -1
- package/dist/shared/interactiveContext.js.map +0 -1
- package/dist/shared/nicknamePrefill.js.map +0 -1
- package/dist/shared/settingsAsks.js.map +0 -1
- package/dist/shared/settingsCarveOut.js.map +0 -1
- package/dist/shared/settingsClasses.js.map +0 -1
- package/dist/shared/settingsConsent.js.map +0 -1
- package/dist/shared/settingsSurface.js.map +0 -1
- package/dist/shared/skillRanking.js.map +0 -1
- package/dist/shared/userMd/formAdapter.js.map +0 -1
- package/dist/shared/userMd/schema.js.map +0 -1
- package/dist/shared/userMd/utils.js.map +0 -1
- package/dist/ui/assets/index-Cxgy63kz.js.map +0 -1
- package/src/scripts/_lib/adr_frontmatter.test.ts +0 -365
- package/src/scripts/_lib/adversarial_council_gate.test.ts +0 -112
- package/src/scripts/_lib/adversarial_reconcile.test.ts +0 -197
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +0 -150
- package/src/scripts/_lib/secret_detector.test.ts +0 -108
- package/src/scripts/_lib/secret_detector_adversarial.test.ts +0 -116
- package/src/scripts/_lib/ui_authority.test.ts +0 -251
- package/src/scripts/_lib/untrusted_content.test.ts +0 -175
- package/src/scripts/check_secret_leak.test.ts +0 -90
- package/src/scripts/design_slop_rules.test.ts +0 -246
- package/src/scripts/self_review_gate.test.ts +0 -296
- package/src/shared/capabilities.test.ts +0 -43
|
@@ -32,8 +32,10 @@
|
|
|
32
32
|
* Dates are plain `YYYY-MM-DD`; ages are whole days in UTC, so the verdict does
|
|
33
33
|
* not depend on the machine's timezone.
|
|
34
34
|
*/
|
|
35
|
+
import { execFileSync } from 'node:child_process';
|
|
35
36
|
import * as fs from 'node:fs';
|
|
36
37
|
import * as path from 'node:path';
|
|
38
|
+
import process from 'node:process';
|
|
37
39
|
|
|
38
40
|
/** Days a baseline may sit unchanged before it must be fixed or reaffirmed. */
|
|
39
41
|
export const STALE_AFTER_DAYS = 56;
|
|
@@ -105,12 +107,132 @@ export function loadBaselines(repoRoot: string, relPath: string = BASELINE_REL):
|
|
|
105
107
|
} catch {
|
|
106
108
|
return { gates: {} };
|
|
107
109
|
}
|
|
110
|
+
return parseBaselines(raw);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseBaselines(raw: string): BaselineFile {
|
|
108
114
|
const parsed = JSON.parse(raw) as Partial<BaselineFile>;
|
|
109
115
|
const out: BaselineFile = { gates: parsed.gates ?? {} };
|
|
110
116
|
if (parsed.$comment !== undefined) out.$comment = parsed.$comment;
|
|
111
117
|
return out;
|
|
112
118
|
}
|
|
113
119
|
|
|
120
|
+
/**
|
|
121
|
+
* A baseline-resolution failure, which is a HARD error and never a fallback.
|
|
122
|
+
*
|
|
123
|
+
* `loadBaselines` returns an empty ratchet when the file is missing, and that
|
|
124
|
+
* is correct for a working-tree read: a repository with no baseline file has no
|
|
125
|
+
* ratchet. It is NOT correct for a target-commit read. There, a failure to
|
|
126
|
+
* resolve the ref or the blob means the governing policy could not be
|
|
127
|
+
* determined — and silently falling back to the working tree would let an
|
|
128
|
+
* infrastructure error change which policy applies, invisibly. The AI council
|
|
129
|
+
* was unanimous on this point (2026-08-25) and stated it applies to whichever
|
|
130
|
+
* invariant won.
|
|
131
|
+
*/
|
|
132
|
+
export class BaselineResolutionError extends Error {
|
|
133
|
+
constructor(message: string) {
|
|
134
|
+
super(message);
|
|
135
|
+
this.name = 'BaselineResolutionError';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Read the baseline as it stands at a specific commit.
|
|
141
|
+
*
|
|
142
|
+
* `road-to-merge-surface-zero` Phase 3.1, under the **ABSOLUTE invariant** the
|
|
143
|
+
* AI council picked 2/2 on 2026-08-25 to resolve blocker B5.
|
|
144
|
+
*
|
|
145
|
+
* ## Why the target commit and not the merge base
|
|
146
|
+
*
|
|
147
|
+
* The merge-base read was the roadmap's original proposal and it selects the
|
|
148
|
+
* *contribution* invariant: a PR passes if it did not worsen the state it
|
|
149
|
+
* branched from. The worked example that killed it uses numbers this repository
|
|
150
|
+
* actually carries — `main` tightens `ci-parity:local-only` 165 → 160, a PR that
|
|
151
|
+
* branched earlier measures 163, the merge-base read returns 165, the PR
|
|
152
|
+
* **passes**, and after it merges `main` measures 163 against a baseline of 160.
|
|
153
|
+
* **A tightening is undone by a PR that never touched the baseline file and
|
|
154
|
+
* never saw a red.**
|
|
155
|
+
*
|
|
156
|
+
* The remedy of checking both was refused on a correctness ground rather than a
|
|
157
|
+
* preference: **violation counts are not necessarily compositional.** Conflict
|
|
158
|
+
* resolution, file movement, generated outputs and interactions with changes on
|
|
159
|
+
* `main` can make a prospective merge regress even when the isolated PR delta is
|
|
160
|
+
* non-positive. If trunk health is the invariant, the merge RESULT is what has
|
|
161
|
+
* to be measured.
|
|
162
|
+
*
|
|
163
|
+
* ## Why the TARGET commit specifically, and not the merge tree
|
|
164
|
+
*
|
|
165
|
+
* The count is measured on the prospective merge tree; the **policy** is read
|
|
166
|
+
* from the target. Reading the baseline out of the merge tree would let a PR
|
|
167
|
+
* loosen the very number it is being judged against, in the same diff.
|
|
168
|
+
*
|
|
169
|
+
* @throws BaselineResolutionError when the ref or the blob cannot be resolved.
|
|
170
|
+
*/
|
|
171
|
+
export function loadBaselinesAt(
|
|
172
|
+
repoRoot: string,
|
|
173
|
+
targetRef: string,
|
|
174
|
+
relPath: string = BASELINE_REL,
|
|
175
|
+
run: (args: string[]) => string = (args) =>
|
|
176
|
+
execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }),
|
|
177
|
+
): BaselineFile {
|
|
178
|
+
let sha: string;
|
|
179
|
+
try {
|
|
180
|
+
sha = run(['rev-parse', '--verify', `${targetRef}^{commit}`]).trim();
|
|
181
|
+
} catch {
|
|
182
|
+
throw new BaselineResolutionError(
|
|
183
|
+
`cannot resolve target ref "${targetRef}" — the governing baseline is unknown, ` +
|
|
184
|
+
'and a working-tree fallback would let an infrastructure error decide the policy.',
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
let raw: string;
|
|
188
|
+
try {
|
|
189
|
+
raw = run(['show', `${sha}:${relPath}`]);
|
|
190
|
+
} catch {
|
|
191
|
+
throw new BaselineResolutionError(
|
|
192
|
+
`${relPath} does not exist at ${sha.slice(0, 9)} ("${targetRef}") — this is a ` +
|
|
193
|
+
'resolution failure, NOT an empty ratchet. A missing blob at a named commit ' +
|
|
194
|
+
'means the read was wrong, not that the ratchet is empty.',
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
return parseBaselines(raw);
|
|
199
|
+
} catch {
|
|
200
|
+
throw new BaselineResolutionError(
|
|
201
|
+
`${relPath} at ${sha.slice(0, 9)} is not parseable JSON — refusing to continue ` +
|
|
202
|
+
'with an unknown policy.',
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The merge-base reading, kept as a DIAGNOSTIC and never as the verdict.
|
|
209
|
+
*
|
|
210
|
+
* Both council seats asked for this explicitly. It does not decide pass/fail; it
|
|
211
|
+
* distinguishes two failures a reader cannot otherwise tell apart:
|
|
212
|
+
*
|
|
213
|
+
* - `branch-regression` — the branch itself made the count worse. Fix the code.
|
|
214
|
+
* - `main-tightened` — the branch is unchanged and `main` improved underneath
|
|
215
|
+
* it. Rebase, then re-run.
|
|
216
|
+
*
|
|
217
|
+
* The distinction matters because the remediations differ, and one seat was
|
|
218
|
+
* pointed about the wording: "rebase and re-run" is incomplete advice for a PR
|
|
219
|
+
* that genuinely reintroduced violations, so a diagnostic must say which case it
|
|
220
|
+
* is rather than always suggesting a rebase.
|
|
221
|
+
*/
|
|
222
|
+
export type RegressionCause = 'branch-regression' | 'main-tightened' | 'none';
|
|
223
|
+
|
|
224
|
+
export function diagnoseRegression(opts: {
|
|
225
|
+
actual: number;
|
|
226
|
+
targetBaseline: number;
|
|
227
|
+
mergeBaseBaseline: number | null;
|
|
228
|
+
}): RegressionCause {
|
|
229
|
+
if (opts.actual <= opts.targetBaseline) return 'none';
|
|
230
|
+
if (opts.mergeBaseBaseline === null) return 'branch-regression';
|
|
231
|
+
// The branch is at or under the policy it branched from, and it still
|
|
232
|
+
// exceeds the target's policy: main tightened while this work was open.
|
|
233
|
+
return opts.actual <= opts.mergeBaseBaseline ? 'main-tightened' : 'branch-regression';
|
|
234
|
+
}
|
|
235
|
+
|
|
114
236
|
export interface RatchetOptions {
|
|
115
237
|
/** Gate name — the key in the baseline file. */
|
|
116
238
|
gate: string;
|
|
@@ -122,6 +244,12 @@ export interface RatchetOptions {
|
|
|
122
244
|
baselineRel?: string;
|
|
123
245
|
/** Override "now" as `YYYY-MM-DD` (tests); defaults to the current UTC date. */
|
|
124
246
|
today?: string;
|
|
247
|
+
/**
|
|
248
|
+
* Read the governing baseline from this commit instead of the working tree.
|
|
249
|
+
* Defaults to `process.env[TARGET_REF_ENV]`; absent means a local run with
|
|
250
|
+
* no merge to judge, which is not the same as a failed resolution.
|
|
251
|
+
*/
|
|
252
|
+
targetRef?: string;
|
|
125
253
|
}
|
|
126
254
|
|
|
127
255
|
/**
|
|
@@ -131,9 +259,30 @@ export interface RatchetOptions {
|
|
|
131
259
|
* does not print or exit. A gate with no entry behaves exactly as it did before
|
|
132
260
|
* the ratchet existed: any violation fails.
|
|
133
261
|
*/
|
|
262
|
+
/**
|
|
263
|
+
* The env var that switches every ratchet onto the ABSOLUTE invariant at once.
|
|
264
|
+
*
|
|
265
|
+
* Set it to the target ref (`origin/main`, or the base SHA a PR job already
|
|
266
|
+
* knows) and all 18 `checkRatchet` call sites read the governing baseline from
|
|
267
|
+
* that commit instead of the working tree — no per-gate migration, no chance of
|
|
268
|
+
* migrating seventeen and forgetting one.
|
|
269
|
+
*
|
|
270
|
+
* **Unset is not a fallback.** A local run with no target configured has no
|
|
271
|
+
* merge to judge, so the working-tree read is the right and only answer there.
|
|
272
|
+
* What the council forbade is different and is honoured: a target that IS
|
|
273
|
+
* configured and fails to resolve throws, rather than quietly reverting to the
|
|
274
|
+
* working tree and changing the governing policy on an infrastructure error.
|
|
275
|
+
*/
|
|
276
|
+
export const TARGET_REF_ENV = 'GATE_BASELINE_TARGET_REF';
|
|
277
|
+
|
|
134
278
|
export function checkRatchet(opts: RatchetOptions): BaselineVerdict {
|
|
135
279
|
const { gate, actual } = opts;
|
|
136
|
-
const
|
|
280
|
+
const rel = opts.baselineRel ?? BASELINE_REL;
|
|
281
|
+
const target = opts.targetRef ?? process.env[TARGET_REF_ENV];
|
|
282
|
+
const file =
|
|
283
|
+
target !== undefined && target !== ''
|
|
284
|
+
? loadBaselinesAt(opts.repoRoot, target, rel)
|
|
285
|
+
: loadBaselines(opts.repoRoot, rel);
|
|
137
286
|
const entry = file.gates[gate];
|
|
138
287
|
|
|
139
288
|
if (entry === undefined) {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skill corpus, measured — the two dimensions `check_estate_count` ratchets.
|
|
3
|
+
*
|
|
4
|
+
* WHY TWO. A file count is what a `one_in_one_out` lint can express; it is not
|
|
5
|
+
* what a skill COSTS. Merging four large skills into one file satisfies a count
|
|
6
|
+
* ratchet while the description payload a host must carry stays put or grows.
|
|
7
|
+
* AI council 2/2 (2026-08-24) asked for both dimensions in one gate for exactly
|
|
8
|
+
* that reason, over the roadmap's own recommendation of a count plus an
|
|
9
|
+
* informational companion figure: "that supports treating tokens as a coarse
|
|
10
|
+
* secondary budget — not leaving the only cost-sensitive metric informational
|
|
11
|
+
* forever."
|
|
12
|
+
*
|
|
13
|
+
* WHY NOT THE OTHER TWO CANDIDATES, measured before asking rather than argued:
|
|
14
|
+
*
|
|
15
|
+
* - **Catalogue bytes** via `_lib/skill_catalogue.ts` read **0** on this
|
|
16
|
+
* checkout. `readProjectedCatalogue` walks `.claude/skills`, which is empty in
|
|
17
|
+
* any tree where `task generate-tools` cannot complete — it fails under
|
|
18
|
+
* `projection.mode=scoped` without the config package. A ratchet whose reading
|
|
19
|
+
* depends on whether a generator ran is a ratchet that reds for the
|
|
20
|
+
* environment.
|
|
21
|
+
* - **Host-listing slots** are the host's decision, not the repository's. A
|
|
22
|
+
* measured install published its own budget event stating it had stripped
|
|
23
|
+
* every description and dropped 402 entries.
|
|
24
|
+
*
|
|
25
|
+
* DEPRECATED SKILLS ARE EXCLUDED, and that is load-bearing rather than tidy:
|
|
26
|
+
* with them counted, deprecating a skill would create no headroom and the
|
|
27
|
+
* retirement mechanism would be unusable against its own gate. (Currently 0
|
|
28
|
+
* skills carry it, so the exclusion is a no-op today and a correctness property
|
|
29
|
+
* tomorrow.)
|
|
30
|
+
*/
|
|
31
|
+
import * as fs from 'node:fs';
|
|
32
|
+
import * as path from 'node:path';
|
|
33
|
+
|
|
34
|
+
import { gpt_tokens } from './token_count.js';
|
|
35
|
+
|
|
36
|
+
/** Where the corpus lives, POSIX — the same string a base-tree subtree takes. */
|
|
37
|
+
export const SKILLS_POSIX = 'src/skills';
|
|
38
|
+
|
|
39
|
+
export interface SkillEstate {
|
|
40
|
+
/** Maintained `SKILL.md` files — `lifecycle: deprecated` excluded. */
|
|
41
|
+
skill_count: number;
|
|
42
|
+
/**
|
|
43
|
+
* Exact-BPE tokens across the maintained skills' `description:` fields.
|
|
44
|
+
*
|
|
45
|
+
* `null` when the tokeniser could not resolve. A ratchet must never compare
|
|
46
|
+
* an exact reading against a proxy one — the numbers differ by more than the
|
|
47
|
+
* growth a gate is trying to catch, so the metric is reported UNRESOLVED
|
|
48
|
+
* rather than measured with whatever was available.
|
|
49
|
+
*/
|
|
50
|
+
skill_description_tokens: number | null;
|
|
51
|
+
/** Excluded from both figures, reported so the exclusion is visible. */
|
|
52
|
+
deprecated_count: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `description:` from a `SKILL.md` frontmatter block, unquoted. */
|
|
56
|
+
export function descriptionOf(text: string): string {
|
|
57
|
+
const fm = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
58
|
+
if (fm === null) return '';
|
|
59
|
+
const m = /^description:\s*(.+)$/m.exec(fm[1]!);
|
|
60
|
+
if (m === null) return '';
|
|
61
|
+
return m[1]!.trim().replace(/^["']|["']$/g, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `lifecycle: deprecated` in the frontmatter — the retirement transition state. */
|
|
65
|
+
export function isDeprecated(text: string): boolean {
|
|
66
|
+
const fm = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
67
|
+
if (fm === null) return false;
|
|
68
|
+
return /^lifecycle:\s*["']?deprecated["']?\s*$/m.test(fm[1]!);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Measure the corpus under `root`.
|
|
73
|
+
*
|
|
74
|
+
* `root` is a repository root OR a materialised base-ref tree holding
|
|
75
|
+
* `src/skills/...`, so the floor side runs this exact function over the base
|
|
76
|
+
* ref's own tree — the property `check_estate_count` already has for the roadmap
|
|
77
|
+
* estate, and the reason the "before" side cannot be rewritten by the change
|
|
78
|
+
* under review.
|
|
79
|
+
*/
|
|
80
|
+
export function measureSkillEstate(root: string): SkillEstate {
|
|
81
|
+
const base = path.join(root, ...SKILLS_POSIX.split('/'));
|
|
82
|
+
let entries: fs.Dirent[];
|
|
83
|
+
try {
|
|
84
|
+
entries = fs.readdirSync(base, { withFileTypes: true });
|
|
85
|
+
} catch {
|
|
86
|
+
return { skill_count: 0, skill_description_tokens: null, deprecated_count: 0 };
|
|
87
|
+
}
|
|
88
|
+
let count = 0;
|
|
89
|
+
let deprecated = 0;
|
|
90
|
+
let tokens = 0;
|
|
91
|
+
let exact = true;
|
|
92
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
93
|
+
if (!e.isDirectory()) continue;
|
|
94
|
+
const p = path.join(base, e.name, 'SKILL.md');
|
|
95
|
+
let text: string;
|
|
96
|
+
try {
|
|
97
|
+
text = fs.readFileSync(p, 'utf-8');
|
|
98
|
+
} catch {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (isDeprecated(text)) {
|
|
102
|
+
deprecated += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
count += 1;
|
|
106
|
+
const r = gpt_tokens(descriptionOf(text));
|
|
107
|
+
if (!r.exact) exact = false;
|
|
108
|
+
tokens += r.tokens;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
skill_count: count,
|
|
112
|
+
skill_description_tokens: exact ? tokens : null,
|
|
113
|
+
deprecated_count: deprecated,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The argument-exhaustion stop predicate, with the ordering encoded.
|
|
3
|
+
*
|
|
4
|
+
* `road-to-inbox-harvest-2026-08-e-council-topology-evidence` Phase 6.2, and the
|
|
5
|
+
* resolution of its `early-stop-vs-dissent-ordering` blocker.
|
|
6
|
+
*
|
|
7
|
+
* ## The ordering IS the substance
|
|
8
|
+
*
|
|
9
|
+
* Anti-conformity repair must fire **before** the cost stop, because
|
|
10
|
+
* **convergence may be conformity collapse rather than solution stability**. A
|
|
11
|
+
* round where everyone agrees because nobody pushed back looks, to a cost meter,
|
|
12
|
+
* exactly like a round where the argument is genuinely finished — and stopping
|
|
13
|
+
* there reports the collapse as consensus. That is the failure Phase 6 exists to
|
|
14
|
+
* prevent, so `dissentRepairAttempted` is a **required conjunct of the
|
|
15
|
+
* predicate**, not a soft check somewhere upstream that the predicate hopes ran.
|
|
16
|
+
*
|
|
17
|
+
* Encoding it as a conjunct rather than as call-order discipline is deliberate:
|
|
18
|
+
* a caller that forgets the order produces a wrong answer silently, while a
|
|
19
|
+
* caller that forgets to set the field produces `false` — the safe direction,
|
|
20
|
+
* and a visible one.
|
|
21
|
+
*
|
|
22
|
+
* ## Why all four, and why none of them is majority size
|
|
23
|
+
*
|
|
24
|
+
* A stop fires only when **every** condition holds:
|
|
25
|
+
*
|
|
26
|
+
* 1. `roundsCompleted >= MIN_ROUNDS` — one round is an opening statement, not an
|
|
27
|
+
* argument. There is nothing to be exhausted yet.
|
|
28
|
+
* 2. `dissentRepairAttempted` — the conformity defence got its chance.
|
|
29
|
+
* 3. every present member is a self-near-duplicate of their own prior round —
|
|
30
|
+
* the members have stopped adding, judged by the novelty logic that already
|
|
31
|
+
* exists rather than by a new similarity measure.
|
|
32
|
+
* 4. no unresolved adversarial trigger — an open objection is unfinished
|
|
33
|
+
* argument whatever the novelty numbers say.
|
|
34
|
+
*
|
|
35
|
+
* **Majority size is absent on purpose** (6.3). Unanimity is the single most
|
|
36
|
+
* available signal and the single least trustworthy one here: it is exactly what
|
|
37
|
+
* conformity collapse produces. A predicate that read it would stop soonest in
|
|
38
|
+
* precisely the case it must not.
|
|
39
|
+
*
|
|
40
|
+
* Everything here is pure and reads already-paid outputs — no model call, which
|
|
41
|
+
* is 6.1's contract and the reason this can be evaluated after every round for
|
|
42
|
+
* free.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/** One round is an opening statement; exhaustion needs an exchange. */
|
|
46
|
+
export const MIN_ROUNDS = 2;
|
|
47
|
+
|
|
48
|
+
export interface StopInputs {
|
|
49
|
+
/** Rounds completed so far. */
|
|
50
|
+
roundsCompleted: number;
|
|
51
|
+
/**
|
|
52
|
+
* Whether anti-conformity repair ran for the round under judgement.
|
|
53
|
+
*
|
|
54
|
+
* The ordering conjunct. `false` when repair was skipped, deferred, or
|
|
55
|
+
* never reached — all three are the same thing to this predicate, because
|
|
56
|
+
* all three leave conformity collapse untested.
|
|
57
|
+
*/
|
|
58
|
+
dissentRepairAttempted: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Per present member: did this round near-duplicate that member's own prior
|
|
61
|
+
* round? Absent members contribute no entry rather than a `false`.
|
|
62
|
+
*/
|
|
63
|
+
selfNearDuplicate: readonly boolean[];
|
|
64
|
+
/** Adversarial triggers raised and not yet resolved. */
|
|
65
|
+
unresolvedAdversarialTriggers: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type StopBlocker =
|
|
69
|
+
| 'too-few-rounds'
|
|
70
|
+
| 'dissent-repair-not-attempted'
|
|
71
|
+
| 'members-still-adding'
|
|
72
|
+
| 'unresolved-adversarial-trigger'
|
|
73
|
+
| 'no-members-present';
|
|
74
|
+
|
|
75
|
+
export interface StopVerdict {
|
|
76
|
+
stop: boolean;
|
|
77
|
+
/** Every condition that failed, not just the first — a caller acting on one
|
|
78
|
+
* reason at a time would re-evaluate N times to learn N blockers. */
|
|
79
|
+
blockers: StopBlocker[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function evaluateStop(x: StopInputs): StopVerdict {
|
|
83
|
+
const blockers: StopBlocker[] = [];
|
|
84
|
+
|
|
85
|
+
if (x.roundsCompleted < MIN_ROUNDS) blockers.push('too-few-rounds');
|
|
86
|
+
|
|
87
|
+
// The ordering conjunct. Deliberately evaluated as its own condition rather
|
|
88
|
+
// than folded into a caller-side guard: see the module docstring.
|
|
89
|
+
if (!x.dissentRepairAttempted) blockers.push('dissent-repair-not-attempted');
|
|
90
|
+
|
|
91
|
+
if (x.selfNearDuplicate.length === 0) {
|
|
92
|
+
// No present member is not "everyone is done" — it is no evidence at
|
|
93
|
+
// all. `every()` over an empty array is vacuously true, which would
|
|
94
|
+
// make an empty council the easiest one to stop.
|
|
95
|
+
blockers.push('no-members-present');
|
|
96
|
+
} else if (!x.selfNearDuplicate.every((d) => d)) {
|
|
97
|
+
blockers.push('members-still-adding');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (x.unresolvedAdversarialTriggers > 0) blockers.push('unresolved-adversarial-trigger');
|
|
101
|
+
|
|
102
|
+
return { stop: blockers.length === 0, blockers };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The rendering contract for a stopped run (6.4).
|
|
107
|
+
*
|
|
108
|
+
* A stopped run must be **textually distinguishable** from a full one. The
|
|
109
|
+
* failure this prevents is quiet and expensive: an artifact that reads as though
|
|
110
|
+
* all configured rounds executed, when three of five did, is a claim about
|
|
111
|
+
* deliberation depth that nobody made on purpose.
|
|
112
|
+
*/
|
|
113
|
+
export interface StopRender {
|
|
114
|
+
roundsCompleted: number;
|
|
115
|
+
roundsConfigured: number;
|
|
116
|
+
savedCalls: number;
|
|
117
|
+
savedCostUsd: number;
|
|
118
|
+
exhaustedMembers: readonly string[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function renderStop(r: StopRender): string {
|
|
122
|
+
return [
|
|
123
|
+
`council: STOPPED EARLY — round ${String(r.roundsCompleted)} of ${String(r.roundsConfigured)}.`,
|
|
124
|
+
' reason: argument exhaustion — every present member near-duplicated their own',
|
|
125
|
+
' prior round, after anti-conformity repair had already been attempted.',
|
|
126
|
+
` exhausted: ${r.exhaustedMembers.length > 0 ? r.exhaustedMembers.join(', ') : '(none named)'}`,
|
|
127
|
+
` saved: ${String(r.savedCalls)} call(s), $${r.savedCostUsd.toFixed(4)}`,
|
|
128
|
+
' NOT a full run: the remaining configured rounds did not execute.',
|
|
129
|
+
].join('\n');
|
|
130
|
+
}
|
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
/**
|
|
3
|
-
* Guard against build-machine path leakage in the tracked install bundle
|
|
3
|
+
* Guard against build-machine path leakage in the tracked install bundle AND in
|
|
4
|
+
* published Markdown.
|
|
5
|
+
*
|
|
6
|
+
* ## The published-.md scope (road-to-inbox-harvest-2026-08-e-command-surface-legibility Phase 0)
|
|
7
|
+
*
|
|
8
|
+
* The bundle roots below were the original scope. A second population ships the
|
|
9
|
+
* same leak class: every `.md` inside `package.json` `files[]` — 1,079 files —
|
|
10
|
+
* and a `/Users/<name>/…` in a shipped skill is the same defect as one in
|
|
11
|
+
* `install.mjs`.
|
|
12
|
+
*
|
|
13
|
+
* **It is EXTENDED here rather than given a second gate**, deliberately: this one
|
|
14
|
+
* already runs in CI, already carries the patterns, the username masking and the
|
|
15
|
+
* `scan_scope` dead-scope protection, and a sibling gate would duplicate all
|
|
16
|
+
* four.
|
|
17
|
+
*
|
|
18
|
+
* ### Why the floor is ZERO UNAPPROVED and not the measured 11
|
|
19
|
+
*
|
|
20
|
+
* The published-.md baseline measured **11 hits in 8 files, none of them a leak**
|
|
21
|
+
* (`agents/evidence/analysis/published-md-path-leakage-baseline.md`): three
|
|
22
|
+
* documentation examples, six occurrences inside the rules that FORBID the
|
|
23
|
+
* pattern and must quote it, two legitimately absolute paths.
|
|
24
|
+
*
|
|
25
|
+
* A numeric floor of 11 was rejected by both council seats, 2026-08-24, for one
|
|
26
|
+
* reason: it lets an approved hit disappear while a real leak takes its slot, and
|
|
27
|
+
* the count stays 11. So the floor is **0 unapproved matches**, and the eleven are
|
|
28
|
+
* pinned exceptions in `.path-leak-allow` — line-pinned, so an entry that drifts
|
|
29
|
+
* off its line stops matching and the gate REDS, which is the safe direction.
|
|
30
|
+
*
|
|
31
|
+
* ### And why NOT "exempt matches inside backticks"
|
|
32
|
+
*
|
|
33
|
+
* That was the other candidate, and it was rejected on a false-negative argument
|
|
34
|
+
* both seats found decisive: **a real leaked path is commonly formatted as code**,
|
|
35
|
+
* so exempting inline code and fenced blocks opens a channel that hides exactly
|
|
36
|
+
* the defect this gate exists to catch. It would also hide a skill author's
|
|
37
|
+
* accidental real path inside an example block — the contribution path where this
|
|
38
|
+
* class is most likely to arrive.
|
|
4
39
|
*
|
|
5
40
|
* road-to-feedback-9.2.0-followups Phase 4.1. Three 9.1 commits
|
|
6
41
|
* (f8752443b, 5933bf1a9, f30adb5a1) each rebuilt `dist/install/install.mjs`
|
|
@@ -75,6 +110,40 @@ const BUNDLE_ROOTS: readonly string[] = [
|
|
|
75
110
|
'dist/cli-delegate',
|
|
76
111
|
];
|
|
77
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Published `.md` — the second population, derived from `package.json` `files[]`
|
|
115
|
+
* rather than guessed, so the scope is what the tarball actually ships. Roots
|
|
116
|
+
* only; the per-file filter below keeps it to `.md`.
|
|
117
|
+
*/
|
|
118
|
+
const PUBLISHED_MD_ROOTS: readonly string[] = [
|
|
119
|
+
'dist/agent-src',
|
|
120
|
+
'docs/guidelines',
|
|
121
|
+
'src/scripts',
|
|
122
|
+
// NOT `src/agent-src` wholesale: `files[]` ships only these two subtrees, and
|
|
123
|
+
// scanning the parent would gate content no consumer receives. Caught by this
|
|
124
|
+
// gate's own test, which asserts every root is inside `files[]`.
|
|
125
|
+
'src/agent-src/scripts',
|
|
126
|
+
'src/agent-src/templates/scripts',
|
|
127
|
+
'agents/templates',
|
|
128
|
+
'src/templates',
|
|
129
|
+
'src/config',
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
/** Named `.md` / text files `files[]` ships individually. */
|
|
133
|
+
const PUBLISHED_MD_FILES: readonly string[] = [
|
|
134
|
+
'AGENTS.md',
|
|
135
|
+
'CHANGELOG.md',
|
|
136
|
+
'CONTRIBUTING.md',
|
|
137
|
+
'MIGRATION.md',
|
|
138
|
+
'README.md',
|
|
139
|
+
'docs/contracts/persona-schema.md',
|
|
140
|
+
'docs/contracts/provider-lifecycle.md',
|
|
141
|
+
'docs/contracts/settings-classes.md',
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
/** Repo-root allow file for audited published-`.md` exceptions. */
|
|
145
|
+
const ALLOW_FILE = '.path-leak-allow';
|
|
146
|
+
|
|
78
147
|
interface LeakPattern {
|
|
79
148
|
readonly name: string;
|
|
80
149
|
readonly re: RegExp;
|
|
@@ -192,6 +261,49 @@ function _splitlines(text: string): string[] {
|
|
|
192
261
|
return text.split('\n').filter((l) => l.length > 0);
|
|
193
262
|
}
|
|
194
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Parse `.path-leak-allow` into `path:line` keys.
|
|
266
|
+
*
|
|
267
|
+
* Line-pinned on purpose, and the trade-off is the same one `.secret-allow`
|
|
268
|
+
* documents for itself: an entry that drifts off its line stops matching and the
|
|
269
|
+
* gate REDS. That is the safe direction — someone re-audits and moves the pin —
|
|
270
|
+
* whereas a path-only entry would silently allow a real leak anywhere in the
|
|
271
|
+
* file. This tree has already been bitten by the opposite: a `.secret-allow` pin
|
|
272
|
+
* sat one line off on `main` for a day, covering nothing, invisible because that
|
|
273
|
+
* gate is diff-scoped.
|
|
274
|
+
*/
|
|
275
|
+
export function parse_allow_file(text: string): Set<string> {
|
|
276
|
+
const out = new Set<string>();
|
|
277
|
+
for (const raw of text.split('\n')) {
|
|
278
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
279
|
+
if (line === '') continue;
|
|
280
|
+
out.add(line);
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function allow_set(): Set<string> {
|
|
286
|
+
try {
|
|
287
|
+
return parse_allow_file(fs.readFileSync(path.join(REPO, ALLOW_FILE), 'utf-8'));
|
|
288
|
+
} catch {
|
|
289
|
+
return new Set();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Every tracked `.md` inside the published roots, plus the named files. */
|
|
294
|
+
function tracked_published_md(): string[] {
|
|
295
|
+
const seen = new Set<string>();
|
|
296
|
+
for (const root of PUBLISHED_MD_ROOTS) {
|
|
297
|
+
for (const rel of _splitlines(_git(['ls-files', root]))) {
|
|
298
|
+
if (rel.endsWith('.md')) seen.add(rel);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const rel of PUBLISHED_MD_FILES) {
|
|
302
|
+
for (const got of _splitlines(_git(['ls-files', rel]))) seen.add(got);
|
|
303
|
+
}
|
|
304
|
+
return [...seen].sort();
|
|
305
|
+
}
|
|
306
|
+
|
|
195
307
|
/** Every tracked path under the bundle roots, deduped and sorted. */
|
|
196
308
|
function tracked_bundle_files(): string[] {
|
|
197
309
|
const seen = new Set<string>();
|
|
@@ -291,7 +403,12 @@ function parse_args(argv: readonly string[]): Options {
|
|
|
291
403
|
|
|
292
404
|
function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
293
405
|
const opts = parse_args(argv);
|
|
294
|
-
const
|
|
406
|
+
const bundle = opts.files.length > 0 ? opts.files : tracked_bundle_files();
|
|
407
|
+
// Published `.md` joins the default target list. With explicit `--file`
|
|
408
|
+
// arguments it does NOT — an ad-hoc single-file run stays a single-file run,
|
|
409
|
+
// which is what the existing tests and the pre-push path rely on.
|
|
410
|
+
const published = opts.files.length > 0 ? [] : tracked_published_md();
|
|
411
|
+
const targets = [...bundle, ...published];
|
|
295
412
|
|
|
296
413
|
// `git ls-files <untracked-root>` returns nothing, by design (BUNDLE_ROOTS
|
|
297
414
|
// lists roots that may not be tracked yet). That tolerance is also the
|
|
@@ -305,8 +422,11 @@ function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
305
422
|
assertScanned({
|
|
306
423
|
gate: 'check_bundle_path_leakage',
|
|
307
424
|
scanned: targets.length,
|
|
308
|
-
units: 'bundle file(s)',
|
|
309
|
-
roots:
|
|
425
|
+
units: 'bundle + published-md file(s)',
|
|
426
|
+
roots:
|
|
427
|
+
opts.files.length > 0
|
|
428
|
+
? ['<explicit file arguments>']
|
|
429
|
+
: [...BUNDLE_ROOTS, ...PUBLISHED_MD_ROOTS],
|
|
310
430
|
});
|
|
311
431
|
} catch (exc) {
|
|
312
432
|
if (exc instanceof DeadScopeError) {
|
|
@@ -316,17 +436,48 @@ function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
316
436
|
throw exc;
|
|
317
437
|
}
|
|
318
438
|
|
|
319
|
-
const
|
|
439
|
+
const allowed = allow_set();
|
|
440
|
+
const all = scan_files(targets);
|
|
441
|
+
// The floor is ZERO UNAPPROVED, never a count. A pinned exception that has
|
|
442
|
+
// drifted off its line simply stops matching, so the hit resurfaces and the
|
|
443
|
+
// gate reds — deliberately, per the header.
|
|
444
|
+
const hits = all.filter((h) => !allowed.has(`${h.file}:${String(h.line)}`));
|
|
445
|
+
const suppressed = all.length - hits.length;
|
|
320
446
|
|
|
321
447
|
if (hits.length > 0) {
|
|
322
448
|
process.stderr.write(`${format_report(hits)}\n`);
|
|
449
|
+
// The per-pattern hints are bundle-shaped ("rebuild from a clean
|
|
450
|
+
// checkout"), which is the wrong instruction for a `.md` hit — nothing is
|
|
451
|
+
// rebuilt to fix prose. Published-md hits get their own line rather than
|
|
452
|
+
// a misleading one.
|
|
453
|
+
if (hits.some((h) => h.file.endsWith('.md'))) {
|
|
454
|
+
process.stderr.write(
|
|
455
|
+
` NOTE: a \`.md\` hit is not fixed by rebuilding. Either anonymise the path,\n` +
|
|
456
|
+
` or — if it IS the pattern being documented — pin it in ${ALLOW_FILE}.\n`,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (suppressed > 0) {
|
|
460
|
+
process.stderr.write(
|
|
461
|
+
` (${suppressed} further match(es) are pinned exceptions in ${ALLOW_FILE})\n`,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
process.stderr.write(
|
|
465
|
+
` A published-.md match that is the PATTERN BEING DOCUMENTED — a rule quoting\n` +
|
|
466
|
+
` the shape it forbids, or an anonymised example — belongs in ${ALLOW_FILE}\n` +
|
|
467
|
+
` as \`<path>:<line>\` with its reason above it. A real path does not.\n`,
|
|
468
|
+
);
|
|
323
469
|
return 1;
|
|
324
470
|
}
|
|
325
471
|
|
|
326
472
|
if (!opts.quiet) {
|
|
473
|
+
// A gate that scans nothing and exits green is indistinguishable from a
|
|
474
|
+
// broken one, so the green path names both populations and the
|
|
475
|
+
// suppression count — an exception set that quietly grows is the failure
|
|
476
|
+
// mode a bare "no leakage" line would hide.
|
|
327
477
|
process.stdout.write(
|
|
328
|
-
`✅ check_bundle_path_leakage: no
|
|
329
|
-
`(${
|
|
478
|
+
`✅ check_bundle_path_leakage: no unapproved path leakage ` +
|
|
479
|
+
`(${String(bundle.length)} bundle + ${String(published.length)} published-md ` +
|
|
480
|
+
`file(s) scanned, ${String(suppressed)} pinned exception(s)).\n`,
|
|
330
481
|
);
|
|
331
482
|
}
|
|
332
483
|
return 0;
|
|
@@ -359,6 +510,10 @@ if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
|
359
510
|
export {
|
|
360
511
|
REPO,
|
|
361
512
|
BUNDLE_ROOTS,
|
|
513
|
+
PUBLISHED_MD_ROOTS,
|
|
514
|
+
PUBLISHED_MD_FILES,
|
|
515
|
+
ALLOW_FILE,
|
|
516
|
+
tracked_published_md,
|
|
362
517
|
LEAK_PATTERNS,
|
|
363
518
|
MAX_SNIPPET,
|
|
364
519
|
MAX_HITS_SHOWN_PER_GROUP,
|