session-orchestrator 3.24.0 → 4.0.1
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/.agents/skills/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.codex-plugin/skills/architecture/SKILL.md +20 -0
- package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
- package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
- package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
- package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
- package/.codex-plugin/skills/close/SKILL.md +21 -0
- package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
- package/.codex-plugin/skills/debug/SKILL.md +21 -0
- package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/discovery/SKILL.md +21 -0
- package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
- package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
- package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
- package/.codex-plugin/skills/eli5/SKILL.md +21 -0
- package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/eval/SKILL.md +21 -0
- package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/evolve/SKILL.md +21 -0
- package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
- package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
- package/.codex-plugin/skills/go/SKILL.md +22 -0
- package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/grill/SKILL.md +21 -0
- package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
- package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
- package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
- package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
- package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/plan/SKILL.md +22 -0
- package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
- package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
- package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
- package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
- package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/release/SKILL.md +22 -0
- package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
- package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session/SKILL.md +21 -0
- package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session-end/SKILL.md +22 -0
- package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
- package/.codex-plugin/skills/session-start/SKILL.md +22 -0
- package/.codex-plugin/skills/spinout/SKILL.md +21 -0
- package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
- package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
- package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test/SKILL.md +21 -0
- package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
- package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
- package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.cursor-plugin/plugin.json +30 -0
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1314 -2
- package/NOTICE +11 -6
- package/README.md +135 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +107 -29
- package/docs/components.md +38 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +69 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +365 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1488 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +60 -14
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-codex-skills.mjs +246 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +292 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +88 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/host-paths.mjs +20 -4
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +30 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +41 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +215 -15
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +414 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +51 -11
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +64 -3
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +30 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +202 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-codex-skills.mjs +191 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
- package/scripts/lib/validate/check-skill-links.mjs +193 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
- package/scripts/lib/validate/check-unwired-features.mjs +163 -15
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/confidential-names.mjs +95 -30
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/repo-files.mjs +48 -14
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +430 -31
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +344 -8
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +164 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/SKILL.md +10 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* defaults from the policy file or built-in defaults apply.
|
|
15
15
|
* --files <f1,f2,...> Comma-separated file list (incremental + per-file).
|
|
16
16
|
* --session-start-ref <r> Git ref for diff base (incremental, to find changed files).
|
|
17
|
+
* --ledger-root <path> Repo root the telemetry event is pinned to (and the
|
|
18
|
+
* root session attribution is read from). Only the
|
|
19
|
+
* pre-push hook passes it; see the emission block below.
|
|
17
20
|
* -h, --help Show this help and exit.
|
|
18
21
|
*
|
|
19
22
|
* Exit codes:
|
|
@@ -31,8 +34,8 @@
|
|
|
31
34
|
* scripts/lib/gates/gate-{baseline,incremental,full,per-file}.mjs
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
35
|
-
import { join, dirname } from 'node:path';
|
|
37
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
38
|
+
import { join, dirname, resolve } from 'node:path';
|
|
36
39
|
import { fileURLToPath } from 'node:url';
|
|
37
40
|
import { spawnSync } from 'node:child_process';
|
|
38
41
|
|
|
@@ -80,7 +83,7 @@ const argv = process.argv.slice(2);
|
|
|
80
83
|
if (argv.includes('-h') || argv.includes('--help')) {
|
|
81
84
|
process.stdout.write(
|
|
82
85
|
'Usage: run-quality-gate.mjs --variant <variant> [--config <json-or-file>] ' +
|
|
83
|
-
'[--files <file1,file2,...>] [--session-start-ref <ref>]\n\n' +
|
|
86
|
+
'[--files <file1,file2,...>] [--session-start-ref <ref>] [--ledger-root <path>]\n\n' +
|
|
84
87
|
'Variants: baseline, incremental, full-gate, per-file\n\n' +
|
|
85
88
|
'Exit codes:\n' +
|
|
86
89
|
' 0 — pass (non-blocking variants always exit 0)\n' +
|
|
@@ -94,6 +97,7 @@ let variant = '';
|
|
|
94
97
|
let config = '';
|
|
95
98
|
let files = '';
|
|
96
99
|
let sessionStartRef = '';
|
|
100
|
+
let ledgerRootArg = '';
|
|
97
101
|
|
|
98
102
|
for (let i = 0; i < argv.length; i++) {
|
|
99
103
|
const arg = argv[i];
|
|
@@ -114,6 +118,10 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
114
118
|
if (i + 1 >= argv.length) die('Missing value for --session-start-ref');
|
|
115
119
|
sessionStartRef = argv[++i];
|
|
116
120
|
break;
|
|
121
|
+
case '--ledger-root':
|
|
122
|
+
if (i + 1 >= argv.length) die('Missing value for --ledger-root');
|
|
123
|
+
ledgerRootArg = argv[++i];
|
|
124
|
+
break;
|
|
117
125
|
default:
|
|
118
126
|
die(`Unknown argument: ${arg}`);
|
|
119
127
|
}
|
|
@@ -208,6 +216,76 @@ function suiteCountsFromGateStdout(stdout) {
|
|
|
208
216
|
return admitSuiteCounts(test);
|
|
209
217
|
}
|
|
210
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Lift `test.failed_files` out of a gate sub-script's JSON stdout envelope.
|
|
221
|
+
*
|
|
222
|
+
* Envelope adapter, same posture as {@link suiteCountsFromGateStdout}: it
|
|
223
|
+
* decides only whether a NAMED-FILE measurement exists, never what the names
|
|
224
|
+
* mean. `null` — never `[]` — for every non-measurement, so the caller OMITS
|
|
225
|
+
* the key instead of publishing an empty array that reads as "no file failed".
|
|
226
|
+
*
|
|
227
|
+
* Only `gate-full.mjs` publishes the key, and only when the runner printed a
|
|
228
|
+
* file-level summary; every other variant emits a bare status string.
|
|
229
|
+
*
|
|
230
|
+
* Never throws.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} stdout — the gate sub-script's captured stdout.
|
|
233
|
+
* @returns {string[]|null}
|
|
234
|
+
*/
|
|
235
|
+
function failedFilesFromGateStdout(stdout) {
|
|
236
|
+
if (typeof stdout !== 'string' || !stdout.trim()) return null;
|
|
237
|
+
let parsed;
|
|
238
|
+
try {
|
|
239
|
+
parsed = JSON.parse(stdout);
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const files = parsed?.test?.failed_files;
|
|
244
|
+
if (!Array.isArray(files) || files.length === 0) return null;
|
|
245
|
+
const named = files.filter((f) => typeof f === 'string' && f.trim());
|
|
246
|
+
return named.length > 0 ? named : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate and resolve the `--ledger-root` flag (see the telemetry block below).
|
|
251
|
+
*
|
|
252
|
+
* Only the pre-push hook passes this, and it hands over a path the gate then
|
|
253
|
+
* WRITES to — so a typo must not silently create an `.orchestrator/metrics/`
|
|
254
|
+
* tree somewhere arbitrary. The check is therefore two-part: the value must be
|
|
255
|
+
* an existing DIRECTORY, and it must already contain an `.orchestrator/`
|
|
256
|
+
* directory — the marker of a root this harness has already been initialised in.
|
|
257
|
+
*
|
|
258
|
+
* The alternative check (`git rev-parse --show-toplevel` with cwd = that path,
|
|
259
|
+
* compared against the value) is rejected on cost: it spawns a process on every
|
|
260
|
+
* gate run to prove a property two `statSync` calls already prove. The one case
|
|
261
|
+
* it accepts and this one rejects — a git root that has never run the
|
|
262
|
+
* orchestrator — is precisely the case with no ledger to pin to.
|
|
263
|
+
*
|
|
264
|
+
* A bad value NEVER crashes the gate: it warns once on stderr and returns
|
|
265
|
+
* `null`, which restores the previous resolution
|
|
266
|
+
* (`CLAUDE_PROJECT_DIR ?? CODEX_PROJECT_DIR ?? repoRoot`) at the call sites.
|
|
267
|
+
* The gate's exit code is the authoritative output; telemetry is best-effort.
|
|
268
|
+
*
|
|
269
|
+
* @param {string} value — raw flag value (`''` when the flag was not passed).
|
|
270
|
+
* @returns {string|null} absolute, validated root — or `null` to fall back.
|
|
271
|
+
*/
|
|
272
|
+
function resolveLedgerRoot(value) {
|
|
273
|
+
const raw = (value || '').trim();
|
|
274
|
+
if (!raw) return null;
|
|
275
|
+
const abs = resolve(raw);
|
|
276
|
+
try {
|
|
277
|
+
if (statSync(abs).isDirectory() && statSync(join(abs, '.orchestrator')).isDirectory()) {
|
|
278
|
+
return abs;
|
|
279
|
+
}
|
|
280
|
+
} catch { /* falls through to the warn below */ }
|
|
281
|
+
warn(
|
|
282
|
+
`--ledger-root '${raw}' is not an initialised project root ` +
|
|
283
|
+
'(existing directory containing .orchestrator/) — falling back to the default ' +
|
|
284
|
+
'telemetry destination.',
|
|
285
|
+
);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
211
289
|
/**
|
|
212
290
|
* Resolve the active wave number from the wave-scope sidecar (#966 step 1).
|
|
213
291
|
*
|
|
@@ -325,24 +403,63 @@ if (result.error && typeof result.status !== 'number') {
|
|
|
325
403
|
// (single emission path). `sessionAttribution` is the shared helper in
|
|
326
404
|
// events.mjs (#941); this CLI wrapper runs against the CWD `repoRoot`, so the
|
|
327
405
|
// bare emitEvent destination (SO_PROJECT_DIR default) is correct here.
|
|
406
|
+
//
|
|
407
|
+
// EXCEPT under the pre-push hook, which is the one caller that runs the gate in
|
|
408
|
+
// a tree that is about to be DELETED. `.husky/pre-push` materialises the tracked
|
|
409
|
+
// tree into a temp dir and deliberately scrubs every `*PROJECT_DIR` name before
|
|
410
|
+
// invoking the gate there, so `getProjectDir()` resolves to that temp tree (it
|
|
411
|
+
// carries both a CLAUDE.md (or AGENTS.md) and a .git) and the record lands in
|
|
412
|
+
// `<tmp>/.orchestrator/metrics/events.jsonl`, which the hook's EXIT trap then
|
|
413
|
+
// removes. Measured 2026-09-06: a pre-push run that BLOCKED a push left no
|
|
414
|
+
// `orchestrator.quality_gate.failed` line in this repo's ledger at all — the
|
|
415
|
+
// gate failure was, by construction, the one event that could never be recorded.
|
|
416
|
+
//
|
|
417
|
+
// `--ledger-root` is that hook's channel for handing back the root it already
|
|
418
|
+
// knows (`git rev-parse --show-toplevel`, read BEFORE it cds). It pins ONLY the
|
|
419
|
+
// telemetry destination and the attribution root — every other path the gate
|
|
420
|
+
// resolves stays inside the tree actually under test, which is the whole point
|
|
421
|
+
// of the materialisation. Absent (every other caller) → unchanged behaviour:
|
|
422
|
+
// `emitEvent`'s own default resolution.
|
|
423
|
+
//
|
|
424
|
+
// It is an ARGV FLAG and not an env var, and that is load-bearing. Measured
|
|
425
|
+
// 2026-09-06 with the env-var form: `SO_GATE_LEDGER_ROOT=$tmp npx vitest run
|
|
426
|
+
// tests/scripts/run-quality-gate.test.mjs -t "telemetry emission"` → `8 failed |
|
|
427
|
+
// 1 passed`. The chain was: hook exports the var → `npm run quality-gate` →
|
|
428
|
+
// `gate-full.mjs` spawns `npm test` → every vitest worker inherits it → the
|
|
429
|
+
// suite's own gate spawns spread `...process.env`, so the pinned root outranked
|
|
430
|
+
// their per-test project dir and the gate's telemetry tests wrote to the hook's
|
|
431
|
+
// root. The gate that releases 4.0.0 would have blocked on itself. An env var is
|
|
432
|
+
// inherited by every descendant; a flag reaches exactly one process.
|
|
433
|
+
//
|
|
328
434
|
// Best-effort: a telemetry failure must NEVER alter the gate's authoritative
|
|
329
435
|
// exit code — which is why the counts parse also lives inside this try.
|
|
330
436
|
const exitCode = result.status ?? 1;
|
|
437
|
+
const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
|
|
331
438
|
try {
|
|
332
439
|
const counts = suiteCountsFromGateStdout(gateStdout);
|
|
440
|
+
// The names behind `counts.failed`. Absent, never `[]` — see
|
|
441
|
+
// `failedFilesFromGateStdout`.
|
|
442
|
+
const failedFiles = failedFilesFromGateStdout(gateStdout);
|
|
333
443
|
// Wave-scope sidecar is read from the SAME project dir the event lands in
|
|
334
444
|
// (emitEvent's own destination precedence), so a tmp-scoped run cannot pick
|
|
335
445
|
// up the host repo's live wave. Mirrors the hook's projectDir resolution.
|
|
336
446
|
const waveNumber = resolveWaveNumber(
|
|
337
|
-
process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot,
|
|
447
|
+
ledgerRoot ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot,
|
|
448
|
+
);
|
|
449
|
+
await emitEvent(
|
|
450
|
+
`orchestrator.quality_gate.${exitCode === 0 ? 'passed' : 'failed'}`,
|
|
451
|
+
{
|
|
452
|
+
variant,
|
|
453
|
+
exit_code: exitCode,
|
|
454
|
+
...(counts ? { counts } : {}),
|
|
455
|
+
...(failedFiles ? { failed_files: failedFiles } : {}),
|
|
456
|
+
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
457
|
+
...sessionAttribution(ledgerRoot ?? repoRoot),
|
|
458
|
+
},
|
|
459
|
+
// `{}` is byte-identical to omitting the argument (`opts.repoRoot ??
|
|
460
|
+
// getProjectDir()`), so the default path is untouched.
|
|
461
|
+
ledgerRoot ? { repoRoot: ledgerRoot } : {},
|
|
338
462
|
);
|
|
339
|
-
await emitEvent(`orchestrator.quality_gate.${exitCode === 0 ? 'passed' : 'failed'}`, {
|
|
340
|
-
variant,
|
|
341
|
-
exit_code: exitCode,
|
|
342
|
-
...(counts ? { counts } : {}),
|
|
343
|
-
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
344
|
-
...sessionAttribution(repoRoot),
|
|
345
|
-
});
|
|
346
463
|
} catch { /* best-effort telemetry — gate result is authoritative */ }
|
|
347
464
|
|
|
348
465
|
process.exit(exitCode);
|
package/scripts/site-numbers.mjs
CHANGED
|
@@ -66,6 +66,31 @@
|
|
|
66
66
|
* --write rewrite the span contents in place, and refresh `site/_census.json`
|
|
67
67
|
* from the same measurement (only when --site is the repo's `site/`).
|
|
68
68
|
*
|
|
69
|
+
* ## The two usage metrics (`npm-downloads-30d`, `github-stars`)
|
|
70
|
+
*
|
|
71
|
+
* These two are the only ones whose source is NOT this repository: they are
|
|
72
|
+
* fetched over the network, and only under `--write` (5 s timeout). `--check`
|
|
73
|
+
* never opens a socket — it compares the page against `site/_census.json`, which
|
|
74
|
+
* IS their truth between writes. That is what keeps the CI/build guard offline
|
|
75
|
+
* and deterministic; a `--check` that fetched would go red whenever npm's API
|
|
76
|
+
* was slow, which is a signal about npm and not about the page.
|
|
77
|
+
*
|
|
78
|
+
* On a fetch failure `--write` keeps the previous snapshot value and warns. It
|
|
79
|
+
* never writes a placeholder over a real number: the number on the page stays
|
|
80
|
+
* the last one that was actually measured, and the WARN names why it did not
|
|
81
|
+
* move. With no snapshot to keep, the metric simply has no value and the run
|
|
82
|
+
* fails loudly (the existing partial-census guard) rather than shipping "n/a".
|
|
83
|
+
*
|
|
84
|
+
* ## The census blocks in `site/llms-full.txt` and `site/llms.txt`
|
|
85
|
+
*
|
|
86
|
+
* The same measurement also fills a marker-bounded line in every file listed in
|
|
87
|
+
* `CENSUS_BLOCK_FILES` (`<!-- census:start -->` … `<!-- census:end -->`) — the
|
|
88
|
+
* two plain-text surfaces LLM crawlers read. Only the text BETWEEN the
|
|
89
|
+
* markers is rewritten; everything else in that file is hand-authored prose and
|
|
90
|
+
* is handed back byte-for-byte. A missing end marker is a hard error, never a
|
|
91
|
+
* silent skip — a generator that quietly stops filling a surface is the exact
|
|
92
|
+
* failure this file exists to end.
|
|
93
|
+
*
|
|
69
94
|
* Exit codes (`.claude/rules/cli-design.md`):
|
|
70
95
|
* 0 — no drift (--check) / files updated or already current (--write)
|
|
71
96
|
* 1 — drift found (--check), or a contract violation in either mode
|
|
@@ -327,7 +352,7 @@ export function isDirty(root) {
|
|
|
327
352
|
['--no-optional-locks', 'status', '--porcelain', '--untracked-files=no'],
|
|
328
353
|
{ cwd: root, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] },
|
|
329
354
|
);
|
|
330
|
-
return out.split('\n').
|
|
355
|
+
return out.split('\n').some(Boolean);
|
|
331
356
|
} catch {
|
|
332
357
|
return null;
|
|
333
358
|
}
|
|
@@ -458,6 +483,37 @@ export const METRIC_DEFS = Object.freeze([
|
|
|
458
483
|
source: 'jq length .orchestrator/policy/blocked-commands.json',
|
|
459
484
|
compute: (root) => fmtCount(countBlockedCommands(root)),
|
|
460
485
|
},
|
|
486
|
+
{
|
|
487
|
+
id: 'npm-downloads-30d',
|
|
488
|
+
provenance: false,
|
|
489
|
+
// Network-sourced: absent from EVERY offline run, which is most of them
|
|
490
|
+
// (--check never fetches). The snapshot is therefore not a fresh-clone
|
|
491
|
+
// convenience here but the metric's normal answer between two --write runs.
|
|
492
|
+
snapshotFallback: true,
|
|
493
|
+
network: true,
|
|
494
|
+
url: 'https://api.npmjs.org/downloads/point/last-month/session-orchestrator',
|
|
495
|
+
field: 'downloads',
|
|
496
|
+
source:
|
|
497
|
+
'curl -s https://api.npmjs.org/downloads/point/last-month/session-orchestrator | jq .downloads (fetched only under --write)',
|
|
498
|
+
compute: (root, ctx) => fmtCount(ctx?.usage?.['npm-downloads-30d'] ?? null),
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
id: 'github-stars',
|
|
502
|
+
provenance: false,
|
|
503
|
+
snapshotFallback: true,
|
|
504
|
+
network: true,
|
|
505
|
+
url: 'https://api.github.com/repos/Kanevry/session-orchestrator',
|
|
506
|
+
field: 'stargazers_count',
|
|
507
|
+
// The API answers unauthenticated requests but rejects ones without a
|
|
508
|
+
// User-Agent, and the Accept header pins the response schema version.
|
|
509
|
+
headers: {
|
|
510
|
+
Accept: 'application/vnd.github+json',
|
|
511
|
+
'User-Agent': 'session-orchestrator-site-numbers',
|
|
512
|
+
},
|
|
513
|
+
source:
|
|
514
|
+
'curl -s https://api.github.com/repos/Kanevry/session-orchestrator | jq .stargazers_count (fetched only under --write)',
|
|
515
|
+
compute: (root, ctx) => fmtCount(ctx?.usage?.['github-stars'] ?? null),
|
|
516
|
+
},
|
|
461
517
|
{
|
|
462
518
|
id: 'counted-at',
|
|
463
519
|
provenance: true,
|
|
@@ -606,7 +662,7 @@ export function writeCensusSnapshot(root, values) {
|
|
|
606
662
|
* `fromSnapshot` lists the metrics answered by `site/_census.json` because
|
|
607
663
|
* their live source was absent.
|
|
608
664
|
*/
|
|
609
|
-
export function collect(root) {
|
|
665
|
+
export function collect(root, ctx = {}) {
|
|
610
666
|
const values = {};
|
|
611
667
|
const missing = [];
|
|
612
668
|
const warnings = [];
|
|
@@ -619,8 +675,8 @@ export function collect(root) {
|
|
|
619
675
|
// LIVE FIRST, always. The snapshot is a fallback, never a cache: reading it
|
|
620
676
|
// first (or memoising a live value into it) would freeze the page on the
|
|
621
677
|
// last release's numbers while the repository moved on.
|
|
622
|
-
let v = def.compute(root);
|
|
623
|
-
if ((v === null || v === undefined || v === '') && def.snapshotFallback
|
|
678
|
+
let v = def.compute(root, ctx);
|
|
679
|
+
if ((v === null || v === undefined || v === '') && def.snapshotFallback) {
|
|
624
680
|
if (snapshot === undefined) snapshot = readCensusSnapshot(root);
|
|
625
681
|
const s = snapshot?.[def.id];
|
|
626
682
|
if (s !== undefined) {
|
|
@@ -632,9 +688,17 @@ export function collect(root) {
|
|
|
632
688
|
else values[def.id] = String(v);
|
|
633
689
|
}
|
|
634
690
|
|
|
635
|
-
|
|
691
|
+
// Only the REPOSITORY-sourced fallbacks are worth a warning. For the two
|
|
692
|
+
// network metrics the snapshot is not a degraded answer but the designed one
|
|
693
|
+
// between two `--write` runs — warning about them would fire on every single
|
|
694
|
+
// `--check`, and a warning that always fires is a broken instrument
|
|
695
|
+
// (`.claude/rules/host-resources.md` HR-101).
|
|
696
|
+
const localFallbacks = fromSnapshot.filter(
|
|
697
|
+
(id) => !METRIC_DEFS.find((d) => d.id === id)?.network,
|
|
698
|
+
);
|
|
699
|
+
if (localFallbacks.length > 0) {
|
|
636
700
|
warnings.push(
|
|
637
|
-
`${
|
|
701
|
+
`${localFallbacks.join(', ')} read from ${CENSUS_FILE.join('/')} — the live source is absent under ${root} ` +
|
|
638
702
|
'(expected in a fresh clone / tarball build; the snapshot is only as current as the last --write)',
|
|
639
703
|
);
|
|
640
704
|
}
|
|
@@ -662,6 +726,215 @@ export function collect(root) {
|
|
|
662
726
|
return { values, missing, warnings, fromSnapshot };
|
|
663
727
|
}
|
|
664
728
|
|
|
729
|
+
// ---------------------------------------------------------------------------
|
|
730
|
+
// The two network metrics
|
|
731
|
+
// ---------------------------------------------------------------------------
|
|
732
|
+
|
|
733
|
+
/** Metrics whose source is an HTTP endpoint rather than this repository. */
|
|
734
|
+
export const NETWORK_METRIC_DEFS = Object.freeze(METRIC_DEFS.filter((d) => d.network));
|
|
735
|
+
|
|
736
|
+
/** Hard ceiling on a single usage fetch. Revisit if a source starts answering slower. */
|
|
737
|
+
export const FETCH_TIMEOUT_MS = 5000;
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Fetch the usage metrics. CALLED ONLY UNDER `--write` — see the header for why
|
|
741
|
+
* `--check` must stay offline.
|
|
742
|
+
*
|
|
743
|
+
* Every failure mode (timeout, non-2xx, unparseable body, missing/non-numeric
|
|
744
|
+
* field) lands in the same place: a WARN naming the reason and the snapshot
|
|
745
|
+
* value that will be kept, and NO entry in the returned map. `collect()` then
|
|
746
|
+
* falls back to the snapshot exactly as it does for a fresh clone's ledgers —
|
|
747
|
+
* so a failed fetch degrades to "the last measured number", never to a
|
|
748
|
+
* placeholder written over a real one.
|
|
749
|
+
*
|
|
750
|
+
* @param {string} root repo root (its `site/_census.json` supplies the WARN's value)
|
|
751
|
+
* @param {{fetchImpl?: Function, timeoutMs?: number, warn?: (s:string)=>void}} [opts]
|
|
752
|
+
* @returns {Promise<Record<string, number>>} only the metrics that actually resolved
|
|
753
|
+
*/
|
|
754
|
+
export async function fetchUsageMetrics(root, opts = {}) {
|
|
755
|
+
// Offline switch (tests, air-gapped CI): SO_SITE_NUMBERS_OFFLINE=1 skips every
|
|
756
|
+
// network call and lets the snapshot answer, exactly like a failed fetch would,
|
|
757
|
+
// minus the per-metric WARN noise. Measured 2026-09-07: two fixture tests that
|
|
758
|
+
// spawned --write went red on a GitHub 403 rate limit; a test must never depend
|
|
759
|
+
// on api.github.com being reachable.
|
|
760
|
+
if (process.env.SO_SITE_NUMBERS_OFFLINE === '1') {
|
|
761
|
+
(opts.warn ?? ((m) => process.stderr.write(`${m}\n`)))(
|
|
762
|
+
'site-numbers: SO_SITE_NUMBERS_OFFLINE=1 — usage metrics answered from the snapshot',
|
|
763
|
+
);
|
|
764
|
+
return {};
|
|
765
|
+
}
|
|
766
|
+
const doFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
767
|
+
const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
768
|
+
const warn = opts.warn ?? ((s) => process.stderr.write(`${s}\n`));
|
|
769
|
+
const out = {};
|
|
770
|
+
let snapshot;
|
|
771
|
+
|
|
772
|
+
for (const def of NETWORK_METRIC_DEFS) {
|
|
773
|
+
try {
|
|
774
|
+
const res = await doFetch(def.url, {
|
|
775
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
776
|
+
headers: def.headers ?? {},
|
|
777
|
+
});
|
|
778
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
779
|
+
const body = await res.json();
|
|
780
|
+
const v = body?.[def.field];
|
|
781
|
+
if (typeof v !== 'number' || !Number.isFinite(v)) {
|
|
782
|
+
throw new Error(`no numeric "${def.field}" field in the response`);
|
|
783
|
+
}
|
|
784
|
+
out[def.id] = Math.trunc(v);
|
|
785
|
+
} catch (err) {
|
|
786
|
+
if (snapshot === undefined) snapshot = readCensusSnapshot(root);
|
|
787
|
+
const kept = snapshot?.[def.id];
|
|
788
|
+
warn(
|
|
789
|
+
`WARN site-numbers: ${def.id} fetch failed (${err?.message ?? String(err)}), keeping snapshot ` +
|
|
790
|
+
`${kept ?? '(none — this metric will have no value and the run will fail)'}`,
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return out;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// ---------------------------------------------------------------------------
|
|
798
|
+
// The marker-bounded census line in site/llms-full.txt
|
|
799
|
+
// ---------------------------------------------------------------------------
|
|
800
|
+
|
|
801
|
+
/** Files the census line lives in, relative to the SITE directory. */
|
|
802
|
+
export const LLMS_FULL_FILE = 'llms-full.txt';
|
|
803
|
+
export const LLMS_FILE = 'llms.txt';
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Every file carrying a marker-bounded census block.
|
|
807
|
+
*
|
|
808
|
+
* A CONSTANT LIST rather than a second code path per file: `site/llms.txt` used
|
|
809
|
+
* to hand-maintain its own "## Surface" line, and it drifted exactly as the page
|
|
810
|
+
* had — measured 2026-09-07 it claimed `661 vitest test files` while every other
|
|
811
|
+
* surface (and the repository) said 662, and `--check` exited 0 because the
|
|
812
|
+
* generator did not know that file existed. Adding a file to this array is the
|
|
813
|
+
* whole change needed to bring it under the guard.
|
|
814
|
+
*/
|
|
815
|
+
export const CENSUS_BLOCK_FILES = Object.freeze([LLMS_FULL_FILE, LLMS_FILE]);
|
|
816
|
+
|
|
817
|
+
export const CENSUS_START = '<!-- census:start -->';
|
|
818
|
+
export const CENSUS_END = '<!-- census:end -->';
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* The metric ids the census line carries, with the label each one gets.
|
|
822
|
+
*
|
|
823
|
+
* A SUBSET of METRIC_IDS on purpose: the line is prose for a reader, not the
|
|
824
|
+
* machine receipt (`site/_census.json` is that, and carries all of them).
|
|
825
|
+
* `version`, `counted-at` and `counted-sha` are not in this list because they
|
|
826
|
+
* are rendered as the line's opening clause, not as `label value` pairs.
|
|
827
|
+
*/
|
|
828
|
+
export const CENSUS_LINE_FIELDS = Object.freeze([
|
|
829
|
+
{ id: 'skills', label: 'skills' },
|
|
830
|
+
{ id: 'commands', label: 'commands' },
|
|
831
|
+
{ id: 'agents', label: 'agents' },
|
|
832
|
+
{ id: 'hooks', label: 'hooks' },
|
|
833
|
+
{ id: 'tests', label: 'test files' },
|
|
834
|
+
{ id: 'sessions', label: 'sessions' },
|
|
835
|
+
{ id: 'learnings', label: 'learnings' },
|
|
836
|
+
{ id: 'npm-downloads-30d', label: 'npm downloads (30d)' },
|
|
837
|
+
{ id: 'github-stars', label: 'GitHub stars' },
|
|
838
|
+
]);
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Render the census line.
|
|
842
|
+
*
|
|
843
|
+
* The `Version X.Y.Z` opening is load-bearing beyond this file: `SURFACES` in
|
|
844
|
+
* `scripts/release.mjs` matches `/Version\s+(\d+\.\d+\.\d+)/g` against
|
|
845
|
+
* `site/llms-full.txt`, so the literal must keep exactly this shape or the
|
|
846
|
+
* release drift check stops seeing the surface it guards.
|
|
847
|
+
*
|
|
848
|
+
* @returns {{line: string, head: string, tail: string}}
|
|
849
|
+
* `head` is the provenance clause (warn-only, like the spans), `tail` the
|
|
850
|
+
* counts (real drift). Split so `--check` can tell the two apart.
|
|
851
|
+
*/
|
|
852
|
+
export function renderCensusLine(values) {
|
|
853
|
+
const head = `Version ${values.version} · counted ${values['counted-at']} at ${values['counted-sha']}`;
|
|
854
|
+
const tail = CENSUS_LINE_FIELDS.map((f) => `${f.label} ${values[f.id]}`).join(' · ');
|
|
855
|
+
return { line: `${head} · ${tail}`, head, tail };
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Replace the text between the two markers, and nothing else.
|
|
860
|
+
*
|
|
861
|
+
* A start marker without an end marker is a HARD ERROR rather than an append:
|
|
862
|
+
* guessing where the block ends would let one bad edit swallow the rest of a
|
|
863
|
+
* hand-authored file.
|
|
864
|
+
*
|
|
865
|
+
* @returns {{ok: true, text: string, changed: boolean, current: string}
|
|
866
|
+
* | {ok: false, reason: string}}
|
|
867
|
+
*/
|
|
868
|
+
export function rewriteCensusBlock(text, line) {
|
|
869
|
+
const start = text.indexOf(CENSUS_START);
|
|
870
|
+
const end = text.indexOf(CENSUS_END);
|
|
871
|
+
if (start === -1 && end === -1) return { ok: false, reason: 'no census markers' };
|
|
872
|
+
if (start === -1) return { ok: false, reason: `"${CENSUS_END}" without "${CENSUS_START}"` };
|
|
873
|
+
if (end === -1) return { ok: false, reason: `"${CENSUS_START}" without "${CENSUS_END}"` };
|
|
874
|
+
if (end < start) return { ok: false, reason: 'census markers are in the wrong order' };
|
|
875
|
+
|
|
876
|
+
const inner = text.slice(start + CENSUS_START.length, end);
|
|
877
|
+
const current = inner.trim();
|
|
878
|
+
const next = `${text.slice(0, start + CENSUS_START.length)}\n${line}\n${text.slice(end)}`;
|
|
879
|
+
return { ok: true, text: next, changed: next !== text, current };
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Judge (and optionally rewrite) the census block of ONE file under `siteDir`.
|
|
884
|
+
*
|
|
885
|
+
* Mirrors the span policy: a difference only in the provenance clause is
|
|
886
|
+
* `stale` (warn-only — a claim about the past does not become false when HEAD
|
|
887
|
+
* moves), a difference in the counts is `drift`.
|
|
888
|
+
*
|
|
889
|
+
* @param {{write?:boolean, file?:string}} [opts] `file` is relative to `siteDir`
|
|
890
|
+
* and defaults to `llms-full.txt`; `syncCensusBlocks` iterates CENSUS_BLOCK_FILES.
|
|
891
|
+
* @returns {{present:boolean, error?:string, drift?:boolean, stale?:boolean,
|
|
892
|
+
* written?:boolean, file:string}}
|
|
893
|
+
*/
|
|
894
|
+
export function syncCensusBlock(siteDir, values, { write = false, file = LLMS_FULL_FILE } = {}) {
|
|
895
|
+
const abs = join(siteDir, file);
|
|
896
|
+
// Absent file: not this generator's business to create one. Every HTML
|
|
897
|
+
// fixture directory in the test suite is such a case.
|
|
898
|
+
if (!existsSync(abs) || !statSync(abs).isFile()) return { present: false, file: abs };
|
|
899
|
+
|
|
900
|
+
const { line, tail } = renderCensusLine(values);
|
|
901
|
+
const text = readFileSync(abs, 'utf8');
|
|
902
|
+
const res = rewriteCensusBlock(text, line);
|
|
903
|
+
if (!res.ok) return { present: true, file: abs, error: res.reason };
|
|
904
|
+
|
|
905
|
+
// Same split as the spans: `counted <date> at <sha>` lagging is a claim about
|
|
906
|
+
// the past, not a wrong number.
|
|
907
|
+
const provenanceOnly =
|
|
908
|
+
res.current !== line &&
|
|
909
|
+
res.current.startsWith(`Version ${values.version} · `) &&
|
|
910
|
+
res.current.endsWith(tail);
|
|
911
|
+
const out = {
|
|
912
|
+
present: true,
|
|
913
|
+
file: abs,
|
|
914
|
+
drift: res.current !== line && !provenanceOnly,
|
|
915
|
+
stale: provenanceOnly,
|
|
916
|
+
written: false,
|
|
917
|
+
};
|
|
918
|
+
if (write && res.changed) {
|
|
919
|
+
writeFileSync(abs, res.text, 'utf8');
|
|
920
|
+
out.written = true;
|
|
921
|
+
}
|
|
922
|
+
return out;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Judge (and optionally rewrite) EVERY file in `CENSUS_BLOCK_FILES`.
|
|
927
|
+
*
|
|
928
|
+
* One measurement, N surfaces — the same reason `writeCensusSnapshot` takes an
|
|
929
|
+
* already-computed `values` map: two census runs inside one invocation can
|
|
930
|
+
* disagree across midnight or a concurrent ledger append.
|
|
931
|
+
*
|
|
932
|
+
* @returns {Array<ReturnType<typeof syncCensusBlock>>} in CENSUS_BLOCK_FILES order
|
|
933
|
+
*/
|
|
934
|
+
export function syncCensusBlocks(siteDir, values, { write = false } = {}) {
|
|
935
|
+
return CENSUS_BLOCK_FILES.map((file) => syncCensusBlock(siteDir, values, { write, file }));
|
|
936
|
+
}
|
|
937
|
+
|
|
665
938
|
// ---------------------------------------------------------------------------
|
|
666
939
|
// Markup
|
|
667
940
|
// ---------------------------------------------------------------------------
|
|
@@ -881,7 +1154,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
881
1154
|
return 2;
|
|
882
1155
|
}
|
|
883
1156
|
|
|
884
|
-
const { values, missing, warnings, fromSnapshot } = collect(root);
|
|
1157
|
+
const { values, missing, warnings, fromSnapshot } = collect(root, { usage: env.usage });
|
|
885
1158
|
if (missing.length > 0) {
|
|
886
1159
|
stderr(
|
|
887
1160
|
`Error: could not measure ${missing.join(', ')} under ${root} — ` +
|
|
@@ -961,6 +1234,38 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
961
1234
|
});
|
|
962
1235
|
}
|
|
963
1236
|
|
|
1237
|
+
// The marker-bounded census line in site/llms-full.txt. Judged with the same
|
|
1238
|
+
// split as the spans (counts = drift, provenance = warn-only) and rewritten
|
|
1239
|
+
// BETWEEN the markers only.
|
|
1240
|
+
const censusDriftLines = [];
|
|
1241
|
+
const censusBlocks = syncCensusBlocks(siteDir, values, { write: args.write });
|
|
1242
|
+
for (const b of censusBlocks) {
|
|
1243
|
+
if (b.error) {
|
|
1244
|
+
stderr(
|
|
1245
|
+
`Error: ${b.file}: ${b.error} — the census block contract is broken ` +
|
|
1246
|
+
`(expected "${CENSUS_START}" … "${CENSUS_END}")`,
|
|
1247
|
+
);
|
|
1248
|
+
contractTotal += 1;
|
|
1249
|
+
} else if (b.present) {
|
|
1250
|
+
if (b.drift && !args.write) {
|
|
1251
|
+
// Held back rather than printed here: under --json, stdout carries the
|
|
1252
|
+
// envelope and NOTHING else (`cli-design.md` — data on stdout). Printing
|
|
1253
|
+
// it inline put a bare DRIFT line in front of the JSON and made the
|
|
1254
|
+
// envelope unparseable, which is the same class of silent failure as an
|
|
1255
|
+
// unjudged surface: a consumer sees a parse error, not a drift report.
|
|
1256
|
+
censusDriftLines.push(`DRIFT ${b.file}: the census block does not match this measurement`);
|
|
1257
|
+
driftTotal += 1;
|
|
1258
|
+
} else if (b.drift) {
|
|
1259
|
+
driftTotal += 1;
|
|
1260
|
+
} else if (b.stale) {
|
|
1261
|
+
stderr(`stale ${b.file}: only the counted date/sha of the census block lags`);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
// Kept as its own name in the --json envelope for the consumers that already
|
|
1266
|
+
// read `llmsFull`; `censusBlocks` below carries all of them.
|
|
1267
|
+
const llms = censusBlocks[CENSUS_BLOCK_FILES.indexOf(LLMS_FULL_FILE)];
|
|
1268
|
+
|
|
964
1269
|
// The named silent-failure class: a generator that matches nothing, changes
|
|
965
1270
|
// nothing, and reports success. Zero spans means the markup contract is not in
|
|
966
1271
|
// the page — that is a defect, in BOTH modes, never a no-op.
|
|
@@ -1040,6 +1345,23 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1040
1345
|
written: writtenTotal,
|
|
1041
1346
|
rejected: rejectedTotal,
|
|
1042
1347
|
fromSnapshot,
|
|
1348
|
+
llmsFull: {
|
|
1349
|
+
file: llms.present ? relative(root, llms.file) : null,
|
|
1350
|
+
present: llms.present,
|
|
1351
|
+
error: llms.error ?? null,
|
|
1352
|
+
drift: llms.drift === true,
|
|
1353
|
+
stale: llms.stale === true,
|
|
1354
|
+
written: llms.written === true,
|
|
1355
|
+
},
|
|
1356
|
+
censusBlocks: censusBlocks.map((b, i) => ({
|
|
1357
|
+
name: CENSUS_BLOCK_FILES[i],
|
|
1358
|
+
file: b.present ? relative(root, b.file) : null,
|
|
1359
|
+
present: b.present,
|
|
1360
|
+
error: b.error ?? null,
|
|
1361
|
+
drift: b.drift === true,
|
|
1362
|
+
stale: b.stale === true,
|
|
1363
|
+
written: b.written === true,
|
|
1364
|
+
})),
|
|
1043
1365
|
censusWritten,
|
|
1044
1366
|
ok,
|
|
1045
1367
|
},
|
|
@@ -1051,6 +1373,9 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1051
1373
|
stdout(
|
|
1052
1374
|
`site-numbers: wrote ${writtenTotal} value(s) across ${files.length} file(s) in ${siteDir}` +
|
|
1053
1375
|
` (${spanTotal} metric cells @ ${values['counted-sha']}${dirty ? '+dirty' : ''})` +
|
|
1376
|
+
censusBlocks
|
|
1377
|
+
.map((b, i) => (b.written ? ` + ${CENSUS_BLOCK_FILES[i]}` : ''))
|
|
1378
|
+
.join('') +
|
|
1054
1379
|
(censusWritten ? ` + ${CENSUS_FILE.join('/')}` : ''),
|
|
1055
1380
|
);
|
|
1056
1381
|
} else {
|
|
@@ -1062,6 +1387,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1062
1387
|
stderr(`stale ${f.file}:${s.line} ${s.metric}: "${s.actual}" → would become "${s.expected}" on --write`);
|
|
1063
1388
|
}
|
|
1064
1389
|
}
|
|
1390
|
+
for (const l of censusDriftLines) stdout(l);
|
|
1065
1391
|
stdout(
|
|
1066
1392
|
driftTotal === 0 && !noSpans && contractTotal === 0
|
|
1067
1393
|
? `site-numbers: ${spanTotal} metric cell(s) current across ${files.length} file(s)`
|
|
@@ -1078,4 +1404,14 @@ const isMain =
|
|
|
1078
1404
|
process.argv[1] !== undefined &&
|
|
1079
1405
|
resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
|
|
1080
1406
|
|
|
1081
|
-
if (isMain)
|
|
1407
|
+
if (isMain) {
|
|
1408
|
+
const argv = process.argv.slice(2);
|
|
1409
|
+
const parsed = parseArgs(argv);
|
|
1410
|
+
// The ONLY network call in this file, and only on the write path — see the
|
|
1411
|
+
// header. A `--check` (the CI/build guard) never reaches this branch.
|
|
1412
|
+
const usage =
|
|
1413
|
+
!parsed.error && parsed.write && !parsed.help && !parsed.version
|
|
1414
|
+
? await fetchUsageMetrics(resolve(parsed.root ?? process.cwd()))
|
|
1415
|
+
: {};
|
|
1416
|
+
process.exit(main(argv, { usage }));
|
|
1417
|
+
}
|