session-orchestrator 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.codex-plugin/agents/explorer.toml +14 -0
- package/.codex-plugin/agents/session-reviewer.toml +23 -0
- package/.codex-plugin/agents/wave-worker.toml +15 -0
- package/.codex-plugin/config.toml +20 -0
- package/.codex-plugin/plugin.json +37 -0
- package/.cursor/rules/000-session-orchestrator.mdc +73 -0
- package/.cursor/rules/010-session-workflow.mdc +170 -0
- package/.cursor/rules/020-quality-gates.mdc +128 -0
- package/.cursor/rules/030-wave-execution.mdc +216 -0
- package/.cursor/rules/040-discovery.mdc +242 -0
- package/.cursor/rules/050-plan.mdc +235 -0
- package/.cursor/rules/060-evolve.mdc +232 -0
- package/.cursor/rules/070-gitlab-ops.mdc +246 -0
- package/.cursor/rules/080-ecosystem-health.mdc +145 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +1544 -0
- package/LICENSE +21 -0
- package/NOTICE +64 -0
- package/README.md +242 -0
- package/SECURITY.md +90 -0
- package/agents/AGENTS.md +136 -0
- package/agents/analyst.md +99 -0
- package/agents/architect-reviewer.md +93 -0
- package/agents/code-implementer.md +106 -0
- package/agents/db-specialist.md +104 -0
- package/agents/dialectic-deriver.md +139 -0
- package/agents/docs-writer.md +113 -0
- package/agents/eval-judge.md +146 -0
- package/agents/memory-proposal-collector.md +297 -0
- package/agents/qa-strategist.md +102 -0
- package/agents/schemas/analyst.schema.json +46 -0
- package/agents/schemas/architect-reviewer.schema.json +50 -0
- package/agents/schemas/code-implementer.schema.json +61 -0
- package/agents/schemas/db-specialist.schema.json +80 -0
- package/agents/schemas/docs-writer.schema.json +56 -0
- package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
- package/agents/schemas/qa-strategist.schema.json +46 -0
- package/agents/schemas/security-reviewer.schema.json +86 -0
- package/agents/schemas/session-reviewer.schema.json +69 -0
- package/agents/schemas/test-writer.schema.json +69 -0
- package/agents/schemas/ui-developer.schema.json +90 -0
- package/agents/schemas/ux-evaluator.schema.json +51 -0
- package/agents/security-reviewer.md +236 -0
- package/agents/session-reviewer.md +201 -0
- package/agents/skill-applied-judge.md +122 -0
- package/agents/test-writer.md +123 -0
- package/agents/ui-developer.md +109 -0
- package/agents/ux-evaluator.md +161 -0
- package/assets/icon.svg +11 -0
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +47 -0
- package/commands/autopilot-multi.md +74 -0
- package/commands/autopilot.md +80 -0
- package/commands/bootstrap.md +56 -0
- package/commands/brainstorm.md +48 -0
- package/commands/close.md +24 -0
- package/commands/debug.md +36 -0
- package/commands/discovery.md +32 -0
- package/commands/dispatcher.md +59 -0
- package/commands/eval.md +28 -0
- package/commands/evolve.md +10 -0
- package/commands/go.md +41 -0
- package/commands/grill.md +45 -0
- package/commands/harness-audit.md +26 -0
- package/commands/memory-cleanup.md +25 -0
- package/commands/persona-panel.md +121 -0
- package/commands/plan.md +15 -0
- package/commands/portfolio.md +97 -0
- package/commands/reconcile.md +23 -0
- package/commands/repo-audit.md +24 -0
- package/commands/session.md +30 -0
- package/commands/spinout.md +15 -0
- package/commands/sunset-review.md +27 -0
- package/commands/templates-ack.md +96 -0
- package/commands/test.md +97 -0
- package/docs/README.md +105 -0
- package/docs/USER-GUIDE.md +1403 -0
- package/docs/ci-setup.md +81 -0
- package/docs/codex-setup.md +142 -0
- package/docs/components.md +74 -0
- package/docs/cursor-setup.md +104 -0
- package/docs/events-schema.md +81 -0
- package/docs/migration-v3.md +148 -0
- package/docs/owner-config-schema.md +154 -0
- package/docs/persona-panel.md +433 -0
- package/docs/pi-setup.md +115 -0
- package/docs/plugin-architecture-v3.md +296 -0
- package/docs/pm-skills-marketplace.md +114 -0
- package/docs/policy-cache-validation-2026-04-28.md +118 -0
- package/docs/rule-authoring.md +316 -0
- package/docs/session-config-reference.md +1439 -0
- package/docs/session-config-template.md +961 -0
- package/docs/vault-docs-architecture.md +297 -0
- package/hooks/_lib/lock-bootstrap.mjs +272 -0
- package/hooks/_lib/lock-reconcile.mjs +93 -0
- package/hooks/_lib/profile-gate.mjs +95 -0
- package/hooks/_lib/transcript-history.mjs +211 -0
- package/hooks/agent-teams-h3-test.sh +362 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/cwd-change-restore.mjs +131 -0
- package/hooks/enforce-commands.mjs +179 -0
- package/hooks/enforce-scope.mjs +273 -0
- package/hooks/hooks-codex.json +60 -0
- package/hooks/hooks-cursor.json +15 -0
- package/hooks/hooks-pi.json +115 -0
- package/hooks/hooks.json +215 -0
- package/hooks/loop-guard.mjs +260 -0
- package/hooks/on-session-end.mjs +217 -0
- package/hooks/on-session-start.mjs +660 -0
- package/hooks/on-stop.mjs +294 -0
- package/hooks/operator-steer.mjs +64 -0
- package/hooks/post-edit-validate.mjs +225 -0
- package/hooks/post-subagent-discovery-validator.mjs +398 -0
- package/hooks/post-tool-batch-wave-signal.mjs +328 -0
- package/hooks/post-tool-failure-corrective-context.mjs +248 -0
- package/hooks/post-tooluse-frontend-slop.mjs +184 -0
- package/hooks/pre-bash-destructive-guard.mjs +515 -0
- package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
- package/hooks/pre-bash-staging-fence.mjs +223 -0
- package/hooks/pre-bash-templates-first.mjs +404 -0
- package/hooks/run-node.sh +72 -0
- package/hooks/skill-invocation-telemetry.mjs +99 -0
- package/hooks/subagent-telemetry.mjs +249 -0
- package/hooks/wave-scope-commit-guard.mjs +191 -0
- package/monitors/monitors.json +14 -0
- package/output-styles/finding-report.md +48 -0
- package/output-styles/session-report.md +53 -0
- package/output-styles/wave-summary.md +38 -0
- package/package.json +94 -0
- package/pi/extensions/session-orchestrator.ts +25 -0
- package/pi/prompts/autopilot-multi.md +12 -0
- package/pi/prompts/autopilot.md +12 -0
- package/pi/prompts/bootstrap.md +12 -0
- package/pi/prompts/brainstorm.md +12 -0
- package/pi/prompts/close.md +11 -0
- package/pi/prompts/debug.md +12 -0
- package/pi/prompts/discovery.md +12 -0
- package/pi/prompts/dispatcher.md +12 -0
- package/pi/prompts/eval.md +12 -0
- package/pi/prompts/evolve.md +12 -0
- package/pi/prompts/go.md +12 -0
- package/pi/prompts/grill.md +12 -0
- package/pi/prompts/harness-audit.md +12 -0
- package/pi/prompts/memory-cleanup.md +12 -0
- package/pi/prompts/persona-panel.md +12 -0
- package/pi/prompts/plan.md +12 -0
- package/pi/prompts/portfolio.md +12 -0
- package/pi/prompts/reconcile.md +12 -0
- package/pi/prompts/repo-audit.md +12 -0
- package/pi/prompts/session.md +12 -0
- package/pi/prompts/spinout.md +12 -0
- package/pi/prompts/sunset-review.md +12 -0
- package/pi/prompts/templates-ack.md +12 -0
- package/pi/prompts/test.md +12 -0
- package/rules/_index.md +51 -0
- package/rules/always-on/commit-discipline.md +26 -0
- package/rules/always-on/npm-quality-gates.md +26 -0
- package/rules/always-on/parallel-sessions.md +43 -0
- package/rules/opt-in-domain/prompt-caching.md +270 -0
- package/rules/opt-in-stack/backend-data.md +188 -0
- package/rules/opt-in-stack/backend.md +390 -0
- package/rules/opt-in-stack/frontend.md +98 -0
- package/rules/opt-in-stack/security-web.md +194 -0
- package/rules/opt-in-stack/swift.md +65 -0
- package/scripts/archive-closed-prds.mjs +416 -0
- package/scripts/autopilot-multi.mjs +802 -0
- package/scripts/autopilot.mjs +383 -0
- package/scripts/backfill-abandoned-sessions.mjs +265 -0
- package/scripts/backfill-learnings-expires.mjs +196 -0
- package/scripts/backfill-learnings.mjs +203 -0
- package/scripts/backfill-sessions.mjs +282 -0
- package/scripts/check-doc-consistency.sh +279 -0
- package/scripts/check-package-manager.mjs +445 -0
- package/scripts/ci/assert-vitest-green.mjs +267 -0
- package/scripts/codex-install.mjs +435 -0
- package/scripts/compute-grounding-injection.sh +186 -0
- package/scripts/cursor-install.mjs +113 -0
- package/scripts/dialectic-deriver.mjs +573 -0
- package/scripts/emit-event.mjs +160 -0
- package/scripts/emit-session.mjs +212 -0
- package/scripts/eval-session.mjs +262 -0
- package/scripts/export-hw-learnings.mjs +437 -0
- package/scripts/gc-stale-worktrees.mjs +666 -0
- package/scripts/generate-pi-prompts.mjs +127 -0
- package/scripts/harness-audit.mjs +287 -0
- package/scripts/lib/agent-frontmatter.mjs +266 -0
- package/scripts/lib/agent-output-schema.mjs +166 -0
- package/scripts/lib/agent-status.mjs +303 -0
- package/scripts/lib/ajv-loader.mjs +34 -0
- package/scripts/lib/auto-dialectic.mjs +382 -0
- package/scripts/lib/auto-dream.mjs +471 -0
- package/scripts/lib/autonomy/suitability.mjs +212 -0
- package/scripts/lib/autopilot/dep-graph.mjs +417 -0
- package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
- package/scripts/lib/autopilot/flags.mjs +104 -0
- package/scripts/lib/autopilot/kill-switches.mjs +174 -0
- package/scripts/lib/autopilot/loop.mjs +320 -0
- package/scripts/lib/autopilot/mr-draft.mjs +520 -0
- package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
- package/scripts/lib/autopilot/recent-runs.mjs +106 -0
- package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
- package/scripts/lib/autopilot/telemetry.mjs +224 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
- package/scripts/lib/autopilot-telemetry.mjs +11 -0
- package/scripts/lib/autopilot.mjs +39 -0
- package/scripts/lib/backlog-scan.mjs +179 -0
- package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
- package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
- package/scripts/lib/build-live-signals.mjs +150 -0
- package/scripts/lib/ci-status-banner.mjs +425 -0
- package/scripts/lib/claude-md-budget-lint.mjs +246 -0
- package/scripts/lib/cli-flags.mjs +158 -0
- package/scripts/lib/codex/plugin-contract.mjs +610 -0
- package/scripts/lib/cold-start-detector.mjs +240 -0
- package/scripts/lib/command-blocker.mjs +458 -0
- package/scripts/lib/common.mjs +333 -0
- package/scripts/lib/config/auto-dream.mjs +77 -0
- package/scripts/lib/config/block-header.mjs +94 -0
- package/scripts/lib/config/broken-window.mjs +114 -0
- package/scripts/lib/config/coercers.mjs +248 -0
- package/scripts/lib/config/cold-start.mjs +92 -0
- package/scripts/lib/config/config-protection.mjs +120 -0
- package/scripts/lib/config/cross-repo.mjs +104 -0
- package/scripts/lib/config/custom-phases.mjs +213 -0
- package/scripts/lib/config/dialectic.mjs +92 -0
- package/scripts/lib/config/discovery-validator.mjs +75 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
- package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
- package/scripts/lib/config/docs-orchestrator.mjs +90 -0
- package/scripts/lib/config/docs-staleness.mjs +96 -0
- package/scripts/lib/config/drift-check.mjs +155 -0
- package/scripts/lib/config/eval.mjs +130 -0
- package/scripts/lib/config/events-rotation.mjs +74 -0
- package/scripts/lib/config/evolve.mjs +308 -0
- package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
- package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
- package/scripts/lib/config/handover-gate.mjs +106 -0
- package/scripts/lib/config/host-paths.mjs +76 -0
- package/scripts/lib/config/io.mjs +54 -0
- package/scripts/lib/config/loop-guard.mjs +117 -0
- package/scripts/lib/config/memory.mjs +150 -0
- package/scripts/lib/config/persona-gate-wave.mjs +258 -0
- package/scripts/lib/config/reconcile.mjs +205 -0
- package/scripts/lib/config/section-extractor.mjs +100 -0
- package/scripts/lib/config/skill-evolution.mjs +112 -0
- package/scripts/lib/config/slopcheck.mjs +99 -0
- package/scripts/lib/config/state-md-lock.mjs +83 -0
- package/scripts/lib/config/templates-first.mjs +94 -0
- package/scripts/lib/config/test.mjs +113 -0
- package/scripts/lib/config/vault-integration.mjs +201 -0
- package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
- package/scripts/lib/config/vault-staleness.mjs +84 -0
- package/scripts/lib/config/vault-sync.mjs +96 -0
- package/scripts/lib/config/verification-auto-fix.mjs +84 -0
- package/scripts/lib/config/wave-reviewers.mjs +133 -0
- package/scripts/lib/config-schema.mjs +345 -0
- package/scripts/lib/config.mjs +474 -0
- package/scripts/lib/convergence-monitor.mjs +389 -0
- package/scripts/lib/coordinator-snapshot.mjs +371 -0
- package/scripts/lib/crypto-digest-utils.mjs +91 -0
- package/scripts/lib/discovery/helpers.mjs +127 -0
- package/scripts/lib/discovery/triage-state.mjs +279 -0
- package/scripts/lib/dispatcher/cli.mjs +257 -0
- package/scripts/lib/dispatcher/enumerate.mjs +243 -0
- package/scripts/lib/dispatcher/rank.mjs +363 -0
- package/scripts/lib/ecosystem-health.mjs +224 -0
- package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
- package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
- package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
- package/scripts/lib/ecosystem-wizard.mjs +48 -0
- package/scripts/lib/env-check.mjs +89 -0
- package/scripts/lib/eval/engine.mjs +605 -0
- package/scripts/lib/eval/judge.mjs +433 -0
- package/scripts/lib/eval/report.mjs +367 -0
- package/scripts/lib/eval/schema.mjs +618 -0
- package/scripts/lib/eval/session-resolve.mjs +137 -0
- package/scripts/lib/eval/sink.mjs +77 -0
- package/scripts/lib/events-rotation.mjs +86 -0
- package/scripts/lib/events-schema.mjs +81 -0
- package/scripts/lib/events.mjs +80 -0
- package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
- package/scripts/lib/exclusivity-matrix.mjs +68 -0
- package/scripts/lib/fetch-baseline.mjs +311 -0
- package/scripts/lib/file-lock.mjs +512 -0
- package/scripts/lib/frontend-detect/detect.mjs +138 -0
- package/scripts/lib/frontend-detect/rules.mjs +295 -0
- package/scripts/lib/frontmatter-guard.mjs +241 -0
- package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
- package/scripts/lib/gates/gate-baseline.mjs +42 -0
- package/scripts/lib/gates/gate-full.mjs +85 -0
- package/scripts/lib/gates/gate-helpers.mjs +231 -0
- package/scripts/lib/gates/gate-incremental.mjs +76 -0
- package/scripts/lib/gates/gate-per-file.mjs +55 -0
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
- package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
- package/scripts/lib/handover-gate.mjs +222 -0
- package/scripts/lib/hardening.mjs +43 -0
- package/scripts/lib/hardware-pattern-detector.mjs +238 -0
- package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
- package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
- package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
- package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
- package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
- package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
- package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
- package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
- package/scripts/lib/harness-audit/categories.mjs +19 -0
- package/scripts/lib/historical-guard.mjs +15 -0
- package/scripts/lib/host-identity.mjs +262 -0
- package/scripts/lib/instruction-budget-guard.mjs +332 -0
- package/scripts/lib/io.mjs +304 -0
- package/scripts/lib/issue-close-strip-labels.mjs +161 -0
- package/scripts/lib/language-mappers/README.md +57 -0
- package/scripts/lib/language-mappers/index.mjs +165 -0
- package/scripts/lib/language-mappers/markdown.mjs +149 -0
- package/scripts/lib/language-mappers/python.mjs +249 -0
- package/scripts/lib/language-mappers/swift.mjs +201 -0
- package/scripts/lib/language-mappers/typescript.mjs +433 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
- package/scripts/lib/learnings/filters.mjs +43 -0
- package/scripts/lib/learnings/io.mjs +255 -0
- package/scripts/lib/learnings/schema.mjs +518 -0
- package/scripts/lib/learnings/surface.mjs +207 -0
- package/scripts/lib/learnings.mjs +42 -0
- package/scripts/lib/lock-reaper.mjs +648 -0
- package/scripts/lib/locks/index.mjs +31 -0
- package/scripts/lib/locks/lock-body.mjs +62 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
- package/scripts/lib/locks/state-md-lock.mjs +351 -0
- package/scripts/lib/loop-readiness-banner.mjs +144 -0
- package/scripts/lib/memory-banner.mjs +478 -0
- package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
- package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
- package/scripts/lib/memory-paths.mjs +31 -0
- package/scripts/lib/memory-proposals/collector.mjs +334 -0
- package/scripts/lib/memory-proposals/schema.mjs +289 -0
- package/scripts/lib/memory-proposals/sink.mjs +507 -0
- package/scripts/lib/memory-proposals/store.mjs +441 -0
- package/scripts/lib/mission-status-schema.mjs +114 -0
- package/scripts/lib/mode-selector/alternatives.mjs +64 -0
- package/scripts/lib/mode-selector/constants.mjs +29 -0
- package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
- package/scripts/lib/mode-selector/rationale.mjs +55 -0
- package/scripts/lib/mode-selector/scoring.mjs +221 -0
- package/scripts/lib/mode-selector-accuracy.mjs +121 -0
- package/scripts/lib/mode-selector.mjs +160 -0
- package/scripts/lib/multi-provider-build/providers.mjs +64 -0
- package/scripts/lib/multi-provider-build/templating.mjs +130 -0
- package/scripts/lib/named-baseline-resolver.mjs +233 -0
- package/scripts/lib/named-vault-resolver.mjs +433 -0
- package/scripts/lib/owner-config/coerce.mjs +29 -0
- package/scripts/lib/owner-config/constants.mjs +21 -0
- package/scripts/lib/owner-config/defaults.mjs +50 -0
- package/scripts/lib/owner-config/error.mjs +19 -0
- package/scripts/lib/owner-config/index.mjs +13 -0
- package/scripts/lib/owner-config/merge.mjs +52 -0
- package/scripts/lib/owner-config/validate.mjs +259 -0
- package/scripts/lib/owner-config-banner.mjs +126 -0
- package/scripts/lib/owner-config-loader.mjs +159 -0
- package/scripts/lib/owner-config.example.yaml +72 -0
- package/scripts/lib/owner-config.mjs +28 -0
- package/scripts/lib/owner-interview.mjs +243 -0
- package/scripts/lib/owner-yaml.mjs +571 -0
- package/scripts/lib/package-manager.mjs +160 -0
- package/scripts/lib/path-utils.mjs +217 -0
- package/scripts/lib/peer-cards/merger.mjs +310 -0
- package/scripts/lib/peer-cards/reader.mjs +125 -0
- package/scripts/lib/peer-cards/schema.mjs +230 -0
- package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
- package/scripts/lib/peer-cards/writer.mjs +138 -0
- package/scripts/lib/peer-discovery.mjs +200 -0
- package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
- package/scripts/lib/persona-panel/consolidator.mjs +370 -0
- package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
- package/scripts/lib/persona-panel/threshold.mjs +130 -0
- package/scripts/lib/pi-hook-bridge.mjs +328 -0
- package/scripts/lib/platform.mjs +266 -0
- package/scripts/lib/playwright-driver/runner.mjs +297 -0
- package/scripts/lib/plugin-root.mjs +210 -0
- package/scripts/lib/pre-dispatch-check.mjs +126 -0
- package/scripts/lib/product-repo-detect.mjs +121 -0
- package/scripts/lib/profiles/registry.mjs +176 -0
- package/scripts/lib/profiles/schema.mjs +209 -0
- package/scripts/lib/qg-command-drift-banner.mjs +88 -0
- package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
- package/scripts/lib/quality-gate.mjs +536 -0
- package/scripts/lib/quality-gates-cache.mjs +228 -0
- package/scripts/lib/quality-gates-policy.mjs +95 -0
- package/scripts/lib/recommendations-v0.mjs +156 -0
- package/scripts/lib/reconcile/eligibility.mjs +203 -0
- package/scripts/lib/reconcile/emitter.mjs +244 -0
- package/scripts/lib/reconcile/engine.mjs +412 -0
- package/scripts/lib/reconcile/idempotency.mjs +239 -0
- package/scripts/lib/reconcile/renderer.mjs +211 -0
- package/scripts/lib/reconcile/writer.mjs +293 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
- package/scripts/lib/resource-probe/evaluate.mjs +190 -0
- package/scripts/lib/resource-probe/parsers.mjs +181 -0
- package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
- package/scripts/lib/resource-probe.mjs +95 -0
- package/scripts/lib/rule-loader.mjs +552 -0
- package/scripts/lib/rules-sync.mjs +439 -0
- package/scripts/lib/scope-gate.mjs +496 -0
- package/scripts/lib/session-close-backfill.mjs +539 -0
- package/scripts/lib/session-discovery.mjs +256 -0
- package/scripts/lib/session-end/phase-skip.mjs +357 -0
- package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
- package/scripts/lib/session-id.mjs +362 -0
- package/scripts/lib/session-lock.mjs +703 -0
- package/scripts/lib/session-registry.mjs +355 -0
- package/scripts/lib/session-schema/aliases.mjs +71 -0
- package/scripts/lib/session-schema/constants.mjs +115 -0
- package/scripts/lib/session-schema/normalizer.mjs +66 -0
- package/scripts/lib/session-schema/timestamps.mjs +64 -0
- package/scripts/lib/session-schema/validator.mjs +453 -0
- package/scripts/lib/session-schema.mjs +71 -0
- package/scripts/lib/session-token-rollup.mjs +137 -0
- package/scripts/lib/sessions-staleness-banner.mjs +247 -0
- package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
- package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
- package/scripts/lib/skill-evolution/engine.mjs +719 -0
- package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
- package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
- package/scripts/lib/skill-health/join.mjs +181 -0
- package/scripts/lib/skill-health/score.mjs +123 -0
- package/scripts/lib/skill-invocations-schema.mjs +214 -0
- package/scripts/lib/skill-judge.mjs +348 -0
- package/scripts/lib/skill-judgments-schema.mjs +264 -0
- package/scripts/lib/slopcheck.mjs +501 -0
- package/scripts/lib/soul-resolve.mjs +118 -0
- package/scripts/lib/spiral-carryover.mjs +495 -0
- package/scripts/lib/state-md/body-sections.mjs +851 -0
- package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
- package/scripts/lib/state-md/mission-status.mjs +247 -0
- package/scripts/lib/state-md/recommendations.mjs +57 -0
- package/scripts/lib/state-md/yaml-parser.mjs +234 -0
- package/scripts/lib/state-md-peer-guard.mjs +232 -0
- package/scripts/lib/state-md.mjs +53 -0
- package/scripts/lib/subagents-schema.mjs +309 -0
- package/scripts/lib/sunset/walker.mjs +1192 -0
- package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
- package/scripts/lib/test-runner/fingerprint.mjs +33 -0
- package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
- package/scripts/lib/tmux-layout/layouts.mjs +224 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
- package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
- package/scripts/lib/validate/check-agents.mjs +457 -0
- package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
- package/scripts/lib/validate/check-commands.mjs +148 -0
- package/scripts/lib/validate/check-component-paths.mjs +112 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
- package/scripts/lib/validate/check-json-files.mjs +116 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
- package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
- package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
- package/scripts/lib/validate/check-pi-package.mjs +110 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
- package/scripts/lib/validate/check-plugin-json.mjs +96 -0
- package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
- package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
- package/scripts/lib/validate/check-rules.mjs +143 -0
- package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
- package/scripts/lib/validate/confidential-names.mjs +169 -0
- package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
- package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
- package/scripts/lib/validate/tier-inference.mjs +100 -0
- package/scripts/lib/validate-vendored-rules.mjs +519 -0
- package/scripts/lib/vault-archive.mjs +404 -0
- package/scripts/lib/vault-backfill/glab.mjs +164 -0
- package/scripts/lib/vault-backfill/manifest.mjs +75 -0
- package/scripts/lib/vault-backfill/template.mjs +130 -0
- package/scripts/lib/vault-consolidate-fs.mjs +331 -0
- package/scripts/lib/vault-migration-rules.mjs +155 -0
- package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
- package/scripts/lib/vault-mirror/namespace.mjs +152 -0
- package/scripts/lib/vault-mirror/process.mjs +567 -0
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
- package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
- package/scripts/lib/vault-mirror/render.mjs +8 -0
- package/scripts/lib/vault-mirror/utils.mjs +217 -0
- package/scripts/lib/vault-relocation-rules.mjs +555 -0
- package/scripts/lib/vault-repo-backfill.mjs +235 -0
- package/scripts/lib/vault-staleness-banner.mjs +142 -0
- package/scripts/lib/vault-status/board-writer.mjs +769 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
- package/scripts/lib/vault-sync-baseline.mjs +152 -0
- package/scripts/lib/wave-context.mjs +29 -0
- package/scripts/lib/wave-executor/pool.mjs +248 -0
- package/scripts/lib/wave-resource-gate.mjs +204 -0
- package/scripts/lib/wave-sizing.mjs +75 -0
- package/scripts/lib/webhook-url.mjs +105 -0
- package/scripts/lib/workspace.mjs +198 -0
- package/scripts/lib/worktree/constants.mjs +35 -0
- package/scripts/lib/worktree/index.mjs +17 -0
- package/scripts/lib/worktree/lifecycle.mjs +287 -0
- package/scripts/lib/worktree/listing.mjs +118 -0
- package/scripts/lib/worktree/meta.mjs +64 -0
- package/scripts/lib/worktree-freshness.mjs +313 -0
- package/scripts/lib/worktree.mjs +15 -0
- package/scripts/lifecycle-sim-v6.mjs +347 -0
- package/scripts/lock-reaper.mjs +185 -0
- package/scripts/mcp-server.sh +241 -0
- package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
- package/scripts/memory-propose.mjs +464 -0
- package/scripts/migrate-cold-start-seed.mjs +404 -0
- package/scripts/migrate-learnings-jsonl.mjs +189 -0
- package/scripts/migrate-legacy-learnings.sh +61 -0
- package/scripts/migrate-sessions-jsonl.mjs +448 -0
- package/scripts/migrate-subagents-jsonl.mjs +196 -0
- package/scripts/migrate-vault-paths.mjs +796 -0
- package/scripts/parse-config.mjs +149 -0
- package/scripts/pi-install.mjs +117 -0
- package/scripts/print-applicable-rules.mjs +247 -0
- package/scripts/promote-vault-strict.mjs +496 -0
- package/scripts/relocate-vault-corpus.mjs +1178 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
- package/scripts/run-quality-gate.mjs +216 -0
- package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
- package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
- package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
- package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
- package/scripts/sweep-expired-learnings.mjs +135 -0
- package/scripts/sync-vault-schema.mjs +376 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
- package/scripts/tmux-layout.mjs +245 -0
- package/scripts/token-audit.sh +191 -0
- package/scripts/typecheck.mjs +42 -0
- package/scripts/upload-social-preview.mjs +316 -0
- package/scripts/validate-config.mjs +46 -0
- package/scripts/validate-plugin-manifests.mjs +163 -0
- package/scripts/validate-plugin.mjs +264 -0
- package/scripts/validate-wave-scope.mjs +289 -0
- package/scripts/vault-backfill.mjs +404 -0
- package/scripts/vault-consolidate.mjs +596 -0
- package/scripts/vault-integration-watcher.mjs +394 -0
- package/scripts/vault-mirror.mjs +430 -0
- package/skills/_shared/bootstrap-gate.md +111 -0
- package/skills/_shared/config-reading.md +226 -0
- package/skills/_shared/instruction-file-resolution.md +79 -0
- package/skills/_shared/model-selection.md +64 -0
- package/skills/_shared/monitor-patterns.md +300 -0
- package/skills/_shared/parallel-aware-auq.md +121 -0
- package/skills/_shared/parallel-aware-preamble.md +185 -0
- package/skills/_shared/platform-tools.md +96 -0
- package/skills/_shared/state-ownership.md +221 -0
- package/skills/architecture/DEEPENING.md +37 -0
- package/skills/architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/architecture/LANGUAGE.md +53 -0
- package/skills/architecture/SKILL.md +92 -0
- package/skills/autopilot/SKILL.md +419 -0
- package/skills/bootstrap/SKILL.md +592 -0
- package/skills/bootstrap/STATE.md.template +24 -0
- package/skills/bootstrap/_shared-template.md +243 -0
- package/skills/bootstrap/deep-template.md +659 -0
- package/skills/bootstrap/fast-template.md +251 -0
- package/skills/bootstrap/intensity-heuristic.md +80 -0
- package/skills/bootstrap/public-fallback.md +342 -0
- package/skills/bootstrap/standard-template.md +736 -0
- package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
- package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
- package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
- package/skills/brainstorm/SKILL.md +268 -0
- package/skills/brainstorm/soul.md +49 -0
- package/skills/claude-md-drift-check/SKILL.md +186 -0
- package/skills/claude-md-drift-check/checker.mjs +1380 -0
- package/skills/claude-md-drift-check/checker.sh +37 -0
- package/skills/claude-md-drift-check/package.json +16 -0
- package/skills/convergence-monitoring/README.md +39 -0
- package/skills/convergence-monitoring/SIGNALS.md +246 -0
- package/skills/convergence-monitoring/SKILL.md +285 -0
- package/skills/daily/SKILL.md +222 -0
- package/skills/daily/generate.sh +92 -0
- package/skills/daily/templates/daily.md.tpl +36 -0
- package/skills/debug/SKILL.md +188 -0
- package/skills/debug/soul.md +35 -0
- package/skills/discovery/SKILL.md +567 -0
- package/skills/discovery/issue-templates.md +237 -0
- package/skills/discovery/probes/docs-staleness.mjs +195 -0
- package/skills/discovery/probes/frontend-slop.mjs +186 -0
- package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
- package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
- package/skills/discovery/probes/vault-staleness.mjs +272 -0
- package/skills/discovery/probes-arch.md +252 -0
- package/skills/discovery/probes-audit.md +95 -0
- package/skills/discovery/probes-code.md +329 -0
- package/skills/discovery/probes-docs.md +76 -0
- package/skills/discovery/probes-feature.md +150 -0
- package/skills/discovery/probes-infra.md +138 -0
- package/skills/discovery/probes-intro.md +25 -0
- package/skills/discovery/probes-session.md +495 -0
- package/skills/discovery/probes-supply-chain.md +94 -0
- package/skills/discovery/probes-ui.md +147 -0
- package/skills/discovery/probes-vault.md +64 -0
- package/skills/discovery/slop-patterns.md +115 -0
- package/skills/dispatcher/SKILL.md +173 -0
- package/skills/docs-orchestrator/SKILL.md +362 -0
- package/skills/docs-orchestrator/audience-mapping.md +140 -0
- package/skills/domain-model/ADR-FORMAT.md +47 -0
- package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
- package/skills/domain-model/SKILL.md +85 -0
- package/skills/ecosystem-health/SKILL.md +119 -0
- package/skills/ecosystem-health/wizard.md +193 -0
- package/skills/eval/SKILL.md +293 -0
- package/skills/eval/rubric-v1.md +218 -0
- package/skills/evolve/SKILL.md +546 -0
- package/skills/frontmatter-guard/SKILL.md +126 -0
- package/skills/gitlab-ops/SKILL.md +368 -0
- package/skills/gitlab-portfolio/SKILL.md +196 -0
- package/skills/grill/SKILL.md +185 -0
- package/skills/grill/soul.md +55 -0
- package/skills/hook-development/SKILL.md +413 -0
- package/skills/mcp-builder/SKILL.md +260 -0
- package/skills/memory-cleanup/SKILL.md +310 -0
- package/skills/mode-selector/SKILL.md +226 -0
- package/skills/peekaboo-driver/SKILL.md +237 -0
- package/skills/peekaboo-driver/soul.md +32 -0
- package/skills/persona-panel/SKILL.md +365 -0
- package/skills/persona-panel/persona-format.md +205 -0
- package/skills/persona-panel/presets/designer-lens.md +87 -0
- package/skills/persona-panel/presets/engineer-lens.md +88 -0
- package/skills/persona-panel/presets/pm-lens.md +86 -0
- package/skills/plan/SKILL.md +496 -0
- package/skills/plan/mode-feature.md +141 -0
- package/skills/plan/mode-new.md +297 -0
- package/skills/plan/mode-retro.md +271 -0
- package/skills/plan/prd-feature-template.md +132 -0
- package/skills/plan/prd-full-template.md +151 -0
- package/skills/plan/prd-reviewer-prompt.md +103 -0
- package/skills/plan/retro-template.md +75 -0
- package/skills/plan/soul.md +62 -0
- package/skills/playwright-driver/SKILL.md +226 -0
- package/skills/playwright-driver/soul.md +30 -0
- package/skills/quality-gates/SKILL.md +212 -0
- package/skills/reconcile/SKILL.md +324 -0
- package/skills/repo-audit/SKILL.md +272 -0
- package/skills/session-end/SKILL.md +1044 -0
- package/skills/session-end/discovery-scan.md +37 -0
- package/skills/session-end/drift-operations.md +97 -0
- package/skills/session-end/learning-patterns.md +78 -0
- package/skills/session-end/metrics-collection.md +175 -0
- package/skills/session-end/phase-3-2-docs-verification.md +148 -0
- package/skills/session-end/phase-3-6-tail.md +344 -0
- package/skills/session-end/phase-3-7a-recommendations.md +86 -0
- package/skills/session-end/plan-verification.md +288 -0
- package/skills/session-end/session-metrics-write.md +223 -0
- package/skills/session-end/vault-operations.md +50 -0
- package/skills/session-end/verification-checklist.md +20 -0
- package/skills/session-plan/SKILL.md +554 -0
- package/skills/session-plan/wave-template.md +37 -0
- package/skills/session-start/SKILL.md +1043 -0
- package/skills/session-start/phase-2-5-docs-planning.md +119 -0
- package/skills/session-start/phase-4-5-resource-health.md +49 -0
- package/skills/session-start/phase-7-1-premise-check.md +47 -0
- package/skills/session-start/phase-7-5-mode-selector.md +237 -0
- package/skills/session-start/phase-8-5-express-path.md +61 -0
- package/skills/session-start/presentation-format.md +81 -0
- package/skills/session-start/soul.md +57 -0
- package/skills/skill-creator/SKILL.md +168 -0
- package/skills/spinout/SKILL.md +76 -0
- package/skills/sunset-review/SKILL.md +96 -0
- package/skills/test-runner/SKILL.md +362 -0
- package/skills/test-runner/rubric-v1.md +388 -0
- package/skills/test-runner/soul.md +46 -0
- package/skills/tmux-layout/SKILL.md +104 -0
- package/skills/ubiquitous-language/SKILL.md +97 -0
- package/skills/using-orchestrator/SKILL.md +144 -0
- package/skills/vault-mirror/SKILL.md +234 -0
- package/skills/vault-sync/SKILL.md +319 -0
- package/skills/vault-sync/package-lock.json +40 -0
- package/skills/vault-sync/package.json +11 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
- package/skills/vault-sync/validator.mjs +658 -0
- package/skills/vault-sync/validator.sh +55 -0
- package/skills/wave-executor/SKILL.md +496 -0
- package/skills/wave-executor/circuit-breaker.md +169 -0
- package/skills/wave-executor/wave-loop.md +1043 -0
- package/skills/write-executable-plan/SKILL.md +237 -0
- package/skills/write-executable-plan/plan-template.md +154 -0
- package/templates/_minimal/CLAUDE.md.tmpl +41 -0
- package/templates/_minimal/README.md.tmpl +15 -0
- package/templates/_minimal/gitignore.tmpl +47 -0
- package/templates/_shared/harte-regeln.md +16 -0
- package/templates/_shared/loop.md +90 -0
- package/templates/_shared/rules/parallel-sessions.md +77 -0
- package/templates/nextjs-minimal/README.md +30 -0
- package/templates/nextjs-minimal/app/layout.tsx +18 -0
- package/templates/nextjs-minimal/app/page.tsx +7 -0
- package/templates/nextjs-minimal/eslint.config.mjs +16 -0
- package/templates/nextjs-minimal/next.config.mjs +4 -0
- package/templates/nextjs-minimal/package.json +27 -0
- package/templates/nextjs-minimal/tsconfig.json +23 -0
- package/templates/node-minimal/README.md +33 -0
- package/templates/node-minimal/eslint.config.mjs +10 -0
- package/templates/node-minimal/package.json +21 -0
- package/templates/node-minimal/src/index.ts +1 -0
- package/templates/node-minimal/tests/sanity.test.ts +5 -0
- package/templates/node-minimal/tsconfig.json +17 -0
- package/templates/personas/README.md +150 -0
- package/templates/personas/accounting-compliance.v1.md +120 -0
- package/templates/personas/accounting-tax-advisor.v1.md +116 -0
- package/templates/personas/buyer-p1-cto.v1.md +125 -0
- package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
- package/templates/personas/buyer-p3-build.v1.md +130 -0
- package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
- package/templates/personas/buyer-p5-solo.v1.md +132 -0
- package/templates/personas/buyer-p6-ld.v1.md +130 -0
- package/templates/personas/klima-ai-expert.v1.md +114 -0
- package/templates/personas/klima-physicist.v1.md +117 -0
- package/templates/python-uv/README.md +28 -0
- package/templates/python-uv/pyproject.toml +32 -0
- package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
- package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
- package/templates/python-uv/tests/test_sanity.py +2 -0
- package/templates/static-html/README.md +19 -0
- package/templates/static-html/index.html +15 -0
- package/templates/static-html/script.js +1 -0
- package/templates/static-html/styles.css +26 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-unicode-safety.mjs — Scan tracked text files for dangerous / invisible
|
|
3
|
+
// Unicode and undeclared homoglyphs (#626).
|
|
4
|
+
//
|
|
5
|
+
// Rationale: a repo whose source, frontmatter, SKILL.md bodies, PR descriptions,
|
|
6
|
+
// and agent prompts are consumed by an LLM is a prime target for "ASCII /
|
|
7
|
+
// Tag smuggling" and bidi-override attacks — invisible code points (zero-width
|
|
8
|
+
// spaces, BOM, word-joiner, Unicode Tag block, bidi embed/override) that render
|
|
9
|
+
// as nothing to a human reviewer but carry instructions or reorder text for the
|
|
10
|
+
// model. This validator flags those code points everywhere, flags non-curated
|
|
11
|
+
// emoji in code/frontmatter (strict) while tolerating the repo's deliberate
|
|
12
|
+
// banner/status-symbol set in prose (lenient), and flags mixed-script homoglyphs
|
|
13
|
+
// inside a single identifier token in strict contexts.
|
|
14
|
+
//
|
|
15
|
+
// Usage: check-unicode-safety.mjs <plugin-root> [--write] [--json]
|
|
16
|
+
// Outputs lines of the form " PASS: ..." / " FAIL: ..." plus a "Results: N
|
|
17
|
+
// passed, M failed" line so the validate-plugin orchestrator's PASS:/FAIL: tally
|
|
18
|
+
// counts it.
|
|
19
|
+
//
|
|
20
|
+
// Exit codes:
|
|
21
|
+
// 0 — clean (no violations)
|
|
22
|
+
// 1 — at least one violation
|
|
23
|
+
// 2 — tool error (missing arg / unreadable root)
|
|
24
|
+
//
|
|
25
|
+
// Contexts:
|
|
26
|
+
// STRICT — code files (.mjs/.js/.ts/.sh), .json, .yml/.yaml, AND the leading
|
|
27
|
+
// frontmatter block (`---\n…\n---`) of .md files. Emoji + invisibles
|
|
28
|
+
// + mixed-script homoglyphs are all flagged here, EXCEPT the curated
|
|
29
|
+
// deliberate-symbol allow-list (banner/status emoji the repo ships).
|
|
30
|
+
// LENIENT — markdown prose body (after frontmatter). Invisibles / bidi /
|
|
31
|
+
// tag-block / zero-width are still flagged; the deliberate emoji set
|
|
32
|
+
// passes. Mixed-script homoglyph detection is NOT applied (German
|
|
33
|
+
// prose + intentional CJK fixtures must never be swept).
|
|
34
|
+
|
|
35
|
+
import { readFileSync, readdirSync, statSync, existsSync, writeFileSync } from 'node:fs';
|
|
36
|
+
import { join, extname, relative, sep } from 'node:path';
|
|
37
|
+
import { execFileSync } from 'node:child_process';
|
|
38
|
+
import { pathToFileURL } from 'node:url';
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// File enumeration — git ls-files primary, recursive fs walk fallback.
|
|
42
|
+
// (ported from check-owner-leakage.mjs, wrapped as pure exported helpers)
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** Text-scan extension allow-list. */
|
|
46
|
+
export const TEXT_EXTS = new Set(['.md', '.mjs', '.js', '.ts', '.json', '.yml', '.yaml', '.sh', '.txt']);
|
|
47
|
+
|
|
48
|
+
/** STRICT-context extensions (emoji + homoglyphs flagged, not just invisibles). */
|
|
49
|
+
const STRICT_EXTS = new Set(['.mjs', '.js', '.ts', '.sh', '.json', '.yml', '.yaml']);
|
|
50
|
+
|
|
51
|
+
/** Extensions whose files `--write` may rewrite (strip dangerous-invisibles). */
|
|
52
|
+
const WRITABLE_EXTS = new Set(['.md', '.txt']);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Return true if a path is a scannable text file by extension.
|
|
56
|
+
* @param {string} filePath
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
export function isTextFile(filePath) {
|
|
60
|
+
return TEXT_EXTS.has(extname(filePath));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function walkDir(root, dir, acc = []) {
|
|
64
|
+
let entries;
|
|
65
|
+
try {
|
|
66
|
+
entries = readdirSync(dir);
|
|
67
|
+
} catch {
|
|
68
|
+
return acc;
|
|
69
|
+
}
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (entry === '.git' || entry === 'node_modules') continue;
|
|
72
|
+
const full = join(dir, entry);
|
|
73
|
+
let s;
|
|
74
|
+
try {
|
|
75
|
+
s = statSync(full);
|
|
76
|
+
} catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (s.isDirectory()) {
|
|
80
|
+
walkDir(root, full, acc);
|
|
81
|
+
} else {
|
|
82
|
+
acc.push(full);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return acc;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Enumerate tracked text files under `root`. Uses `git ls-files`; falls back to
|
|
90
|
+
* a recursive fs walk when git is unavailable / `root` is not a repo.
|
|
91
|
+
* @param {string} root absolute plugin root
|
|
92
|
+
* @returns {string[]} absolute file paths
|
|
93
|
+
*/
|
|
94
|
+
export function getTrackedFiles(root) {
|
|
95
|
+
let files;
|
|
96
|
+
try {
|
|
97
|
+
const output = execFileSync('git', ['ls-files'], { cwd: root, encoding: 'utf8' });
|
|
98
|
+
files = output.split('\n').filter(Boolean).map((f) => join(root, f));
|
|
99
|
+
} catch {
|
|
100
|
+
files = walkDir(root, root);
|
|
101
|
+
}
|
|
102
|
+
return files.filter(isTextFile);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Dangerous-invisible code-point set (always flagged, every context).
|
|
107
|
+
// Ported from ecc check-unicode-safety.js isDangerousInvisibleCodePoint, with
|
|
108
|
+
// two deliberate differences:
|
|
109
|
+
// - the variation-selector range (U+FE00–U+FE0F) is handled SEPARATELY (see
|
|
110
|
+
// isOrphanVariationSelector) so legit emoji-presentation selectors don't
|
|
111
|
+
// false-positive;
|
|
112
|
+
// - U+00AD SOFT HYPHEN is ADDED (not in ecc) — an invisible discretionary
|
|
113
|
+
// hyphen that smuggles into prose; the repo had exactly one stray instance.
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* True when `codePoint` is an unconditionally dangerous invisible / format
|
|
118
|
+
* control. Variation selectors are NOT decided here (orphan-only — see below).
|
|
119
|
+
* @param {number} codePoint
|
|
120
|
+
* @returns {boolean}
|
|
121
|
+
*/
|
|
122
|
+
export function isDangerousInvisibleCodePoint(codePoint) {
|
|
123
|
+
return (
|
|
124
|
+
(codePoint >= 0x200b && codePoint <= 0x200d) || // ZWSP / ZWNJ / ZWJ
|
|
125
|
+
codePoint === 0x2060 || // WORD JOINER
|
|
126
|
+
codePoint === 0xfeff || // BOM / ZWNBSP
|
|
127
|
+
(codePoint >= 0x202a && codePoint <= 0x202e) || // bidi embed / override
|
|
128
|
+
(codePoint >= 0x2066 && codePoint <= 0x2069) || // bidi isolates
|
|
129
|
+
(codePoint >= 0xe0000 && codePoint <= 0xe007f) || // Unicode Tag block (ASCII smuggling)
|
|
130
|
+
(codePoint >= 0xe0100 && codePoint <= 0xe01ef) || // VS supplement
|
|
131
|
+
codePoint === 0x180e || // MONGOLIAN VOWEL SEPARATOR
|
|
132
|
+
codePoint === 0x115f || // HANGUL CHOSEONG FILLER
|
|
133
|
+
codePoint === 0x1160 || // HANGUL JUNGSEONG FILLER
|
|
134
|
+
(codePoint >= 0x2061 && codePoint <= 0x2064) || // invisible math operators
|
|
135
|
+
codePoint === 0x3164 || // HANGUL FILLER
|
|
136
|
+
codePoint === 0x00ad // SOFT HYPHEN (added beyond ecc)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Variation-selector range (U+FE00–U+FE0F). A VS-16 (U+FE0F) that immediately
|
|
141
|
+
// follows an Extended_Pictographic code point is the legitimate emoji-
|
|
142
|
+
// presentation selector and must NOT be flagged. Any other variation selector
|
|
143
|
+
// (orphan / standalone, or a VS not preceded by an emoji) is suspicious.
|
|
144
|
+
const EXTENDED_PICTOGRAPHIC = /\p{Extended_Pictographic}/u;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* True when the variation selector at `prevChar`→`char` is an ORPHAN — i.e.
|
|
148
|
+
* not a legitimate emoji-presentation selector following a pictographic base.
|
|
149
|
+
* @param {number} codePoint code point of the variation selector
|
|
150
|
+
* @param {string} prevChar the preceding code point (as a string), or ''
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
function isOrphanVariationSelector(codePoint, prevChar) {
|
|
154
|
+
if (codePoint < 0xfe00 || codePoint > 0xfe0f) return false;
|
|
155
|
+
if (codePoint === 0xfe0f && prevChar && EXTENDED_PICTOGRAPHIC.test(prevChar)) {
|
|
156
|
+
return false; // legit emoji-presentation selector
|
|
157
|
+
}
|
|
158
|
+
return true; // orphan / standalone variation selector
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Emoji + deliberate-symbol allow-list.
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
/** Emoji / regional-indicator detection (non-global — safe for per-char .test). */
|
|
166
|
+
const EMOJI_RE = /(?:\p{Extended_Pictographic}|\p{Regional_Indicator})/u;
|
|
167
|
+
|
|
168
|
+
// The repo deliberately ships a curated banner/status emoji set in CLI output
|
|
169
|
+
// strings, hooks, docs, and SKILL bodies. Every code point in this set is an
|
|
170
|
+
// intentional symbol, NOT a smuggling vector — it passes in BOTH strict and
|
|
171
|
+
// lenient contexts. The list was built from a full survey of the tracked tree
|
|
172
|
+
// (18 distinct emoji code points; see #626 Wave-1/Wave-2 discovery) plus the
|
|
173
|
+
// task-named allow-list. A NEW emoji outside this set is still flagged.
|
|
174
|
+
const ALLOWED_EMOJI_CODEPOINTS = new Set([
|
|
175
|
+
0x1f3af, // 🎯
|
|
176
|
+
0x1f465, // 👥
|
|
177
|
+
0x1f4ca, // 📊
|
|
178
|
+
0x1f4cb, // 📋
|
|
179
|
+
0x1f4da, // 📚
|
|
180
|
+
0x1f501, // 🔁
|
|
181
|
+
0x1f50d, // 🔍
|
|
182
|
+
0x1f527, // 🔧
|
|
183
|
+
0x1f534, // 🔴
|
|
184
|
+
0x1f5a5, // 🖥
|
|
185
|
+
0x1f6a6, // 🚦
|
|
186
|
+
0x1f6a8, // 🚨
|
|
187
|
+
0x2139, // ℹ
|
|
188
|
+
0x2194, // ↔
|
|
189
|
+
0x26a0, // ⚠
|
|
190
|
+
0x2705, // ✅
|
|
191
|
+
0x274c, // ❌
|
|
192
|
+
0x2b50, // ⭐
|
|
193
|
+
0x00a9, // © (copyright — defensive, ecc allow-list)
|
|
194
|
+
0x00ae, // ® (registered — defensive)
|
|
195
|
+
0x2122, // ™ (trademark — defensive)
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* True when `char` is a curated deliberate symbol that must never be flagged.
|
|
200
|
+
* @param {string} char a single emoji match
|
|
201
|
+
* @returns {boolean}
|
|
202
|
+
*/
|
|
203
|
+
function isAllowedEmojiSymbol(char) {
|
|
204
|
+
return ALLOWED_EMOJI_CODEPOINTS.has(char.codePointAt(0));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Mixed-script homoglyph detection (STRICT contexts only, conservative).
|
|
209
|
+
// Flags an identifier-like token that mixes Latin with Cyrillic — the classic
|
|
210
|
+
// homoglyph attack (e.g. Cyrillic U+0430 "а" inside an ASCII word; the Cyrillic
|
|
211
|
+
// glyph is visually identical to Latin "a").
|
|
212
|
+
//
|
|
213
|
+
// Greek is DELIBERATELY EXCLUDED (#626): Greek math letters (λ π Δ Σ θ µ) are
|
|
214
|
+
// visually distinct from Latin — they are NOT homoglyph-attack vectors — and are
|
|
215
|
+
// legitimately used as identifiers in code (`const λmax = 5`). Including Greek in
|
|
216
|
+
// the mixed-script trigger produced a CI-landmine false-positive. German umlauts
|
|
217
|
+
// (ä ö ü ß) are Latin script → never trigger. CJK is neither Latin/Cyrillic →
|
|
218
|
+
// never trigger. Prose is NOT scanned (lenient).
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
const IDENT_TOKEN_RE = /[\p{L}\p{M}\p{N}_]+/gu;
|
|
222
|
+
const SCRIPT_LATIN = /\p{Script=Latin}/u;
|
|
223
|
+
const SCRIPT_CYRILLIC = /\p{Script=Cyrillic}/u;
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Frontmatter extraction (a .md file's leading `---\n…\n---` block).
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Return the length (in chars) of the leading YAML frontmatter block, or 0 when
|
|
231
|
+
* the file has none. The frontmatter region is treated as STRICT.
|
|
232
|
+
*
|
|
233
|
+
* A single leading U+FEFF BOM is tolerated before the opening `---\n` (#626):
|
|
234
|
+
* without this carve-out, a BOM-prefixed `.md` file fails the `startsWith` test
|
|
235
|
+
* and its frontmatter is silently treated as LENIENT prose. The BOM itself is
|
|
236
|
+
* still flagged as dangerous-invisible by the code-point walk (which scans the
|
|
237
|
+
* original, un-stripped text), so the carve-out only fixes detection. The
|
|
238
|
+
* returned length is an offset into the ORIGINAL text, so the +1 for the
|
|
239
|
+
* stripped BOM is added back.
|
|
240
|
+
* @param {string} text
|
|
241
|
+
* @returns {number} number of leading characters that are frontmatter
|
|
242
|
+
*/
|
|
243
|
+
function frontmatterLength(text) {
|
|
244
|
+
// Strip a single leading BOM for the detection test only; track the offset so
|
|
245
|
+
// the returned length still indexes into the original `text`.
|
|
246
|
+
const bomOffset = text.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
247
|
+
const body = bomOffset ? text.slice(1) : text;
|
|
248
|
+
if (!body.startsWith('---\n')) return 0;
|
|
249
|
+
const end = body.indexOf('\n---', 4);
|
|
250
|
+
if (end === -1) return 0;
|
|
251
|
+
return bomOffset + end + 4; // include the closing `---`, offset by the BOM
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Per-line / column helpers.
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 1-based line + column for a character index into `text`.
|
|
260
|
+
* @param {string} text
|
|
261
|
+
* @param {number} index char index of the code unit
|
|
262
|
+
* @returns {{line: number, column: number}}
|
|
263
|
+
*/
|
|
264
|
+
function lineAndColumn(text, index) {
|
|
265
|
+
const before = text.slice(0, index);
|
|
266
|
+
const line = before.split('\n').length;
|
|
267
|
+
const lastNewline = before.lastIndexOf('\n');
|
|
268
|
+
const column = index - lastNewline; // 1-based column
|
|
269
|
+
return { line, column };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Core collector (pure).
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Collect every Unicode-safety violation across `rootOrFiles`.
|
|
278
|
+
*
|
|
279
|
+
* Pure + import-safe: no process.exit, no console. Returns the violation list so
|
|
280
|
+
* callers and tests can assert against it directly.
|
|
281
|
+
*
|
|
282
|
+
* @param {string} rootOrFiles absolute plugin root (files are enumerated via git)
|
|
283
|
+
* @returns {Array<{file: string, line: number, column: number, kind: string, codePoint: string}>}
|
|
284
|
+
* `file` is RELATIVE to the root. `kind` ∈
|
|
285
|
+
* {'dangerous-invisible','orphan-variation-selector','emoji','mixed-script'}.
|
|
286
|
+
*/
|
|
287
|
+
export function collectUnicodeViolations(rootOrFiles) {
|
|
288
|
+
const root = rootOrFiles;
|
|
289
|
+
const files = getTrackedFiles(root);
|
|
290
|
+
|
|
291
|
+
// Exclude this validator's own source + test file (the dangerous-invisible
|
|
292
|
+
// doc-comments and the test fixtures would otherwise self-trip the scanner).
|
|
293
|
+
const SELF_EXCLUSIONS = new Set([
|
|
294
|
+
'scripts/lib/validate/check-unicode-safety.mjs',
|
|
295
|
+
'tests/lib/validate/check-unicode-safety.test.mjs',
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
/** @type {Array<{file: string, line: number, column: number, kind: string, codePoint: string}>} */
|
|
299
|
+
const violations = [];
|
|
300
|
+
|
|
301
|
+
for (const filePath of files) {
|
|
302
|
+
const rel = relative(root, filePath).split(sep).join('/');
|
|
303
|
+
if (SELF_EXCLUSIONS.has(rel)) continue;
|
|
304
|
+
|
|
305
|
+
let text;
|
|
306
|
+
try {
|
|
307
|
+
text = readFileSync(filePath, 'utf8');
|
|
308
|
+
} catch {
|
|
309
|
+
continue; // unreadable — skip
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const ext = extname(filePath);
|
|
313
|
+
const fmLen = ext === '.md' ? frontmatterLength(text) : 0;
|
|
314
|
+
const fileIsStrict = STRICT_EXTS.has(ext);
|
|
315
|
+
|
|
316
|
+
// STRICT region of a char index: whole file when extension is strict, OR
|
|
317
|
+
// the frontmatter prefix of a .md file.
|
|
318
|
+
const indexIsStrict = (idx) => fileIsStrict || (fmLen > 0 && idx < fmLen);
|
|
319
|
+
|
|
320
|
+
// Walk code points once for invisibles / variation selectors / emoji.
|
|
321
|
+
let index = 0;
|
|
322
|
+
let prevChar = '';
|
|
323
|
+
for (const char of text) {
|
|
324
|
+
const cp = char.codePointAt(0);
|
|
325
|
+
|
|
326
|
+
if (isDangerousInvisibleCodePoint(cp)) {
|
|
327
|
+
const { line, column } = lineAndColumn(text, index);
|
|
328
|
+
violations.push({
|
|
329
|
+
file: rel,
|
|
330
|
+
line,
|
|
331
|
+
column,
|
|
332
|
+
kind: 'dangerous-invisible',
|
|
333
|
+
codePoint: `U+${cp.toString(16).toUpperCase()}`,
|
|
334
|
+
});
|
|
335
|
+
} else if (isOrphanVariationSelector(cp, prevChar)) {
|
|
336
|
+
const { line, column } = lineAndColumn(text, index);
|
|
337
|
+
violations.push({
|
|
338
|
+
file: rel,
|
|
339
|
+
line,
|
|
340
|
+
column,
|
|
341
|
+
kind: 'orphan-variation-selector',
|
|
342
|
+
codePoint: `U+${cp.toString(16).toUpperCase()}`,
|
|
343
|
+
});
|
|
344
|
+
} else if (EMOJI_RE.test(char) && !isAllowedEmojiSymbol(char) && indexIsStrict(index)) {
|
|
345
|
+
// Non-curated emoji in a STRICT context (code file / .json / .yml /
|
|
346
|
+
// .md frontmatter). In LENIENT prose, non-curated emoji are tolerated;
|
|
347
|
+
// the curated deliberate set (isAllowedEmojiSymbol) passes everywhere.
|
|
348
|
+
const { line, column } = lineAndColumn(text, index);
|
|
349
|
+
violations.push({
|
|
350
|
+
file: rel,
|
|
351
|
+
line,
|
|
352
|
+
column,
|
|
353
|
+
kind: 'emoji',
|
|
354
|
+
codePoint: `U+${cp.toString(16).toUpperCase()}`,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
prevChar = char;
|
|
359
|
+
index += char.length;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Mixed-script homoglyph detection — STRICT context only, conservative.
|
|
363
|
+
if (fileIsStrict || fmLen > 0) {
|
|
364
|
+
// Scan only the strict region. For strict-ext files that is the whole
|
|
365
|
+
// file; for .md it is the frontmatter prefix.
|
|
366
|
+
const region = fileIsStrict ? text : text.slice(0, fmLen);
|
|
367
|
+
IDENT_TOKEN_RE.lastIndex = 0;
|
|
368
|
+
let m;
|
|
369
|
+
while ((m = IDENT_TOKEN_RE.exec(region)) !== null) {
|
|
370
|
+
const token = m[0];
|
|
371
|
+
const hasLatin = SCRIPT_LATIN.test(token);
|
|
372
|
+
const hasCyrillic = SCRIPT_CYRILLIC.test(token);
|
|
373
|
+
// Greek is excluded by design (#626) — Latin+Greek is NOT a homoglyph
|
|
374
|
+
// attack and legit Greek-letter identifiers must not be swept.
|
|
375
|
+
if (hasLatin && hasCyrillic) {
|
|
376
|
+
const { line, column } = lineAndColumn(text, m.index);
|
|
377
|
+
// Report the code point of the first Cyrillic char in the token for
|
|
378
|
+
// a precise pointer.
|
|
379
|
+
let offending = token.codePointAt(0);
|
|
380
|
+
for (const c of token) {
|
|
381
|
+
const ccp = c.codePointAt(0);
|
|
382
|
+
if (SCRIPT_CYRILLIC.test(c)) {
|
|
383
|
+
offending = ccp;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
violations.push({
|
|
388
|
+
file: rel,
|
|
389
|
+
line,
|
|
390
|
+
column,
|
|
391
|
+
kind: 'mixed-script',
|
|
392
|
+
codePoint: `U+${offending.toString(16).toUpperCase()}`,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return violations;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// --write: strip ONLY the unambiguous dangerous-invisible chars from writable
|
|
404
|
+
// files. (We deliberately do NOT port ecc's markdown-whitespace-collapse — it
|
|
405
|
+
// is out of scope and would churn unrelated formatting.)
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Strip every dangerous-invisible code point (and orphan variation selector)
|
|
410
|
+
* from `text`. Curated emoji and legit emoji-presentation selectors are kept.
|
|
411
|
+
* @param {string} text
|
|
412
|
+
* @returns {string}
|
|
413
|
+
*/
|
|
414
|
+
export function stripDangerousInvisibles(text) {
|
|
415
|
+
let out = '';
|
|
416
|
+
let prevChar = '';
|
|
417
|
+
for (const char of text) {
|
|
418
|
+
const cp = char.codePointAt(0);
|
|
419
|
+
if (isDangerousInvisibleCodePoint(cp)) {
|
|
420
|
+
// drop
|
|
421
|
+
} else if (isOrphanVariationSelector(cp, prevChar)) {
|
|
422
|
+
// drop orphan VS
|
|
423
|
+
} else {
|
|
424
|
+
out += char;
|
|
425
|
+
}
|
|
426
|
+
prevChar = char;
|
|
427
|
+
}
|
|
428
|
+
return out;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
// Runner.
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Run the validator against a plugin root. Prints PASS/FAIL lines and a Results
|
|
437
|
+
* summary (unless `json` is set, in which case the violation array is emitted as
|
|
438
|
+
* JSON to stdout). Returns the process exit code: 0 clean, 1 violations, 2 tool
|
|
439
|
+
* error.
|
|
440
|
+
*
|
|
441
|
+
* @param {string} pluginRoot
|
|
442
|
+
* @param {{write?: boolean, json?: boolean}} [opts]
|
|
443
|
+
* @returns {number}
|
|
444
|
+
*/
|
|
445
|
+
export function runCheckUnicodeSafety(pluginRoot, opts = {}) {
|
|
446
|
+
const { write = false, json = false } = opts;
|
|
447
|
+
|
|
448
|
+
if (!json) {
|
|
449
|
+
console.log('--- Check: Unicode safety (invisible / bidi / tag-block / homoglyph) ---');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Tool-error gate: root must exist and be a directory.
|
|
453
|
+
try {
|
|
454
|
+
if (!pluginRoot || !existsSync(pluginRoot) || !statSync(pluginRoot).isDirectory()) {
|
|
455
|
+
console.error(` tool-error: plugin root not found or not a directory: ${pluginRoot}`);
|
|
456
|
+
return 2;
|
|
457
|
+
}
|
|
458
|
+
} catch (/** @type {unknown} */ e) {
|
|
459
|
+
console.error(` tool-error: cannot stat plugin root: ${(/** @type {Error} */ (e)).message}`);
|
|
460
|
+
return 2;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Optional --write: strip dangerous-invisibles from writable files first, so
|
|
464
|
+
// the subsequent scan reflects the cleaned tree.
|
|
465
|
+
if (write) {
|
|
466
|
+
for (const filePath of getTrackedFiles(pluginRoot)) {
|
|
467
|
+
const ext = extname(filePath);
|
|
468
|
+
if (!WRITABLE_EXTS.has(ext)) continue;
|
|
469
|
+
const rel = relative(pluginRoot, filePath).split(sep).join('/');
|
|
470
|
+
if (rel === 'scripts/lib/validate/check-unicode-safety.mjs') continue;
|
|
471
|
+
if (rel === 'tests/lib/validate/check-unicode-safety.test.mjs') continue;
|
|
472
|
+
let text;
|
|
473
|
+
try {
|
|
474
|
+
text = readFileSync(filePath, 'utf8');
|
|
475
|
+
} catch {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const stripped = stripDangerousInvisibles(text);
|
|
479
|
+
if (stripped !== text) {
|
|
480
|
+
try {
|
|
481
|
+
writeFileSync(filePath, stripped, 'utf8');
|
|
482
|
+
if (!json) console.log(` stripped dangerous-invisibles from ${rel}`);
|
|
483
|
+
} catch {
|
|
484
|
+
// ignore write failure — the scan below will still report the leak
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const violations = collectUnicodeViolations(pluginRoot);
|
|
491
|
+
|
|
492
|
+
if (json) {
|
|
493
|
+
process.stdout.write(JSON.stringify(violations) + '\n');
|
|
494
|
+
return violations.length > 0 ? 1 : 0;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let passed = 0;
|
|
498
|
+
let failed = 0;
|
|
499
|
+
const pass = (msg) => { console.log(` PASS: ${msg}`); passed++; };
|
|
500
|
+
const fail = (msg) => { console.log(` FAIL: ${msg}`); failed++; };
|
|
501
|
+
|
|
502
|
+
if (violations.length === 0) {
|
|
503
|
+
pass('no dangerous / invisible Unicode or homoglyphs found');
|
|
504
|
+
} else {
|
|
505
|
+
for (const v of violations) {
|
|
506
|
+
fail(`${v.file}:${v.line}:${v.column} — ${v.kind} ${v.codePoint}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
console.log('');
|
|
511
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
512
|
+
return failed > 0 ? 1 : 0;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// CLI entry — only when executed directly, never on import (keeps the exports
|
|
516
|
+
// safe to import from tests without triggering process.exit).
|
|
517
|
+
const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
|
|
518
|
+
if (isMain) {
|
|
519
|
+
const args = process.argv.slice(2);
|
|
520
|
+
const flags = new Set(args.filter((a) => a.startsWith('--')));
|
|
521
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
522
|
+
const pluginRoot = positional[0];
|
|
523
|
+
if (!pluginRoot) {
|
|
524
|
+
console.error('Usage: check-unicode-safety.mjs <plugin-root> [--write] [--json]');
|
|
525
|
+
process.exit(2);
|
|
526
|
+
}
|
|
527
|
+
process.exit(
|
|
528
|
+
runCheckUnicodeSafety(pluginRoot, {
|
|
529
|
+
write: flags.has('--write'),
|
|
530
|
+
json: flags.has('--json'),
|
|
531
|
+
}),
|
|
532
|
+
);
|
|
533
|
+
}
|