session-orchestrator 3.23.0 → 4.0.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/.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 +1 -1
- 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 +13 -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 +1401 -0
- package/NOTICE +11 -6
- package/README.md +127 -92
- package/agents/db-specialist.md +0 -1
- 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 +249 -48
- package/docs/codex-setup.md +66 -22
- package/docs/components.md +37 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +51 -10
- package/docs/instruction-delivery.md +62 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +341 -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 +8 -8
- package/docs/session-config-reference.md +120 -61
- package/docs/session-config-template.md +40 -33
- package/docs/telemetry/telemetry-claims.md +11 -10
- package/docs/telemetry.md +187 -4
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/atomic-json.mjs +111 -0
- package/hooks/_lib/hook-import-set.json +1487 -0
- package/hooks/_lib/subagent-paths.mjs +143 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +11 -31
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/enforce-scope.mjs +35 -6
- 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 +280 -14
- package/hooks/on-session-start.mjs +153 -4
- package/hooks/on-stop.mjs +371 -17
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +189 -4
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +278 -392
- package/hooks/post-tool-batch-wave-signal.mjs +272 -44
- package/hooks/post-tool-failure-corrective-context.mjs +11 -34
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +24 -30
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/plugin.json +27 -0
- package/scripts/autopilot.mjs +26 -12
- package/scripts/backfill-abandoned-sessions.mjs +130 -15
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/emit-event.mjs +10 -2
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +249 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auq/parse.mjs +5 -29
- package/scripts/lib/auto-dialectic.mjs +68 -0
- 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/autopilot/worktree-pipeline.mjs +82 -6
- package/scripts/lib/build-live-signals.mjs +25 -22
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +82 -6
- package/scripts/lib/cold-start-detector.mjs +23 -14
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +63 -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 +9 -3
- 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 +388 -0
- 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 +234 -0
- 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/config.mjs +31 -3
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/enumerate.mjs +2 -17
- 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-schema.mjs +48 -0
- package/scripts/lib/events.mjs +256 -7
- package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +26 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
- 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-banner.mjs +20 -8
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +43 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +207 -14
- package/scripts/lib/peer-discovery.mjs +20 -2
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +406 -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 +249 -9
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/scope-gate.mjs +36 -0
- package/scripts/lib/session-close-backfill.mjs +125 -18
- package/scripts/lib/session-discovery.mjs +57 -3
- package/scripts/lib/session-end/phase-skip.mjs +2 -2
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +187 -11
- 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 +36 -2
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +18 -1
- package/scripts/lib/session-transition.mjs +1 -1
- package/scripts/lib/sessions-canonical.mjs +446 -0
- 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 +255 -17
- package/scripts/lib/telemetry/sync.mjs +417 -24
- package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-doc-cli-commands.mjs +9 -33
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
- package/scripts/lib/validate/check-skill-links.mjs +163 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +455 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
- package/scripts/lib/validate/check-unwired-features.mjs +0 -9
- package/scripts/lib/validate/check-validator-registration.mjs +254 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/markdown-fences.mjs +196 -0
- 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/board-lock.mjs +185 -0
- package/scripts/lib/vault-status/board-writer.mjs +174 -135
- package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
- 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 +502 -0
- package/scripts/lib/wave-resource-gate.mjs +133 -7
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +142 -8
- package/scripts/materialize-wave-scope.mjs +32 -9
- package/scripts/memory-propose.mjs +146 -8
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/promote-vault-strict.mjs +4 -15
- package/scripts/release.mjs +337 -29
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +36 -4
- 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 +187 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/vault-consolidate.mjs +3 -11
- package/scripts/vault-integration-watcher.mjs +2 -4
- package/scripts/vault-mirror.mjs +111 -26
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-auq.md +31 -2
- package/skills/_shared/parallel-aware-preamble.md +18 -4
- 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/ecosystem-health/SKILL.md +4 -1
- package/skills/ecosystem-health/wizard.md +5 -0
- package/skills/evolve/SKILL.md +87 -11
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +38 -2
- package/skills/remote-offload/SKILL.md +89 -0
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +19 -9
- 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 +41 -900
- 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 +155 -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/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +16 -2
- 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 -1271
- 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 -269
- 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
|
@@ -37,6 +37,7 @@ import { emitEvent } from '../events.mjs';
|
|
|
37
37
|
import { main as gcMain } from '../../gc-stale-worktrees.mjs';
|
|
38
38
|
import { SEMANTIC_ID_RE } from '../session-id.mjs';
|
|
39
39
|
import { repoPathHash } from '../session-registry.mjs';
|
|
40
|
+
import { leaveSourceRoot } from '../session-transition.mjs';
|
|
40
41
|
// Marker location is owned by its READER (session-end Phase 4a) — importing the
|
|
41
42
|
// constant from there keeps writer and reader on one string. The dependency runs
|
|
42
43
|
// heavy→lean (this pipeline → the dependency-free cleanup helper), never back.
|
|
@@ -731,6 +732,21 @@ function writePromotionMarker({ wtPath, sourceRoot, sessionId, branch }) {
|
|
|
731
732
|
* @param {string} params.sessionId - Semantic session-ID matching SEMANTIC_ID_RE.
|
|
732
733
|
* @param {string} params.branch - Branch name (existing or new) matching ENTER_WORKTREE_BRANCH_RE.
|
|
733
734
|
* @param {string} params.repoRoot - Path to the source git repository (passed explicitly to avoid CWD drift per #219).
|
|
735
|
+
* @param {string} [params.rawSessionId] - The RAW (physical) session id that owns
|
|
736
|
+
* the SOURCE root's `session.lock` and registry entry. Optional. When it is a
|
|
737
|
+
* non-empty string, the source root is LEFT mechanically (#1170): after the
|
|
738
|
+
* destination worktree provably exists, `leaveSourceRoot()` runs and its
|
|
739
|
+
* result is reported as `left` in the return value. OMITTING it (strictly
|
|
740
|
+
* `undefined`) preserves the pre-#1170 behaviour exactly — no teardown, no
|
|
741
|
+
* `left` key, no WARN. Passing something UNUSABLE (`null`, `''`, a non-string —
|
|
742
|
+
* `readLock({ repoRoot }).session_id` returns `null` on a missing/unreadable
|
|
743
|
+
* lock, which is how this happens in practice) is NOT the same case: it WARNs
|
|
744
|
+
* and reports `left: { ok: false, reason: 'raw-session-id-unusable' }`, because
|
|
745
|
+
* a silent skip there is the phantom-owner state of #1069. NOT the semantic
|
|
746
|
+
* `sessionId` above, which is a naming key, never an ownership key.
|
|
747
|
+
* @param {string} [params.reason='worktree-promotion'] - Departure reason
|
|
748
|
+
* recorded verbatim in the `orchestrator.session.root_left` payload. Only used
|
|
749
|
+
* when `rawSessionId` is given.
|
|
734
750
|
* @param {object} [opts]
|
|
735
751
|
* @param {Function} [opts.$] - zx-like template-tag executor (DI seam); falls back to lazy `await import('zx')`.
|
|
736
752
|
* Every freshly created worktree also gets a `.orchestrator/promoted-from.json`
|
|
@@ -739,18 +755,27 @@ function writePromotionMarker({ wtPath, sourceRoot, sessionId, branch }) {
|
|
|
739
755
|
* session with a different id. Marker writing is best-effort and never fails
|
|
740
756
|
* the promotion.
|
|
741
757
|
*
|
|
742
|
-
* @returns {Promise<{ wtPath: string, reused: boolean, branch?: string, promotedFrom?: string, reusedBranch?: true }>}
|
|
758
|
+
* @returns {Promise<{ wtPath: string, reused: boolean, branch?: string, promotedFrom?: string, reusedBranch?: true, left?: { ok: boolean, steps: object, reason?: string } }>}
|
|
743
759
|
* `branch` is the branch the new worktree actually landed on — equal to the
|
|
744
760
|
* `branch` param except in case 1 above, where it is `so/<sessionId>` and
|
|
745
761
|
* `promotedFrom` carries the requested source branch. `reusedBranch` is
|
|
746
762
|
* present (and `true`) only when an existing `so/<sessionId>` was checked out
|
|
747
763
|
* rather than created. All three are absent on the `reused: true` path (no
|
|
748
|
-
* branch was chosen — the worktree pre-existed).
|
|
764
|
+
* branch was chosen — the worktree pre-existed). `left` is present on BOTH
|
|
765
|
+
* success exits — and ONLY — when `rawSessionId` was supplied (including when
|
|
766
|
+
* it was supplied unusable, where it reports the refusal).
|
|
749
767
|
* @throws {TypeError} when any required param is missing or fails validation.
|
|
750
768
|
* @throws {WorktreeBoundaryError} when the computed worktree path escapes `basePath`.
|
|
751
769
|
* @throws {WorktreePromotionBranchError} when `so/<sessionId>` is checked out elsewhere.
|
|
752
770
|
*/
|
|
753
|
-
export async function enterWorktree({
|
|
771
|
+
export async function enterWorktree({
|
|
772
|
+
basePath,
|
|
773
|
+
sessionId,
|
|
774
|
+
branch,
|
|
775
|
+
repoRoot,
|
|
776
|
+
rawSessionId,
|
|
777
|
+
reason = 'worktree-promotion',
|
|
778
|
+
} = {}, opts = {}) {
|
|
754
779
|
// -------------------------------------------------------------------------
|
|
755
780
|
// Step 1: Input validation (TypeError on any malformed param).
|
|
756
781
|
// -------------------------------------------------------------------------
|
|
@@ -839,11 +864,62 @@ export async function enterWorktree({ basePath, sessionId, branch, repoRoot } =
|
|
|
839
864
|
);
|
|
840
865
|
}
|
|
841
866
|
|
|
867
|
+
// -------------------------------------------------------------------------
|
|
868
|
+
// Step 3b: The source-root departure (#1170), as a MECHANICAL step.
|
|
869
|
+
//
|
|
870
|
+
// Before #1170 this was skill PROSE at four sites: `enterWorktree()` created
|
|
871
|
+
// the destination and the coordinator was trusted to call `leaveSourceRoot()`
|
|
872
|
+
// afterwards. A step that only exists in prose is a step that is sometimes
|
|
873
|
+
// skipped — and skipping it is exactly the phantom-peer / double-live-lock
|
|
874
|
+
// state #1069 was filed about.
|
|
875
|
+
//
|
|
876
|
+
// Ordering is load-bearing and only runs on a proven-existing destination:
|
|
877
|
+
// every throw above leaves the source root untouched, because a departure
|
|
878
|
+
// from a root whose successor does not exist strands the session with no
|
|
879
|
+
// live root at all. `leaveSourceRoot()` never throws, so a failed teardown
|
|
880
|
+
// WARNs and is reported in `left` — it never fails the promotion.
|
|
881
|
+
// -------------------------------------------------------------------------
|
|
882
|
+
const departSourceRoot = async (result) => {
|
|
883
|
+
// NOT PASSED and PASSED-BUT-UNUSABLE are different callers with different
|
|
884
|
+
// bugs, and collapsing them re-opened #1069 silently. `undefined` is the
|
|
885
|
+
// documented opt-out (the autopilot callers, which own no source session):
|
|
886
|
+
// byte-identical pre-#1170 behaviour, no `left`, no WARN.
|
|
887
|
+
if (rawSessionId === undefined) return result;
|
|
888
|
+
// Anything else falsy or non-string means a caller INTENDED to depart and
|
|
889
|
+
// handed us something unusable. The documented derivation is
|
|
890
|
+
// `readLock({ repoRoot }).session_id`, which is `null` whenever the lock is
|
|
891
|
+
// missing or unreadable — so the realistic failure lands here, and before
|
|
892
|
+
// this branch it returned early with nothing on stderr: promotion succeeds,
|
|
893
|
+
// source root never deregistered, phantom owner (#1069).
|
|
894
|
+
if (typeof rawSessionId !== 'string' || rawSessionId.length === 0) {
|
|
895
|
+
const shown = rawSessionId === null
|
|
896
|
+
? 'null'
|
|
897
|
+
: typeof rawSessionId === 'string' ? 'empty string' : typeof rawSessionId;
|
|
898
|
+
console.warn(
|
|
899
|
+
`enterWorktree: rawSessionId unusable (${shown}) — source root NOT departed; ` +
|
|
900
|
+
'read it via readLock({ repoRoot }).session_id',
|
|
901
|
+
);
|
|
902
|
+
result.left = { ok: false, reason: 'raw-session-id-unusable' };
|
|
903
|
+
return result;
|
|
904
|
+
}
|
|
905
|
+
const left = await leaveSourceRoot({
|
|
906
|
+
repoRoot,
|
|
907
|
+
sessionId: rawSessionId,
|
|
908
|
+
semanticSessionId: sessionId,
|
|
909
|
+
reason,
|
|
910
|
+
});
|
|
911
|
+
if (left.ok !== true) {
|
|
912
|
+
console.warn(`enterWorktree: leaveSourceRoot: ${left.reason ?? 'unknown'}`);
|
|
913
|
+
}
|
|
914
|
+
result.left = left;
|
|
915
|
+
return result;
|
|
916
|
+
};
|
|
917
|
+
|
|
842
918
|
// -------------------------------------------------------------------------
|
|
843
919
|
// Step 4: Idempotency — reuse if worktree already exists with .git.
|
|
844
920
|
// -------------------------------------------------------------------------
|
|
845
921
|
if (fs.existsSync(wtPath) && fs.existsSync(path.join(wtPath, '.git'))) {
|
|
846
|
-
return { wtPath, reused: true };
|
|
922
|
+
return await departSourceRoot({ wtPath, reused: true });
|
|
847
923
|
}
|
|
848
924
|
|
|
849
925
|
// -------------------------------------------------------------------------
|
|
@@ -931,7 +1007,7 @@ export async function enterWorktree({ basePath, sessionId, branch, repoRoot } =
|
|
|
931
1007
|
// Present only when it happened, so the common shape stays byte-identical
|
|
932
1008
|
// for every existing consumer and strict-equality pin.
|
|
933
1009
|
if (reusedBranch) result.reusedBranch = true;
|
|
934
|
-
return result;
|
|
1010
|
+
return await departSourceRoot(result);
|
|
935
1011
|
}
|
|
936
|
-
return { wtPath, reused: false, branch };
|
|
1012
|
+
return await departSourceRoot({ wtPath, reused: false, branch });
|
|
937
1013
|
}
|
|
@@ -20,6 +20,7 @@ import { parseStateMd, parseRecommendations } from './state-md.mjs';
|
|
|
20
20
|
import { normalizeSession, tailRealSessions } from './session-schema.mjs';
|
|
21
21
|
import { parseBootstrapLock } from './bootstrap-lock-freshness.mjs';
|
|
22
22
|
import { scanBacklog, DEFAULT_BACKLOG_LIMIT } from './backlog-scan.mjs';
|
|
23
|
+
import { readCanonicalSessions } from './sessions-canonical.mjs';
|
|
23
24
|
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// Public API
|
|
@@ -113,29 +114,31 @@ export async function buildLiveSignals(opts = {}) {
|
|
|
113
114
|
let recentSessions = [];
|
|
114
115
|
|
|
115
116
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
117
|
+
// #1186: readCanonicalSessions applies the #1167 newest-wins-per-`session_id`
|
|
118
|
+
// / attestable-`supersedes` collapse BEFORE the tail is taken. The raw read
|
|
119
|
+
// this replaced parsed every line unconditionally (malformed lines skipped
|
|
120
|
+
// on JSON.parse failure only), so a duplicated `session_id` — a
|
|
121
|
+
// crash-recovery re-append, or the #1068 abandoned-stub/supersede pair —
|
|
122
|
+
// counted as TWO entries toward `sessionTailN`, silently narrowing the
|
|
123
|
+
// REAL window by however many duplicates sat in the file's tail.
|
|
124
|
+
//
|
|
125
|
+
// Behavioural change: a well-formed record with NO `session_id` (never
|
|
126
|
+
// legitimate per REQUIRED_FIELDS, but possible on a hand-edited or
|
|
127
|
+
// pre-schema legacy line) is now DROPPED rather than counted — it cannot
|
|
128
|
+
// be deduplicated by identity, so `canonicalizeSessions` excludes it (see
|
|
129
|
+
// sessions-canonical.mjs's own contract). Missing-file / unreadable-file
|
|
130
|
+
// handling (ENOENT vs EACCES/EISDIR, #1188) is delegated to the shared
|
|
131
|
+
// reader; both still degrade to `[]` here, matching Branch 3's contract.
|
|
132
|
+
const canonical = readCanonicalSessions({ filePath: sessionsPath });
|
|
133
|
+
const parsed = canonical.map((obj) => normalizeSession(obj));
|
|
134
|
+
// #834: `status: 'abandoned'` phantom stubs must still be filtered out
|
|
135
|
+
// BEFORE the tail is taken, or sessionTailN silently means "last N LINES"
|
|
136
|
+
// instead of "last N REAL sessions". tailRealSessions() does the filter +
|
|
137
|
+
// tail.
|
|
138
|
+
recentSessions = tailRealSessions(parsed, sessionTailN);
|
|
137
139
|
} catch {
|
|
138
|
-
// Branch 3: file unreadable — recentSessions stays []
|
|
140
|
+
// Branch 3: file unreadable — recentSessions stays [] (readCanonicalSessions
|
|
141
|
+
// itself never throws, but the graceful-null contract is kept as a backstop).
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
// --- Branch 5: bootstrap.lock ---
|
|
@@ -3,8 +3,22 @@
|
|
|
3
3
|
* Checks CI status for the current HEAD commit and returns a structured
|
|
4
4
|
* result for session-start Phase 4 banner rendering.
|
|
5
5
|
*
|
|
6
|
-
* Plain-JS — no Zod dependency. Never throws.
|
|
7
|
-
*
|
|
6
|
+
* Plain-JS — no Zod dependency. Never throws.
|
|
7
|
+
*
|
|
8
|
+
* THREE return states (#1031), not two:
|
|
9
|
+
* - `null` — read, and there is nothing to report (a
|
|
10
|
+
* benign ABSENCE: no VCS remote, no pipeline
|
|
11
|
+
* worth a banner, green with no soft failures)
|
|
12
|
+
* - `{status, ok, details, …}` — a real CI reading (`green` | `red` |
|
|
13
|
+
* `unknown`)
|
|
14
|
+
* - `{severity, message, degraded, ok:false}` — the state could NOT be read.
|
|
15
|
+
* `degraded` is a member of the frozen {@link DEGRADED_REASONS} enum
|
|
16
|
+
* exported below. This must NEVER be read as "CI is green".
|
|
17
|
+
*
|
|
18
|
+
* Until #1031 every unreadable state collapsed onto `null`, which in the banner
|
|
19
|
+
* contract reads as all-clear — the exact confusion #1022 (`gh repo view -R` →
|
|
20
|
+
* `unknown shorthand flag`) hid behind on every GitHub repo. The shape is the
|
|
21
|
+
* one `scripts/lib/mirror-issues-banner.mjs` established.
|
|
8
22
|
*
|
|
9
23
|
* Supports GitLab (via glab) and GitHub (via gh).
|
|
10
24
|
* VCS is auto-detected from the repo's git remotes via
|
|
@@ -29,6 +43,78 @@ const execFileAsync = promisify(_execFile);
|
|
|
29
43
|
/** Default timeout in milliseconds for CLI invocations. */
|
|
30
44
|
export const DEFAULT_TIMEOUT_MS = 8000;
|
|
31
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Closed set of `degraded` reasons (#1031). A degraded result means "the CI
|
|
48
|
+
* state was NOT successfully read" — never "CI is green".
|
|
49
|
+
*
|
|
50
|
+
* Deliberately THIS module's own enum rather than a merged one shared with
|
|
51
|
+
* `mirror-issues-banner.mjs` / `git-config-drift.mjs`: those two already carry
|
|
52
|
+
* DIFFERENT member sets for their own failure surfaces, and a merged superset
|
|
53
|
+
* would hand every consumer members its probe can never emit — an enum whose
|
|
54
|
+
* members are not exhaustively reachable cannot be switched on exhaustively,
|
|
55
|
+
* which is the only reason to freeze one. The members here are exactly what
|
|
56
|
+
* this probe can fail at:
|
|
57
|
+
*
|
|
58
|
+
* - `cli-missing` a required binary (`git`, `glab`, `gh`) is not on PATH.
|
|
59
|
+
* - `timeout` a subprocess outlived its budget. Not silent since #1031:
|
|
60
|
+
* a hang is not actionable, but "state unknown" IS the
|
|
61
|
+
* finding, and silence renders identically to green.
|
|
62
|
+
* - `parse-error` a CLI answered with output this module could not read
|
|
63
|
+
* (unparseable JSON, or valid JSON of the wrong shape).
|
|
64
|
+
* - `query-failed` residual bucket — a present CLI ran and rejected the
|
|
65
|
+
* invocation, or a resolvable remote whose API target could
|
|
66
|
+
* not be derived. Folding these into `parse-error` would
|
|
67
|
+
* mislabel a rejected flag as malformed output.
|
|
68
|
+
* - `git-error` the VCS probe itself failed (`git remote -v` non-zero for
|
|
69
|
+
* a reason other than "not a work tree"). Distinct from
|
|
70
|
+
* `query-failed` because it means the module never got as
|
|
71
|
+
* far as choosing a CI platform.
|
|
72
|
+
*
|
|
73
|
+
* @type {readonly ['cli-missing','timeout','parse-error','query-failed','git-error']}
|
|
74
|
+
*/
|
|
75
|
+
export const DEGRADED_REASONS = Object.freeze([
|
|
76
|
+
'cli-missing',
|
|
77
|
+
'timeout',
|
|
78
|
+
'parse-error',
|
|
79
|
+
'query-failed',
|
|
80
|
+
'git-error',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build the third return state. Distinct from `null` on purpose: `null` in the
|
|
85
|
+
* banner contract reads as "all clear", which a failed read has NOT
|
|
86
|
+
* established.
|
|
87
|
+
*
|
|
88
|
+
* The `detail` is appended bounded (BV-004: 200 chars — long enough for a
|
|
89
|
+
* `Command failed: <argv>` first line, short enough not to bury the
|
|
90
|
+
* session-start banner it prints beside; revisit if a CLI starts emitting a
|
|
91
|
+
* diagnostic that needs more). Callers pass ALREADY-redacted text — every
|
|
92
|
+
* source here is either a fixed string or a `redactUrlCredentials` result.
|
|
93
|
+
*
|
|
94
|
+
* @param {'cli-missing'|'timeout'|'parse-error'|'query-failed'|'git-error'} reason
|
|
95
|
+
* @param {string} [detail] Optional, already-redacted diagnostic tail
|
|
96
|
+
* @returns {{ severity: 'warn', ok: false, message: string, degraded: string }}
|
|
97
|
+
*/
|
|
98
|
+
function degradedResult(reason, detail) {
|
|
99
|
+
// Redaction (the callers' job) and control-byte escaping are DIFFERENT
|
|
100
|
+
// protections: `redactUrlCredentials` removes secrets, it does not neutralise
|
|
101
|
+
// ANSI/CR. `parseCliJson` already escapes the text it embeds (see the call at
|
|
102
|
+
// the JSON.parse catch), but the degraded tail reaches the operator's terminal
|
|
103
|
+
// by a second route — a CLI stderr quoted into `err.message` — and skipped it.
|
|
104
|
+
// Escape here so BOTH routes are covered at their common exit. Slice first,
|
|
105
|
+
// escape after: the budget is 200 chars of DETAIL, and escaping cannot then
|
|
106
|
+
// leave a cut mid-`\uXXXX`.
|
|
107
|
+
const tail = detail ? ` — ${escapeControlBytes(String(detail).trim().slice(0, 200))}` : '';
|
|
108
|
+
return {
|
|
109
|
+
severity: 'warn',
|
|
110
|
+
ok: false,
|
|
111
|
+
message:
|
|
112
|
+
`⚠ ci-status: CI status for HEAD could not be determined (${reason}) — ` +
|
|
113
|
+
`state UNKNOWN, not "green".${tail}`,
|
|
114
|
+
degraded: reason,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
32
118
|
/**
|
|
33
119
|
* Wraps execFile with a per-call timeout race.
|
|
34
120
|
*
|
|
@@ -148,10 +234,10 @@ function parseCliJson(stdout, label, expect) {
|
|
|
148
234
|
// `cause` preserves the original for a debugger; the reason is ALSO
|
|
149
235
|
// inlined into the message because the outer catch reads `err.message`
|
|
150
236
|
// only — a cause-only wrapper would lose it on the operator-facing line.
|
|
151
|
-
throw new Error(
|
|
237
|
+
throw tagParseError(new Error(
|
|
152
238
|
`${label} returned unparseable JSON (${reason}) — ${shown}`,
|
|
153
239
|
{ cause: err },
|
|
154
|
-
);
|
|
240
|
+
));
|
|
155
241
|
}
|
|
156
242
|
|
|
157
243
|
// A shape mismatch reports the JSON TYPE, never the payload. The parse
|
|
@@ -161,25 +247,47 @@ function parseCliJson(stdout, label, expect) {
|
|
|
161
247
|
// its contents echoed anywhere. A type name carries no body content.
|
|
162
248
|
const actual = jsonTypeOf(parsed);
|
|
163
249
|
if (expect === 'array' && actual !== 'array') {
|
|
164
|
-
throw new Error(`${label} returned JSON of an unexpected shape — expected an array, got ${actual}`);
|
|
250
|
+
throw tagParseError(new Error(`${label} returned JSON of an unexpected shape — expected an array, got ${actual}`));
|
|
165
251
|
}
|
|
166
252
|
if (expect === 'object' && actual !== 'object') {
|
|
167
|
-
throw new Error(`${label} returned JSON of an unexpected shape — expected an object, got ${actual}`);
|
|
253
|
+
throw tagParseError(new Error(`${label} returned JSON of an unexpected shape — expected an object, got ${actual}`));
|
|
168
254
|
}
|
|
169
255
|
return parsed;
|
|
170
256
|
}
|
|
171
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Stamp the `parse-error` degraded reason onto an error raised by
|
|
260
|
+
* {@link parseCliJson}.
|
|
261
|
+
*
|
|
262
|
+
* Carried on the error object rather than re-derived from its message text in
|
|
263
|
+
* the outer catch: a message-substring test would silently reclassify the
|
|
264
|
+
* moment a wording changes, and the two failures (`unparseable JSON`, wrong
|
|
265
|
+
* shape) are one class only because THIS function says so.
|
|
266
|
+
*
|
|
267
|
+
* @param {Error} err
|
|
268
|
+
* @returns {Error}
|
|
269
|
+
*/
|
|
270
|
+
function tagParseError(err) {
|
|
271
|
+
return Object.assign(err, { degradedReason: 'parse-error' });
|
|
272
|
+
}
|
|
273
|
+
|
|
172
274
|
/**
|
|
173
275
|
* The one failure this probe can produce that the frozen
|
|
174
276
|
* `REMOTE_RESOLUTION_REASONS` set has no member for: the async timeout race
|
|
175
277
|
* every subprocess here runs under has no counterpart in the SYNCHRONOUS
|
|
176
278
|
* `vcs-repo-spec.mjs` core (it uses `execFileSync`, which cannot time out).
|
|
177
279
|
*
|
|
178
|
-
* Deliberately NOT a query-failure for
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
280
|
+
* Deliberately NOT a query-failure for WARNING purposes — a timeout emits no
|
|
281
|
+
* `console.warn`, matching how `checkCiStatus`'s outer catch has always
|
|
282
|
+
* treated one, and warning on a hung `git` but not on a hung `glab` would be
|
|
283
|
+
* an inconsistency inside a single banner.
|
|
284
|
+
*
|
|
285
|
+
* It is no longer SILENT, though (#1031). The old argument — "a hang is not a
|
|
286
|
+
* fact an operator can act on the way 'git is not installed' is" — is
|
|
287
|
+
* superseded: what the operator acts on is not the hang, it is that CI state
|
|
288
|
+
* is UNKNOWN, and returning `null` rendered that identically to green. A
|
|
289
|
+
* timeout therefore yields `degradedResult('timeout')` on both this path and
|
|
290
|
+
* the outer catch.
|
|
183
291
|
*/
|
|
184
292
|
const PROBE_TIMEOUT = 'probe-timeout';
|
|
185
293
|
|
|
@@ -203,6 +311,32 @@ const PROBE_TIMEOUT = 'probe-timeout';
|
|
|
203
311
|
*/
|
|
204
312
|
const SILENT_QUERY_FAILURES = new Set(['not-a-git-repo']);
|
|
205
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Project a `REMOTE_RESOLUTION_REASONS` query failure onto this module's own
|
|
316
|
+
* {@link DEGRADED_REASONS} member.
|
|
317
|
+
*
|
|
318
|
+
* Written as explicit branches rather than a lookup table so that EVERY reason
|
|
319
|
+
* literal this module can emit sits inside a literal call to the builder
|
|
320
|
+
* call — which is what makes the enum-membership census in
|
|
321
|
+
* `tests/lib/ci-status-banner.test.mjs` a regex over the source instead of a
|
|
322
|
+
* hand-typed list that drifts.
|
|
323
|
+
*
|
|
324
|
+
* The fallthrough is `query-failed`, not a throw: a reason added to the frozen
|
|
325
|
+
* upstream set must degrade visibly (fail-toward-visible), never crash the
|
|
326
|
+
* probe or slip back onto the silent path.
|
|
327
|
+
*
|
|
328
|
+
* @param {string} reason
|
|
329
|
+
* @param {string} [detail] Already-redacted git stderr
|
|
330
|
+
* @returns {{ severity: 'warn', ok: false, message: string, degraded: string }}
|
|
331
|
+
*/
|
|
332
|
+
function degradedForVcsReason(reason, detail) {
|
|
333
|
+
// git itself is a CLI, and "not on PATH" is the same fact for it as for
|
|
334
|
+
// glab/gh — one member, not two spellings of it.
|
|
335
|
+
if (reason === 'git-unavailable') return degradedResult('cli-missing', detail);
|
|
336
|
+
if (reason === 'git-error') return degradedResult('git-error', detail);
|
|
337
|
+
return degradedResult('query-failed', detail);
|
|
338
|
+
}
|
|
339
|
+
|
|
206
340
|
/**
|
|
207
341
|
* Classify a rejected ASYNC `execFile` into a `REMOTE_RESOLUTION_REASONS`
|
|
208
342
|
* member.
|
|
@@ -651,24 +785,22 @@ async function checkGithub(repoRoot, deps = {}) {
|
|
|
651
785
|
/**
|
|
652
786
|
* Checks CI status for the current HEAD commit.
|
|
653
787
|
*
|
|
654
|
-
* Returns `null` (silent no-op)
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
* module could not parse (see {@link parseCliJson}).
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
* one test covering it asserted only the `null` and let the file-wide
|
|
671
|
-
* `console.warn` spy swallow the rest.
|
|
788
|
+
* Returns `null` (silent no-op) ONLY for a measured ABSENCE: the repo has no
|
|
789
|
+
* usable VCS remote (not a git repo, no remotes at all, or >= 2 remotes with no
|
|
790
|
+
* preference match). That is a complete answer — there is no CI here.
|
|
791
|
+
*
|
|
792
|
+
* Returns a DEGRADED result (`{severity:'warn', ok:false, message, degraded}`,
|
|
793
|
+
* see {@link DEGRADED_REASONS}) when the state could not be read: the
|
|
794
|
+
* VCS-detection query itself failed (`git` not on PATH, `git remote -v`
|
|
795
|
+
* erroring), a required CLI is missing, an invocation timed out, a present CLI
|
|
796
|
+
* rejected its invocation, a GitLab API target could not be derived, or a CLI
|
|
797
|
+
* returned output this module could not parse (see {@link parseCliJson}).
|
|
798
|
+
* Callers MUST treat that as "state unknown", never as clean — the generic
|
|
799
|
+
* degraded path in `scripts/lib/session-start-probes.mjs` does exactly that.
|
|
800
|
+
*
|
|
801
|
+
* Missing-CLI and timeout additionally stay SILENT on the `console.warn`
|
|
802
|
+
* channel (they are normal states on a CLI-less machine); every other degraded
|
|
803
|
+
* reason also emits a warn.
|
|
672
804
|
*
|
|
673
805
|
* @param {{
|
|
674
806
|
* repoRoot?: string,
|
|
@@ -686,6 +818,11 @@ async function checkGithub(repoRoot, deps = {}) {
|
|
|
686
818
|
* `resolveGitlabProjectTarget`, which proves host and project path from one
|
|
687
819
|
* sanitized remote; GitHub retains the #872 spec/host resolvers.
|
|
688
820
|
* @returns {Promise<null | {
|
|
821
|
+
* severity: 'warn',
|
|
822
|
+
* ok: false,
|
|
823
|
+
* message: string,
|
|
824
|
+
* degraded: 'cli-missing'|'timeout'|'parse-error'|'query-failed'|'git-error',
|
|
825
|
+
* } | {
|
|
689
826
|
* status: 'green'|'red'|'unknown',
|
|
690
827
|
* ok: boolean,
|
|
691
828
|
* lastGreen?: { sha: string, pipelineId: number, ageCommits: number, ageDays: number|null },
|
|
@@ -723,24 +860,31 @@ export async function checkCiStatus(opts = {}, deps = {}) {
|
|
|
723
860
|
if (!vcs) {
|
|
724
861
|
const detected = await detectVcs(repoRoot, depsWithExec);
|
|
725
862
|
if (!detected.ok) {
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
|
|
863
|
+
// An ABSENCE (`no-remotes`, `no-matching-remote`, `unsafe-value`) is a
|
|
864
|
+
// real, benign answer and stays `null` AND silent — degrading there
|
|
865
|
+
// would print a banner on every session-start in a remote-less repo and
|
|
866
|
+
// train operators to ignore this line, which is the more expensive
|
|
867
|
+
// error. A QUERY FAILURE (`git-unavailable`, `git-error`) means the
|
|
868
|
+
// question could not be asked at all: since #1031 that is the third
|
|
869
|
+
// state, no longer a warn-only side channel on top of an all-clear
|
|
870
|
+
// `null`. `not-a-git-repo` is the one query failure this banner reads
|
|
871
|
+
// as an absence — see {@link SILENT_QUERY_FAILURES}.
|
|
872
|
+
if (detected.reason === PROBE_TIMEOUT) {
|
|
873
|
+
return degradedResult('timeout');
|
|
874
|
+
}
|
|
736
875
|
if (isQueryFailure(detected.reason) && !SILENT_QUERY_FAILURES.has(detected.reason)) {
|
|
737
|
-
const
|
|
738
|
-
?
|
|
876
|
+
const stderr = detected.stderr
|
|
877
|
+
? redactUrlCredentials(detected.stderr).trim()
|
|
739
878
|
: '';
|
|
879
|
+
const detail = stderr ? ` — ${stderr}` : '';
|
|
740
880
|
console.warn(
|
|
741
881
|
`WARN ci-status-banner: VCS detection failed (${detected.reason}), banner suppressed — ` +
|
|
742
882
|
`CI state is UNKNOWN, not "green".${detail}`,
|
|
743
883
|
);
|
|
884
|
+
return degradedForVcsReason(
|
|
885
|
+
detected.reason,
|
|
886
|
+
stderr ? `${detected.reason}: ${stderr}` : detected.reason,
|
|
887
|
+
);
|
|
744
888
|
}
|
|
745
889
|
return null;
|
|
746
890
|
}
|
|
@@ -765,7 +909,10 @@ export async function checkCiStatus(opts = {}, deps = {}) {
|
|
|
765
909
|
'WARN ci-status-banner: a GitLab remote was detected but its host/project path ' +
|
|
766
910
|
'could not be derived, banner suppressed — CI state is UNKNOWN, not "green".',
|
|
767
911
|
);
|
|
768
|
-
return
|
|
912
|
+
return degradedResult(
|
|
913
|
+
'query-failed',
|
|
914
|
+
'a GitLab remote was detected but its host/project path could not be derived',
|
|
915
|
+
);
|
|
769
916
|
}
|
|
770
917
|
return await checkGitlab(repoRoot, now, { ...depsWithExec, gitlabProject });
|
|
771
918
|
}
|
|
@@ -779,47 +926,45 @@ export async function checkCiStatus(opts = {}, deps = {}) {
|
|
|
779
926
|
// Unknown VCS value — silent no-op.
|
|
780
927
|
return null;
|
|
781
928
|
} catch (err) {
|
|
782
|
-
//
|
|
783
|
-
// These are all no-op conditions per the spec.
|
|
929
|
+
// Never throws: every failure below leaves as the third return state.
|
|
784
930
|
const msg = err instanceof Error ? err.message : String(err);
|
|
785
931
|
|
|
786
|
-
// Timeout and ENOENT (missing CLI)
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
932
|
+
// Timeout and ENOENT (missing CLI) stay SILENT on the warn channel — a
|
|
933
|
+
// machine without `glab` is a normal state, and a warn on every
|
|
934
|
+
// session-start there drowns the #1022 signal this channel carries. Since
|
|
935
|
+
// #1031 they are no longer silent on the RETURN channel: neither state
|
|
936
|
+
// established that CI is green, so both degrade.
|
|
937
|
+
if (msg === 'timeout') return degradedResult('timeout');
|
|
938
|
+
if (err && err.code === 'ENOENT') {
|
|
939
|
+
return degradedResult('cli-missing', redactUrlCredentials(msg));
|
|
792
940
|
}
|
|
793
941
|
|
|
794
942
|
// Everything else means the CLI was PRESENT but the invocation failed —
|
|
795
|
-
// non-zero exit, rejected flag, unparseable output.
|
|
796
|
-
//
|
|
797
|
-
// is exactly how #1022 (`gh repo view -R` → `unknown
|
|
798
|
-
// stayed invisible on every GitHub repo.
|
|
799
|
-
// contract (null ⇒ silent no-op) for backwards compatibility with the
|
|
800
|
-
// Phase-4 callers already written against it, so leave a stderr trace
|
|
801
|
-
// instead: non-blocking, but the next defect of this class is no longer
|
|
802
|
-
// silent. Credentials redacted defense-in-depth (#907) — the message can
|
|
803
|
-
// quote the failed argv, which carries the repo spec.
|
|
943
|
+
// non-zero exit, rejected flag, unparseable output. A bare `null` here made
|
|
944
|
+
// that state indistinguishable from "CLI not installed" AND from "CI is
|
|
945
|
+
// green", which is exactly how #1022 (`gh repo view -R` → `unknown
|
|
946
|
+
// shorthand flag: 'R'`) stayed invisible on every GitHub repo.
|
|
804
947
|
//
|
|
805
|
-
//
|
|
806
|
-
//
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
948
|
+
// #1031 finished the migration this comment used to describe as pending:
|
|
949
|
+
// the third state `{ severity, message, degraded }` — the shape
|
|
950
|
+
// `scripts/lib/mirror-issues-banner.mjs` established — now leaves here too,
|
|
951
|
+
// alongside (not instead of) the stderr trace. `parse-error` is carried on
|
|
952
|
+
// the error by `tagParseError`; everything else is the residual
|
|
953
|
+
// `query-failed`.
|
|
810
954
|
//
|
|
811
|
-
//
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
|
|
815
|
-
// collapses every CLI-side failure onto `null`, which a caller reads as
|
|
816
|
-
// all-clear, and it cannot be widened without changing a return contract
|
|
817
|
-
// 13 sibling banners share. Same verdict, stated caller-side, in
|
|
818
|
-
// `skills/session-start/SKILL.md` § Phase 4 (the mirror-issues
|
|
819
|
-
// paragraph): "Do not reproduce it."
|
|
955
|
+
// Credentials redacted defense-in-depth (#907) — the message can quote the
|
|
956
|
+
// failed argv, which carries the repo spec — and the redacted text is what
|
|
957
|
+
// reaches BOTH the warn and the operator-facing `message`.
|
|
958
|
+
const redacted = redactUrlCredentials(msg);
|
|
820
959
|
console.warn(
|
|
821
|
-
`WARN ci-status-banner: CI status check failed, banner suppressed — ${
|
|
960
|
+
`WARN ci-status-banner: CI status check failed, banner suppressed — ${redacted}`,
|
|
822
961
|
);
|
|
823
|
-
|
|
962
|
+
// Literal reasons on BOTH branches, never `degradedResult(reason)` with a
|
|
963
|
+
// computed variable: the enum census in the test file is a regex over this
|
|
964
|
+
// source, and a variable hides the member from it.
|
|
965
|
+
if (err && typeof err === 'object' && err.degradedReason === 'parse-error') {
|
|
966
|
+
return degradedResult('parse-error', redacted);
|
|
967
|
+
}
|
|
968
|
+
return degradedResult('query-failed', redacted);
|
|
824
969
|
}
|
|
825
970
|
}
|