You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Validate check check-test-posix-shell (#1613, BLOCKING): validate-plugin fails when a test under tests/ spawns sh, bash, /bin/sh or /bin/bash by bare name instead of through posixShell() from tests/_helpers/posix-shell.mjs. On native Windows sh is not on PATH and bash is WSL, which cannot see Windows paths; the native run at fd8d3673 counted 301 red tests in this class. The existing sites are baselined (WARN) until #1613 converts them, and a baseline entry without a finding fails as baseline-stale.
Guard rule git-hooks-missing-in-worktree (#1732): the destructive-command guard now refuses git commit and git push in a linked worktree whose relative core.hooksPath (husky: .husky/_) exists in the main checkout but not in the worktree. npm ci with ignore-scripts=true never runs husky's prepare, so such a worktree ran no pre-commit, commit-msg or pre-push hook, with exit 0 and no output; on 2026-10-22 a push went out without its full gate that way. The deny names the fix, each step as its own command: install the dependencies from the lockfile (npm ci / pnpm install --frozen-lockfile), then npx husky. It does not list the forms the rule lets through. The directory follows the payload cwd, literal cd <dir> and git -C <dir>; anything the guard cannot read (a variable, a cd inside a subshell or command substitution, --git-dir, -c core.hooksPath=…) is not judged, and neither is an explicit skip (--no-verify, commit -n or -nm, HUSKY=0). The policy now has 13 blocking and 4 warning rules.
Snapshot probe for the curated GitHub export (#1712): node scripts/snapshot-probe.mjs --host <alias> checks out the exact tree the curated export would publish, outside the repo, as a one-commit repo without origin. It runs validate-plugin locally and the full gate on the remote host, and writes a receipt keyed by the snapshot tree under .orchestrator/tmp/snapshot-probe/. Exit 3 means the snapshot is red. Exit 2 means the suite did not run or did not finish (unknown host, fallback, gate timeout); in that case no receipt is written. writeSnapshotTree() is extracted from buildGithubExport, so the probe tests the tree the export publishes. runRemoteGate() is extracted from the pre-push router; its defaults leave the hook unchanged.
Release preflight rows snapshot-probe and npm-token-publish (#1712):
Under mirror: curated, release.mjs --check passes only with a green probe receipt for the release commit's snapshot tree that includes a suite run.
npm-token-publish reads npm token list and goes red when the token in use lacks Bypass-2FA, write permission, a fitting scope or a valid expiry. This is the EOTP case of 5.13.0, where whoami was green.
An EOTP publish failure now says that nothing was published.
Workflow lint check-workflow-continue-on-error (#1712): validate-plugin rejects job-level continue-on-error in .github/workflows/, because it keeps only the workflow green while the job's check run still concludes failure. The informational Windows smoke now tolerates its individual steps and reports a failure as a ::warning annotation.
Validate check check-metrics-ledger-root (#1722, BLOCKING): every line under scripts/, hooks/ and skills/ that builds a .orchestrator/metrics path must call a resolver in the same statement, declare a relative UPPER_CASE constant whose joins are checked too, or carry a // ledger-root: <reason> marker. Deliberately local modules sit on a reasoned allowlist that fails when an entry goes stale.
Explicit --evidence-candidates and --source-repo options for the learning index. Candidate artifacts resolve to current canonical records before existing selection policies; the feature is off by default and reports fallback diagnostics separately. See docs/evidence-candidates.md.
Native Windows smoke and a non-blocking windows-latest job (#1612): scripts/ci/windows-smoke.mjs runs the gate process probes (captured UTF-8 output, exit codes, a missing command, a timeout that must end a child and two grandchildren, literal argv with spaces, ', %PATH% and &), every hooks/hooks.json command through Git Bash, the destructive-guard deny and five first-run SessionStarts, with registry, telemetry and vault redirected to a throwaway config directory. 49/49 on Windows 11 with Node 24.14 and Windows PowerShell 5.1; on POSIX it prints nur win32 and exits 0. Two GitHub jobs on windows-latest, both continue-on-error and outside gate: test-windows-smoke (lint, typecheck, the smoke; moves into gate after ten green main runs) and test-windows-suite (the full suite with the fail-closed JSON verifier, informational while the test-portability backlog is open).
Portfolio audit 2026-10-12 (docs/audits/2026-10-12-portfolio-analyse/): fleet telemetry over 69 projects and 4,500 session records, GitLab activity on both hosts, the learnings corpus, the lead/navigator layer, the instruction budget and the native Claude Code overlap, consolidated into 15 accepted and 7 rejected decisions (E1 to E15, V1 to V7) plus an executed issue plan (#1583 to #1588 opened, 12 issues closed or folded, 21 labels normalised).
isMeasuredSession(rec) in scripts/lib/sessions-canonical.mjs (E1, #1584): one predicate for "a session that actually ran", excluding backfill stubs, _synthetic records and typeless zero-wave records. Wired into build-live-signals (so the mode-selector no longer counts stubs), the dispatcher staleness ranking, site-numbers and the cold-start detector via countSessionsInJsonl(raw, { measuredOnly: true }). Fleet census 2026-10-12: 59.1 % of all session records were stubs, and isRealSession alone still passed 49 of them. For the same reason the session count on the website drops from 481 to 253 with this release (site/_census.json, counted 2026-10-20 at 95136cb8): the old figure counted the stubs.
projects-root, a host-local scan root (#1562): the cross-repo confinement and scan root (getConfinementRoot(), used by the host-wide session sweep for the vault live-status board, vault-integration-watcher, run-migrate-v2-cross-repo and promote-vault-strict) can now point somewhere other than ~/Projects. Set it with the env variable SO_PROJECTS_ROOT or with paths.projects-root in owner.yaml; the env variable wins over owner.yaml, which wins over ~/Projects, and the test-only CROSS_REPO_CONFINEMENT_ROOT still outranks both. A leading ~ is expanded; a value that is still not absolute is ignored with one stderr warning naming its source and is never resolved against the working directory. It is deliberately not a Session Config key, so a personal absolute path never lands in a committed CLAUDE.md.
so-hud protection gates (#1551): so-hud 0.2.0 checks shell commands (Bash, Monitor, PowerShell) and file edits before they run and tool results before they reach the model, and the strictness profile (locker, ausgewogen, streng) now changes behaviour. This is a behaviour change for installed 0.1.0 users: outward actions now ask twice by default, secret-printing commands are refused, and on the default profile ausgewogen rule traps such as git add -A are refused too. Commands that would print a secret (SEC-008: cat .env, a bare env, echo $TOKEN, docker inspect without --format, a token in curl arguments or a curl URL) are refused on every profile, with the safe replacement in the refusal. Rule traps (git add ./-A/--all, claude -p without env -u ANTHROPIC_API_KEY, ${PIPESTATUS[, grep -c … || echo 0) warn on locker and are refused otherwise. Outward actions (push to main/master, force, tags or delete; glab mr merge/glab mr accept/gh pr merge and API writes to main; release create; package publish; production deploys on Vercel, Supabase, Fly and Railway) are held wherever a surface is attached (terminal, desktop, IDE, phone) until the owner confirms in two questions within 30 seconds, then run exactly once. Only a plain feature-branch push runs without a question — git push [-u] origin feat/x, git push [-u] origin HEAD or a bare git push, when every earlier command on the line is a git call from a local allowlist (fetch with no option and no :: word; log, show and diff with display options only, so --output, --ext-diff, --textconv and --upload-pack ask); any earlier command that is not git, and any earlier output redirection other than onto exactly /dev/null or between stdout and stderr (also on a git step, cd, an assignment or exec), makes every push form ask, and after add, commit, status, fetch or checkout -- <paths> on the line a push asks unless so-hud measured that no git hook can run (core.hooksPath and every hook.* config key unset, no hook file without .sample) and the step ran in the push's own folder as written (git -C ../b commit && git push … asks; in a husky repository git commit … && git push … on one line therefore asks) — with exact options, nothing earlier on the line that changes the branch, the folder or git config, and the standard push configuration (#1569); lowercase head, --no-verify, push options (-o), abbreviated options and every other shape ask. Outward commands are recognised inside bash -c, eval, gh/glab api …/merge and workspace publishes, and a line that hides an outward word in a construct the parser cannot read ($(…), backticks, xargs, shell functions) asks too. A run with no surface at all (a plain claude -p) cannot ask: so-hud writes a receipt and lets the call run, and there merges, releases, publishes and deploys are checked by no Session Orchestrator gate. An edit of a file in another live Session Orchestrator session's wave scope in the same working copy warns, asks or is refused by profile; sessions that do not run Session Orchestrator are invisible to this check. Tokens in tool results reach the model as [<kind> entfernt · sha256:<12 hex>]. Each decision on an outward action appends one receipt per held action, and each asked collision one receipt (a hash of tool and command or file path, never the command), to ~/.config/session-orchestrator/hud/aktionen.jsonl. If the gate fails, the call is refused, except on locker for calls that are neither outward nor SEC-008. The command-line gates stay authoritative where they exist. scripts/so-hud.mjs --repo-root now resolves to the git toplevel, so a session started in a subdirectory reads its wave scope and its peers correctly. Details: docs/mods.md.
so-hud profile band and /profile (#1552): until a profile is chosen, a band above the prompt shows the current one (Profil: ausgewogen (Vorgabe, noch nicht gewählt)), its effect, what holds on every profile, and one button per profile. The buttons have no hotkeys, because a digit typed into an empty prompt would press one. A press saves the choice in so-hud's own strictness row of /config (plus the store key profile-chosen), and every new session starts with it. /profile shows profile, effect and buttons without a model turn; its text is stored in the conversation like any command output, and Schließen hides the band for the session. Band and command work without a Session Orchestrator installation. The profile steers only so-hud's own gates, not Session Orchestrator's guards.
Curated GitHub mirror (mirror: curated, #1631, #1594, #1645): scripts/github-export.mjs builds one squashed snapshot commit with a Source-Commit: trailer and leaves out the paths listed in .orchestrator/policy/public-export.json. It refuses when an excluded path survives, the leakage scan fails or github/main moved outside the export. Session-end pushes that snapshot from main, never HEAD. scripts/release.mjs tags the release commit on GitLab and its snapshot on GitHub, publishes to GitHub only through the export and honours mirror: none.
Inbox store role for learnings (#1516, #1685, #1686): a host with learnings.store-role: inbox in owner.yaml (or SO_LEARNINGS_STORE_ROLE=inbox) appends new learnings to learnings-inbox.jsonl and never rewrites learnings.jsonl. Readers, sweeps, migrations, the backfill and the dialectic readers see both files. The writer host ingests inbox records with a receipt, and a sweep that crashed before its receipt is healed on the next run. The role resolves fail-closed, and an unknown key in the owner.yamllearnings section is rejected.
Session-end lists open [Abnahme] issues (#1678): acceptance issues linked to the plan or epic appear with owner and not-before date in their own "Abnahme offen" section and never count as completed. They close only on oracle evidence, not from a Closes trailer.
Open handover points carry an owner-queue mark (#1639): scripts/handover-queue-check.mjs finds open points in a handover without queue:#<id> or issue:#<iid> and, with --apply, files them through owner-queue add and writes the mark. Measured on 2026-10-15: 35 of 111 open points in fleet handovers were recorded nowhere.
Session start ref per own work tree (#1659, #1668, #1652): the start event records the ref of the session's own tree, emit-session drops a start ref from an outdated checkout, and a wave with 0 agents counts as coordinator-direct.
SO_GATE_HOSTS for the pre-push gate (#1623, #1625, #1626, #1627): SO_GATE_HOSTS=<alias,...> replaces the configured host order for one push; an unknown alias, or a list none of whose hosts can run the gate, blocks with exit 2 instead of falling back to the local gate. A host counts as ready by its gate-ready token, not by offload doctor's exit code. After the gate passes, each pushed ref is resolved again and a moved ref warns. The so-hud p95 timing bound warns instead of failing.
Changed
/plan gains a pre-mortem panel, an oracle per acceptance criterion and separate acceptance issues (#1677).
New Phase 4.5: two read-only briefs, Refute and Deepen (skills/plan/prd-premortem-panel.md), run against the code before the PRD reviewer. Their re-checked predictions go into § Risks, and every acceptance criterion gets an Oracle: line.
External Acceptance: criteria whose oracle cannot run in the implementing session become [Abnahme] issues with owner, date and fallback. The implementing issue does not close them.
Reviewer: gains criterion 8, Verifiability. "Not a pessimist" becomes "not speculative, carry an evidence pointer".
Research and templates: research starts from raw material (reproduce the premise, census local equivalents). The feature template gains an Evidence Baseline and a First Real Use / Kill line. The Wave 1 scope question asks what shows "done" before it asks for the appetite.
Removed: the Opportunity Score is replaced by a ranking on measured signal.
Autonomous runs: a path for runs without AskUserQuestion.
Fixes:/plan new meets the PRD commit gate in the new repo, and two drifts are gone (the stale repo count, the missing 2×2 triage in mode-new.md).
Measured basis (2026-10-19): three shipped PRDs, replayed in history-truncated clones, anticipated 0 of 43 later surprises. Each brief alone hit 7–8, both together 11. Real-world acceptance stayed undone in 3 of 3.
A gate command that is not found fails instead of being skipped (#1612, reported in GH#76 by dviolante): runCheck reports exit 127 (and 9009 from a batch file on Windows) as fail with a hint to fix the command or set it to skip explicitly; Full Gate no longer passes a session whose configured test command never ran. An explicit skip and the visible stub markers are unchanged. On Windows cmd /c exits 1 for an unknown command, which was already a fail.
Claude Code starts the MCP server with Node (#1612): .claude-plugin/plugin.json declares session-orchestrator as node ${CLAUDE_PLUGIN_ROOT}/scripts/mcp-server.mjs, which replaces the bash -c entry from .mcp.json for Claude Code only (the manifest's server of the same name wins). On native Windows bash resolved to the WSL launcher and the server never started. Codex, Cursor and Pi keep .mcp.json. Both servers answered initialize, tools/list, session_config and session_metrics byte-identically.
post-edit-validate reports a timeout as timeout (#1612): the configured typecheck-command runs as one shell command instead of being split at spaces, and a run that exceeds the hook's 2 s budget is reported as status: "timeout" rather than fail.
The event webhook POST no longer uses fetch (#1612): with CLANK_EVENT_URL and CLANK_EVENT_SECRET set, emitEvent still fires the POST without waiting for it (several events per hook at 3 s each would exceed the 5 s hook budget), now through scripts/lib/http-request.mjs with a 64 KiB response cap.