session-orchestrator 3.21.0 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +11 -2
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +10 -10
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +269 -0
- package/README.md +18 -15
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -1
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/memory-proposal-collector.md +6 -4
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/eli5.md +33 -0
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/commands/release.md +5 -3
- package/commands/test.md +2 -2
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +14 -0
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +7 -6
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +76 -4
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +52 -5
- package/docs/session-config-reference.md +57 -12
- package/docs/session-config-template.md +4 -3
- package/docs/telemetry.md +22 -0
- package/hooks/_lib/lock-bootstrap.mjs +26 -16
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/enforce-scope.mjs +64 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +12 -2
- package/hooks/on-session-end.mjs +237 -104
- package/hooks/on-session-start.mjs +404 -110
- package/hooks/on-stop.mjs +48 -9
- package/hooks/post-subagent-discovery-validator.mjs +34 -3
- package/hooks/post-tool-batch-wave-signal.mjs +11 -2
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +133 -21
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/monitors/monitors.json +6 -0
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +12 -8
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
- package/scripts/lib/build-live-signals.mjs +24 -5
- package/scripts/lib/ci-status-banner.mjs +221 -68
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +57 -6
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +84 -11
- package/scripts/lib/gates/gate-helpers.mjs +93 -21
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/io.mjs +239 -0
- package/scripts/lib/issue-budget.mjs +284 -31
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +5 -2
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +104 -4
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +281 -13
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +165 -7
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +90 -0
- package/scripts/lib/session-close-backfill.mjs +369 -28
- package/scripts/lib/session-discovery.mjs +13 -3
- package/scripts/lib/session-end/phase-skip.mjs +37 -4
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +159 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +111 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +467 -72
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/sync.mjs +46 -8
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
- package/scripts/lib/validate/check-hooks-symmetry.mjs +59 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-unwired-features.mjs +340 -32
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-writer.mjs +211 -10
- package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
- package/scripts/lib/vcs-repo-spec.mjs +182 -13
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/lib/wave-transcript-tail.mjs +869 -0
- package/scripts/materialize-wave-scope.mjs +478 -0
- package/scripts/mcp-server.sh +11 -2
- package/scripts/parse-config.mjs +65 -0
- package/scripts/release.mjs +443 -122
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +6 -0
- package/scripts/validate-wave-scope.mjs +73 -1
- package/scripts/vault-backfill.mjs +32 -5
- package/scripts/vault-mirror.mjs +203 -34
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +31 -25
- package/skills/_shared/parallel-aware-preamble.md +35 -4
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +61 -8
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +5 -4
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/discovery/SKILL.md +28 -24
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/gitlab-ops/SKILL.md +30 -26
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/journey-audit/SKILL.md +269 -0
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/npm-publish/SKILL.md +4 -4
- package/skills/peekaboo-driver/SKILL.md +18 -6
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +35 -23
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +44 -4
- package/skills/session-end/SKILL.md +70 -24
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +48 -5
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +96 -48
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/session-start/soul.md +110 -0
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +4 -2
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +129 -24
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/templates/_shared/journey-manifest.md +110 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -42,7 +42,7 @@ This runs BEFORE the local session-lock acquire in Phase 1.2 — the preamble's
|
|
|
42
42
|
**Outcome handling:**
|
|
43
43
|
- `PASS_THROUGH` → continue to Phase 1
|
|
44
44
|
- `EXCLUSIVE_BLOCKED` → exit Phase 0 cleanly per the AUQ outcome (`Warten` / `Andere Session beenden` / `Abbrechen` — all three return without initializing STATE.md)
|
|
45
|
-
- `PROMOTION_OFFER` with user picking "Worktree anlegen + starten" → call `enterWorktree({ basePath, sessionId, branch, repoRoot })` from `scripts/lib/autopilot/worktree-pipeline.mjs`. Compute params: `basePath = path.dirname(repoRoot)`, `sessionId` from resolveSemanticSessionId()
|
|
45
|
+
- `PROMOTION_OFFER` with user picking "Worktree anlegen + starten" → call `enterWorktree({ basePath, sessionId, branch, repoRoot })` from `scripts/lib/autopilot/worktree-pipeline.mjs`. Compute params: `basePath = path.dirname(repoRoot)`, `sessionId` from resolveSemanticSessionId() **for the worktree-name attribution label only**, `branch` from current HEAD, `repoRoot = process.cwd()`. It is not a lock/registry ownership key; the new worktree's Phase 1.2 obtains its own physical raw `session_id`. Because `branch` is the current HEAD it is normally checked out by `repoRoot` already, so `enterWorktree` treats it as a start point only and lands the promoted worktree on a fresh `so/<sessionId>` branch, returning `{ branch: 'so/<sessionId>', promotedFrom: '<branch>' }` (#1067) — the new worktree's STATE.md `branch` MUST record `so/<sessionId>` and note `promoted from <branch>@<repoRoot>`, never the source branch alone. On success, and BEFORE exiting Phase 0, call `leaveSourceRoot({ repoRoot, sessionId: <the raw physical session_id that owns this root's lock/registry entry>, semanticSessionId, reason: 'worktree-promotion' })` from `scripts/lib/session-transition.mjs`. **`sessionId` is the RAW physical `session_id` read from this root's `.orchestrator/session.lock` via `readLock({ repoRoot })` — never the semantic label, and never the id in `current-session.json`, which may describe a peer session (#863); a wrong id now aborts the teardown with `ok: false, reason: 'lock-session-mismatch:<owner>'` and removes nothing.** — the promotion is a PROCESS BOUNDARY, not a live migration (#1069): the old root is deregistered and its `session.lock` released BEFORE the new worktree's own Phase 1.2 acquires, so the two roots never both own a live claim at once. It never throws; report a `{ ok: false }` result as a stderr WARN (`parallel-aware: leaveSourceRoot: <reason>`) and continue — the destination worktree already exists, so aborting here would leave exactly the two-live-roots state the call prevents. Then exit Phase 0 immediately — the new worktree's own session-start runs from scratch (Phase 1 onwards), Phase 1.2 session-lock-acquire is the new worktree's responsibility. On enterWorktree failure (`WorktreeBoundaryError` or `git worktree add` non-zero exit), emit stderr WARN `parallel-aware: enterWorktree failed: <err>; falling back to Manuell` and proceed via the Manuell path.
|
|
46
46
|
- `PROMOTION_OFFER` with user picking "Manuell — in-place daneben" → append Deviation, continue to Phase 1
|
|
47
47
|
- `PROMOTION_OFFER` with user picking "Abbrechen" → exit cleanly
|
|
48
48
|
|
|
@@ -112,14 +112,14 @@ if (content && !isDispatcherAutonomyBlockPresent(content)) {
|
|
|
112
112
|
|
|
113
113
|
Acquire a distributed session-lock to detect parallel sessions in the same repo before initializing STATE.md. This prevents two concurrent Claude/Codex sessions from stomping each other's wave state and metrics writes.
|
|
114
114
|
|
|
115
|
-
**Mechanical wiring (Epic #583, 2026-05-27):** The SessionStart hook (`hooks/on-session-start.mjs` → `hooks/_lib/lock-bootstrap.mjs`) now writes `.orchestrator/session.lock` mechanically BEFORE this skill's prose runs. The prose Phase 1.2 becomes confirmatory — it verifies the lock exists with the expected shape via `readLock({ repoRoot: process.cwd() })`. Re-call `acquire()` only if `readLock()` returns `null` (mechanical hook failed) OR the existing lock's `session_id` does not match the current session's id (a rare divergence — surface via AUQ before overwriting). The decision flow below still applies to all three outcomes (active / stale / fs-error) when the prose path needs to acquire.
|
|
115
|
+
**Mechanical wiring (Epic #583, 2026-05-27):** The SessionStart hook (`hooks/on-session-start.mjs` → `hooks/_lib/lock-bootstrap.mjs`) now writes `.orchestrator/session.lock` mechanically BEFORE this skill's prose runs. The prose Phase 1.2 becomes confirmatory — it verifies the lock exists with the expected shape via `readLock({ repoRoot: process.cwd() })`. Re-call `acquire()` only if `readLock()` returns `null` (mechanical hook failed) OR the existing lock's raw `session_id` does not exactly match the current session's raw id (a rare divergence — surface via AUQ before overwriting). A matching `semantic_session_id`, STATE.md `session`, or owner proof cannot repair that mismatch. The decision flow below still applies to all three outcomes (active / stale / fs-error) when the prose path needs to acquire.
|
|
116
116
|
|
|
117
117
|
```javascript
|
|
118
118
|
import { acquire, forceAcquire } from 'scripts/lib/session-lock.mjs';
|
|
119
119
|
const result = acquire({ sessionId, mode: sessionType, ttlHours: 4, repoRoot: process.cwd() });
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
Where `sessionId` is the
|
|
122
|
+
Where `sessionId` is the physical raw identity for this invocation: the native harness-provided raw id, or a generated UUID when no trustworthy raw id exists. It is the only value passed to `acquire()` and the only live lock/registry ownership key. `semanticSessionId` may be recorded separately as an attribution/history label and may populate STATE.md `session`; neither label is a substitute for `sessionId`. `sessionType` is the session mode (`housekeeping`, `feature`, or `deep`).
|
|
123
123
|
|
|
124
124
|
### Decision flow
|
|
125
125
|
|
|
@@ -131,48 +131,57 @@ Where `sessionId` is the session identifier derived from the session type and ti
|
|
|
131
131
|
```js
|
|
132
132
|
AskUserQuestion({
|
|
133
133
|
questions: [{
|
|
134
|
-
question: `Another session
|
|
135
|
-
header: "Session
|
|
134
|
+
question: `Another session holds the lock here — started ${ageHours}h ago, mode=${existingLock.mode}, host=${existingLock.host}, pid=${existingLock.pid}. Wait, or take the lock?`,
|
|
135
|
+
header: "Session lock",
|
|
136
136
|
multiSelect: false,
|
|
137
137
|
options: [
|
|
138
|
-
{ label: "Abort (Recommended)", description: "
|
|
139
|
-
{ label: "Force-take the lock", description: "Overwrites the active lock.
|
|
138
|
+
{ label: "Abort (Recommended)", description: "Stop here and let the other session finish, then start again. Nothing is written until it releases the lock, and two sessions sharing one wave state overwrite each other's metrics." },
|
|
139
|
+
{ label: "Force-take the lock", description: "Overwrites the active lock and starts anyway. Only when that session is certainly gone — otherwise both keep writing the same wave state and one of them loses everything." },
|
|
140
140
|
],
|
|
141
141
|
}],
|
|
142
142
|
});
|
|
143
143
|
```
|
|
144
144
|
- **Codex CLI / Cursor IDE fallback (numbered Markdown list):**
|
|
145
145
|
```
|
|
146
|
-
|
|
147
|
-
1. Abort (Recommended) — let the other session finish.
|
|
148
|
-
2. Force-take the lock —
|
|
146
|
+
Another session holds the lock here — started <ageHours>h ago, mode=<mode>, host=<host>, pid=<pid>. Wait, or take the lock?
|
|
147
|
+
1. Abort (Recommended) — stop here and let the other session finish, then start again; nothing is written until it releases the lock.
|
|
148
|
+
2. Force-take the lock — overwrites the active lock. Only when that session is certainly gone, otherwise both keep writing the same wave state and one loses everything.
|
|
149
149
|
Reply with the number of your choice.
|
|
150
150
|
```
|
|
151
151
|
- On **Abort**: exit session-start cleanly with a brief stderr note (`session-lock: aborted — active lock held by session_id=<id>`). Do NOT initialize STATE.md.
|
|
152
152
|
- On **Force-take**: call `forceAcquire({ sessionId, mode: sessionType, ttlHours: 4, repoRoot: process.cwd() })`. After Phase 1.5 initializes STATE.md, append a deviation via `appendDeviation()`:
|
|
153
153
|
`Force-took session lock from session_id=<existingLock.session_id>, age=<ageHours>h, mode=<existingLock.mode>, pid=<existingLock.pid>`. Continue.
|
|
154
154
|
|
|
155
|
-
3. **`result.ok === false`** with `reason === 'stale-
|
|
156
|
-
- A stale lock was found (
|
|
155
|
+
3. **`result.ok === false`** with `reason === 'stale-heartbeat'`:
|
|
156
|
+
- A stale lock was found (its last heartbeat is older than its ttl). Likely left behind by a session that crashed or was force-killed. The lock's recorded `pid` is NOT consulted — it belongs to the ephemeral hook subprocess that wrote the lock, never to the session; measured 2026-08-23: 7 of 7 recorded pids were dead, including the live heartbeating session's own (#1137).
|
|
157
157
|
- Present a choice via `AskUserQuestion`:
|
|
158
158
|
```js
|
|
159
|
+
// `heartbeatAgeMinutes` and `ageHours` come straight off the acquire() result (#1137);
|
|
160
|
+
// `sameHost` is not on the result — compute it first. Use hostnamesMatch, NEVER a raw
|
|
161
|
+
// `===` against os.hostname(): the hostname flips spelling on a single machine
|
|
162
|
+
// (measured 2026-08-24: `Mac.home` and `Ferdinands-MacBook-Pro.local` ten minutes apart),
|
|
163
|
+
// so a raw comparison labels this machine's OWN lock "another machine" (#1072).
|
|
164
|
+
// `||`, not `??` — an EMPTY-STRING host_id must fall back to `host`, or
|
|
165
|
+
// hostnamesMatch('', …) is false and this machine reads its own lock as
|
|
166
|
+
// cross-host. Production uses `lockHostCandidate()` from host-identity.mjs.
|
|
167
|
+
const sameHost = hostnamesMatch(existingLock.host_id || existingLock.host, os.hostname());
|
|
159
168
|
AskUserQuestion({
|
|
160
169
|
questions: [{
|
|
161
|
-
question: `
|
|
162
|
-
header: "Stale
|
|
170
|
+
question: `A stale session lock is in the way — started ${ageHours}h ago on host=${existingLock.host}${sameHost ? '' : ' (another machine)'}, its ttl=${existingLock.ttl_hours}h has expired, and its last heartbeat was ${Math.round(heartbeatAgeMinutes)} minutes ago. Reclaim it?`,
|
|
171
|
+
header: "Stale lock",
|
|
163
172
|
multiSelect: false,
|
|
164
173
|
options: [
|
|
165
|
-
{ label: "Reclaim (Recommended)", description: "
|
|
166
|
-
{ label: "Abort — investigate manually", description: "
|
|
174
|
+
{ label: "Reclaim (Recommended)", description: "Overwrites the stale lock and continues, because its time-to-live has run out. When that process is really dead, nothing of the old session is lost." },
|
|
175
|
+
{ label: "Abort — investigate manually", description: "Stops here and writes nothing. The lock file `.orchestrator/session.lock` (it names the process that wrote it) tells you whether that session is still alive." },
|
|
167
176
|
],
|
|
168
177
|
}],
|
|
169
178
|
});
|
|
170
179
|
```
|
|
171
180
|
- **Codex CLI / Cursor IDE fallback (numbered Markdown list):**
|
|
172
181
|
```
|
|
173
|
-
|
|
174
|
-
1. Reclaim (Recommended) —
|
|
175
|
-
2. Abort —
|
|
182
|
+
A stale session lock is in the way — started <ageHours>h ago on <host>, ttl=<ttlHours>h expired, last heartbeat <heartbeatAgeMinutes> minutes ago. Reclaim it?
|
|
183
|
+
1. Reclaim (Recommended) — overwrites the stale lock and continues, because its time-to-live has run out and that process is no longer holding anything.
|
|
184
|
+
2. Abort — stops here and writes nothing. The lock file `.orchestrator/session.lock` (it names the process that wrote it) tells you whether that session is still alive.
|
|
176
185
|
Reply with the number of your choice.
|
|
177
186
|
```
|
|
178
187
|
- On **Reclaim**: call `forceAcquire({ sessionId, mode: sessionType, ttlHours: 4, repoRoot: process.cwd() })`. After Phase 1.5 initializes STATE.md, append a deviation:
|
|
@@ -186,21 +195,24 @@ Where `sessionId` is the session identifier derived from the session type and ti
|
|
|
186
195
|
|
|
187
196
|
### Cross-host behaviour
|
|
188
197
|
|
|
189
|
-
When `existingLock.host !== os.hostname()`,
|
|
198
|
+
When `hostnamesMatch(existingLock.host_id || existingLock.host, os.hostname())` is **false** — never a raw `existingLock.host !== os.hostname()`, which labels this machine's own lock "another machine" the moment the hostname flips spelling (#1072; mirror the Phase-1.2 snippet above) — the lock was written on another machine and nothing local can corroborate its heartbeat. `checkStale()` carries no `pidAlive` field at all (REMOVED in #1151; #1137 had left it as an always-`null` stub) — `heartbeatAgeMinutes` is the magnitude to reason from, and `isLive` the verdict. In this case:
|
|
190
199
|
- For `reason === 'active'`: the recommendation is **Abort** — cross-host locks cannot be verified as dead.
|
|
191
200
|
- For stale reasons: the recommendation is still **Reclaim** only if TTL is clearly expired (>2× ttl_hours). Otherwise default to **Abort**.
|
|
192
201
|
- **Never auto-reclaim cross-host locks** under any circumstance — always present the AUQ and let the user decide.
|
|
193
|
-
- The AUQ question text for cross-host cases should note: `"(cross-host —
|
|
202
|
+
- The AUQ question text for cross-host cases should note: `"(cross-host — the heartbeat cannot be corroborated locally)"`. Do NOT phrase it as PID liveness: the pid on a lock belongs to the ephemeral writer subprocess, not the session, and is never probed (#1137/#1151).
|
|
194
203
|
|
|
195
204
|
## Phase 1.2.1: Peer-Guard (Epic #583 defense-in-depth)
|
|
196
205
|
|
|
197
206
|
> Skip this phase if `persistence` config is `false`.
|
|
198
207
|
|
|
199
|
-
After Phase 1.2 acquires (or confirms) the lock,
|
|
208
|
+
After Phase 1.2 acquires (or confirms) the lock, use `findPeers(repoRoot, { mySessionId: callerSessionHint })` for the STATE.md peer guard. `callerSessionHint` is the original semantic attribution label when one exists, otherwise the raw `sessionId`: `findPeers` may translate the semantic hint for the discovered lock/registry surface only after the exact raw binding check in `parallel-aware-preamble.md`, while keeping the original hint for STATE.md. This catches the rare case where lock-based detection missed an active peer (e.g., the peer's `session.lock` was force-deleted by an out-of-band sweep but STATE.md is still `status: active`, OR the peer's registry write succeeded but the lock-bootstrap hook crashed before the lock landed).
|
|
200
209
|
|
|
201
210
|
```javascript
|
|
202
211
|
import { findPeers } from '$PLUGIN_ROOT/scripts/lib/peer-discovery.mjs';
|
|
203
|
-
|
|
212
|
+
// Keep the STATE.md comparison in its original attribution-label space.
|
|
213
|
+
// findPeers performs the guarded semantic→raw translation only for discovered peers.
|
|
214
|
+
const callerSessionHint = semanticSessionId ?? sessionId;
|
|
215
|
+
const { peers } = await findPeers(process.cwd(), { mySessionId: callerSessionHint });
|
|
204
216
|
const peer = peers.find((p) => p.source === 'state-md') ?? null;
|
|
205
217
|
// Phase 1.2.1 consumes only the 'state-md' subset (STATE.md surface only).
|
|
206
218
|
if (peer) {
|
|
@@ -214,7 +226,7 @@ if (peer) {
|
|
|
214
226
|
|
|
215
227
|
1. **`peer === null`** → no active peer owns STATE.md. Continue to Phase 1.5.
|
|
216
228
|
2. **`peer !== null`** → STATE.md is owned by a live peer session. **Do NOT proceed with the default Phase 1.5/1b STATE.md overwrite.** Fire the Worktree-Promotion AUQ from `skills/_shared/parallel-aware-auq.md` (same options the Phase 0.5 preamble would emit on `PROMOTION_OFFER`).
|
|
217
|
-
- User picks "Worktree anlegen + starten" → call `enterWorktree(...)` and exit Phase 1 immediately (the new worktree's own session-start runs from scratch).
|
|
229
|
+
- User picks "Worktree anlegen + starten" → call `enterWorktree(...)`, then — BEFORE exiting Phase 1 — `leaveSourceRoot({ repoRoot, sessionId, semanticSessionId, reason: 'worktree-promotion' })` from `scripts/lib/session-transition.mjs` — `sessionId` is the RAW physical `session_id` read from this root's `.orchestrator/session.lock` via `readLock({ repoRoot })`, never the semantic label and never `current-session.json` (which may describe a peer, #863) — (#1069 process boundary: this site runs AFTER Phase 1.2 already acquired the lock, so the old root MUST be deregistered and its lock released here, or the new worktree's own Phase 1.2 finds a phantom owner). It never throws; on `{ ok: false }` emit a stderr WARN `parallel-aware: leaveSourceRoot: <reason>` and continue — the destination worktree already exists, so aborting here would leave exactly the two-live-roots state the call prevents. Then exit Phase 1 immediately (the new worktree's own session-start runs from scratch).
|
|
218
230
|
- User picks "Manuell — in-place daneben" → append a Deviation describing the missed peer detection, continue to Phase 1.5. STATE.md WILL be overwritten — the user has explicitly accepted that risk.
|
|
219
231
|
- User picks "Abbrechen" → exit cleanly.
|
|
220
232
|
|
|
@@ -367,29 +379,60 @@ If `snaps.length >= 1` → present the following choice:
|
|
|
367
379
|
|
|
368
380
|
**Claude Code (AskUserQuestion):**
|
|
369
381
|
|
|
382
|
+
Before asking, read what "Recover" would actually put back — the operator decides on that diff, not on the word:
|
|
383
|
+
|
|
384
|
+
```js
|
|
385
|
+
import { execFileSync } from 'node:child_process';
|
|
386
|
+
|
|
387
|
+
// Read-only: `git stash show` prints a diffstat and never touches the working tree.
|
|
388
|
+
// Capped at 12 lines so the preview box stays shorter than the option list beside it.
|
|
389
|
+
const stat = execFileSync('git', ['stash', 'show', '--stat', snaps[0].sha], { encoding: 'utf8' })
|
|
390
|
+
.split('\n').slice(0, 12).join('\n');
|
|
391
|
+
const refs = snaps.map((s) => s.ref).join('\n');
|
|
392
|
+
```
|
|
393
|
+
|
|
370
394
|
```js
|
|
371
395
|
AskUserQuestion({
|
|
372
396
|
questions: [{
|
|
373
|
-
question:
|
|
397
|
+
question: `${snaps.length} snapshot(s) from the resumed session, newest ${humanAgeOf(snaps[0].createdAt)}. Recover, keep, discard?`,
|
|
374
398
|
header: "Snapshot",
|
|
375
399
|
multiSelect: false,
|
|
376
400
|
options: [
|
|
377
|
-
{
|
|
378
|
-
|
|
379
|
-
|
|
401
|
+
{
|
|
402
|
+
label: "Recover (Recommended)",
|
|
403
|
+
description: "Puts the newest saved state back into your working tree and commits nothing. You can drop any of those changes afterwards.",
|
|
404
|
+
preview: `These files come back:\n\n\`\`\`\n${stat}\n\`\`\``,
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
label: "Keep as backup",
|
|
408
|
+
description: "Nothing happens now: `refs/so-snapshots/*` (the saved states) stay, and `git stash apply $(git rev-parse <ref>)` (this puts one back) works later.",
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
label: "Discard all",
|
|
412
|
+
description: "Deletes every saved state of this session for good: `refs/so-snapshots/<sessionId>/*` (all of them) is gone, and there is no second copy.",
|
|
413
|
+
preview: `Deleted for good:\n\n\`\`\`\n${refs}\n\`\`\``,
|
|
414
|
+
},
|
|
380
415
|
],
|
|
381
416
|
}],
|
|
382
417
|
});
|
|
383
418
|
```
|
|
384
419
|
|
|
420
|
+
`preview` renders beside the option list and only works with `multiSelect: false`. It is used here because the answer decides which literal text lands in the working tree — "Recover" is a diff, "Discard all" is a list of refs that stop existing. "Keep as backup" carries none: keeping is exactly the state the operator already sees.
|
|
421
|
+
|
|
385
422
|
**Codex CLI / Cursor IDE fallback (numbered Markdown list):**
|
|
386
423
|
|
|
424
|
+
These harnesses have no preview box, so the same diffstat is printed inline — it is the only place the operator ever sees it:
|
|
425
|
+
|
|
387
426
|
```markdown
|
|
388
|
-
|
|
427
|
+
"Recover" would put these files back:
|
|
428
|
+
|
|
429
|
+
<git stash show --stat <snaps[0].sha>, capped at 12 lines>
|
|
430
|
+
|
|
431
|
+
<N> snapshot(s) from the resumed session, newest <age>. Recover, keep, discard?
|
|
389
432
|
|
|
390
|
-
1. **Recover (Recommended)** —
|
|
391
|
-
2. **Keep as backup** —
|
|
392
|
-
3. **Discard all** —
|
|
433
|
+
1. **Recover (Recommended)** — puts the newest saved state back into your working tree and commits nothing. You can drop any of those changes afterwards.
|
|
434
|
+
2. **Keep as backup** — nothing happens now: `refs/so-snapshots/*` (the saved states) stay, and `git stash apply $(git rev-parse <ref>)` (this puts one back) works later.
|
|
435
|
+
3. **Discard all** — deletes every saved state of this session for good: `refs/so-snapshots/<sessionId>/*` (all of them) is gone, and there is no second copy.
|
|
393
436
|
|
|
394
437
|
Reply with the number of your choice.
|
|
395
438
|
```
|
|
@@ -471,7 +514,7 @@ await sweepBoard({
|
|
|
471
514
|
|
|
472
515
|
This single call does three things:
|
|
473
516
|
|
|
474
|
-
1. **Sets THIS repo's board row to `in-progress`** with the current semantic-session-id, branch, mode, and heartbeat (read off this repo's `session.lock` v2 lease + the host-wide registry — both already written by Phase 1.2's `acquire()`).
|
|
517
|
+
1. **Sets THIS repo's board row to `in-progress`** with the current semantic-session-id **attribution label** (never a lock/registry ownership key), branch, mode, and heartbeat (read off this repo's `session.lock` v2 lease + the host-wide registry — both already written by Phase 1.2's `acquire()`).
|
|
475
518
|
2. **Re-derives THIS repo's status from its live lease**, so a stale lease left by a prior crashed session in this same repo renders as `force-closed` (heartbeat older than the v2 ttl, default 4h — `DEFAULT_TTL_HOURS` in `scripts/lib/session-lock.mjs`, evaluated via `isLockLive`) and is **never silently dropped** — its fields are read straight off the dead lock.
|
|
476
519
|
3. **Re-derives every OTHER busy repo's status host-wide** via `enumerateCandidates` — a dead lease in repo B renders `force-closed` on the board the next time ANY repo's session-start runs `sweepBoard`, closing the #676→#716 gap. `frei` (lock-less) repos are excluded from re-derivation to avoid board noise; their prior rows, and the prior rows of any repo `enumerateCandidates` did not surface, are preserved unchanged via the idempotent merge — never dropped.
|
|
477
520
|
|
|
@@ -676,13 +719,14 @@ Group issues by:
|
|
|
676
719
|
- **info-only version mismatch** (patch or minor version only): `"ℹ bootstrap.lock: plugin-version=<lock-ver> (current=<plugin-ver>) — minor drift only, no action required."`
|
|
677
720
|
- **legacy lock without plugin-version** (soft signal only): `"ℹ bootstrap.lock: lock predates plugin-version field; consider /bootstrap --refresh-lock to stamp a current plugin-version reference."`
|
|
678
721
|
|
|
679
|
-
Additionally, if `.orchestrator/metrics/vault-staleness.jsonl` exists in the current repo (vault-integration enabled), read the most recent line via `scripts/lib/vault-staleness-banner.mjs` (`checkVaultStaleness({repoRoot})`).
|
|
722
|
+
Additionally, if `.orchestrator/metrics/vault-staleness.jsonl` exists in the current repo (vault-integration enabled), read the most recent line via `scripts/lib/vault-staleness-banner.mjs` (`checkVaultStaleness({repoRoot})`). The helper returns THREE shapes, not two: `null` (silent no-op) when the JSONL is absent, malformed, or `stale_count === 0`; a fresh finding when `stale_count > 0` and the record itself is recent; or `{severity: 'warn', kind: 'probe-stale', message, ageDays, timestamp}` when `stale_count > 0` but the record's own `timestamp` is older than `MAX_RECORD_AGE_DAYS` (7 days, #1159) — the probe has stopped running, so its recorded findings are NOT a current finding about the repo. `severity` is `'warn'` in this case too (not a distinct `'info'` value) so a caller reading only `severity` still renders it; `kind` is what a caller uses to tell "probe went stale" apart from "projects are actually stale" (single-vocabulary fix, #1158/#1159 review N3). Render a banner alongside the bootstrap-lock warning for every non-null result:
|
|
680
723
|
- **warn** (`stale_count > 0`, max `delta_hours <= 48`): `"⚠ vault-staleness: <N> projects stale (max delta: <X>h) — last run <timestamp>."`
|
|
681
724
|
- **alert** (`stale_count > 0`, max `delta_hours > 48`): `"⚠ vault-staleness: <N> projects stale (max delta: <X>h) — Clank-Vault-Sync cron likely broken, see agents/vault#70 fix pattern."`
|
|
725
|
+
- **probe-stale** (`kind: 'probe-stale'`): `"⚠ vault-staleness: last probe record is <N> days old (<timestamp>) — the probe has not run since; the recorded <N> stale projects are NOT a current finding."`
|
|
682
726
|
|
|
683
|
-
|
|
727
|
+
Skip silently on `null`; do not block the session in any case. This passage does not restate a severity-mapping table for the probe-stale shape — the `vault-staleness` entry in the probe registry (`scripts/lib/session-start-probes.mjs`, `PROBES`) is the single declaration site for how a probe's result maps onto the rendered banner severity, and (post-N3) needs no custom mapping for this probe at all.
|
|
684
728
|
|
|
685
|
-
Additionally, if the current repo has a configured `origin` remote and `glab` (GitLab) or `gh` (GitHub) is available, invoke the CI-status probe (`scripts/lib/ci-status-banner.mjs`) via `checkCiStatus({ repoRoot: process.cwd() })
|
|
729
|
+
Additionally, if the current repo has a configured `origin` remote and `glab` (GitLab) or `gh` (GitHub) is available, invoke the CI-status probe (`scripts/lib/ci-status-banner.mjs`) via `await checkCiStatus({ repoRoot: process.cwd() })` — it is `export async function` (`ci-status-banner.mjs:555`), so a caller following this line without `await` gets a Promise and renders nothing. The helper returns `null` (silent no-op) when no VCS remote, no CLI tool, parse failure, or CLI timeout (8s default). When `result.status === 'red'`, render a banner alongside the bootstrap-lock and vault-staleness warnings:
|
|
686
730
|
- **Red** (`status === 'red'`): `"🚨 CI RED on HEAD (pipeline #<currentPipelineId>) — last green: #<lastGreen.pipelineId> (commit <SHA-7>, <redCount> pipelines ago). Failing job: <failingJobName>"`
|
|
687
731
|
- **Green with soft failures** (`status === 'green'` AND `result.allowFailureJobs` is present): `"⚠ CI green on HEAD, but <N> allow_failure job(s) FAILED: <names>. A pipeline reports success regardless of these — a job red on every run stays invisible at the pipeline level."` Render this even though the pipeline passed: the whole point is that pipeline status cannot express it.
|
|
688
732
|
- **Green** (no `allowFailureJobs`) or **unknown**: silent (no banner) — informational only.
|
|
@@ -789,7 +833,9 @@ Group issues by:
|
|
|
789
833
|
|
|
790
834
|
The return contract has THREE states, not the usual two, and the third is the point: `null` means either "no mirror remote" or "queried successfully, zero open issues"; `{ severity, message, count, repoSpec, issues }` means N > 0; and `{ severity, message, repoSpec, degraded }` means the query did NOT succeed, where `degraded` is one of `cli-missing | timeout | parse-error | auth-error | query-failed`. Render `result.message` verbatim in either non-null case. A `degraded` result must be read as *"the mirror's state is unknown"* — never as clean. `scripts/lib/ci-status-banner.mjs` collapses all three of missing-CLI, unparseable output and absent-remote onto `null`, which in the banner contract reads as "all clear"; that collapse is why this gap survived unseen. Do not reproduce it.
|
|
791
835
|
|
|
792
|
-
Additionally, invoke the git-config-drift probe (`scripts/lib/git-config-drift.mjs`) via `checkGitConfigDrift({ repoRoot })` (synchronous — no await; `env` defaults to `process.env`). It reads `git config --local --list` with a FILTERED environment, so an ambient `GIT_DIR` cannot redirect the probe itself at a foreign repository and let it call this one clean. **Three states, not two:** `null` = read and clean; `{ severity: 'warn', message, findings }` = at least one unexpected entry (a local identity override, a local `commit.gpgsign`, a remote on a reserved fixture host, a `core.hooksPath` not pointing at `.husky/_
|
|
836
|
+
Additionally, invoke the git-config-drift probe (`scripts/lib/git-config-drift.mjs`) via `checkGitConfigDrift({ repoRoot })` (synchronous — no await; `env` defaults to `process.env`). It reads `git config --local --list` with a FILTERED environment, so an ambient `GIT_DIR` cannot redirect the probe itself at a foreign repository and let it call this one clean. **Three states, not two:** `null` = read and clean; `{ severity: 'warn', message, findings }` = at least one unexpected entry (a local identity override, a local `commit.gpgsign`, a remote on a reserved fixture host, a `core.hooksPath` not pointing at `.husky/_` AND not DECLARED by the repo, or `GIT_DIR`/`GIT_WORK_TREE` set in the environment); `{ …, degraded }` = the config could NOT be read — **never render that as clean.** Render `result.message` alongside the other banners.
|
|
837
|
+
|
|
838
|
+
The `core.hooksPath` trigger is narrower than "points somewhere other than `.husky/_`" — a hooksPath the repo DECLARES is accepted without any config key. Declared means `git ls-files -- <hooksPath>` finds at least one TRACKED file DIRECTLY under it (not nested deeper) whose BASENAME is a real git hook name (`pre-commit`, `pre-push`, `commit-msg`, …) — a tracked directory of ordinary source, or an untracked file merely named like a hook, does not count. This document does not restate the hook-name list — `GIT_HOOK_NAMES` in `scripts/lib/git-config-drift.mjs` (sourced from `git help hooks`) is the single declaration site (#1158 review N1 — the first cut of this rule accepted any tracked file under the path at all, which a fixture-planted `scripts/pre-commit` left untracked, sitting beside ordinary tracked source, would have bypassed silently).
|
|
793
839
|
|
|
794
840
|
This is the only probe that inspects `.git/config`, and that is the whole point: `git status` cannot see that file. On 2026-08-19 a coordinator diagnostic exported `GIT_DIR` at this repository while the suite ran; test fixtures wrote a foreign remote and their own `user.email`/`user.name` into the local config, and the identity then authored two commits that reached both remotes. A recovery pass checked HEAD, the index and all 1614 tracked files, found everything clean, and missed it — because none of those surfaces show `.git/config`. It surfaced two hours later, from an agent measuring something else.
|
|
795
841
|
|
|
@@ -1027,6 +1073,8 @@ Cross-reference: PRD F2.3 acceptance criteria (#505); `scripts/lib/memory-banner
|
|
|
1027
1073
|
|
|
1028
1074
|
> Skip this phase silently when `persistence: false` in Session Config. Also skip silently when non-interactive (headless / CI — no TTY to prompt on), and when the consent decision has already been made (stored `granted`/`denied`, an env override, or the fleet flag). In all of these `resolveConsent().prompt` is `false` and the phase is a no-op — it must NEVER print anything or slow session-start in the common (already-decided / headless) case.
|
|
1029
1075
|
|
|
1076
|
+
> **The trigger is MECHANICAL since #1138.** `hooks/on-session-start.mjs` calls `resolveConsent()` itself and, when `prompt === true` and the run is not CI, injects a one-line instruction into the session via `hookSpecificOutput.additionalContext`. This phase is the WORDING and the fallback — the AUQ text below is the single source of truth for what gets asked — but it is no longer what decides *whether* to ask. Two consequences: (a) the coordinator may receive that instruction before it ever reaches this line, and should act on it then; (b) the hook gates on `isCiEnv()`, **not** `!isHeadless()` as the snippet below does — measured 2026-08-23, `isHeadless()` returns `true` in ANY non-TTY subprocess (`isHeadless()=true isCiEnv()=false stdout.isTTY=undefined`), which includes both a hook process and the `node -e` a coordinator would run this snippet in. Executed verbatim in a Bash tool call, the snippet below therefore resolves `prompt: false` every time; keep it as the semantic reference, and trust the hook for the firing decision.
|
|
1077
|
+
|
|
1030
1078
|
Anonymous usage telemetry is **strictly opt-in** and, on a host that has never decided, is offered exactly once via a single interactive AskUserQuestion. The consent machine lives in `scripts/lib/telemetry/consent.mjs`; this phase only decides *whether* to prompt and then records the operator's answer. The `resolveConsent()` precedence machine is fail-closed — `prompt` is `true` only for a fresh, interactive, not-yet-decided, not-fleet, not-env-overridden host.
|
|
1031
1079
|
|
|
1032
1080
|
```javascript
|
|
@@ -1049,12 +1097,12 @@ if (!c.prompt) {
|
|
|
1049
1097
|
```js
|
|
1050
1098
|
AskUserQuestion({
|
|
1051
1099
|
questions: [{
|
|
1052
|
-
question: "Anonyme Usage-Telemetrie aktivieren? Strikt opt-in,
|
|
1053
|
-
header: "
|
|
1100
|
+
question: "Anonyme Usage-Telemetrie aktivieren? Strikt opt-in, jederzeit abschaltbar; was genau gesendet wird: docs/telemetry.md",
|
|
1101
|
+
header: "Telemetrie",
|
|
1054
1102
|
multiSelect: false,
|
|
1055
1103
|
options: [
|
|
1056
|
-
{ label: "Ja, aktivieren", description: "
|
|
1057
|
-
{ label: "Nein", description: "
|
|
1104
|
+
{ label: "Ja, aktivieren", description: "Sendet anonyme Zähl- und Strukturdaten (welche Phase lief, Erfolg oder Abbruch), whitelist-projiziert: keine Pfade, keine Prompts, keine Repo-Namen." },
|
|
1105
|
+
{ label: "Nein", description: "Sendet nichts; die Frage kommt hier nicht wieder. Einschalten geht später mit `node scripts/telemetry.mjs` (das ist der Befehl dafür)." },
|
|
1058
1106
|
],
|
|
1059
1107
|
}],
|
|
1060
1108
|
});
|
|
@@ -1064,9 +1112,9 @@ AskUserQuestion({
|
|
|
1064
1112
|
|
|
1065
1113
|
- **Codex CLI / Cursor IDE fallback (numbered Markdown list — AUQ-004 exception 1):**
|
|
1066
1114
|
```
|
|
1067
|
-
Anonyme Usage-Telemetrie aktivieren? Strikt opt-in,
|
|
1068
|
-
1. Ja, aktivieren —
|
|
1069
|
-
2. Nein —
|
|
1115
|
+
Anonyme Usage-Telemetrie aktivieren? Strikt opt-in, jederzeit abschaltbar; was genau gesendet wird: docs/telemetry.md
|
|
1116
|
+
1. Ja, aktivieren — sendet anonyme Zähl- und Strukturdaten (welche Phase lief, Erfolg oder Abbruch), whitelist-projiziert: keine Pfade, keine Prompts, keine Repo-Namen.
|
|
1117
|
+
2. Nein — sendet nichts; die Frage kommt hier nicht wieder. Einschalten geht später mit `node scripts/telemetry.mjs` (das ist der Befehl dafür).
|
|
1070
1118
|
Reply with the number of your choice. (No option is pre-recommended — the choice is yours.)
|
|
1071
1119
|
```
|
|
1072
1120
|
|
|
@@ -1146,13 +1194,13 @@ Present your findings following that structure. Key rules:
|
|
|
1146
1194
|
|
|
1147
1195
|
### Phase 8.5: Express Path Evaluation (#214)
|
|
1148
1196
|
|
|
1149
|
-
After the user confirms session type and scope, evaluate whether the Express Path applies. Activation requires ALL three: `express-path.enabled: true` in Session Config (default: `true
|
|
1197
|
+
After the user confirms session type and scope, evaluate whether the Express Path applies. **Do not judge the conditions by hand — run `node scripts/express-path.mjs --repo-root "$PWD" --session-type <type> --task-count <N> --parallel-agents <true|false>`.** That CLI is the canonical caller (#1146): it makes the decision AND records it as `orchestrator.express_path.evaluated`, on refusal as well as activation. stdout is one JSON line `{"activated":<bool>,"reasons":[…]}`; exit 0 means the evaluation completed, so branch on `activated`, never on the exit code. Activation requires ALL three: `express-path.enabled: true` in Session Config (default: `true`; an explicit `false` still runs the evaluation and records `disabled-by-config`, then the normal 5-wave session-plan flow proceeds), session type `housekeeping`, and scope ≤ 3 sequential issues. The 13 prior coordinator-direct sessions in `CLAUDE.md` (or `AGENTS.md` on Codex CLI; 2026-04 series) were all running this pattern implicitly — this phase codifies what was already proven to work.
|
|
1150
1198
|
|
|
1151
|
-
When all conditions are met, emits the banner:
|
|
1199
|
+
When all conditions are met, the CLI emits the banner on stderr:
|
|
1152
1200
|
```
|
|
1153
1201
|
Express path activated — <N> tasks, coordinator-direct, no inter-wave checks.
|
|
1154
1202
|
```
|
|
1155
|
-
|
|
1203
|
+
Carry that banner into Phase 9 and hand off to session-plan as usual — session-plan short-circuits to a 1-wave `coordinator-direct` plan, which is the artifact `/go` detects. Tasks are then executed coordinator-direct (bypassing wave-executor, subagent dispatch and inter-wave checkpoints) and a Deviations entry is logged in STATE.md. Silent no-op when any condition fails — proceeds normally to Phase 9.
|
|
1156
1204
|
|
|
1157
1205
|
**See `phase-8-5-express-path.md` for full details.**
|
|
1158
1206
|
|
|
@@ -1173,7 +1221,7 @@ After user alignment:
|
|
|
1173
1221
|
|
|
1174
1222
|
- **NEVER make assumptions** about code state based on memory or docs — always verify in actual files
|
|
1175
1223
|
- **NEVER skip the Q&A phase** — the user MUST confirm direction before wave planning
|
|
1176
|
-
- **ALWAYS
|
|
1224
|
+
- **ALWAYS verify parallel subagent work against the started set**, never against the launch ack — `run_in_background: true` is allowed and recommended for wave dispatch (`skills/wave-executor/wave-loop.md § Started-Set Verification`); skills that need every result before their next phase (persona-panel, discovery, test-runner, session-end) keep `false` and say why
|
|
1177
1225
|
- **ALWAYS check `.env` or `.env.local`** for VCS host, API keys, and service URLs
|
|
1178
1226
|
- **ALWAYS present options with pros/cons and a clear recommendation** — never just list facts
|
|
1179
1227
|
- **ALWAYS update VCS issue status** when claiming work — use the issue update command per the "Common CLI Commands" section of the gitlab-ops skill
|
|
@@ -58,15 +58,15 @@ AskUserQuestion({
|
|
|
58
58
|
options: [
|
|
59
59
|
{
|
|
60
60
|
label: "Dev (Recommended)", // add "(Recommended)" to each detected audience
|
|
61
|
-
description: "Architektur-, Modul
|
|
61
|
+
description: "Architektur-, Modul-, Refactoring-Änderungen. Dann ändern sich CLAUDE.md bzw. AGENTS.md und `docs/dev/**, docs/adr/**` (Handbuch und Entscheidungen)."
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
label: "User",
|
|
65
|
-
description: "Öffentlich sichtbare Änderungen
|
|
65
|
+
description: "Öffentlich sichtbare Änderungen. Dann werden README.md und `docs/user/**, examples/**` (was Nutzer davon lesen) nachgezogen."
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
label: "Vault",
|
|
69
|
-
description: "Strategische oder Status-Änderungen
|
|
69
|
+
description: "Strategische oder Status-Änderungen. Dann wird `<vault>/01-projects/<slug>/context.md, decisions.md, people.md` (die Projektakte dazu) nachgezogen."
|
|
70
70
|
}
|
|
71
71
|
]
|
|
72
72
|
}]
|
|
@@ -76,13 +76,13 @@ AskUserQuestion({
|
|
|
76
76
|
**Codex CLI / Cursor IDE fallback (numbered Markdown list):**
|
|
77
77
|
|
|
78
78
|
```markdown
|
|
79
|
-
Welche Audiences berührt dieser Scope? (Mehrfachauswahl möglich)
|
|
80
|
-
|
|
81
79
|
Auto-detected: [dev] ← list detected audiences here, or "none" if empty intersection
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
Welche Audiences berührt dieser Scope? Mehrfachauswahl möglich.
|
|
82
|
+
|
|
83
|
+
1. **Dev (Recommended)** — Architektur-, Modul-, Refactoring-Änderungen. Dann ändern sich CLAUDE.md bzw. AGENTS.md und `docs/dev/**, docs/adr/**` (Handbuch und Entscheidungen).
|
|
84
|
+
2. **User** — öffentlich sichtbare Änderungen. Dann werden README.md und `docs/user/**, examples/**` (was Nutzer davon lesen) nachgezogen.
|
|
85
|
+
3. **Vault** — strategische oder Status-Änderungen. Dann wird `<vault>/01-projects/<slug>/context.md, decisions.md, people.md` (die Projektakte dazu) nachgezogen.
|
|
86
86
|
|
|
87
87
|
Enter one or more numbers (comma-separated), or press Enter to accept the recommended default.
|
|
88
88
|
```
|
|
@@ -16,31 +16,88 @@ const verdict = evaluate(snapshot, config['resource-thresholds'], {
|
|
|
16
16
|
});
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The `evaluate()` result has
|
|
20
|
-
- `verdict`: `green` | `warn` | `critical`
|
|
21
|
-
- `reasons`: array of human-readable explanations
|
|
19
|
+
The `evaluate()` result has four fields:
|
|
20
|
+
- `verdict`: `green` | `warn` | `critical` (the `degraded` tier is no longer produced)
|
|
21
|
+
- `reasons`: array of human-readable explanations, including `info:`-prefixed
|
|
22
|
+
lines for signals that were seen but deliberately not acted on
|
|
22
23
|
- `recommended_agents_per_wave_cap`: integer cap (0 = coordinator-direct) or null
|
|
24
|
+
- `signals`: `{ hard: string[], soft: string[] }` — which axes fired (#1089)
|
|
23
25
|
|
|
24
26
|
The third `options` argument is optional (HR-003/HR-004, baseline #60) — when `config['heavy-repo']` is `true`, the cap is forced to at most `config['agents-per-wave']` regardless of the live verdict (static preflight ceiling; more-restrictive-wins against whatever the resource signals already computed). Omitting `options` entirely preserves pre-#60 behaviour.
|
|
25
27
|
|
|
26
|
-
## Adaptive Rules (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
## Adaptive Rules (rebuilt in #1089 — see `.claude/rules/host-resources.md`)
|
|
29
|
+
|
|
30
|
+
The rule set is **signal precedence + the two-signal rule**, not a list of
|
|
31
|
+
independent thresholds ORed together. Measured 2026-08-21 over 1477
|
|
32
|
+
`orchestrator.session.started` events across 18 repos, the previous OR-of-three
|
|
33
|
+
produced warn-or-worse on **99.0%** of session starts — a warning that fires
|
|
34
|
+
almost always changes no decision except how fast it gets ignored.
|
|
35
|
+
|
|
36
|
+
**Memory — judge on the best signal present, never on a worse one:**
|
|
37
|
+
|
|
38
|
+
| Precedence | Signal | Hard (→ critical) | Soft |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| 1 | `memory_pressure_pct_free` (macOS) | `< 15%` | `< 30%` |
|
|
41
|
+
| 2 | `ram_available_gb` (macOS, vm_stat) | `< ram-free-critical-gb` | `< ram-free-min-gb` |
|
|
42
|
+
| 3 | `ram_free_gb` (`os.freemem`) | same | same |
|
|
43
|
+
|
|
44
|
+
Level 3 is reached only when neither better signal exists — i.e. on
|
|
45
|
+
Linux/Windows, where `os.freemem()` is accurate. On Darwin it reports `Pages
|
|
46
|
+
free` only (median **0.4 GB** across the corpus, on 24-128 GB hosts), so gating
|
|
47
|
+
on it there fired the *critical* threshold on 84.0% of starts.
|
|
48
|
+
|
|
49
|
+
**Other axes:**
|
|
50
|
+
|
|
51
|
+
| Signal | Threshold | Class |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| CPU, judged on **min(1m, 5m)** (#943) | above `cpu-load-max-pct` (default 90) | soft |
|
|
54
|
+
| Live peer **sessions** from the registry | ≥ `concurrent-sessions-warn` (default 5) | soft |
|
|
55
|
+
| Claude **processes** (fallback only, registry unreadable) | ≥ threshold × 6 | soft |
|
|
56
|
+
| Swap, **only while memory is unhealthy** | `> 3072 MB` hard / `> 1024 MB` soft | both |
|
|
57
|
+
| Zombie processes with a live peer/process context | ≥ 1 | **info** (reported, never counted — see HR-104) |
|
|
58
|
+
| SSH detected AND `ssh-no-docker: true` | — | info note |
|
|
59
|
+
|
|
60
|
+
**Verdict composition:**
|
|
61
|
+
|
|
62
|
+
- any **hard** signal → `critical`, recommend coordinator-direct (0 agents)
|
|
63
|
+
- **two or more independent soft** signals → `warn`, cap agents-per-wave at 2
|
|
64
|
+
- exactly **one soft** signal → `green`, reported in `reasons`, **no cap**
|
|
65
|
+
- none → `green`
|
|
66
|
+
|
|
67
|
+
`evaluate()` additionally returns `signals: { hard: [...], soft: [...] }` so a
|
|
68
|
+
caller can log which axes fired rather than re-deriving them from prose.
|
|
69
|
+
|
|
70
|
+
**Unit note (#1089):** `concurrent-sessions-warn` is denominated in SESSIONS. It
|
|
71
|
+
was compared against `claude_processes_count` until this rebuild — a measured 6x
|
|
72
|
+
unit error (median processes:sessions = 6.0 over 1461 paired samples) that made
|
|
73
|
+
the threshold fire on 93.6% of starts instead of 4.2%. `probe()` now supplies
|
|
74
|
+
`peer_sessions_count` from the session registry (`detectPeers()`, self excluded,
|
|
75
|
+
heartbeat-fresh); the rescaled process count is a fallback for hosts where the
|
|
76
|
+
registry is unreadable.
|
|
77
|
+
|
|
78
|
+
**CPU methodology (#943):** the gate/probe runs right after the coordinator's own
|
|
79
|
+
CPU-saturating quality-gate run by construction, so the 1-minute load average
|
|
80
|
+
systematically carries that decaying tail (observed: 96% → 75% within 36s).
|
|
81
|
+
`probe()` therefore also emits `cpu_load_5m` / `cpu_load_5m_pct`, and both
|
|
82
|
+
`evaluate()` and `evaluateWaveResourceGate()` judge CPU on **min(1m, 5m)**:
|
|
83
|
+
only-1m-high is reported as an informational "decaying transient" and is NOT
|
|
84
|
+
counted as a signal at all — so it cannot become the second signal that triggers
|
|
85
|
+
a cap. When `cpu_load_5m_pct` is `null` (Windows, zero-load), judging falls back
|
|
86
|
+
to `cpu_load_pct` alone.
|
|
37
87
|
|
|
38
88
|
## Presentation
|
|
39
89
|
|
|
40
90
|
Print a one-line Resource Health verdict immediately after Phase 4's output:
|
|
41
91
|
|
|
42
92
|
```
|
|
43
|
-
Resource Health: ⚠ warn —
|
|
93
|
+
Resource Health: ⚠ warn — two signals agree (cpu + concurrency); capping agents-per-wave at 2.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
On `green` with a reported signal, print the line but say plainly that nothing
|
|
97
|
+
was capped — a bare signal with no consequence reads as a suppressed warning:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Resource Health: ✓ green — 6884 MB swap present but memory_pressure healthy (53% free); no cap.
|
|
44
101
|
```
|
|
45
102
|
|
|
46
103
|
When `config['heavy-repo']` is `true` and the HR-004 preflight ceiling actually reduces `recommended_agents_per_wave_cap` below what the live verdict alone would have produced, print an additional banner line right after the verdict line:
|
|
@@ -49,10 +106,16 @@ When `config['heavy-repo']` is `true` and the HR-004 preflight ceiling actually
|
|
|
49
106
|
⚠ Heavy-repo mode active — agents-per-wave capped to 4 (Session Config heavy-repo: true)
|
|
50
107
|
```
|
|
51
108
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
109
|
+
**AUQ only on `critical`** (#1089). A `warn` applies its cap and reports it in
|
|
110
|
+
one line — it does not interrupt. Under the previous rule set warn-or-worse was
|
|
111
|
+
the verdict on 99.0% of session starts, so an AUQ here was an operator interrupt
|
|
112
|
+
on essentially every session, which `.claude/rules/ask-via-tool.md` AUQ-005
|
|
113
|
+
names outright ("an AUQ that blocks nothing"). `critical` means coordinator-direct
|
|
114
|
+
— zero agents — which genuinely changes the plan, so it earns the prompt:
|
|
115
|
+
|
|
116
|
+
1. **Proceed coordinator-direct** (0 agents) — Recommended
|
|
117
|
+
2. **Proceed as originally planned** (operator accepts the risk)
|
|
118
|
+
3. **Abort** (no wave planning runs; operator closes or investigates)
|
|
56
119
|
|
|
57
120
|
When SSH is detected and the session type is `deep`, auto-append this note to the plan handoff to session-plan (no user prompt needed):
|
|
58
121
|
> Host is SSH-attached — Docker-dependent wave steps should run on a local dev host.
|
|
@@ -22,13 +22,25 @@ import { buildLiveSignals } from '$PLUGIN_ROOT/scripts/lib/build-live-signals.mj
|
|
|
22
22
|
|
|
23
23
|
// Pass the surfaced top-N learnings (already computed in Phase 6.6) to avoid
|
|
24
24
|
// re-reading learnings.jsonl. Other paths default to canonical locations
|
|
25
|
-
// (.claude/STATE.md, .orchestrator/metrics/sessions.jsonl,
|
|
25
|
+
// RELATIVE TO repoRoot (.claude/STATE.md, .orchestrator/metrics/sessions.jsonl,
|
|
26
|
+
// .orchestrator/bootstrap.lock).
|
|
27
|
+
//
|
|
28
|
+
// `repoRoot` is passed explicitly (#1071). It defaults to process.cwd(), but
|
|
29
|
+
// naming it here is the point: session-start may run from a worktree or a
|
|
30
|
+
// subdirectory, and an implicit default silently binds STATE.md, sessions.jsonl,
|
|
31
|
+
// bootstrap.lock and the backlog scan to whatever directory the process happens
|
|
32
|
+
// to sit in — not to the repo the recommendation is about.
|
|
26
33
|
const signals = await buildLiveSignals({
|
|
34
|
+
repoRoot: process.cwd(), // absolute project root — bind it, never infer it
|
|
27
35
|
learnings: surfacedTopLearnings, // array, may be empty
|
|
28
|
-
backlogLimit: 50,
|
|
29
36
|
});
|
|
30
37
|
```
|
|
31
38
|
|
|
39
|
+
Do **not** hand-write a `backlogLimit` here. The window default lives once, in
|
|
40
|
+
`backlog-scan.mjs` (`DEFAULT_BACKLOG_LIMIT`); the `50` that used to stand in this
|
|
41
|
+
example read 50 of 89 open issues and reported `critical: 0` / `stale: 0` from the
|
|
42
|
+
truncated window (fixed in `c20d4d2`).
|
|
43
|
+
|
|
32
44
|
`buildLiveSignals` is the single SSOT for the Signals shape consumed by `selectMode` and by the autopilot driver protocol (see `skills/autopilot/SKILL.md § Production Wiring`). Phase 7.5 here and the autopilot in-process driver MUST go through this helper — do not inline the recipe in either call site.
|
|
33
45
|
|
|
34
46
|
**Key source bindings (Phase → field):**
|
|
@@ -43,7 +55,7 @@ const signals = await buildLiveSignals({
|
|
|
43
55
|
| `recentSessions` | tail-10 of `.orchestrator/metrics/sessions.jsonl` | Phase 6.6 |
|
|
44
56
|
| `bootstrapLock` | `.orchestrator/bootstrap.lock` via `parseBootstrapLock` | Phase 4 |
|
|
45
57
|
| `learnings` | surfaced top-N learnings (confidence > 0.3) | Phase 6.6 |
|
|
46
|
-
| `backlog` | `scanBacklog({limit:
|
|
58
|
+
| `backlog` | `scanBacklog({limit: DEFAULT_BACKLOG_LIMIT, repoRoot})` from `backlog-scan.mjs` (live VCS scan) | Phase 7.5 Step 1 |
|
|
47
59
|
|
|
48
60
|
## Step 2: Invoke selectMode
|
|
49
61
|
|