@event4u/agent-config 14.9.0 → 14.11.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 +384 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/design.md +88 -0
- package/dist/agent-src/commands/project/analyze.md +47 -0
- package/dist/agent-src/commands/review/changes.md +67 -6
- package/dist/agent-src/commands/roadmap/create.md +49 -0
- package/dist/agent-src/commands/roadmap/next.md +27 -11
- package/dist/agent-src/commands/roadmap/process-full.md +14 -0
- package/dist/agent-src/commands/video/from-script.md +60 -3
- package/dist/agent-src/commands/video/stitch.md +14 -0
- package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
- package/dist/agent-src/rules/active-remediation.md +3 -3
- package/dist/agent-src/rules/code-provenance.md +2 -2
- package/dist/agent-src/rules/context-hygiene.md +13 -33
- package/dist/agent-src/rules/council-availability.md +2 -2
- package/dist/agent-src/rules/decision-revisit-gate.md +3 -1
- package/dist/agent-src/rules/design-fidelity.md +18 -0
- package/dist/agent-src/rules/design-review-after-ui-write.md +2 -2
- package/dist/agent-src/rules/evaluator-independence.md +56 -44
- package/dist/agent-src/rules/fix-what-you-see.md +9 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
- package/dist/agent-src/rules/minimal-safe-diff.md +14 -20
- package/dist/agent-src/rules/missing-skill-recovery.md +13 -1
- package/dist/agent-src/rules/playbook-precedence.md +105 -0
- package/dist/agent-src/rules/recurring-criticism.md +3 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +14 -13
- package/dist/agent-src/rules/scale-discipline.md +24 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -2
- package/dist/agent-src/rules/settings-ask-protocol.md +2 -2
- package/dist/agent-src/rules/ui-audit-gate.md +27 -15
- package/dist/agent-src/rules/untrusted-input-defense.md +2 -2
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +7 -0
- package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +33 -0
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +17 -3
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +31 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +19 -0
- package/dist/agent-src/skills/alerting-doctrine/SKILL.md +225 -0
- package/dist/agent-src/skills/alerting-doctrine/evals/triggers.json +17 -0
- package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/authz-review/SKILL.md +0 -1
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +32 -0
- package/dist/agent-src/skills/brand/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-audit/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-identity/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-strategy/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +3 -0
- package/dist/agent-src/skills/check-refs/SKILL.md +3 -0
- package/dist/agent-src/skills/code-refactoring/SKILL.md +1 -1
- package/dist/agent-src/skills/code-review/SKILL.md +11 -0
- package/dist/agent-src/skills/command-routing/SKILL.md +6 -0
- package/dist/agent-src/skills/condense-memory/SKILL.md +4 -1
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +4 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +10 -0
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +3 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +3 -0
- package/dist/agent-src/skills/design-intelligence/data/landing.csv +36 -35
- package/dist/agent-src/skills/design-intelligence/data/stacks/threejs.csv +1 -0
- package/dist/agent-src/skills/design-review/SKILL.md +80 -2
- package/dist/agent-src/skills/design-review/references/verification-automation.md +41 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +35 -40
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +50 -0
- package/dist/agent-src/skills/design-tokens/SKILL.md +3 -0
- package/dist/agent-src/skills/design-variations/SKILL.md +3 -0
- package/dist/agent-src/skills/developer-like-execution/SKILL.md +3 -0
- package/dist/agent-src/skills/discovery-interview/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +3 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +20 -3
- package/dist/agent-src/skills/fe-design/SKILL.md +74 -41
- package/dist/agent-src/skills/fe-design/evals/triggers.json +65 -53
- package/dist/agent-src/skills/fe-design/references/craft-floor.md +52 -0
- package/dist/agent-src/skills/fe-design/references/source-led-port.md +36 -0
- package/dist/agent-src/skills/file-editor/SKILL.md +5 -0
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +2 -2
- package/dist/agent-src/skills/git-workflow/SKILL.md +3 -0
- package/dist/agent-src/skills/github-ci/SKILL.md +1 -1
- package/dist/agent-src/skills/html-deck/SKILL.md +3 -0
- package/dist/agent-src/skills/iconography/SKILL.md +3 -0
- package/dist/agent-src/skills/image-editing/SKILL.md +3 -0
- package/dist/agent-src/skills/image-generation/SKILL.md +3 -0
- package/dist/agent-src/skills/image-provider-routing/SKILL.md +3 -0
- package/dist/agent-src/skills/incident-commander/SKILL.md +10 -0
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +173 -0
- package/dist/agent-src/skills/js-library-packaging/evals/triggers.json +17 -0
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +270 -0
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-injection-defense/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-migration/SKILL.md +3 -0
- package/dist/agent-src/skills/laravel-validation/SKILL.md +1 -1
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +3 -0
- package/dist/agent-src/skills/legal-intake-triage/SKILL.md +1 -1
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +1 -1
- package/dist/agent-src/skills/lint-skills/SKILL.md +3 -0
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +3 -0
- package/dist/agent-src/skills/logging-monitoring/SKILL.md +234 -51
- package/dist/agent-src/skills/logo-generation/SKILL.md +3 -0
- package/dist/agent-src/skills/md-language-check/SKILL.md +3 -0
- package/dist/agent-src/skills/messaging-architecture/SKILL.md +1 -1
- package/dist/agent-src/skills/migration-architect/SKILL.md +1 -1
- package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +1 -1
- package/dist/agent-src/skills/module-management/SKILL.md +1 -1
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +149 -0
- package/dist/agent-src/skills/monorepo-workspace/evals/triggers.json +17 -0
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +7 -1
- package/dist/agent-src/skills/multi-tenancy/SKILL.md +4 -0
- package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
- package/dist/agent-src/skills/operational-readiness/SKILL.md +184 -0
- package/dist/agent-src/skills/operational-readiness/evals/triggers.json +17 -0
- package/dist/agent-src/skills/override-management/SKILL.md +3 -0
- package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-improvement/SKILL.md +3 -0
- package/dist/agent-src/skills/pest-testing/SKILL.md +3 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +162 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -0
- package/dist/agent-src/skills/privacy-review/SKILL.md +3 -0
- package/dist/agent-src/skills/prompt-engineering-image/SKILL.md +3 -0
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/quality-tools/SKILL.md +5 -0
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +52 -21
- package/dist/agent-src/skills/readme-reviewer/SKILL.md +3 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +3 -0
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +3 -0
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +3 -3
- package/dist/agent-src/skills/refine-prompt/SKILL.md +3 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +3 -0
- package/dist/agent-src/skills/requesting-code-review/SKILL.md +2 -2
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +23 -41
- package/dist/agent-src/skills/roadmap-writing/references/follow-up-roadmap-shape.md +47 -0
- package/dist/agent-src/skills/rtk-output-filtering/SKILL.md +22 -6
- package/dist/agent-src/skills/runway-cognition/SKILL.md +3 -0
- package/dist/agent-src/skills/scene-expander/SKILL.md +1 -1
- package/dist/agent-src/skills/scene-expander/scene-blueprint.schema.yaml +26 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +5 -1
- package/dist/agent-src/skills/server-hardening/SKILL.md +159 -0
- package/dist/agent-src/skills/server-hardening/evals/triggers.json +17 -0
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +3 -0
- package/dist/agent-src/skills/skill-management/SKILL.md +3 -0
- package/dist/agent-src/skills/skill-reviewer/SKILL.md +3 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +6 -0
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +3 -0
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +194 -0
- package/dist/agent-src/skills/storybook-workshop/evals/triggers.json +17 -0
- package/dist/agent-src/skills/storybook-workshop/scripts/story_contrast_floor.ts +134 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +25 -0
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +1 -1
- package/dist/agent-src/skills/terraform/SKILL.md +3 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -1
- package/dist/agent-src/skills/threat-modeling/SKILL.md +0 -3
- package/dist/agent-src/skills/token-optimizer/SKILL.md +3 -0
- package/dist/agent-src/skills/typography-system/SKILL.md +3 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -1
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +3 -0
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/dist/agent-src/skills/validate-feature-fit/SKILL.md +3 -0
- package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +2 -2
- package/dist/agent-src/skills/wireframe/SKILL.md +3 -0
- package/dist/agent-src/skills/wireframe/references/story-beat-ledger.schema.json +115 -0
- package/dist/agent-src/skills/workspace-link/SKILL.md +131 -0
- package/dist/agent-src/skills/workspace-link/evals/triggers.json +17 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/playbook.md +75 -0
- package/dist/agent-src/templates/roadmaps.md +37 -0
- package/dist/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
- package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
- package/dist/cli/commands/uiAudit.js +221 -0
- package/dist/cli/commands/uiAudit.js.map +1 -0
- package/dist/cli/commands/uiRender.js +206 -0
- package/dist/cli/commands/uiRender.js.map +1 -0
- package/dist/cli/main.js +22 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/content.js +64 -1
- package/dist/cli/mcp/content.js.map +1 -1
- package/dist/cli/mcp/dispatch.js +175 -10
- package/dist/cli/mcp/dispatch.js.map +1 -1
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-H4VIVD4C.js → chunk-236PNQZK.js} +413 -391
- package/dist/cli-delegate/{chunk-G23AT6GD.js → chunk-DMSZHXZS.js} +57 -1
- package/dist/cli-delegate/{chunk-77GR3VD4.js → chunk-FF7WAFMR.js} +58 -3
- package/dist/cli-delegate/{chunk-PLSG6UL5.js → chunk-K4FFODTS.js} +341 -204
- package/dist/cli-delegate/{chunk-YM4GZDJG.js → chunk-L43NCYPB.js} +5 -0
- package/dist/cli-delegate/{chunk-B2ERU2VA.js → chunk-WOKWX5PL.js} +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +8 -6
- package/dist/cli-delegate/cmd_conformance.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +4 -4
- 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 +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +70 -9
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +380 -145
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +18 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +16 -6
- package/dist/hooks/dispatch.js +2337 -1436
- package/dist/install/install.mjs +795 -708
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1685 -1250
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/lean_projection_mode.js +40 -0
- package/dist/scripts/_lib/lean_projection_mode.js.map +1 -0
- package/dist/scripts/_lib/scoped_projection.js +84 -0
- package/dist/scripts/_lib/scoped_projection.js.map +1 -1
- package/dist/scripts/_lib/ui_surface.js +89 -0
- package/dist/scripts/_lib/ui_surface.js.map +1 -0
- package/dist/scripts/condense.js +6 -10
- package/dist/scripts/condense.js.map +1 -1
- package/dist/scripts/project_thin_rules.js +40 -2
- package/dist/scripts/project_thin_rules.js.map +1 -1
- package/dist/server/schemas/settings.js +3 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/skillRanking.js +159 -0
- package/dist/shared/skillRanking.js.map +1 -0
- package/docs/contracts/provider-lifecycle.md +47 -18
- package/docs/contracts/settings-classes.md +14 -4
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +49 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
- package/docs/guidelines/design-antipatterns.md +4 -2
- package/docs/guidelines/design-asset-discipline.md +38 -0
- package/docs/guidelines/design-fidelity-mechanics.md +45 -29
- package/docs/guidelines/design-handover-extraction.md +35 -5
- package/docs/guidelines/design-modes.md +41 -1
- package/docs/guidelines/monorepo-antipatterns.md +141 -0
- package/llms.txt +50 -39
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
- package/src/config/agent-settings.template.yml +28 -0
- package/src/config/assurance-capability-registry.json +223 -0
- package/src/config/assurance-threshold-budget.json +65 -0
- package/src/config/ci-local-parity.yml +14 -0
- package/src/config/discovery/unassigned-artefacts.yml +2 -0
- package/src/config/dispatch-economy-metrics.json +4 -2
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +227 -8
- package/src/config/gate-violation-baselines.json +76 -72
- package/src/config/hook-latency-budget.json +1 -0
- package/src/config/hook-token-budget.json +137 -125
- package/src/config/pack-size-budget.json +4 -2
- package/src/config/preamble-payload-budget.json +71 -67
- package/src/config/quorum-attendance-budget.json +12 -4
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/config/rule-stub-ceilings.json +112 -91
- package/src/scripts/_cli/cmd_doctor.ts +15 -16
- package/src/scripts/_cli/doctor_install_mode.ts +49 -0
- package/src/scripts/_cli/doctor_overrides.ts +154 -0
- package/src/scripts/_dispatch.bash +17 -0
- package/src/scripts/_lib/as_of.ts +412 -0
- package/src/scripts/_lib/exec_evidence.ts +7 -0
- package/src/scripts/_lib/hook_settings.ts +33 -0
- package/src/scripts/_lib/host_listing_model.ts +192 -0
- package/src/scripts/_lib/lean_projection_mode.ts +44 -0
- package/src/scripts/_lib/mcp_bridge.ts +199 -0
- package/src/scripts/_lib/review_independence.ts +62 -10
- package/src/scripts/_lib/rule_injection.ts +239 -0
- package/src/scripts/_lib/run_checkpoint.ts +54 -1
- package/src/scripts/_lib/scoped_projection.ts +96 -0
- package/src/scripts/_lib/session_register.ts +44 -2
- package/src/scripts/_lib/skill_catalogue.ts +38 -2
- package/src/scripts/_lib/transcript_reads.ts +207 -0
- package/src/scripts/_lib/ui_authority.test.ts +251 -0
- package/src/scripts/_lib/ui_authority.ts +343 -0
- package/src/scripts/ai-video/adapters/fal.sh +8 -3
- package/src/scripts/ai-video/adapters/gemini-veo.sh +10 -0
- package/src/scripts/ai-video/adapters/higgsfield.sh +10 -2
- package/src/scripts/ai-video/adapters/kling.sh +10 -0
- package/src/scripts/ai-video/adapters/sora.sh +10 -0
- package/src/scripts/ai-video/lib/calibrate-cost.sh +145 -0
- package/src/scripts/ai-video/lib/model-capabilities/README.md +53 -7
- package/src/scripts/ai-video/lib/model-capabilities/comfyui.json +15 -3
- package/src/scripts/ai-video/lib/model-capabilities/fal.json +36 -6
- package/src/scripts/ai-video/lib/model-capabilities/gemini-veo.json +23 -0
- package/src/scripts/ai-video/lib/model-capabilities/higgsfield.json +23 -0
- package/src/scripts/ai-video/lib/model-capabilities/kling.json +23 -0
- package/src/scripts/ai-video/lib/model-capabilities/musetalk.json +8 -2
- package/src/scripts/ai-video/lib/model-capabilities/replicate.json +36 -6
- package/src/scripts/ai-video/lib/model-capabilities/sora.json +45 -0
- package/src/scripts/ai-video/lib/model-capabilities/syncso.json +8 -2
- package/src/scripts/ai-video/lib/parse-blueprint.sh +52 -5
- package/src/scripts/ai-video/lib/resume-scan.sh +92 -3
- package/src/scripts/ai-video/lib/seam-plan.sh +212 -0
- package/src/scripts/ai-video/smoke-trace.sh +450 -2
- package/src/scripts/ai-video/stitch.sh +191 -10
- package/src/scripts/ai-video/test-pipeline.sh +84 -6
- package/src/scripts/ai_council/cli_agency_bounds.ts +139 -0
- package/src/scripts/ai_council/cli_least_agency_canary.ts +347 -0
- package/src/scripts/ai_council/clients.ts +3 -2
- package/src/scripts/ai_council/consensus.ts +63 -6
- package/src/scripts/ai_council/events_log.ts +46 -1
- package/src/scripts/ai_council/orchestrator.ts +103 -108
- package/src/scripts/ai_council/orchestrator_results.ts +166 -0
- package/src/scripts/ai_council/prompts.ts +12 -5
- package/src/scripts/ai_council/quorum.ts +82 -1
- package/src/scripts/ai_council/quorum_wiring.ts +82 -5
- package/src/scripts/ai_council/seating.ts +211 -0
- package/src/scripts/ai_council/stance_tally.ts +22 -0
- package/src/scripts/bench_hook_latency.ts +62 -0
- package/src/scripts/build_proof.ts +12 -1
- package/src/scripts/capture_skill_catalogue.ts +21 -0
- package/src/scripts/check_always_budget.ts +2 -1
- package/src/scripts/check_augmentignore.ts +2 -1
- package/src/scripts/check_beta_review_markers.ts +2 -1
- package/src/scripts/check_composite_arming.ts +364 -0
- package/src/scripts/check_corpus_staleness.ts +2 -1
- package/src/scripts/check_council_pin_staleness.ts +3 -1
- package/src/scripts/check_enforcement_coverage.ts +116 -3
- package/src/scripts/check_enforcement_denominator.ts +365 -0
- package/src/scripts/check_gate_coverage.ts +2 -1
- package/src/scripts/check_hook_bundle_freshness.ts +13 -1
- package/src/scripts/check_knowledge_cards.ts +2 -1
- package/src/scripts/check_knowledge_pages.ts +3 -2
- package/src/scripts/check_memory.ts +2 -1
- package/src/scripts/check_no_automerge_key.ts +287 -0
- package/src/scripts/check_pack_conformance_fixture.ts +565 -0
- package/src/scripts/check_pack_passport_reconciliation.ts +274 -0
- package/src/scripts/check_proposal.ts +2 -1
- package/src/scripts/check_reach_staleness.ts +2 -1
- package/src/scripts/check_release_adjacent_health.ts +2 -1
- package/src/scripts/check_release_pr_shape.ts +5 -0
- package/src/scripts/check_review_schema.ts +34 -1
- package/src/scripts/check_roadmap_trackable.ts +48 -3
- package/src/scripts/check_source_size_budget.ts +2 -1
- package/src/scripts/check_standing_payload_delta.ts +505 -0
- package/src/scripts/check_trigger_evals.ts +2 -1
- package/src/scripts/classify_change_risk.ts +281 -0
- package/src/scripts/compute_skill_tiers.ts +186 -0
- package/src/scripts/condense.ts +8 -13
- package/src/scripts/council_attendance_metrics.ts +183 -0
- package/src/scripts/council_cli.ts +10 -8
- package/src/scripts/council_parse_rate.ts +116 -0
- package/src/scripts/derive_playbooks.ts +406 -0
- package/src/scripts/design_slop_rules.test.ts +7 -0
- package/src/scripts/design_slop_rules.ts +56 -2
- package/src/scripts/dispatch_r2_reviewer.ts +16 -0
- package/src/scripts/frontend_corpus_hash.ts +116 -0
- package/src/scripts/generate_pack_manifests.ts +78 -0
- package/src/scripts/grade_target_readiness.ts +478 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +97 -16
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/design_pass_hook.ts +356 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +30 -7
- package/src/scripts/hooks/rule_inject_hook.ts +318 -0
- package/src/scripts/hot_context_hook.ts +74 -7
- package/src/scripts/install.ts +40 -89
- package/src/scripts/lint_adapter_tier.ts +663 -0
- package/src/scripts/lint_archived_skills.ts +93 -0
- package/src/scripts/lint_budget_ownership.ts +2 -1
- package/src/scripts/lint_deterministic_time.ts +470 -0
- package/src/scripts/lint_framework_leakage.ts +5 -0
- package/src/scripts/lint_handoffs.ts +525 -1
- package/src/scripts/lint_judge_prompt_expectation.ts +155 -0
- package/src/scripts/lint_marketplace.ts +88 -0
- package/src/scripts/lint_media_policy_linkage.ts +140 -1
- package/src/scripts/lint_one_off_age.ts +6 -1
- package/src/scripts/lint_override_kernel_guard.ts +87 -5
- package/src/scripts/lint_roadmap_complexity.ts +56 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +24 -7
- package/src/scripts/lint_rule_norm_pin.ts +321 -0
- package/src/scripts/lint_spawn_payload.ts +42 -5
- package/src/scripts/lint_symptom_intake.ts +2 -1
- package/src/scripts/mcp_parity_smoke.ts +30 -4
- package/src/scripts/mcp_render.ts +66 -1
- package/src/scripts/mcp_tool_fingerprint.ts +241 -0
- package/src/scripts/measure_risk_class_drift.ts +140 -0
- package/src/scripts/measure_skill_ranker_baseline.ts +212 -0
- package/src/scripts/media/lib/adapter-common.sh +211 -1
- package/src/scripts/media/lib/adapter-contract.md +74 -6
- package/src/scripts/model_rule_injection.ts +907 -0
- package/src/scripts/project_thin_rules.ts +45 -2
- package/src/scripts/release.ts +66 -860
- package/src/scripts/release_drill.ts +59 -3
- package/src/scripts/release_env.ts +298 -0
- package/src/scripts/release_publication.ts +720 -0
- package/src/scripts/roadmap_context.ts +778 -0
- package/src/scripts/schemas/fidelity-measurement-sheet.schema.json +169 -0
- package/src/scripts/schemas/pack.schema.json +62 -4
- package/src/scripts/schemas/rule.schema.json +24 -2
- package/src/scripts/schemas/skill.schema.json +41 -2
- package/src/scripts/schemas/ui-authority.schema.json +114 -0
- package/src/scripts/session_register_hook.ts +29 -3
- package/src/scripts/sessions_cli.ts +67 -3
- package/src/scripts/skill_tools/score_skill_relevance.ts +36 -53
- package/src/scripts/tighten_skill_descriptions.ts +264 -0
- package/src/scripts/token_sink_report.ts +239 -0
- package/src/scripts/trigger_coverage.ts +122 -3
- package/src/scripts/update_counts.ts +7 -3
- package/src/scripts/validate_frontmatter.ts +66 -0
- package/src/server/schemas/settings.ts +5 -0
- package/src/shared/skillRanking.ts +183 -0
|
@@ -19,17 +19,21 @@
|
|
|
19
19
|
* 4. No archived slug still has a live SKILL.md (no zombies).
|
|
20
20
|
* 5. No live SKILL.md cites an archived slug as a router target in its
|
|
21
21
|
* frontmatter `replaced_by:` field.
|
|
22
|
+
* 6. A slug that had a `SKILL.md` at the base ref and has none in the
|
|
23
|
+
* working tree carries an archive note. See {@link removed_without_note}.
|
|
22
24
|
*
|
|
23
25
|
* Exit codes:
|
|
24
26
|
* 0 contract holds
|
|
25
27
|
* 1 one or more violations
|
|
26
28
|
*/
|
|
27
29
|
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
28
31
|
import * as fs from 'node:fs';
|
|
29
32
|
import * as path from 'node:path';
|
|
30
33
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
31
34
|
|
|
32
35
|
import { artefact_roots } from './_lib/agent_src.js';
|
|
36
|
+
import { resolveBaseRef } from './_lib/ratchet_base_ref.js';
|
|
33
37
|
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
34
38
|
|
|
35
39
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -243,6 +247,19 @@ function main(): number {
|
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
|
|
250
|
+
// Rule 6 — removals that never entered the ledger.
|
|
251
|
+
const removalCheck = removed_without_note(REPO, archivedKeys, live);
|
|
252
|
+
if ('skipped' in removalCheck) {
|
|
253
|
+
process.stdout.write(`rule 6 skipped: ${removalCheck.skipped}\n`);
|
|
254
|
+
} else {
|
|
255
|
+
for (const slug of removalCheck.removed) {
|
|
256
|
+
errors.push(
|
|
257
|
+
`${slug}: SKILL.md removed since the base ref with no ` +
|
|
258
|
+
`agents/evidence/archived-skills/${slug}.md note`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
246
263
|
if (errors.length) {
|
|
247
264
|
process.stderr.write(
|
|
248
265
|
`❌ lint_archived_skills: ${errors.length} violation(s) across ${notes.length} note(s)\n`,
|
|
@@ -261,6 +278,82 @@ function main(): number {
|
|
|
261
278
|
return 0;
|
|
262
279
|
}
|
|
263
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Rule 6 — a slug that left the tree without entering the ledger.
|
|
283
|
+
*
|
|
284
|
+
* WHY THIS LIVES IN THE GATE AND NOT IN A SCAFFOLDER. The roadmap step that
|
|
285
|
+
* asked for this (`road-to-skill-link-integrity-and-manifest-sync` Phase 1
|
|
286
|
+
* Step 3) named `src/scripts/new_skill.ts --archive` as the extension point,
|
|
287
|
+
* on the premise that it is "a creation path with no symmetric removal path".
|
|
288
|
+
* That premise is false: `new_skill.ts` scaffolds into a `packages/<pack>/`
|
|
289
|
+
* container that ADR-051 retired, and its `main()` returns exit 2 with
|
|
290
|
+
* `error: no packages/ tree found` in this tree, because skill authoring moved
|
|
291
|
+
* to `src/skills/`. A mode added there could not run.
|
|
292
|
+
*
|
|
293
|
+
* The obligation is a CI property, not a tool property, and the distinction is
|
|
294
|
+
* the reason the ledger is empty: nothing forces a maintainer or an agent to
|
|
295
|
+
* delete a directory *through* a tool, so a scaffolder mode is a bypass by
|
|
296
|
+
* construction. Three slugs left this tree without a note while
|
|
297
|
+
* `agents/evidence/archived-skills/` held only a README.
|
|
298
|
+
*
|
|
299
|
+
* A RENAME IS A REMOVAL HERE, DELIBERATELY. Git would call
|
|
300
|
+
* `verify-before-complete` → `verify-completion-evidence` a rename; every link
|
|
301
|
+
* to the old slug still broke, and repairing eight of them is what produced
|
|
302
|
+
* this rule. So the old slug owes a note.
|
|
303
|
+
*
|
|
304
|
+
* THE COMPARISON IS AGAINST THE MERGE BASE, NOT THE TIP OF MAIN, and this was
|
|
305
|
+
* found the hard way: the first run of this rule reported `js-library-packaging`
|
|
306
|
+
* and `storybook-workshop` as unnoted removals. Neither had been removed — both
|
|
307
|
+
* were ADDED on main after this branch forked, so a branch that is merely
|
|
308
|
+
* BEHIND its base read every new skill as a deletion. That is the false-red
|
|
309
|
+
* class `_lib/ratchet_base_ref.ts` warns about in its own docstring ("the gate
|
|
310
|
+
* gets a reputation for false reds, and someone turns it off"). `git merge-base`
|
|
311
|
+
* answers the question the rule actually asks — did THIS branch remove it —
|
|
312
|
+
* because a skill added on main after the fork is not in the merge base at all.
|
|
313
|
+
*
|
|
314
|
+
* THE SKIP IS STATED, NEVER SILENT. With no resolvable base ref there is no
|
|
315
|
+
* "before" side, and comparing against an assumed-empty base would report every
|
|
316
|
+
* skill in the tree as removed. `resolveBaseRef` returns null in that case and
|
|
317
|
+
* this returns a `skipped` reason the caller prints — the honest default named
|
|
318
|
+
* in that helper's own contract. It is also blind to an uncommitted removal;
|
|
319
|
+
* CI is the authoritative gate and CI only ever sees committed work.
|
|
320
|
+
*/
|
|
321
|
+
export function removed_without_note(
|
|
322
|
+
repoRoot: string = REPO,
|
|
323
|
+
archived: ReadonlySet<string> = new Set(archived_slugs()),
|
|
324
|
+
liveSlugs: ReadonlySet<string> = new Set(live_skill_slugs()),
|
|
325
|
+
): { skipped: string } | { removed: string[] } {
|
|
326
|
+
const base = resolveBaseRef(repoRoot);
|
|
327
|
+
if (base === null) {
|
|
328
|
+
return { skipped: 'no base ref resolved — nothing to compare against' };
|
|
329
|
+
}
|
|
330
|
+
const mb = spawnSync('git', ['merge-base', 'HEAD', base], {
|
|
331
|
+
cwd: repoRoot,
|
|
332
|
+
encoding: 'utf-8',
|
|
333
|
+
});
|
|
334
|
+
// A missing merge base is not "compare against the tip instead" — that is
|
|
335
|
+
// the false-red above. It is no comparable before-side at all.
|
|
336
|
+
const forkPoint = mb.status === 0 ? String(mb.stdout).trim() : '';
|
|
337
|
+
if (!forkPoint) {
|
|
338
|
+
return { skipped: `no merge base between HEAD and ${base}` };
|
|
339
|
+
}
|
|
340
|
+
const res = spawnSync(
|
|
341
|
+
'git',
|
|
342
|
+
['ls-tree', '-r', '--name-only', forkPoint, '--', 'src/skills'],
|
|
343
|
+
{ cwd: repoRoot, encoding: 'utf-8' },
|
|
344
|
+
);
|
|
345
|
+
if (res.status !== 0) {
|
|
346
|
+
return { skipped: `git ls-tree ${forkPoint} failed — base ref is not readable here` };
|
|
347
|
+
}
|
|
348
|
+
const atBase = new Set<string>();
|
|
349
|
+
for (const line of String(res.stdout).split('\n')) {
|
|
350
|
+
const m = /^src\/skills\/([^/]+)\/SKILL\.md$/.exec(line.trim());
|
|
351
|
+
if (m) atBase.add(m[1] as string);
|
|
352
|
+
}
|
|
353
|
+
const removed = [...atBase].filter((slug) => !liveSlugs.has(slug) && !archived.has(slug)).sort();
|
|
354
|
+
return { removed };
|
|
355
|
+
}
|
|
356
|
+
|
|
264
357
|
// --- helpers --------------------------------------------------------------
|
|
265
358
|
|
|
266
359
|
function _exists(p: string): boolean {
|
|
@@ -31,6 +31,7 @@ import * as fs from 'node:fs';
|
|
|
31
31
|
import * as path from 'node:path';
|
|
32
32
|
import { fileURLToPath } from 'node:url';
|
|
33
33
|
|
|
34
|
+
import { asOf } from './_lib/as_of.js';
|
|
34
35
|
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
35
36
|
|
|
36
37
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
@@ -154,7 +155,7 @@ export function main(): number {
|
|
|
154
155
|
throw exc;
|
|
155
156
|
}
|
|
156
157
|
const errors: string[] = [];
|
|
157
|
-
const today =
|
|
158
|
+
const today = asOf().toISOString().slice(0, 10);
|
|
158
159
|
for (const abs of files) {
|
|
159
160
|
const rel = path.relative(REPO_ROOT, abs);
|
|
160
161
|
let doc: Record<string, unknown>;
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* CI gate: a gate script does not read the wall clock directly.
|
|
4
|
+
*
|
|
5
|
+
* WHY. A gate's verdict should be a function of the tree, not of the hour it
|
|
6
|
+
* ran. Measured on 2026-08-23, before this gate existed, **17** of the
|
|
7
|
+
* `check_*` / `lint_*` scripts under `src/scripts/` read the clock inline, every
|
|
8
|
+
* one of them to make an age or staleness judgement. None of those 17 verdicts
|
|
9
|
+
* was reproducible from the commit alone: the same tree greens on Monday and
|
|
10
|
+
* reds on Friday, and a reviewer cannot recover which "now" produced the verdict
|
|
11
|
+
* CI published.
|
|
12
|
+
*
|
|
13
|
+
* `src/scripts/_lib/as_of.ts` is the seam that fixes it — one resolver, four
|
|
14
|
+
* rungs (`--as-of` argv, `AC_AS_OF` env, the commit date under CI, and the wall
|
|
15
|
+
* clock with an explicit non-reproducibility WARN). This gate is what stops the
|
|
16
|
+
* defect coming back: substituting the seam once is a diff, keeping it
|
|
17
|
+
* substituted is a ratchet.
|
|
18
|
+
*
|
|
19
|
+
* SCOPE is deliberately the gate prefixes only (`check_*`, `lint_*` under
|
|
20
|
+
* `src/scripts/`). A generator, a bench runner, or a hook legitimately reads
|
|
21
|
+
* real time; a *verdict* should not. Widening this to the whole tree would turn
|
|
22
|
+
* a determinism ratchet into a style rule and earn the suppressions that come
|
|
23
|
+
* with one.
|
|
24
|
+
*
|
|
25
|
+
* WHY THE MATCH RUNS OVER STRIPPED SOURCE. This file's own docstring names the
|
|
26
|
+
* construct it forbids, and its self-test fixtures are string literals
|
|
27
|
+
* containing it. A gate whose scope includes its own filename (`lint_*` does)
|
|
28
|
+
* and which matched raw text would refuse itself — so comments and string /
|
|
29
|
+
* template literals are blanked to spaces (line numbers preserved) before the
|
|
30
|
+
* patterns run. The same reason `check_no_automerge_key` anchors on a key rather
|
|
31
|
+
* than a word.
|
|
32
|
+
*
|
|
33
|
+
* THE ESCAPE HATCH, AND WHY IT IS NARROW. `// wall-clock-required: <reason>` on
|
|
34
|
+
* the offending line exempts it, and the reason is mandatory — a bare marker is
|
|
35
|
+
* still a finding. It exists for the one legitimate case this ratchet must not
|
|
36
|
+
* break: measuring *elapsed duration* inside a single run, where real time is
|
|
37
|
+
* the subject rather than a threshold input. Prefer `performance.now()` there;
|
|
38
|
+
* the marker is for when a `Date` is genuinely what the API needs.
|
|
39
|
+
*
|
|
40
|
+
* Exit codes:
|
|
41
|
+
* 0 — no gate script reads the wall clock directly
|
|
42
|
+
* 1 — at least one raw wall-clock read (or a marker with no reason)
|
|
43
|
+
* 2 — the gate could not run (dead scan scope, bad args)
|
|
44
|
+
*/
|
|
45
|
+
import * as fs from 'node:fs';
|
|
46
|
+
import * as os from 'node:os';
|
|
47
|
+
import * as path from 'node:path';
|
|
48
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
49
|
+
|
|
50
|
+
import { GateLedger, LedgerUsageError } from './_lib/gate_ledger.js';
|
|
51
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
52
|
+
import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
53
|
+
|
|
54
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
55
|
+
const DEFAULT_ROOT = path.join(path.dirname(_HERE), '..', '..');
|
|
56
|
+
|
|
57
|
+
/** This script's repo-relative path, for the self-test's CLI invocations. */
|
|
58
|
+
const SELF = 'src/scripts/lint_deterministic_time.ts';
|
|
59
|
+
|
|
60
|
+
const SELF_TEST_MIN_CASES = 6;
|
|
61
|
+
const SELF_TEST_MIN_REJECT = 3;
|
|
62
|
+
|
|
63
|
+
/** The directory the gate walks, and the prefixes that make a file a gate. */
|
|
64
|
+
const SCAN_DIR = 'src/scripts';
|
|
65
|
+
const GATE_PREFIXES = ['check_', 'lint_'] as const;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The single sanctioned reader of the wall clock. Repo-relative, and outside
|
|
69
|
+
* {@link GATE_PREFIXES} by construction, so it is never in the scan set — named
|
|
70
|
+
* here so the failure message can point at it.
|
|
71
|
+
*/
|
|
72
|
+
const SEAM = 'src/scripts/_lib/as_of.ts';
|
|
73
|
+
|
|
74
|
+
/** Same-line exemption. The reason after the colon must be non-empty. */
|
|
75
|
+
const MARKER = 'wall-clock-required:';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The forbidden constructs. Written as tolerant regexes rather than literals so
|
|
79
|
+
* this file's own source does not contain the strings it bans — see the
|
|
80
|
+
* docstring's note on stripped source; belt and braces, and the belt is cheap.
|
|
81
|
+
*/
|
|
82
|
+
const PATTERNS: readonly { id: string; re: RegExp; shown: string }[] = [
|
|
83
|
+
{ id: 'date-now', re: /\bDate\s*\.\s*now\s*\(\s*\)/g, shown: 'Date' + '.now()' },
|
|
84
|
+
{ id: 'new-date', re: /\bnew\s+Date\s*\(\s*\)/g, shown: 'new ' + 'Date()' },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Neutralise every non-code region, preserving line numbers and line lengths so
|
|
89
|
+
* a match's line number is still the source's.
|
|
90
|
+
*
|
|
91
|
+
* Two details are load-bearing and were both found by running this gate against
|
|
92
|
+
* the real corpus before trusting it:
|
|
93
|
+
*
|
|
94
|
+
* 1. **Comments become spaces, string bodies become `x`.** Blanking a string to
|
|
95
|
+
* spaces collapses `new Date("2026-01-01")` into `new Date( )`,
|
|
96
|
+
* which the empty-parens pattern then matches — a false positive on a
|
|
97
|
+
* perfectly deterministic parse. `check_knowledge_pages.ts:103` is exactly
|
|
98
|
+
* that shape. Filling with a non-space keeps the argument visible as *an*
|
|
99
|
+
* argument without keeping its content.
|
|
100
|
+
* 2. **`${...}` inside a template literal is code.** Blanking it wholesale hid a
|
|
101
|
+
* real finding: `check_gate_coverage.ts:982` reads the clock inside a
|
|
102
|
+
* template substitution, and the first draft of this gate reported it clean.
|
|
103
|
+
*
|
|
104
|
+
* Regex literals are left alone deliberately: a regex describing the forbidden
|
|
105
|
+
* construct (this file's own {@link PATTERNS}) does not itself match it, so
|
|
106
|
+
* there is nothing to hide, and detecting a regex literal from a `/` needs
|
|
107
|
+
* expression context this scanner does not have.
|
|
108
|
+
*/
|
|
109
|
+
export function stripNonCode(src: string): string {
|
|
110
|
+
const out = src.split('');
|
|
111
|
+
const n = src.length;
|
|
112
|
+
const put = (i: number, ch: string): void => {
|
|
113
|
+
if (out[i] !== '\n') out[i] = ch;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** `//` to end of line. */
|
|
117
|
+
function lineComment(i: number): number {
|
|
118
|
+
while (i < n && src[i] !== '\n') put(i++, ' ');
|
|
119
|
+
return i;
|
|
120
|
+
}
|
|
121
|
+
/** `/*` to the closing delimiter. */
|
|
122
|
+
function blockComment(i: number): number {
|
|
123
|
+
put(i++, ' ');
|
|
124
|
+
put(i++, ' ');
|
|
125
|
+
while (i < n && !(src[i] === '*' && src[i + 1] === '/')) put(i++, ' ');
|
|
126
|
+
if (i < n) {
|
|
127
|
+
put(i++, ' ');
|
|
128
|
+
put(i++, ' ');
|
|
129
|
+
}
|
|
130
|
+
return i;
|
|
131
|
+
}
|
|
132
|
+
/** A `'` or `"` literal; an unterminated one stops at the newline. */
|
|
133
|
+
function simpleString(i: number): number {
|
|
134
|
+
const quote = src[i];
|
|
135
|
+
put(i++, 'x');
|
|
136
|
+
while (i < n) {
|
|
137
|
+
if (src[i] === '\\') {
|
|
138
|
+
put(i++, 'x');
|
|
139
|
+
if (i < n) put(i++, 'x');
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (src[i] === quote) {
|
|
143
|
+
put(i++, 'x');
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
if (src[i] === '\n') break;
|
|
147
|
+
put(i++, 'x');
|
|
148
|
+
}
|
|
149
|
+
return i;
|
|
150
|
+
}
|
|
151
|
+
/** A `\`` literal, recursing into every `${...}` as code. */
|
|
152
|
+
function template(i: number): number {
|
|
153
|
+
put(i++, 'x');
|
|
154
|
+
while (i < n) {
|
|
155
|
+
if (src[i] === '\\') {
|
|
156
|
+
put(i++, 'x');
|
|
157
|
+
if (i < n) put(i++, 'x');
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (src[i] === '`') {
|
|
161
|
+
put(i++, 'x');
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (src[i] === '$' && src[i + 1] === '{') {
|
|
165
|
+
i = substitution(i + 2);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
put(i++, 'x');
|
|
169
|
+
}
|
|
170
|
+
return i;
|
|
171
|
+
}
|
|
172
|
+
/** Code inside `${ ... }`, up to the brace that closes it. */
|
|
173
|
+
function substitution(i: number): number {
|
|
174
|
+
let depth = 1;
|
|
175
|
+
while (i < n && depth > 0) {
|
|
176
|
+
const c = src[i];
|
|
177
|
+
const next = src[i + 1];
|
|
178
|
+
if (c === '/' && next === '/') {
|
|
179
|
+
i = lineComment(i);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (c === '/' && next === '*') {
|
|
183
|
+
i = blockComment(i);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (c === "'" || c === '"') {
|
|
187
|
+
i = simpleString(i);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (c === '`') {
|
|
191
|
+
i = template(i);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (c === '{') depth += 1;
|
|
195
|
+
if (c === '}') depth -= 1;
|
|
196
|
+
i += 1;
|
|
197
|
+
}
|
|
198
|
+
return i;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let i = 0;
|
|
202
|
+
while (i < n) {
|
|
203
|
+
const c = src[i];
|
|
204
|
+
const next = src[i + 1];
|
|
205
|
+
if (c === '/' && next === '/') {
|
|
206
|
+
i = lineComment(i);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (c === '/' && next === '*') {
|
|
210
|
+
i = blockComment(i);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (c === "'" || c === '"') {
|
|
214
|
+
i = simpleString(i);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (c === '`') {
|
|
218
|
+
i = template(i);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
i += 1;
|
|
222
|
+
}
|
|
223
|
+
return out.join('');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface Finding {
|
|
227
|
+
file: string;
|
|
228
|
+
line: number;
|
|
229
|
+
construct: string;
|
|
230
|
+
/** `raw` — an unmarked read. `bare-marker` — a marker with no reason. */
|
|
231
|
+
kind: 'raw' | 'bare-marker';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Does this raw line carry a marker, and does the marker carry a reason? */
|
|
235
|
+
function markerOn(rawLine: string): 'none' | 'bare' | 'reasoned' {
|
|
236
|
+
const at = rawLine.indexOf(MARKER);
|
|
237
|
+
if (at < 0) return 'none';
|
|
238
|
+
return rawLine.slice(at + MARKER.length).trim() === '' ? 'bare' : 'reasoned';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function scanSource(relPath: string, src: string): Finding[] {
|
|
242
|
+
const rawLines = src.split('\n');
|
|
243
|
+
const codeLines = stripNonCode(src).split('\n');
|
|
244
|
+
const findings: Finding[] = [];
|
|
245
|
+
for (let n = 0; n < codeLines.length; n += 1) {
|
|
246
|
+
const code = codeLines[n] ?? '';
|
|
247
|
+
for (const pat of PATTERNS) {
|
|
248
|
+
pat.re.lastIndex = 0;
|
|
249
|
+
if (!pat.re.test(code)) continue;
|
|
250
|
+
const marker = markerOn(rawLines[n] ?? '');
|
|
251
|
+
if (marker === 'reasoned') continue;
|
|
252
|
+
findings.push({
|
|
253
|
+
file: relPath,
|
|
254
|
+
line: n + 1,
|
|
255
|
+
construct: pat.shown,
|
|
256
|
+
kind: marker === 'bare' ? 'bare-marker' : 'raw',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return findings;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Repo-relative paths of every gate script under {@link SCAN_DIR}. */
|
|
264
|
+
export function gateScripts(root: string): string[] {
|
|
265
|
+
const dir = path.join(root, SCAN_DIR);
|
|
266
|
+
let entries: string[];
|
|
267
|
+
try {
|
|
268
|
+
entries = fs.readdirSync(dir);
|
|
269
|
+
} catch {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
return entries
|
|
273
|
+
.filter(
|
|
274
|
+
(name) =>
|
|
275
|
+
name.endsWith('.ts') &&
|
|
276
|
+
!name.endsWith('.test.ts') &&
|
|
277
|
+
GATE_PREFIXES.some((p) => name.startsWith(p)),
|
|
278
|
+
)
|
|
279
|
+
.sort()
|
|
280
|
+
.map((name) => `${SCAN_DIR}/${name}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function check(root: string): { code: number; findings: Finding[]; scanned: number } {
|
|
284
|
+
const files = gateScripts(root);
|
|
285
|
+
const findings: Finding[] = [];
|
|
286
|
+
const ledger = new GateLedger('lint_deterministic_time');
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
reportScanned({
|
|
290
|
+
gate: 'lint_deterministic_time',
|
|
291
|
+
scanned: files.length,
|
|
292
|
+
units: 'gate script(s)',
|
|
293
|
+
roots: [SCAN_DIR],
|
|
294
|
+
});
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (err instanceof DeadScopeError) {
|
|
297
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
298
|
+
return { code: 2, findings, scanned: files.length };
|
|
299
|
+
}
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
ledger.plan(files);
|
|
305
|
+
for (const rel of files) {
|
|
306
|
+
const found = scanSource(rel, fs.readFileSync(path.join(root, rel), 'utf-8'));
|
|
307
|
+
if (found.length > 0) {
|
|
308
|
+
findings.push(...found);
|
|
309
|
+
ledger.fail(rel, `${String(found.length)} raw wall-clock read(s)`);
|
|
310
|
+
} else {
|
|
311
|
+
ledger.complete(rel);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} catch (err) {
|
|
315
|
+
if (err instanceof LedgerUsageError) {
|
|
316
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
317
|
+
return { code: 2, findings, scanned: files.length };
|
|
318
|
+
}
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
ledger.report();
|
|
322
|
+
|
|
323
|
+
return { code: findings.length > 0 ? 1 : 0, findings, scanned: files.length };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* ------------------------------------------------------------------ *
|
|
327
|
+
* Self-test
|
|
328
|
+
* ------------------------------------------------------------------ */
|
|
329
|
+
|
|
330
|
+
function fixtureRoot(files: Record<string, string>): string {
|
|
331
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'det-time-'));
|
|
332
|
+
fs.mkdirSync(path.join(dir, SCAN_DIR), { recursive: true });
|
|
333
|
+
for (const [name, body] of Object.entries(files)) {
|
|
334
|
+
fs.writeFileSync(path.join(dir, SCAN_DIR, name), body);
|
|
335
|
+
}
|
|
336
|
+
return dir;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const CLEAN_GATE = ['import { asOf } from ', "'./_lib/as_of.js';", '\nconst now = asOf();\n'].join('');
|
|
340
|
+
|
|
341
|
+
export function selfTest(): number {
|
|
342
|
+
const made: string[] = [];
|
|
343
|
+
const run = (files: Record<string, string>): number => {
|
|
344
|
+
const d = fixtureRoot(files);
|
|
345
|
+
made.push(d);
|
|
346
|
+
return runGateCli(DEFAULT_ROOT, SELF, ['--quiet', '--root', d], DEFAULT_ROOT);
|
|
347
|
+
};
|
|
348
|
+
// Built by concatenation so this file's own source stays clean.
|
|
349
|
+
const rawNow = 'const t = ' + 'Date' + '.now();\n';
|
|
350
|
+
const rawNew = 'const d = new ' + 'Date();\n';
|
|
351
|
+
|
|
352
|
+
const cases: SelfTestCase[] = [
|
|
353
|
+
{
|
|
354
|
+
name: `rejects a planted ${PATTERNS[0]?.shown ?? ''} in a check_ script`,
|
|
355
|
+
expect: 'reject',
|
|
356
|
+
run: () => run({ 'check_planted.ts': rawNow }),
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: `rejects a planted bare ${PATTERNS[1]?.shown ?? ''} in a lint_ script`,
|
|
360
|
+
expect: 'reject',
|
|
361
|
+
run: () => run({ 'lint_planted.ts': rawNew }),
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: 'rejects a marker with no reason',
|
|
365
|
+
expect: 'reject',
|
|
366
|
+
run: () => run({ 'check_planted.ts': rawNow.trimEnd() + ' // ' + MARKER + '\n' }),
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: 'accepts a marker carrying a reason',
|
|
370
|
+
expect: 'accept',
|
|
371
|
+
run: () =>
|
|
372
|
+
run({
|
|
373
|
+
'check_ok.ts': rawNow.trimEnd() + ' // ' + MARKER + ' elapsed-duration probe\n',
|
|
374
|
+
}),
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'accepts a mention inside a comment or a string literal',
|
|
378
|
+
expect: 'accept',
|
|
379
|
+
run: () =>
|
|
380
|
+
run({
|
|
381
|
+
'check_prose.ts':
|
|
382
|
+
'// forbids ' + 'Date' + '.now()\nconst s = "new ' + 'Date()";\n',
|
|
383
|
+
}),
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: 'accepts a gate that routes through the seam',
|
|
387
|
+
expect: 'accept',
|
|
388
|
+
run: () => run({ 'check_ok.ts': CLEAN_GATE }),
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: 'rejects a moved scan root rather than reporting clean (exit 2)',
|
|
392
|
+
expect: 'reject',
|
|
393
|
+
run: () =>
|
|
394
|
+
runGateCli(
|
|
395
|
+
DEFAULT_ROOT,
|
|
396
|
+
SELF,
|
|
397
|
+
['--quiet', '--root', path.join(DEFAULT_ROOT, 'no-such-root')],
|
|
398
|
+
DEFAULT_ROOT,
|
|
399
|
+
),
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
return runSelfTest({
|
|
405
|
+
gate: 'lint_deterministic_time',
|
|
406
|
+
cases,
|
|
407
|
+
minCases: SELF_TEST_MIN_CASES,
|
|
408
|
+
minRejectCases: SELF_TEST_MIN_REJECT,
|
|
409
|
+
});
|
|
410
|
+
} finally {
|
|
411
|
+
for (const d of made) fs.rmSync(d, { recursive: true, force: true });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function main(argv?: readonly string[]): number {
|
|
416
|
+
const args = argv ?? process.argv.slice(2);
|
|
417
|
+
if (args.includes('--self-test')) return selfTest();
|
|
418
|
+
const quiet = args.includes('--quiet');
|
|
419
|
+
const rootIdx = args.indexOf('--root');
|
|
420
|
+
const root = rootIdx >= 0 ? (args[rootIdx + 1] ?? DEFAULT_ROOT) : DEFAULT_ROOT;
|
|
421
|
+
|
|
422
|
+
const { code, findings, scanned } = check(root);
|
|
423
|
+
if (code === 2) return 2;
|
|
424
|
+
|
|
425
|
+
if (findings.length > 0) {
|
|
426
|
+
process.stdout.write('❌ gate script(s) read the wall clock directly:\n\n');
|
|
427
|
+
for (const f of findings) {
|
|
428
|
+
const why =
|
|
429
|
+
f.kind === 'bare-marker'
|
|
430
|
+
? `\`${MARKER}\` with no reason`
|
|
431
|
+
: `raw \`${f.construct}\``;
|
|
432
|
+
process.stdout.write(` ${f.file}:${String(f.line)} — ${why}\n`);
|
|
433
|
+
}
|
|
434
|
+
process.stdout.write(
|
|
435
|
+
`\nA verdict that depends on the hour it ran is not reproducible from the\n` +
|
|
436
|
+
`commit. Route through \`asOf()\` in ${SEAM} — it resolves --as-of, AC_AS_OF,\n` +
|
|
437
|
+
`the commit date under CI, and the wall clock with an explicit WARN.\n` +
|
|
438
|
+
`If real elapsed time IS the subject, say so on the line:\n` +
|
|
439
|
+
` // ${MARKER} <reason>\n`,
|
|
440
|
+
);
|
|
441
|
+
return 1;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!quiet) {
|
|
445
|
+
process.stdout.write(
|
|
446
|
+
`✅ no raw wall-clock read in ${String(scanned)} gate script(s) under ${SCAN_DIR}/.\n`,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function _isCliEntry(): boolean {
|
|
453
|
+
if (process.argv[1] === undefined) return false;
|
|
454
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
455
|
+
if (import.meta.url === argvUrl) return true;
|
|
456
|
+
try {
|
|
457
|
+
return (
|
|
458
|
+
fs.realpathSync(fileURLToPath(import.meta.url)) ===
|
|
459
|
+
fs.realpathSync(path.resolve(process.argv[1]))
|
|
460
|
+
);
|
|
461
|
+
} catch {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
467
|
+
process.exit(main());
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export { SCAN_DIR, GATE_PREFIXES, SEAM, MARKER };
|
|
@@ -102,6 +102,11 @@ const CARVE_OUT_PATTERNS = [
|
|
|
102
102
|
'^mobile-e2e',
|
|
103
103
|
'^project-analysis-(laravel|symfony|nextjs|react|node-express|zend-laminas)',
|
|
104
104
|
'^docker', '^aws-', '^grafana', '^playwright',
|
|
105
|
+
// JS/TS workspace tooling. Same class as `^composer-` one line up: a skill
|
|
106
|
+
// ABOUT a package manager cannot describe it without naming its manifest.
|
|
107
|
+
// `package.json` / `node_modules` in these two files is the subject, not a
|
|
108
|
+
// mandate that some unrelated project adopt Node.
|
|
109
|
+
'^monorepo-workspace$', '^workspace-link$',
|
|
105
110
|
'^laravel-', '^docker-', '^symfony-', '^copilot-', '^devcontainer',
|
|
106
111
|
'-routing$',
|
|
107
112
|
];
|