session-orchestrator 3.20.0 → 3.22.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/rules/000-session-orchestrator.mdc +3 -2
- package/.cursor/rules/030-wave-execution.mdc +10 -8
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +8 -8
- package/CHANGELOG.md +515 -0
- package/README.md +16 -11
- package/agents/analyst.md +1 -1
- package/agents/architect-reviewer.md +1 -1
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +1 -1
- package/agents/dialectic-deriver.md +1 -1
- package/agents/docs-writer.md +1 -1
- package/agents/memory-proposal-collector.md +7 -5
- package/agents/qa-strategist.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +42 -1
- package/agents/skill-applied-judge.md +1 -1
- package/agents/test-writer.md +1 -1
- package/agents/ui-developer.md +1 -1
- package/agents/ux-evaluator.md +1 -1
- package/commands/eli5.md +33 -0
- package/commands/release.md +62 -0
- package/commands/test.md +2 -2
- package/docs/components.md +6 -5
- package/docs/migration-v3.md +9 -6
- package/docs/persona-panel.md +3 -1
- package/docs/scope-collision-guard.md +167 -0
- package/docs/session-config-reference.md +31 -8
- package/hooks/_lib/lock-bootstrap.mjs +19 -13
- package/hooks/enforce-scope.mjs +103 -3
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +76 -97
- package/hooks/on-session-start.mjs +195 -104
- package/hooks/on-stop.mjs +127 -12
- package/hooks/post-bash-write-verify.mjs +8 -32
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +17 -18
- package/hooks/pre-task-scope-disjoint.mjs +1042 -0
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/pi/prompts/release.md +12 -0
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +10 -9
- package/scripts/emit-session.mjs +42 -0
- package/scripts/export-hw-learnings.mjs +61 -2
- 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 +5 -5
- package/scripts/lib/backlog-scan.mjs +106 -15
- package/scripts/lib/build-live-signals.mjs +7 -3
- package/scripts/lib/ci-status-banner.mjs +267 -77
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/gates/gate-full.mjs +3 -3
- package/scripts/lib/gates/gate-helpers.mjs +17 -6
- package/scripts/lib/git-config-drift.mjs +471 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
- package/scripts/lib/io.mjs +432 -7
- package/scripts/lib/issue-budget.mjs +63 -9
- package/scripts/lib/learnings/select.mjs +157 -3
- package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
- package/scripts/lib/mirror-issues-banner.mjs +266 -0
- package/scripts/lib/named-vault-resolver.mjs +105 -16
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-cards/schema.mjs +6 -2
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/project-hygiene.mjs +64 -4
- package/scripts/lib/reconcile/renderer.mjs +17 -4
- package/scripts/lib/reconcile/writer.mjs +69 -30
- package/scripts/lib/redact-spans.mjs +89 -0
- 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/scope-baseline.mjs +77 -17
- package/scripts/lib/scope-gate.mjs +658 -0
- package/scripts/lib/secret-masker.mjs +262 -0
- package/scripts/lib/session-lock.mjs +34 -10
- package/scripts/lib/session-registry.mjs +9 -1
- package/scripts/lib/spiral-carryover.mjs +23 -2
- package/scripts/lib/state-md/mission-status.mjs +164 -58
- package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
- package/scripts/lib/validate/check-agents.mjs +77 -5
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-commands.mjs +2 -20
- package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +48 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/validate/check-skills.mjs +191 -0
- package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
- package/scripts/lib/validate/check-unwired-features.mjs +219 -11
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
- package/scripts/lib/validate/frontmatter-block.mjs +61 -0
- package/scripts/lib/validate/tier-inference.mjs +46 -8
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vault-mirror/namespace.mjs +146 -1
- package/scripts/lib/vault-mirror/process.mjs +264 -31
- package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
- package/scripts/lib/vault-status/board-writer.mjs +300 -56
- package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
- package/scripts/lib/vcs-repo-spec.mjs +680 -30
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/materialize-wave-scope.mjs +281 -0
- package/scripts/print-learnings-index.mjs +30 -3
- package/scripts/release.mjs +983 -107
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/site-numbers.mjs +1049 -0
- package/scripts/validate-plugin.mjs +64 -0
- package/scripts/validate-wave-scope.mjs +286 -12
- package/scripts/vault-backfill.mjs +32 -5
- package/scripts/vault-mirror.mjs +26 -1
- package/skills/_shared/monitor-patterns.md +24 -4
- package/skills/_shared/parallel-aware-auq.md +30 -24
- package/skills/_shared/parallel-aware-preamble.md +31 -2
- package/skills/_shared/state-ownership.md +49 -6
- package/skills/bootstrap/SKILL.md +2 -1
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/claude-md-drift-check/SKILL.md +9 -1
- package/skills/debug/SKILL.md +4 -1
- package/skills/discovery/SKILL.md +28 -24
- package/skills/discovery/issue-templates.md +4 -4
- package/skills/discovery/probes-code.md +2 -2
- package/skills/discovery/probes-feature.md +6 -6
- package/skills/discovery/probes-infra.md +2 -2
- package/skills/discovery/probes-session.md +5 -5
- package/skills/dispatcher/SKILL.md +10 -1
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/frontmatter-guard/SKILL.md +9 -1
- package/skills/gitlab-ops/SKILL.md +73 -59
- package/skills/gitlab-portfolio/SKILL.md +10 -1
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/memory-cleanup/SKILL.md +20 -7
- package/skills/npm-publish/SKILL.md +23 -51
- package/skills/peekaboo-driver/SKILL.md +3 -3
- package/skills/persona-panel/SKILL.md +3 -1
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +42 -12
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +3 -3
- package/skills/repo-audit/SKILL.md +10 -1
- package/skills/session-end/SKILL.md +97 -22
- package/skills/session-end/metrics-collection.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +37 -2
- package/skills/session-end/session-metrics-write.md +4 -10
- package/skills/session-plan/SKILL.md +2 -2
- package/skills/session-plan/wave-template.md +1 -1
- package/skills/session-start/SKILL.md +82 -36
- 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/soul.md +110 -0
- package/skills/spinout/SKILL.md +5 -1
- package/skills/sunset-review/SKILL.md +11 -1
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/tmux-layout/SKILL.md +7 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/vault-mirror/SKILL.md +10 -1
- package/skills/vault-sync/SKILL.md +10 -1
- package/skills/vault-sync/validator.mjs +55 -6
- package/skills/wave-executor/wave-loop.md +64 -12
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/scripts/lib/mission-status-schema.mjs +0 -114
- 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
|
@@ -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`. On success, 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,21 +131,21 @@ 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.
|
|
@@ -158,21 +158,21 @@ Where `sessionId` is the session identifier derived from the session type and ti
|
|
|
158
158
|
```js
|
|
159
159
|
AskUserQuestion({
|
|
160
160
|
questions: [{
|
|
161
|
-
question: `
|
|
162
|
-
header: "Stale
|
|
161
|
+
question: `A stale session lock is in the way — started ${ageHours}h ago, its ttl=${existingLock.ttl_hours}h has expired, and pid=${existingLock.pid} on host=${existingLock.host} is ${reason === 'stale-pid-dead' ? 'confirmed dead' : 'still running or status unknown'}. Reclaim it?`,
|
|
162
|
+
header: "Stale lock",
|
|
163
163
|
multiSelect: false,
|
|
164
164
|
options: [
|
|
165
|
-
{ label: "Reclaim (Recommended)", description: "
|
|
166
|
-
{ label: "Abort — investigate manually", description: "
|
|
165
|
+
{ 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." },
|
|
166
|
+
{ 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
167
|
],
|
|
168
168
|
}],
|
|
169
169
|
});
|
|
170
170
|
```
|
|
171
171
|
- **Codex CLI / Cursor IDE fallback (numbered Markdown list):**
|
|
172
172
|
```
|
|
173
|
-
|
|
174
|
-
1. Reclaim (Recommended) —
|
|
175
|
-
2. Abort —
|
|
173
|
+
A stale session lock is in the way — started <ageHours>h ago, ttl=<ttlHours>h expired, pid=<pid> on <host>. Reclaim it?
|
|
174
|
+
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.
|
|
175
|
+
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
176
|
Reply with the number of your choice.
|
|
177
177
|
```
|
|
178
178
|
- On **Reclaim**: call `forceAcquire({ sessionId, mode: sessionType, ttlHours: 4, repoRoot: process.cwd() })`. After Phase 1.5 initializes STATE.md, append a deviation:
|
|
@@ -196,11 +196,14 @@ When `existingLock.host !== os.hostname()`, PID liveness cannot be checked (`pid
|
|
|
196
196
|
|
|
197
197
|
> Skip this phase if `persistence` config is `false`.
|
|
198
198
|
|
|
199
|
-
After Phase 1.2 acquires (or confirms) the lock,
|
|
199
|
+
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
200
|
|
|
201
201
|
```javascript
|
|
202
202
|
import { findPeers } from '$PLUGIN_ROOT/scripts/lib/peer-discovery.mjs';
|
|
203
|
-
|
|
203
|
+
// Keep the STATE.md comparison in its original attribution-label space.
|
|
204
|
+
// findPeers performs the guarded semantic→raw translation only for discovered peers.
|
|
205
|
+
const callerSessionHint = semanticSessionId ?? sessionId;
|
|
206
|
+
const { peers } = await findPeers(process.cwd(), { mySessionId: callerSessionHint });
|
|
204
207
|
const peer = peers.find((p) => p.source === 'state-md') ?? null;
|
|
205
208
|
// Phase 1.2.1 consumes only the 'state-md' subset (STATE.md surface only).
|
|
206
209
|
if (peer) {
|
|
@@ -367,29 +370,60 @@ If `snaps.length >= 1` → present the following choice:
|
|
|
367
370
|
|
|
368
371
|
**Claude Code (AskUserQuestion):**
|
|
369
372
|
|
|
373
|
+
Before asking, read what "Recover" would actually put back — the operator decides on that diff, not on the word:
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
import { execFileSync } from 'node:child_process';
|
|
377
|
+
|
|
378
|
+
// Read-only: `git stash show` prints a diffstat and never touches the working tree.
|
|
379
|
+
// Capped at 12 lines so the preview box stays shorter than the option list beside it.
|
|
380
|
+
const stat = execFileSync('git', ['stash', 'show', '--stat', snaps[0].sha], { encoding: 'utf8' })
|
|
381
|
+
.split('\n').slice(0, 12).join('\n');
|
|
382
|
+
const refs = snaps.map((s) => s.ref).join('\n');
|
|
383
|
+
```
|
|
384
|
+
|
|
370
385
|
```js
|
|
371
386
|
AskUserQuestion({
|
|
372
387
|
questions: [{
|
|
373
|
-
question:
|
|
388
|
+
question: `${snaps.length} snapshot(s) from the resumed session, newest ${humanAgeOf(snaps[0].createdAt)}. Recover, keep, discard?`,
|
|
374
389
|
header: "Snapshot",
|
|
375
390
|
multiSelect: false,
|
|
376
391
|
options: [
|
|
377
|
-
{
|
|
378
|
-
|
|
379
|
-
|
|
392
|
+
{
|
|
393
|
+
label: "Recover (Recommended)",
|
|
394
|
+
description: "Puts the newest saved state back into your working tree and commits nothing. You can drop any of those changes afterwards.",
|
|
395
|
+
preview: `These files come back:\n\n\`\`\`\n${stat}\n\`\`\``,
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
label: "Keep as backup",
|
|
399
|
+
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.",
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
label: "Discard all",
|
|
403
|
+
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.",
|
|
404
|
+
preview: `Deleted for good:\n\n\`\`\`\n${refs}\n\`\`\``,
|
|
405
|
+
},
|
|
380
406
|
],
|
|
381
407
|
}],
|
|
382
408
|
});
|
|
383
409
|
```
|
|
384
410
|
|
|
411
|
+
`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.
|
|
412
|
+
|
|
385
413
|
**Codex CLI / Cursor IDE fallback (numbered Markdown list):**
|
|
386
414
|
|
|
415
|
+
These harnesses have no preview box, so the same diffstat is printed inline — it is the only place the operator ever sees it:
|
|
416
|
+
|
|
387
417
|
```markdown
|
|
388
|
-
|
|
418
|
+
"Recover" would put these files back:
|
|
419
|
+
|
|
420
|
+
<git stash show --stat <snaps[0].sha>, capped at 12 lines>
|
|
389
421
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
422
|
+
<N> snapshot(s) from the resumed session, newest <age>. Recover, keep, discard?
|
|
423
|
+
|
|
424
|
+
1. **Recover (Recommended)** — puts the newest saved state back into your working tree and commits nothing. You can drop any of those changes afterwards.
|
|
425
|
+
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.
|
|
426
|
+
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
427
|
|
|
394
428
|
Reply with the number of your choice.
|
|
395
429
|
```
|
|
@@ -471,7 +505,7 @@ await sweepBoard({
|
|
|
471
505
|
|
|
472
506
|
This single call does three things:
|
|
473
507
|
|
|
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()`).
|
|
508
|
+
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
509
|
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
510
|
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
511
|
|
|
@@ -785,7 +819,19 @@ Group issues by:
|
|
|
785
819
|
|
|
786
820
|
Non-blocking. Cross-reference: `scripts/lib/ci-status-banner.mjs` (the sibling project-facing probe) and `.claude/rules/test-value.md` § TV-005 (why structural gates beat unit-test volume).
|
|
787
821
|
|
|
788
|
-
|
|
822
|
+
Additionally, invoke the mirror-issues probe (`scripts/lib/mirror-issues-banner.mjs`) via `await checkMirrorIssues({ repoRoot })`. This is the only probe that deliberately queries the platform the session did NOT auto-detect. `skills/gitlab-ops/SKILL.md` § VCS Auto-Detection selects exactly one platform via if/else, so in a repo whose `origin` is GitLab and whose `github` remote is a public mirror, no code path ever reads the mirror's issue tracker — issues filed there by external reporters are structurally invisible to every session. The VCS family is therefore hard-pinned to `'github'` inside the module rather than auto-detected. It takes no Session Config key: `resolveRepoSpec({ repoRoot, vcs: 'github' })` derives the `gh -R` spec from `git remote`, which makes the probe self-disabling — a repo with no GitHub mirror resolves to `undefined`, returns `null`, and spawns no subprocess.
|
|
823
|
+
|
|
824
|
+
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.
|
|
825
|
+
|
|
826
|
+
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/_`, 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.
|
|
827
|
+
|
|
828
|
+
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.
|
|
829
|
+
|
|
830
|
+
The complementary halves live elsewhere and are not duplicates of this probe: `tests/setup/scrub-git-env.mjs` (wired via `setupFiles` in `vitest.config.mjs`) removes the redirecting variables before any test runs, and `scripts/lib/validate/check-test-git-config-target.mjs` censuses untargeted state-mutating git calls in `tests/**`. The census is WARN-only by measurement — its first cut was 11 hits, all false positives — and it explicitly reports `gitDirInheritable`, the population it cannot close, because the incident's own call sites passed a correct `cwd` and were redirected anyway.
|
|
831
|
+
|
|
832
|
+
Non-blocking. Cross-reference: `scripts/lib/vcs-repo-spec.mjs` (`isQueryFailure` — the same absence-vs-query-failure split this probe's `degraded` state implements).
|
|
833
|
+
|
|
834
|
+
All banners are non-blocking — display in the Session Overview, do not halt the session. If `bootstrap-lock-freshness.mjs` is absent (pre-#186 plugin install) or `peer-cards/staleness-banner.mjs` is absent (pre-#503 plugin install) or `loop-readiness-banner.mjs` is absent (pre-#633 plugin install) or `instruction-budget-guard.mjs` is absent (pre-#687 plugin install) or `reconcile-nudge-banner.mjs` is absent (pre-#723 plugin install) or `sessions-staleness-banner.mjs` is absent (pre-#724 plugin install) or `sessions-integrity-banner.mjs` is absent (pre-#958 plugin install) or `owner-config-banner.mjs` is absent (pre-#820 plugin install) or `moc-staleness-banner.mjs` / `context-coverage-banner.mjs` are absent (pre-#831 plugin install) or `claude-md-budget-lint.mjs` is absent (pre-#878 plugin install) or `mirror-issues-banner.mjs` is absent (pre-#1022 plugin install), skip silently.
|
|
789
835
|
|
|
790
836
|
## Phase 4.5: Resource Health (v3.1.0)
|
|
791
837
|
|
|
@@ -1037,12 +1083,12 @@ if (!c.prompt) {
|
|
|
1037
1083
|
```js
|
|
1038
1084
|
AskUserQuestion({
|
|
1039
1085
|
questions: [{
|
|
1040
|
-
question: "Anonyme Usage-Telemetrie aktivieren? Strikt opt-in,
|
|
1041
|
-
header: "
|
|
1086
|
+
question: "Anonyme Usage-Telemetrie aktivieren? Strikt opt-in, jederzeit abschaltbar; was genau gesendet wird: docs/telemetry.md",
|
|
1087
|
+
header: "Telemetrie",
|
|
1042
1088
|
multiSelect: false,
|
|
1043
1089
|
options: [
|
|
1044
|
-
{ label: "Ja, aktivieren", description: "
|
|
1045
|
-
{ label: "Nein", description: "
|
|
1090
|
+
{ 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." },
|
|
1091
|
+
{ 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)." },
|
|
1046
1092
|
],
|
|
1047
1093
|
}],
|
|
1048
1094
|
});
|
|
@@ -1052,9 +1098,9 @@ AskUserQuestion({
|
|
|
1052
1098
|
|
|
1053
1099
|
- **Codex CLI / Cursor IDE fallback (numbered Markdown list — AUQ-004 exception 1):**
|
|
1054
1100
|
```
|
|
1055
|
-
Anonyme Usage-Telemetrie aktivieren? Strikt opt-in,
|
|
1056
|
-
1. Ja, aktivieren —
|
|
1057
|
-
2. Nein —
|
|
1101
|
+
Anonyme Usage-Telemetrie aktivieren? Strikt opt-in, jederzeit abschaltbar; was genau gesendet wird: docs/telemetry.md
|
|
1102
|
+
1. Ja, aktivieren — sendet anonyme Zähl- und Strukturdaten (welche Phase lief, Erfolg oder Abbruch), whitelist-projiziert: keine Pfade, keine Prompts, keine Repo-Namen.
|
|
1103
|
+
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).
|
|
1058
1104
|
Reply with the number of your choice. (No option is pre-recommended — the choice is yours.)
|
|
1059
1105
|
```
|
|
1060
1106
|
|
|
@@ -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.
|
|
@@ -64,6 +64,116 @@ The active level is `efficiency.output-level` in `~/.config/session-orchestrator
|
|
|
64
64
|
- Shape: explain the WHY behind each recommendation, name the alternatives you rejected and why, spell out unfamiliar terms on first use.
|
|
65
65
|
- Escalation: `expand <topic>` — see § Escalation above.
|
|
66
66
|
|
|
67
|
+
### Register — how a sentence reads
|
|
68
|
+
|
|
69
|
+
The budgets above set *how much* you say. This sets *how*. It binds at every
|
|
70
|
+
level and is not itself a budget: applying it changes word order and word
|
|
71
|
+
choice, not line count. It is the canonical statement for this repo — the
|
|
72
|
+
other three souls (`plan`, `brainstorm`, `grill`) point here rather than
|
|
73
|
+
copying it.
|
|
74
|
+
|
|
75
|
+
**Write for someone who knows this project but has not seen what you just saw.**
|
|
76
|
+
What he needs to decide stands in the text, not in the file it points at — in
|
|
77
|
+
the AUQ payload and in every finding you post.
|
|
78
|
+
|
|
79
|
+
This is not "explain it like he is five". The operator owns this repo. He is
|
|
80
|
+
not missing knowledge, he is missing **observation** — he did not watch the
|
|
81
|
+
command you just ran or read the file you just opened. A knowledge framing
|
|
82
|
+
would be factually wrong and condescending at the same time. Write across, not
|
|
83
|
+
down: same expertise as yours, minus your last ten minutes.
|
|
84
|
+
|
|
85
|
+
#### Plain words, real things
|
|
86
|
+
|
|
87
|
+
> **Say more simply what actually happens — and introduce nothing that does not exist.**
|
|
88
|
+
>
|
|
89
|
+
> **The test:** delete every noun the system does not contain. If the sentence
|
|
90
|
+
> is still true and complete, it was no analogy. If it collapses, the analogy
|
|
91
|
+
> was load-bearing — replace it with a description of what actually happens.
|
|
92
|
+
|
|
93
|
+
Five worked cases, in rising difficulty:
|
|
94
|
+
|
|
95
|
+
1. "Waiting means the other session finishes first." — **allowed.** Sessions
|
|
96
|
+
and waiting both exist; nothing foreign was introduced.
|
|
97
|
+
2. "Think of the session as a level crossing." — **forbidden.** Delete "level
|
|
98
|
+
crossing" and nothing is left. Say what happens instead: one session holds
|
|
99
|
+
`.orchestrator/session.lock`, the other waits for it.
|
|
100
|
+
3. "The token budget is used up." — **allowed.** `TOKEN_BUDGET_EXCEEDED` is a
|
|
101
|
+
real identifier and "budget" is the system's own word. Adding "…like a tank
|
|
102
|
+
of fuel" would be forbidden — the tank does not exist.
|
|
103
|
+
4. "Think of the kill-switches as a fuse box." — **forbidden, and wrong on the
|
|
104
|
+
facts.** Fuses trip on overload; the kill-switches also test elapsed time and
|
|
105
|
+
confidence. The image sounds helpful and is not. A wrong picture costs more
|
|
106
|
+
than no picture, because the operator reasons from it.
|
|
107
|
+
5. **Dead metaphors.** A proper name may itself be a metaphor —
|
|
108
|
+
`pre-bash-destructive-guard` is called a guard — and you use the name as
|
|
109
|
+
given. Reviving the image is the violation: "the guard will not let it
|
|
110
|
+
through" invites the operator to picture a guard and then reason from the
|
|
111
|
+
picture instead of from the hook. Name the identifier, then say what it
|
|
112
|
+
does: the hook denies the Bash call.
|
|
113
|
+
|
|
114
|
+
#### Precedence over § "Never traded for brevity"
|
|
115
|
+
|
|
116
|
+
There is a real collision above: "say it more simply" can water down a precise
|
|
117
|
+
error message. Resolve it in three steps.
|
|
118
|
+
|
|
119
|
+
1. **Simplifying removes words, never facts.** If a path, a number, an error
|
|
120
|
+
code, an identifier, or an instruction to act disappears, that is data loss,
|
|
121
|
+
not simplification — and § "Never traded for brevity" already forbids it.
|
|
122
|
+
2. **When both will not fit in one sentence: precision in the sentence,
|
|
123
|
+
plainness in the one beside it.** The exact term is never replaced, only
|
|
124
|
+
accompanied. It is what the operator greps, quotes, and pastes into an issue.
|
|
125
|
+
3. **The mechanical tie-breaker:** could the token you are about to cut ever
|
|
126
|
+
appear in a `grep`? Then it stays.
|
|
127
|
+
|
|
128
|
+
Measured 2026-08-22 at `a4f93cf`: of 191 option descriptions in this repo, the
|
|
129
|
+
20 that match the safety lexicon (`SAFETY_PATTERN` in
|
|
130
|
+
`scripts/lib/auq/schema.mjs`) run 26–108 codepoints — all of them under both K6
|
|
131
|
+
length thresholds (`descriptionCharsWarn` 120, `descriptionCharsFail` 150). The
|
|
132
|
+
collision therefore does not occur today. This precedence rule is a precaution,
|
|
133
|
+
not a repair.
|
|
134
|
+
|
|
135
|
+
#### Worked example — an operator-visible message
|
|
136
|
+
|
|
137
|
+
`formatBlockReason()` in `scripts/lib/issue-budget.mjs` is what the operator
|
|
138
|
+
sees when the issue cap blocks a creation. Rendered with the collector-issue
|
|
139
|
+
sink, before:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
issue-budget: session cap reached — 12/12 issues already created.
|
|
143
|
+
This request was NOT created. It is parked as overflow entry #3 in:
|
|
144
|
+
.orchestrator/runtime/issue-budget-overflow.jsonl
|
|
145
|
+
session-end Phase 5 will fold all overflow entries into ONE collector issue `[Backlog-Sammel] <session-id>, N zurückgestellte Punkte`. Nothing is lost.
|
|
146
|
+
Exempt from the cap: priority::critical, the carryover class (SPIRAL/FAILED, [Carryover]),
|
|
147
|
+
and broken-window closure issues — those are never deferred.
|
|
148
|
+
To raise the cap for this repo, edit `issue-budget.max-per-session` in the Session Config;
|
|
149
|
+
`mode: warn` reports without blocking, `mode: off` disables the gate.
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
After:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Nothing is lost — the issue is parked, and nothing needs doing right now.
|
|
156
|
+
issue-budget: session cap reached — 12/12 issues already created, so this one was NOT created.
|
|
157
|
+
It is parked as overflow entry #3 in:
|
|
158
|
+
.orchestrator/runtime/issue-budget-overflow.jsonl
|
|
159
|
+
session-end Phase 5 folds all overflow entries into ONE collector issue `[Backlog-Sammel] <session-id>, N zurückgestellte Punkte`.
|
|
160
|
+
Exempt from the cap: priority::critical, the carryover class (SPIRAL/FAILED, [Carryover]),
|
|
161
|
+
and broken-window closure issues — those are never deferred.
|
|
162
|
+
To raise the cap for this repo, edit `issue-budget.max-per-session` in the Session Config; `mode: warn` reports without blocking, `mode: off` disables the gate.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Three changes, and only these three: the operator's own question — *must I do
|
|
166
|
+
something?* — moved to line 1, carrying "Nothing is lost" up from line 4 where
|
|
167
|
+
he used to reach it last; `will fold` became the active `folds`; and the first
|
|
168
|
+
two lines merged on a causal `so`, which is why "This request" is now "this
|
|
169
|
+
one" — the same subject, already named in the sentence.
|
|
170
|
+
|
|
171
|
+
The whole word-level diff is four dropped tokens: `This`, `request`, `will`,
|
|
172
|
+
`fold`. Not one of them is a path, a count, a label, a config key, or a mode
|
|
173
|
+
value; every one of those survives character for character. Eight lines before,
|
|
174
|
+
eight lines after — **this register is not a diet.** It is the same facts, in
|
|
175
|
+
the order the reader needs them.
|
|
176
|
+
|
|
67
177
|
### Companion dials
|
|
68
178
|
|
|
69
179
|
Same file, same lookup, same fallback-to-default rule:
|
package/skills/spinout/SKILL.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
name: spinout
|
|
3
3
|
user-invocable: true
|
|
4
4
|
model: sonnet
|
|
5
|
-
description:
|
|
5
|
+
description: >
|
|
6
|
+
Use when extracting a project into its own repo — a venture spinout (e.g. a product leaving its
|
|
7
|
+
incubator repo) or a sanitized content-snapshot fork. Guided 5-step runbook: target sphere + path,
|
|
8
|
+
confidentiality/sanitize check, copy + fresh git init, SNAPSHOT-FREEZE marker in the source repo,
|
|
9
|
+
remotes + registration. Trigger on 'spin out X', 'extract this into its own repo', 'fork X sanitized'.
|
|
6
10
|
---
|
|
7
11
|
|
|
8
12
|
# spinout — Guided Project-Extraction Runbook
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sunset-review
|
|
3
|
-
description:
|
|
3
|
+
description: >
|
|
4
|
+
Use this skill when the user wants to identify unused, near-zero-use, or stale skills/agents/commands in
|
|
5
|
+
the plugin surface so they can be demoted or retired. Combines agent-dispatch telemetry (start-events
|
|
6
|
+
only) with static reference scanning, classifies every surface item into Active / Investigate / Demote /
|
|
7
|
+
Retire, and emits a Markdown report plus JSON sidecar. NEVER auto-deletes — surfaces candidates for
|
|
8
|
+
human decision. Quarterly cadence. <example>Context: The plugin surface has grown and the maintainer
|
|
9
|
+
wants to prune dead weight. user: "/sunset-review" assistant: "Running the sunset walk — classifying
|
|
10
|
+
skills, agents, and commands by usage telemetry + static refs, grouped by Retire / Demote / Investigate
|
|
11
|
+
/ Active. No item is deleted automatically; I'll surface Retire/Demote candidates for your decision."
|
|
12
|
+
<commentary>The user wants a usage-driven prune candidate list; this skill runs the read-only walker,
|
|
13
|
+
presents grouped verdicts, and writes a sidecar — it never deletes.</commentary></example>
|
|
4
14
|
model: inherit
|
|
5
15
|
color: amber
|
|
6
16
|
---
|
|
@@ -216,11 +216,11 @@ Group `medium` and `low` findings and present via a single `AskUserQuestion` cal
|
|
|
216
216
|
AskUserQuestion({
|
|
217
217
|
questions: [{
|
|
218
218
|
question: `<N> medium/low findings to triage. How to handle?`,
|
|
219
|
-
header: "
|
|
219
|
+
header: "Triage",
|
|
220
220
|
options: [
|
|
221
221
|
{
|
|
222
222
|
label: "Create all (Recommended)",
|
|
223
|
-
description: "
|
|
223
|
+
description: "Files <N> new issues at once, all with label from:test-runner — fastest, and you can still close any of them afterwards."
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
226
|
label: "Review each",
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tmux-layout
|
|
3
|
-
description:
|
|
3
|
+
description: >
|
|
4
|
+
Use this skill when the operator wants a prepared tmux visualization layout for the session's
|
|
5
|
+
side-channels (STATE.md tail, CI-watch, events.jsonl tail). Renders a 4-pane default layout or debug
|
|
6
|
+
layout. Read-only side-channel observability — the coordinator chat stays in the operator's original
|
|
7
|
+
terminal. Trigger phrases: "tmux layout", "split panes for ci watch", "visualize session side-channels",
|
|
8
|
+
"show me state-md tail and ci".
|
|
4
9
|
model: inherit
|
|
5
10
|
color: cyan
|
|
6
11
|
tools: Read, Bash, Grep, Glob
|
|
@@ -47,7 +52,7 @@ The skill prints a one-line tmux command. Paste it into a SECOND terminal (do no
|
|
|
47
52
|
|---|---|---|
|
|
48
53
|
| 1 | **Shell** (operator scratch — NOT claude) | `bash` (interactive) |
|
|
49
54
|
| 2 | STATE.md tail | `tail -F <state-dir>/STATE.md` |
|
|
50
|
-
| 3 | CI watch (poll-loop wrapper) | `while true; do clear; glab ci status
|
|
55
|
+
| 3 | CI watch (poll-loop wrapper) | `while true; do clear; glab ci status -R <spec> --output json \| jq -r '.jobs[] \| ...'; sleep 15; done` |
|
|
51
56
|
| 4 | events.jsonl wave/gate filter | `tail -F .orchestrator/metrics/events.jsonl \| jq --unbuffered 'select(.event \| test("wave\|gate\|spiral"))'` |
|
|
52
57
|
| 5 | agent-status telemetry (#565, only with `--with-status-pane`) | `while true; do clear; jq . .orchestrator/runtime/agent-status-current.json 2>/dev/null \|\| echo ...; sleep 2; done` |
|
|
53
58
|
|