session-orchestrator 3.22.0 → 3.24.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 +8 -0
- 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/050-plan.mdc +2 -2
- 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/remote-offload/SKILL.md +13 -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 +446 -0
- package/README.md +22 -17
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -2
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- 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/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +194 -25
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +7 -7
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +120 -10
- 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 +49 -2
- package/docs/session-config-reference.md +89 -9
- package/docs/session-config-template.md +38 -7
- package/docs/telemetry/telemetry-claims.md +11 -10
- package/docs/telemetry.md +52 -1
- package/hooks/_lib/atomic-json.mjs +111 -0
- package/hooks/_lib/lock-bootstrap.mjs +8 -4
- package/hooks/_lib/subagent-paths.mjs +143 -0
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/cwd-change-restore.mjs +9 -29
- package/hooks/enforce-scope.mjs +93 -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 +2 -2
- package/hooks/on-session-end.mjs +486 -19
- package/hooks/on-session-start.mjs +263 -12
- package/hooks/on-stop.mjs +392 -24
- package/hooks/post-bash-write-verify.mjs +104 -4
- package/hooks/post-subagent-discovery-validator.mjs +182 -21
- package/hooks/post-tool-batch-wave-signal.mjs +165 -42
- package/hooks/post-tool-failure-corrective-context.mjs +9 -32
- package/hooks/pre-bash-issue-budget.mjs +117 -4
- package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
- 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/hooks/subagent-telemetry.mjs +11 -26
- package/monitors/monitors.json +6 -0
- package/package.json +1 -1
- 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/autopilot.mjs +31 -12
- package/scripts/backfill-abandoned-sessions.mjs +80 -11
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/emit-event.mjs +10 -2
- 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/parse.mjs +5 -29
- package/scripts/lib/auto-dialectic.mjs +68 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +318 -18
- package/scripts/lib/build-live-signals.mjs +49 -27
- package/scripts/lib/ci-status-banner.mjs +158 -11
- package/scripts/lib/cold-start-detector.mjs +23 -14
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/block-header.mjs +55 -0
- package/scripts/lib/config/discovery-validator.mjs +7 -2
- package/scripts/lib/config/health-endpoints.mjs +383 -0
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/remote-hosts.mjs +233 -0
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +87 -8
- 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/enumerate.mjs +2 -17
- package/scripts/lib/events-schema.mjs +48 -0
- package/scripts/lib/events.mjs +238 -5
- package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +81 -8
- package/scripts/lib/gates/gate-helpers.mjs +76 -15
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
- package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/issue-budget.mjs +229 -30
- 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 +25 -10
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/peer-discovery.mjs +20 -2
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +43 -3
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +517 -18
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +148 -3
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +126 -0
- package/scripts/lib/session-close-backfill.mjs +427 -37
- package/scripts/lib/session-discovery.mjs +69 -5
- package/scripts/lib/session-end/phase-skip.mjs +38 -5
- 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 +220 -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-canonical.mjs +446 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +90 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +350 -52
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/schema.mjs +74 -8
- package/scripts/lib/telemetry/sync.mjs +91 -16
- package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
- package/scripts/lib/validate/check-agents.mjs +66 -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 +25 -65
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -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-skill-script-paths.mjs +436 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
- package/scripts/lib/validate/check-unwired-features.mjs +333 -32
- package/scripts/lib/validate/check-validator-registration.mjs +248 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
- package/scripts/lib/validate/markdown-fences.mjs +196 -0
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- 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-lock.mjs +185 -0
- package/scripts/lib/vault-status/board-writer.mjs +381 -141
- package/scripts/lib/vault-status/narrative-mirror.mjs +190 -27
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-executor/remote-dispatch.mjs +504 -0
- package/scripts/lib/wave-resource-gate.mjs +127 -7
- package/scripts/lib/wave-transcript-tail.mjs +889 -0
- package/scripts/materialize-wave-scope.mjs +228 -15
- package/scripts/mcp-server.sh +11 -2
- package/scripts/memory-propose.mjs +132 -8
- package/scripts/parse-config.mjs +65 -0
- package/scripts/promote-vault-strict.mjs +4 -15
- package/scripts/site-numbers.mjs +36 -4
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +29 -0
- package/scripts/validate-wave-scope.mjs +67 -0
- package/scripts/vault-consolidate.mjs +3 -11
- package/scripts/vault-integration-watcher.mjs +2 -4
- package/scripts/vault-mirror.mjs +305 -51
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +31 -2
- package/skills/_shared/parallel-aware-preamble.md +19 -4
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +29 -2
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +3 -3
- 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/claude-md-drift-check/checker.mjs +39 -11
- package/skills/contract-version-bump/SKILL.md +1 -1
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/ecosystem-health/SKILL.md +4 -1
- package/skills/ecosystem-health/wizard.md +5 -0
- package/skills/evolve/SKILL.md +38 -1
- package/skills/journey-audit/SKILL.md +270 -0
- package/skills/peekaboo-driver/SKILL.md +15 -3
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +46 -3
- package/skills/remote-offload/SKILL.md +89 -0
- package/skills/session-end/SKILL.md +17 -4
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +20 -9
- 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 +54 -17
- 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/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +5 -3
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +143 -22
- package/templates/_shared/journey-manifest.md +110 -0
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -152,9 +152,10 @@ After learnings are written (Phase 3.6), determine whether to emit a **manual-ca
|
|
|
152
152
|
```javascript
|
|
153
153
|
import { shouldDispatchAutoDream } from '${PLUGIN_ROOT}/scripts/lib/auto-dream.mjs';
|
|
154
154
|
import { resolveMemoryDir } from '${PLUGIN_ROOT}/scripts/lib/memory-paths.mjs';
|
|
155
|
-
const
|
|
155
|
+
const repoRoot = process.cwd();
|
|
156
|
+
const memoryDir = resolveMemoryDir(repoRoot);
|
|
156
157
|
const decision = await shouldDispatchAutoDream({
|
|
157
|
-
repoRoot
|
|
158
|
+
repoRoot,
|
|
158
159
|
memoryDir,
|
|
159
160
|
threshold: config['memory-cleanup-threshold'] ?? 5,
|
|
160
161
|
softLimit: config['memory-cleanup-soft-limit'] ?? 180,
|
|
@@ -245,14 +246,15 @@ After learnings are written (Phase 3.6) and the auto-dream decision is made (Pha
|
|
|
245
246
|
|
|
246
247
|
1. Read `dialectic.cadence` (default 5), `dialectic.model` (default haiku), `dialectic.budget-tokens` (default 8000) from `$CONFIG`.
|
|
247
248
|
|
|
248
|
-
2. Invoke `
|
|
249
|
+
2. Invoke `decideAndRecordAutoDialectic` from `scripts/lib/auto-dialectic.mjs`:
|
|
249
250
|
```javascript
|
|
250
|
-
import {
|
|
251
|
-
const decision = await
|
|
251
|
+
import { decideAndRecordAutoDialectic } from '${PLUGIN_ROOT}/scripts/lib/auto-dialectic.mjs';
|
|
252
|
+
const decision = await decideAndRecordAutoDialectic({
|
|
252
253
|
repoRoot: process.cwd(),
|
|
253
254
|
cadence: config.dialectic?.cadence ?? 5,
|
|
254
255
|
});
|
|
255
256
|
```
|
|
257
|
+
Same return shape as `shouldDispatchAutoDialectic` (`{trigger, reason, signals}`) — `decideAndRecordAutoDialectic` calls it internally and additionally emits the mechanical `orchestrator.dialectic.nudge_decided` telemetry record on all four return paths (#1200 part c), so the nudge decision is observable without depending on this prose actually reaching step 5/7.
|
|
256
258
|
|
|
257
259
|
3. If `decision.trigger === false`: log `auto-dialectic: not triggered (${decision.reason})` and continue. Emit no nudge. Do NOT update `.orchestrator/dialectic-last-run`.
|
|
258
260
|
|
|
@@ -290,17 +292,19 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
290
292
|
|
|
291
293
|
1. Read Session Config: `reconcile.enabled` (default `false`), `reconcile['rule-expiry-days']` (default `null` — falls back to per-type TTL in the engine), `reconcile['confidence-floor']` (default `0.5`), `reconcile['min-rule-days']` (default `7` — floor window (days) applied to a proposed rule's `expires-at` so a near-dead or already-elapsed natural expiry never produces a born-dead rule, issue #741.1), `reconcile['min-insight-chars']` (default `24` — opt-in minimum insight length gating the eligibility placeholder-insight check, issue #741.2), `reconcile['max-proposals-per-run']` (default `10` — volume brake, issue #900 D; the engine sorts eligible learnings by confidence DESC and proposes at most this many per run). If `reconcile.enabled` is not `true`, log `reconcile: disabled (reconcile.enabled=false)` and skip all remaining steps.
|
|
292
294
|
|
|
293
|
-
2. Invoke `
|
|
295
|
+
2. Invoke `runReconcileAtSessionEnd` from `scripts/lib/reconcile/engine.mjs`:
|
|
294
296
|
|
|
295
297
|
```javascript
|
|
296
|
-
import {
|
|
297
|
-
const { proposals, rejected, summary, error } = await
|
|
298
|
+
import { runReconcileAtSessionEnd } from '${PLUGIN_ROOT}/scripts/lib/reconcile/engine.mjs';
|
|
299
|
+
const { proposals, rejected, summary, error } = await runReconcileAtSessionEnd({
|
|
298
300
|
repoRoot: process.cwd(),
|
|
299
301
|
ruleExpiryDays: config.reconcile['rule-expiry-days'] ?? undefined,
|
|
300
302
|
minRuleDays: config.reconcile['min-rule-days'] ?? undefined,
|
|
301
303
|
minInsightChars: config.reconcile['min-insight-chars'] ?? undefined,
|
|
302
304
|
maxProposalsPerRun: config.reconcile['max-proposals-per-run'] ?? undefined,
|
|
303
305
|
now: new Date(),
|
|
306
|
+
// trigger is pinned to 'session-end' IN CODE by runReconcileAtSessionEnd
|
|
307
|
+
// (#1201 Part A) — this prose block no longer sets it.
|
|
304
308
|
});
|
|
305
309
|
```
|
|
306
310
|
|
|
@@ -384,12 +388,19 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
384
388
|
approved,
|
|
385
389
|
rejected: [...rejected, ...operatorRejected],
|
|
386
390
|
repoRoot: process.cwd(),
|
|
391
|
+
// #1099 — FORWARD BOTH. `decideReconcile()` already resolved them onto its
|
|
392
|
+
// RUN decision (`scripts/lib/session-end/phase-skip.mjs`, `targets` +
|
|
393
|
+
// `baselineRoot`); dropping them here silently pins every session to
|
|
394
|
+
// repo-local writes no matter what `reconcile.targets` says. Absent
|
|
395
|
+
// `baselineRoot` is the documented no-op path, not an error.
|
|
396
|
+
targets: decision.targets,
|
|
397
|
+
baselineRoot: decision.baselineRoot,
|
|
387
398
|
sessionId,
|
|
388
399
|
});
|
|
389
400
|
// writeResult = { written: number, archived: number, errors: string[] }
|
|
390
401
|
```
|
|
391
402
|
|
|
392
|
-
`writeApprovedRules` is lock-serialised (via `withFileLock` on `.orchestrator/rules.lock`) and writes each approved proposal to `.claude/rules/<slug>.md`. Rejected proposals (engine-rejected + operator-rejected) are archived to `.orchestrator/reconcile.rejected.log` with reason `user-declined` for operator-rejected and the engine's own audit reason for engine-rejected.
|
|
403
|
+
`writeApprovedRules` is lock-serialised (via `withFileLock` on `.orchestrator/rules.lock`) and writes each approved proposal to the directory its target names — `.claude/rules/<slug>.md` for `repo-local`, `<baselineRoot>/proposals/<slug>.md` for `baseline`. Each target's write root is confined separately; the leaf comes from the renderer-minted `slug`, never from a caller-supplied path. Rejected proposals (engine-rejected + operator-rejected) are archived to `.orchestrator/reconcile.rejected.log` with reason `user-declined` for operator-rejected and the engine's own audit reason for engine-rejected.
|
|
393
404
|
|
|
394
405
|
8. Log outcome for Phase 6 Final Report: `reconcile: ${surfaced.length} surfaced → ${approved.length} approved (written: ${writeResult.written}), ${operatorRejected.length} operator-declined${writeResult.errors.length > 0 ? `, ${writeResult.errors.length} write-errors (see sweep.log)` : ''}`.
|
|
395
406
|
|
|
@@ -11,12 +11,20 @@ Compute the v0 recommendation from in-memory session metrics and additively writ
|
|
|
11
11
|
```bash
|
|
12
12
|
node --input-type=module -e "
|
|
13
13
|
import {appendFileSync, mkdirSync} from 'node:fs';
|
|
14
|
+
import {execSync} from 'node:child_process';
|
|
14
15
|
import {updateFrontmatterFieldsOnDisk} from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
|
|
15
16
|
import {computeV0Recommendation} from '${PLUGIN_ROOT}/scripts/lib/recommendations-v0.mjs';
|
|
16
17
|
|
|
17
18
|
const SWEEP_LOG = '.orchestrator/metrics/sweep.log';
|
|
18
19
|
|
|
19
20
|
try {
|
|
21
|
+
// repoRoot is REQUIRED — updateFrontmatterFieldsOnDisk calls requireRepoRoot()
|
|
22
|
+
// and THROWS on undefined (scripts/lib/state-md/frontmatter-mutators.mjs).
|
|
23
|
+
// Resolve it explicitly; there is no implicit cwd fallback by design (PSA
|
|
24
|
+
// parallel-session CWD drift). Passing an undefined repoRoot here sends every close down
|
|
25
|
+
// the fail-open catch below and silently omits all 5 fields (GitLab #1036).
|
|
26
|
+
const repoRoot = execSync('git rev-parse --show-toplevel', {encoding: 'utf8'}).trim();
|
|
27
|
+
|
|
20
28
|
// In-memory session metrics — pulled from the session's running state,
|
|
21
29
|
// NOT re-read from sessions.jsonl (which was just-written in Phase 3.7).
|
|
22
30
|
const completionRate = <number from session metrics: completed_issues / planned_issues>;
|
|
@@ -33,7 +41,7 @@ try {
|
|
|
33
41
|
'rationale': rec.rationale,
|
|
34
42
|
};
|
|
35
43
|
|
|
36
|
-
await updateFrontmatterFieldsOnDisk(
|
|
44
|
+
await updateFrontmatterFieldsOnDisk(repoRoot, fields);
|
|
37
45
|
console.log('Recommendations written: ' + rec.mode + ' (' + rec.rationale + ')');
|
|
38
46
|
} catch (err) {
|
|
39
47
|
// AC3: defensive — exception must NOT block Phase 3.4 status: completed.
|
|
@@ -44,11 +52,17 @@ try {
|
|
|
44
52
|
error: String(err && err.message ? err.message : err),
|
|
45
53
|
};
|
|
46
54
|
appendFileSync(SWEEP_LOG, JSON.stringify(evt) + '\n');
|
|
47
|
-
|
|
55
|
+
// Name the CAUSE on stderr, not just the consequence: the fail-open path is
|
|
56
|
+
// correct policy, but a WARN that says only 'fields omitted' is what let
|
|
57
|
+
// #1036 survive 5 weeks across two closes (sweep.log had the message, nobody
|
|
58
|
+
// read sweep.log). The operator sees this line in the close transcript.
|
|
59
|
+
console.error('⚠ Phase 3.7a: recommendation compute failed — ' + evt.error + ' — fields omitted, sweep.log entry written. Continuing.');
|
|
48
60
|
}
|
|
49
61
|
"
|
|
50
62
|
```
|
|
51
63
|
|
|
64
|
+
**Repo-root contract (#1036 / Kanevry#65):** `updateFrontmatterFieldsOnDisk(repoRoot, fields)` takes the repo root as its FIRST argument and rejects a missing one via `requireRepoRoot()` — there is no implicit `process.cwd()` fallback, deliberately (parallel-session CWD drift, PSA rules). Run the snippet verbatim, including the `repoRoot` binding: an `undefined` first argument throws *before* STATE.md is touched, and the AC3 catch in the snippet turns that throw into a green close with all 5 fields missing. That failure is invisible in STATE.md — it looks exactly like "this session produced no recommendation".
|
|
65
|
+
|
|
52
66
|
**Data source guarantee:** The three inputs (`completionRate`, `carryoverRatio`, `carryoverIssues`) MUST come from the in-memory session metrics object built in Phase 1.7, NOT from a re-read of `.orchestrator/metrics/sessions.jsonl`. Reading the just-written JSONL would introduce a circular dependency and risk reading a truncated line if Phase 3.7's `appendJsonl` was mid-flush.
|
|
53
67
|
|
|
54
68
|
**Field precision:**
|
|
@@ -52,6 +52,8 @@ These are passed via the conversation context (not a file). Parse the preceding
|
|
|
52
52
|
|
|
53
53
|
> Check this **before Step 0**. If the express path is active, this skill emits a minimal 1-wave plan and exits — no role decomposition, no wave splitting, no agent count computation.
|
|
54
54
|
|
|
55
|
+
> Phase 8.5 of session-start hands off here NORMALLY when the express path activates — it does not skip session-plan (#1146). The banner below is printed by `node scripts/express-path.mjs`, and the 1-wave plan this section emits is the artifact `/go` detects.
|
|
56
|
+
|
|
55
57
|
**Detect express-path activation:** Search the conversation context for the banner line:
|
|
56
58
|
|
|
57
59
|
```
|
|
@@ -136,11 +138,14 @@ Before assigning tasks to waves, discover available agents for this session:
|
|
|
136
138
|
- Role keys: `impl`, `test`, `db`, `ui`, `security`, `compliance`, `docs`, `perf`
|
|
137
139
|
- Example: `agent-mapping: { impl: code-editor, test: test-specialist, db: database-architect }`
|
|
138
140
|
- If present, these explicit mappings take priority over auto-matching
|
|
141
|
+
- A value MAY carry a channel prefix: `session-orchestrator:<plugin-agent>` or `cursor:<model>` (foreign model, #1150). An unknown prefix is rejected fail-loud by `scripts/lib/config.mjs` at parse time — see `docs/session-config-reference.md` § `agent-mapping` values.
|
|
139
142
|
|
|
140
143
|
**Validation:** If `agent-mapping` specifies an agent name, verify the agent exists:
|
|
141
144
|
- For project agents: check `<state-dir>/agents/<name>.md` exists
|
|
142
145
|
- For plugin agents: check the agent is registered (contains `:` separator)
|
|
143
|
-
-
|
|
146
|
+
- For `cursor:<model>` (foreign channel): the existence check is on the CHANNEL, not the model — `cursor-agent` on `PATH` and logged in (`cursor-agent status`). The model string is free-form and is validated only at dispatch time, because the model catalogue lives outside this repo.
|
|
147
|
+
- If the agent doesn't exist — or the cursor channel is unavailable (binary missing / not logged in) — warn the user and fall back to auto-discovery for that role (same fallback shape in both cases; never hard-fail the plan)
|
|
148
|
+
- **Two constraints the plan must carry into the wave, both owned by `skills/wave-executor/wave-loop.md` § Third branch: foreign-model dispatch** (one place owns the contract — do not restate it here): a `cursor:<model>` mapping is INERT for any `never_foreign` role (impl-core, security-review, migration, release, secrets, incident, refactor-crosscut — the adapter refuses it), and every foreign run requires a MANDATORY Claude semantic diff-review before merge-back. Plan the review as work, not as a formality.
|
|
144
149
|
|
|
145
150
|
3. **Build Agent Registry** (resolution priority):
|
|
146
151
|
- **Priority 1**: Project agents (from `<state-dir>/agents/` — see Platform Note) — matched by name
|
|
@@ -19,6 +19,7 @@ For each wave, define agents with:
|
|
|
19
19
|
- `Isolation: worktree` means the wave-executor will pass `isolation: "worktree"` to the Agent tool, giving each agent its own git worktree copy
|
|
20
20
|
- `MaxTurns` is enforced via the agent prompt — wave-executor includes a turn limit instruction in each agent's prompt
|
|
21
21
|
- `status` is the mission-status enum value for this wave-plan item (#340). Always `brainstormed` in the initial plan. Wave-executor updates it at gate transitions (validated → in-dev → testing → completed). Rollback to `brainstormed` is allowed from any state. The five values are listed in `SKILL.md` § Mission-Status Enum; nothing validates them mechanically — `setMissionStatus` writes the string it is given to both STATE.md surfaces on purpose, so keeping the value in-enum is the coordinator's job.
|
|
22
|
+
- The wave-plan item's `id` becomes the `taskId` every `setMissionStatus` call for this item uses, and `setMissionStatus` REFUSES ids outside `[a-z][a-z0-9]*(?:-[a-z0-9]+)*-\d+` (lowercase segments, single hyphens, trailing bare digits) with `refused: 'id-grammar'` and a stderr WARN — nothing is written. Mint ids matching it: `m-1`, `docs-2`, `w2-1`, `w2-a-10` all accepted; `w2-a10`, `w3-p2`, `W3-I1`, `Docs_2` all refused.
|
|
22
23
|
|
|
23
24
|
> **Deconfliction rule:** Before finalizing agent specs for a wave, verify that no two agents in the same wave list overlapping `Files:` paths. If overlap is found, either merge the agents into one or move one task to a later wave. Two agents editing the same file in parallel causes merge conflicts that require manual resolution.
|
|
24
25
|
|
|
@@ -41,8 +41,9 @@ This runs BEFORE the local session-lock acquire in Phase 1.2 — the preamble's
|
|
|
41
41
|
|
|
42
42
|
**Outcome handling:**
|
|
43
43
|
- `PASS_THROUGH` → continue to Phase 1
|
|
44
|
+
- `PASS_THROUGH` with a non-empty `advisory` array (GH#67) → a `discovered` peer with `lockSuperseded: true` never fires the Promotion AUQ (it stays visible, per the #1085 advisory-lock contract — it is not filtered). Print ONE advisory line per entry: `parallel-aware: registry entry <sessionId> (last heartbeat <N> min ago) is superseded by this root's live lock <lockOwnerId> — likely a finished task on a platform without SessionEnd (GH#67); still counted for PSA-001 awareness`, then continue to Phase 1. It remains PSA-002-relevant if the same id also shows up in STATE.md (`source: 'state-md'` is handled by Phase 1.2.1 unchanged).
|
|
44
45
|
- `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
|
|
46
|
+
- `PROMOTION_OFFER` with user picking "Worktree anlegen + starten" → call `enterWorktree({ basePath, sessionId, branch, repoRoot, rawSessionId, reason: 'worktree-promotion' })` from `scripts/lib/autopilot/worktree-pipeline.mjs` — since #1170 this ONE call does both jobs: it creates the destination worktree AND, because `rawSessionId` is supplied, releases the source root internally (see below), so no separate `leaveSourceRoot` call is made at this site. Compute params: `basePath = path.dirname(repoRoot)`, `sessionId` from resolveSemanticSessionId() **for the worktree-name attribution label only**, `branch` from current HEAD, `repoRoot = process.cwd()`, `rawSessionId` from `readLock({ repoRoot }).session_id`. `sessionId` (the semantic label) 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. **`rawSessionId` 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 aborts the internal `leaveSourceRoot()` teardown with `left.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. `enterWorktree()`'s return value carries the outcome as `left: { ok, steps, reason? }`; `leaveSourceRoot()` never throws, so on `left.ok !== true` `enterWorktree` itself emits the stderr WARN `enterWorktree: leaveSourceRoot: <reason>` and the promotion continues regardless — the destination worktree already exists by the time this runs, 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
47
|
- `PROMOTION_OFFER` with user picking "Manuell — in-place daneben" → append Deviation, continue to Phase 1
|
|
47
48
|
- `PROMOTION_OFFER` with user picking "Abbrechen" → exit cleanly
|
|
48
49
|
|
|
@@ -53,6 +54,25 @@ This runs BEFORE the local session-lock acquire in Phase 1.2 — the preamble's
|
|
|
53
54
|
|
|
54
55
|
Read and parse Session Config per `skills/_shared/config-reading.md`. Store result as `$CONFIG`.
|
|
55
56
|
|
|
57
|
+
## Phase 1.05: Skill-Invocation Self-Report (#1199)
|
|
58
|
+
|
|
59
|
+
> Emit an L1 skill-invocation record for `session-start` itself. The PreToolUse `Skill`-matcher hook only captures skills dispatched via the `Skill` tool — a **prose-invoked** skill like this one is invisible to it (verified gap: external users show 0/20 sessions with a `session-start` row in `skill-invocations.jsonl`, vs. 93/93 for the operator). This self-report closes that gap so L2/L3 skill-health has a `session-start` selection signal. Best-effort, try/catch-silent — it never blocks Phase 1.1.
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
try {
|
|
63
|
+
const { appendSkillInvocation, DEFAULT_SKILL_INVOCATIONS_PATH } =
|
|
64
|
+
await import('${PLUGIN_ROOT}/scripts/lib/skill-invocations-schema.mjs');
|
|
65
|
+
const nodePath = await import('node:path');
|
|
66
|
+
await appendSkillInvocation(nodePath.join(process.cwd(), DEFAULT_SKILL_INVOCATIONS_PATH), {
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
event: 'selected',
|
|
69
|
+
skill: 'session-orchestrator:session-start',
|
|
70
|
+
session_id: null, // no session.lock is bound yet at Phase 1 — the raw id is acquired at Phase 1.2 (#1199)
|
|
71
|
+
phase: 'session-start',
|
|
72
|
+
});
|
|
73
|
+
} catch { /* self-report is advisory — never blocks Phase 1.1 */ }
|
|
74
|
+
```
|
|
75
|
+
|
|
56
76
|
## Phase 1.1: Dispatcher-Autonomy Migration Capture (one-time, per-repo)
|
|
57
77
|
|
|
58
78
|
> Closes session-orchestrator issue #681 (Epic #673 P3 — one-time per-repo dispatcher-autonomy capture). Migration trigger: the first session-start after this feature ships on a repo whose committed `dispatcher-autonomy:` block is still absent. Cross-reference `.claude/rules/ask-via-tool.md` (AUQ via tool, not prose).
|
|
@@ -152,13 +172,22 @@ Where `sessionId` is the physical raw identity for this invocation: the native h
|
|
|
152
172
|
- 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
173
|
`Force-took session lock from session_id=<existingLock.session_id>, age=<ageHours>h, mode=<existingLock.mode>, pid=<existingLock.pid>`. Continue.
|
|
154
174
|
|
|
155
|
-
3. **`result.ok === false`** with `reason === 'stale-
|
|
156
|
-
- A stale lock was found (
|
|
175
|
+
3. **`result.ok === false`** with `reason === 'stale-heartbeat'`:
|
|
176
|
+
- 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
177
|
- Present a choice via `AskUserQuestion`:
|
|
158
178
|
```js
|
|
179
|
+
// `heartbeatAgeMinutes` and `ageHours` come straight off the acquire() result (#1137);
|
|
180
|
+
// `sameHost` is not on the result — compute it first. Use hostnamesMatch, NEVER a raw
|
|
181
|
+
// `===` against os.hostname(): the hostname flips spelling on a single machine
|
|
182
|
+
// (measured 2026-08-24: `Mac.home` and `Ferdinands-MacBook-Pro.local` ten minutes apart),
|
|
183
|
+
// so a raw comparison labels this machine's OWN lock "another machine" (#1072).
|
|
184
|
+
// `||`, not `??` — an EMPTY-STRING host_id must fall back to `host`, or
|
|
185
|
+
// hostnamesMatch('', …) is false and this machine reads its own lock as
|
|
186
|
+
// cross-host. Production uses `lockHostCandidate()` from host-identity.mjs.
|
|
187
|
+
const sameHost = hostnamesMatch(existingLock.host_id || existingLock.host, os.hostname());
|
|
159
188
|
AskUserQuestion({
|
|
160
189
|
questions: [{
|
|
161
|
-
question: `A stale session lock is in the way — started ${ageHours}h ago
|
|
190
|
+
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?`,
|
|
162
191
|
header: "Stale lock",
|
|
163
192
|
multiSelect: false,
|
|
164
193
|
options: [
|
|
@@ -170,7 +199,7 @@ Where `sessionId` is the physical raw identity for this invocation: the native h
|
|
|
170
199
|
```
|
|
171
200
|
- **Codex CLI / Cursor IDE fallback (numbered Markdown list):**
|
|
172
201
|
```
|
|
173
|
-
A stale session lock is in the way — started <ageHours>h ago
|
|
202
|
+
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?
|
|
174
203
|
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
204
|
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
205
|
Reply with the number of your choice.
|
|
@@ -186,11 +215,11 @@ Where `sessionId` is the physical raw identity for this invocation: the native h
|
|
|
186
215
|
|
|
187
216
|
### Cross-host behaviour
|
|
188
217
|
|
|
189
|
-
When `existingLock.host !== os.hostname()`,
|
|
218
|
+
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
219
|
- For `reason === 'active'`: the recommendation is **Abort** — cross-host locks cannot be verified as dead.
|
|
191
220
|
- For stale reasons: the recommendation is still **Reclaim** only if TTL is clearly expired (>2× ttl_hours). Otherwise default to **Abort**.
|
|
192
221
|
- **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 —
|
|
222
|
+
- 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
223
|
|
|
195
224
|
## Phase 1.2.1: Peer-Guard (Epic #583 defense-in-depth)
|
|
196
225
|
|
|
@@ -213,11 +242,13 @@ if (peer) {
|
|
|
213
242
|
}
|
|
214
243
|
```
|
|
215
244
|
|
|
245
|
+
GH#67 note: the `lockSuperseded` advisory-downgrade described in Phase 0.5's outcome handling applies only to the `discovered` peer subset — this phase's `peer` is always `source: 'state-md'`, so a `discovered`-side `lockSuperseded: true` never suppresses this guard; the Worktree-Promotion AUQ still fires exactly as below whenever a live STATE.md peer is found.
|
|
246
|
+
|
|
216
247
|
### Decision flow
|
|
217
248
|
|
|
218
249
|
1. **`peer === null`** → no active peer owns STATE.md. Continue to Phase 1.5.
|
|
219
250
|
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`).
|
|
220
|
-
- User picks "Worktree anlegen + starten" → call `enterWorktree(
|
|
251
|
+
- User picks "Worktree anlegen + starten" → call `enterWorktree({ ..., rawSessionId, reason: 'worktree-promotion' })` from `scripts/lib/autopilot/worktree-pipeline.mjs` — since #1170 this ONE call also releases the source root: it calls `leaveSourceRoot({ repoRoot, sessionId: rawSessionId, semanticSessionId, reason })` from `scripts/lib/session-transition.mjs` internally, on BOTH success exits, so no separate `leaveSourceRoot` call is made at this site. `rawSessionId` 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). The return value's `left` field carries `leaveSourceRoot()`'s result; it never throws, so on `left.ok !== true` `enterWorktree` itself emits the stderr WARN `enterWorktree: leaveSourceRoot: <reason>` and the promotion continues regardless — 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).
|
|
221
252
|
- 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.
|
|
222
253
|
- User picks "Abbrechen" → exit cleanly.
|
|
223
254
|
|
|
@@ -515,7 +546,7 @@ This single call does three things:
|
|
|
515
546
|
|
|
516
547
|
- **Generator-marked + idempotent.** The board carries the `_generator: session-orchestrator-active-sessions@1` frontmatter sentinel; repeated writes that produce identical content are no-ops, so re-running this phase never churns the file.
|
|
517
548
|
- **Host-local + git-ignorable.** The board lives under the operator's vault tree (under `$HOME`), never inside any repo — it is never committed.
|
|
518
|
-
- **NEVER touches the
|
|
549
|
+
- **NEVER touches the human-authored `_overview.md`.** The writer hard-refuses any path whose basename is `_overview.md` (returns `{ action: 'skipped-handwritten' }`), and only ever overwrites files it owns (frontmatter `_generator` matches the marker). The handwritten overview is structurally safe.
|
|
519
550
|
|
|
520
551
|
### Non-blocking behavior
|
|
521
552
|
|
|
@@ -710,13 +741,14 @@ Group issues by:
|
|
|
710
741
|
- **info-only version mismatch** (patch or minor version only): `"ℹ bootstrap.lock: plugin-version=<lock-ver> (current=<plugin-ver>) — minor drift only, no action required."`
|
|
711
742
|
- **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."`
|
|
712
743
|
|
|
713
|
-
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})`).
|
|
744
|
+
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:
|
|
714
745
|
- **warn** (`stale_count > 0`, max `delta_hours <= 48`): `"⚠ vault-staleness: <N> projects stale (max delta: <X>h) — last run <timestamp>."`
|
|
715
746
|
- **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."`
|
|
747
|
+
- **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."`
|
|
716
748
|
|
|
717
|
-
|
|
749
|
+
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.
|
|
718
750
|
|
|
719
|
-
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() })
|
|
751
|
+
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:
|
|
720
752
|
- **Red** (`status === 'red'`): `"🚨 CI RED on HEAD (pipeline #<currentPipelineId>) — last green: #<lastGreen.pipelineId> (commit <SHA-7>, <redCount> pipelines ago). Failing job: <failingJobName>"`
|
|
721
753
|
- **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.
|
|
722
754
|
- **Green** (no `allowFailureJobs`) or **unknown**: silent (no banner) — informational only.
|
|
@@ -823,7 +855,9 @@ Group issues by:
|
|
|
823
855
|
|
|
824
856
|
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
857
|
|
|
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/_
|
|
858
|
+
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.
|
|
859
|
+
|
|
860
|
+
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).
|
|
827
861
|
|
|
828
862
|
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
863
|
|
|
@@ -1061,6 +1095,8 @@ Cross-reference: PRD F2.3 acceptance criteria (#505); `scripts/lib/memory-banner
|
|
|
1061
1095
|
|
|
1062
1096
|
> 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.
|
|
1063
1097
|
|
|
1098
|
+
> **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.
|
|
1099
|
+
|
|
1064
1100
|
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.
|
|
1065
1101
|
|
|
1066
1102
|
```javascript
|
|
@@ -1180,13 +1216,13 @@ Present your findings following that structure. Key rules:
|
|
|
1180
1216
|
|
|
1181
1217
|
### Phase 8.5: Express Path Evaluation (#214)
|
|
1182
1218
|
|
|
1183
|
-
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
|
|
1219
|
+
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.
|
|
1184
1220
|
|
|
1185
|
-
When all conditions are met, emits the banner:
|
|
1221
|
+
When all conditions are met, the CLI emits the banner on stderr:
|
|
1186
1222
|
```
|
|
1187
1223
|
Express path activated — <N> tasks, coordinator-direct, no inter-wave checks.
|
|
1188
1224
|
```
|
|
1189
|
-
|
|
1225
|
+
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.
|
|
1190
1226
|
|
|
1191
1227
|
**See `phase-8-5-express-path.md` for full details.**
|
|
1192
1228
|
|
|
@@ -1207,7 +1243,7 @@ After user alignment:
|
|
|
1207
1243
|
|
|
1208
1244
|
- **NEVER make assumptions** about code state based on memory or docs — always verify in actual files
|
|
1209
1245
|
- **NEVER skip the Q&A phase** — the user MUST confirm direction before wave planning
|
|
1210
|
-
- **ALWAYS
|
|
1246
|
+
- **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
|
|
1211
1247
|
- **ALWAYS check `.env` or `.env.local`** for VCS host, API keys, and service URLs
|
|
1212
1248
|
- **ALWAYS present options with pros/cons and a clear recommendation** — never just list facts
|
|
1213
1249
|
- **ALWAYS update VCS issue status** when claiming work — use the issue update command per the "Common CLI Commands" section of the gitlab-ops skill
|
|
@@ -1219,6 +1255,7 @@ After user alignment:
|
|
|
1219
1255
|
| File | Purpose |
|
|
1220
1256
|
|------|---------|
|
|
1221
1257
|
| `soul.md` | Identity and communication principles |
|
|
1258
|
+
| (inline) Phase 1.05 | Skill-Invocation Self-Report (#1199) — mirrors session-end Phase 0.6 |
|
|
1222
1259
|
| (inline) Phase 1.2 | Session Lock Acquire — `acquire()` call, active/stale/cross-host AUQ flows, `forceAcquire()` on user consent, deviation note wiring |
|
|
1223
1260
|
| (inline) Phase 1.7 | Vault Live-Status Board (#674/#716) — `sweepBoard()` from `scripts/lib/vault-status/board-writer.mjs`; gated on `vault-integration.enabled: true`; marks this repo `in-progress` + host-wide staleness sweep via `enumerateCandidates()` (`scripts/lib/dispatcher/enumerate.mjs`), so a crashed session in ANY repo renders `force-closed` from any repo's session-start; generator-marked + idempotent; never touches `_overview.md`; non-blocking (falls back to single-repo `mirrorBoard()` on enumeration failure) |
|
|
1224
1261
|
| `presentation-format.md` | Phase 8 output templates and AskUserQuestion examples |
|
|
@@ -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
|
|
|
@@ -4,13 +4,57 @@
|
|
|
4
4
|
|
|
5
5
|
After the user confirms the session type and scope via the Q&A above, evaluate whether the **Express Path** applies before handing off to session-plan. The express path collapses the full 5-wave plan into a single coordinator-direct phase for lightweight sessions.
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Do not evaluate these conditions by hand — run the CLI (#1119, #1146).**
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node scripts/express-path.mjs \
|
|
11
|
+
--repo-root "$PWD" \
|
|
12
|
+
--session-type <housekeeping|feature|deep> \
|
|
13
|
+
--task-count <N> \
|
|
14
|
+
--parallel-agents <true|false>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
stdout is one JSON line — `{"activated":<bool>,"reasons":[…]}`. The activation banner and every
|
|
18
|
+
diagnostic go to stderr. Exit `0` means the evaluation COMPLETED, activation and refusal alike:
|
|
19
|
+
branch on the `activated` field, never on the exit code. Exit `1` is an input error (a missing or
|
|
20
|
+
invalid `--session-type` / `--task-count` / `--repo-root`), exit `2` an unreadable config file.
|
|
21
|
+
`--parallel-agents` is optional and tri-state — omitting it asserts nothing and leaves the field
|
|
22
|
+
out of the record, which is NOT the same claim as `false`.
|
|
23
|
+
|
|
24
|
+
`--repo-root` is required and is never defaulted from `SO_PROJECT_DIR` (#941): an ambient
|
|
25
|
+
destination once put a synthetic record into the operator's real fleet ledger.
|
|
26
|
+
|
|
27
|
+
Until #1146 this block was a fenced `js` snippet with an `import` — prose no process runs. Measured
|
|
28
|
+
at HEAD `01eb35d`: `rg -n "evaluateExpressPath" scripts hooks` returned the definition and nothing
|
|
29
|
+
else, so the module had ZERO production callers. A hook cannot close that gap either — `sessionType`
|
|
30
|
+
and `taskCount` exist only AFTER the Phase 8 Q&A, and no hook event fires there. The caller has to
|
|
31
|
+
be something the coordinator RUNS at this point in the flow, which is what `scripts/express-path.mjs`
|
|
32
|
+
is.
|
|
33
|
+
|
|
34
|
+
`evaluateExpressPath` (behind the CLI) makes the decision AND records it as `orchestrator.express_path.evaluated`
|
|
35
|
+
— on **every** evaluation, activation and refusal alike. That is the whole point: until #1119 the
|
|
36
|
+
conditions below were prose only, `scripts/lib/config.mjs` discarded `express-path` **even when the
|
|
37
|
+
block was present** (measured: 88 keys emitted, none of them this one), and the ledger held **0**
|
|
38
|
+
express-path events across its entire history. Whether the path ever fired was unanswerable.
|
|
39
|
+
Re-deriving the conditions in a coordinator turn re-opens exactly that hole; the conditions below
|
|
40
|
+
are the specification the module implements, not a second implementation.
|
|
41
|
+
|
|
42
|
+
`reasons` carries the blocking codes when `activated: false` and the satisfied ones when `true`.
|
|
43
|
+
Nothing short-circuits, so a refusal names **every** blocker — a reader can see whether trimming
|
|
44
|
+
the issue list alone would have helped. Unmeasured inputs are omitted from the payload, never
|
|
45
|
+
written as `0`/`false`, and an unmeasured `sessionType`/`taskCount` fails CLOSED.
|
|
46
|
+
|
|
47
|
+
**Activation conditions (the module's specification):**
|
|
8
48
|
|
|
9
49
|
1. `express-path.enabled` is `true` in Session Config (default: `true` — opt-in by default, opt-out via `express-path.enabled: false`).
|
|
10
50
|
2. Session type is `housekeeping` (the user confirmed `housekeeping` in Phase 8).
|
|
11
51
|
3. Agreed issue scope is ≤ 3 issues AND no parallel agents are required (i.e., tasks are sequential, no wave decomposition needed).
|
|
12
52
|
|
|
13
|
-
|
|
53
|
+
> Condition 3 carries **two** clauses, so the module takes **four** inputs, not three. The
|
|
54
|
+
> condition matrix below and `docs/session-config-reference.md` both list a `housekeeping` / 1–3 /
|
|
55
|
+
> `enabled: true` row that still does NOT activate, because parallel agents are required.
|
|
56
|
+
|
|
57
|
+
**Backward compat:** when `express-path.enabled: false`, the normal 5-wave session-plan flow runs as before. Note that the EVALUATION itself is not skipped — run the CLI regardless, and it returns `{"activated":false,"reasons":["disabled-by-config"]}` and records that refusal. An opt-out that leaves no record is indistinguishable in the ledger from an evaluation that never happened, which is the #1119 hole.
|
|
14
58
|
|
|
15
59
|
**Historical context:** The 13 prior coordinator-direct sessions documented in `CLAUDE.md` (or `AGENTS.md` on Codex CLI; 2026-04 series — vault-mirror GH#31, phased-rollout #307, v3.2.0 release, etc.) were all running this pattern implicitly: no wave decomposition, coordinator executes tasks directly in sequence. This phase codifies what was already proven to work.
|
|
16
60
|
|
|
@@ -22,23 +66,42 @@ Emit the following banner immediately after the Phase 8 Q&A resolves:
|
|
|
22
66
|
Express path activated — <N> tasks, coordinator-direct, no inter-wave checks.
|
|
23
67
|
```
|
|
24
68
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
69
|
+
> **RESOLVED (#1146, operator decision) — session-plan RUNS, in shortened form.** Five documents
|
|
70
|
+
> described the post-activation routing and two of them said session-plan was skipped entirely.
|
|
71
|
+
> That reading cannot work: `commands/go.md` gates on a 1-wave Express Path plan, which under a
|
|
72
|
+
> skipped session-plan would never have been produced — `/go` would look for a plan that does not
|
|
73
|
+
> exist. The routing is now one sentence everywhere:
|
|
74
|
+
>
|
|
75
|
+
> **Phase 8.5 evaluates via `scripts/express-path.mjs`, prints the banner, then hands off NORMALLY
|
|
76
|
+
> to Phase 9 → session-plan.** session-plan detects the banner and its
|
|
77
|
+
> `## Express Path Short-Circuit (#214)` section emits a minimal 1-wave `coordinator-direct` plan
|
|
78
|
+
> (0 agents dispatched, no role decomposition, no wave splitting). `/go` detects that plan per
|
|
79
|
+
> `commands/go.md` § Express Path Detection and routes to coord-direct execution plus
|
|
80
|
+
> session-end auto-invocation — never to wave-executor.
|
|
81
|
+
>
|
|
82
|
+
> What activation skips is the WAVE MACHINERY (subagent dispatch, role decomposition, inter-wave
|
|
83
|
+
> checkpoints), not the planning handoff. The two sites that said otherwise —
|
|
84
|
+
> this file and `skills/session-start/SKILL.md` — were corrected in the same pass;
|
|
85
|
+
> `docs/session-config-reference.md`, `skills/session-plan/SKILL.md` and `commands/go.md`
|
|
86
|
+
> already carried the surviving reading.
|
|
87
|
+
|
|
88
|
+
Hand off to Phase 9 as usual. The coordinator then executes the 1-wave plan session-plan emits directly, without dispatching subagents:
|
|
89
|
+
|
|
90
|
+
1. Proceed to Phase 9 (session-plan handoff) carrying the banner. session-plan short-circuits to the 1-wave `coordinator-direct` plan; `/go` detects it and does NOT invoke wave-executor.
|
|
91
|
+
2. For each agreed task (in dependency order): execute as a direct coordinator action — read files, make changes, run quality checks inline. No subagents, no inter-wave checkpoints.
|
|
92
|
+
3. Log the express-path activation in STATE.md `## Deviations` section: `Express path: N tasks executed coord-direct (express-path.enabled: true, session-type: housekeeping, scope: N issues)` — written BEFORE session-end is invoked. Then invoke `skills/session-end/SKILL.md` directly.
|
|
30
93
|
4. After session-end completes successfully: verify STATE.md `status` is `completed` and `## Deviations` contains the express-path entry from step 3. If either is missing, warn the user with a one-line note and instructions to re-run `/close` manually. Then return the final session summary to the user.
|
|
31
94
|
|
|
32
95
|
**Persistence contract:**
|
|
33
96
|
|
|
34
|
-
|
|
97
|
+
Step 1 is the Phase 9 handoff and ends the session-start turn — the operator types `/go` next, exactly as on the normal path. Steps 2–4 then MUST all happen within a SINGLE coordinator turn, the one `/go` opens. Specifically:
|
|
35
98
|
|
|
36
|
-
- Step
|
|
37
|
-
- Step
|
|
38
|
-
- Step
|
|
99
|
+
- Step 2 (execute tasks) happens first in that turn's main flow.
|
|
100
|
+
- Step 3a (deviations log) is written BEFORE session-end is invoked. The coordinator calls `appendDeviation()` from `scripts/lib/state-md.mjs` to append the `Express path:` bullet to the `## Deviations` section while STATE.md is still `status: active`.
|
|
101
|
+
- Step 3b (invoke session-end) flips `status` to `completed`, writes the metrics record to `.orchestrator/metrics/sessions.jsonl`, and runs the standard close flow. Session-end has no Express Path-specific logic — it treats this run identically to any other completed session.
|
|
39
102
|
- Step 4 (verification) is the coordinator's final action before returning control. The verification check uses `parseStateMd()` from `scripts/lib/state-md.mjs` to read the file and check `frontmatter.status === 'completed'` and that the body contains the literal string `Express path:`.
|
|
40
103
|
|
|
41
|
-
|
|
104
|
+
When `/go` is invoked and session-plan emitted a 1-wave Express Path plan (per `skills/session-plan/SKILL.md` § "Express Path Short-Circuit"), the `/go` command MUST detect this and route to coord-direct execution + session-end auto-invocation, NOT to wave-executor. See `commands/go.md` for the detection branch — that plan is the artifact `/go` keys on, which is why Phase 8.5 hands off to session-plan rather than skipping it.
|
|
42
105
|
|
|
43
106
|
**When Express Path does NOT activate** (conditions not met):
|
|
44
107
|
|
|
@@ -56,6 +119,8 @@ Proceed normally to Phase 9 (session-plan handoff). The express-path evaluation
|
|
|
56
119
|
|
|
57
120
|
## See Also
|
|
58
121
|
|
|
122
|
+
- `scripts/express-path.mjs` — the CLI this phase runs; `scripts/lib/express-path.mjs` holds the decision + its `orchestrator.express_path.evaluated` record
|
|
123
|
+
- `skills/session-plan/SKILL.md` § "Express Path Short-Circuit (#214)" — the 1-wave plan Phase 9 emits when the banner is present
|
|
59
124
|
- `commands/go.md` — Express Path detection and auto-invocation of session-end after coord-direct tasks
|
|
60
125
|
- `skills/session-end/SKILL.md` — Phase 1 pre-check (Rule 2) blocks `/close` when STATE.md `status: completed`; auto-invocation from express-path bypasses this
|
|
61
126
|
- `commands/close.md` — Rule 2 wording the user sees if express-path persistence breaks
|
|
@@ -92,6 +92,26 @@ const vaultNoteTypeSchema = z.enum([
|
|
|
92
92
|
'board',
|
|
93
93
|
]);
|
|
94
94
|
|
|
95
|
+
// VENDORED COPY. SSOT: projects-baseline/packages/zod-schemas/src/vault-frontmatter.ts
|
|
96
|
+
// (`vaultNoteStatusSchema`). This file is a standalone CLI with no exports, so the
|
|
97
|
+
// values cannot be imported — they are hand-kept in sync, and that is the known cost.
|
|
98
|
+
//
|
|
99
|
+
// Measured 2026-08-23: FOUR hand-maintained copies of this enum exist —
|
|
100
|
+
// 1. the SSOT above
|
|
101
|
+
// 2. this file
|
|
102
|
+
// 3. sven-infra `02-cron/vault-overview-sync.sh:216`
|
|
103
|
+
// 4. `tests/lib/vault-mirror/render-sessions.test.mjs:490`, whose own comment names
|
|
104
|
+
// this file as its source and then transcribes it
|
|
105
|
+
// Copy 4 is deliberately NOT widened: it asserts membership for a mapper that emits
|
|
106
|
+
// only `verified`/`draft`, so adding values it cannot produce would weaken it.
|
|
107
|
+
//
|
|
108
|
+
// A drift test reading the SSOT directly was considered and REJECTED: the SSOT lives
|
|
109
|
+
// in a sibling repo resolved host-locally, so such a test passes on this machine and
|
|
110
|
+
// fails in CI, where projects-baseline is not checked out. The durable fix is
|
|
111
|
+
// generation from the SSOT at build time, not a test that reads across a repo
|
|
112
|
+
// boundary. Revisit trigger: a fifth copy, or the first CI-visible drift.
|
|
113
|
+
//
|
|
114
|
+
// Order below mirrors the SSOT exactly, so a diff of the two lists is readable.
|
|
95
115
|
const vaultNoteStatusSchema = z.enum([
|
|
96
116
|
'draft',
|
|
97
117
|
'active',
|
|
@@ -100,6 +120,17 @@ const vaultNoteStatusSchema = z.enum([
|
|
|
100
120
|
'production',
|
|
101
121
|
'mvp',
|
|
102
122
|
'idea',
|
|
123
|
+
// Added 2026-08-23 (baseline MR !27, merge 6f38aeb). sven renders these into
|
|
124
|
+
// `01-projects/*/_overview.md`; measured the same day, SIX of them already carried
|
|
125
|
+
// one of these values (4x dead, 1x paused, 1x maintenance). Falsified rather than
|
|
126
|
+
// assumed: removing the four again produced exactly 6 `status` errors, re-adding
|
|
127
|
+
// them produced 0. Without them the strict gate blocks every vault session-close.
|
|
128
|
+
// Slugs deliberately not listed here — the scanner treats them as private
|
|
129
|
+
// (`check-owner-leakage` CP6), and the count is the load-bearing part anyway.
|
|
130
|
+
'maintenance',
|
|
131
|
+
'planned',
|
|
132
|
+
'paused',
|
|
133
|
+
'dead',
|
|
103
134
|
]);
|
|
104
135
|
|
|
105
136
|
const vaultFrontmatterSchema = z
|