session-orchestrator 3.21.0 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +11 -2
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +10 -10
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +269 -0
- package/README.md +18 -15
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -1
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/memory-proposal-collector.md +6 -4
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/eli5.md +33 -0
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/commands/release.md +5 -3
- package/commands/test.md +2 -2
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +14 -0
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +7 -6
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +76 -4
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +52 -5
- package/docs/session-config-reference.md +57 -12
- package/docs/session-config-template.md +4 -3
- package/docs/telemetry.md +22 -0
- package/hooks/_lib/lock-bootstrap.mjs +26 -16
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/enforce-scope.mjs +64 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +12 -2
- package/hooks/on-session-end.mjs +237 -104
- package/hooks/on-session-start.mjs +404 -110
- package/hooks/on-stop.mjs +48 -9
- package/hooks/post-subagent-discovery-validator.mjs +34 -3
- package/hooks/post-tool-batch-wave-signal.mjs +11 -2
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +133 -21
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/monitors/monitors.json +6 -0
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +12 -8
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
- package/scripts/lib/build-live-signals.mjs +24 -5
- package/scripts/lib/ci-status-banner.mjs +221 -68
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +57 -6
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +84 -11
- package/scripts/lib/gates/gate-helpers.mjs +93 -21
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/io.mjs +239 -0
- package/scripts/lib/issue-budget.mjs +284 -31
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +5 -2
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +104 -4
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +281 -13
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +165 -7
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +90 -0
- package/scripts/lib/session-close-backfill.mjs +369 -28
- package/scripts/lib/session-discovery.mjs +13 -3
- package/scripts/lib/session-end/phase-skip.mjs +37 -4
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +159 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +111 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +467 -72
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/sync.mjs +46 -8
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
- package/scripts/lib/validate/check-hooks-symmetry.mjs +59 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-unwired-features.mjs +340 -32
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-writer.mjs +211 -10
- package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
- package/scripts/lib/vcs-repo-spec.mjs +182 -13
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/lib/wave-transcript-tail.mjs +869 -0
- package/scripts/materialize-wave-scope.mjs +478 -0
- package/scripts/mcp-server.sh +11 -2
- package/scripts/parse-config.mjs +65 -0
- package/scripts/release.mjs +443 -122
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +6 -0
- package/scripts/validate-wave-scope.mjs +73 -1
- package/scripts/vault-backfill.mjs +32 -5
- package/scripts/vault-mirror.mjs +203 -34
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +31 -25
- package/skills/_shared/parallel-aware-preamble.md +35 -4
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +61 -8
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +5 -4
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/discovery/SKILL.md +28 -24
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/gitlab-ops/SKILL.md +30 -26
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/journey-audit/SKILL.md +269 -0
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/npm-publish/SKILL.md +4 -4
- package/skills/peekaboo-driver/SKILL.md +18 -6
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +35 -23
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +44 -4
- package/skills/session-end/SKILL.md +70 -24
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +48 -5
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +96 -48
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/session-start/soul.md +110 -0
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +4 -2
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +129 -24
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/templates/_shared/journey-manifest.md +110 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -122,7 +122,38 @@ fi
|
|
|
122
122
|
Resolve `$PLUGIN_ROOT` per `skills/_shared/config-reading.md` (the standard resolution chain:
|
|
123
123
|
`$CLAUDE_PLUGIN_ROOT` → `$CODEX_PLUGIN_ROOT` → `$CURSOR_RULES_DIR` → common install locations).
|
|
124
124
|
|
|
125
|
-
### 2.2
|
|
125
|
+
### 2.2 Resolve the Effective Write-Targets
|
|
126
|
+
|
|
127
|
+
`reconcile.targets` says WHERE approved rules land. Resolve it BEFORE surfacing
|
|
128
|
+
the approval AUQ — the operator must never be asked to approve a write to a
|
|
129
|
+
destination that cannot exist:
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
import { resolveEffectiveTargets } from '$PLUGIN_ROOT/scripts/lib/reconcile/engine.mjs';
|
|
133
|
+
|
|
134
|
+
const { targets, baselineRoot, dropped, reason } = resolveEffectiveTargets({
|
|
135
|
+
targets: CONFIG.reconcile?.targets, // ['repo-local'] | ['baseline'] | both
|
|
136
|
+
baselineRoot: CONFIG['plan-baseline-path'], // already 3-tier-resolved by config.mjs
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Target | Writes to | Root |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `repo-local` (default) | `<repoRoot>/.claude/rules/<slug>.md` | `repoRoot` |
|
|
143
|
+
| `baseline` (#1099) | `<baselineRoot>/proposals/<slug>.md` | `plan-baseline-path`, resolved `SO_BASELINE_PATH` env > `owner.yaml` `paths.baseline-path` > committed |
|
|
144
|
+
|
|
145
|
+
`baseline` is DROPPED (with one stderr WARN, and `dropped: ['baseline']` in the
|
|
146
|
+
return) when the root is unresolvable on all three tiers, is still the committed
|
|
147
|
+
`OVERRIDE-IN-…` placeholder, or is not absolute. A dropped target means: do not
|
|
148
|
+
surface its proposals in the AUQ at all. If `targets` comes back EMPTY, stop
|
|
149
|
+
here and report the `reason` — there is nowhere to write.
|
|
150
|
+
|
|
151
|
+
Writing to `baseline` is still advisory and AUQ-gated exactly like `repo-local`:
|
|
152
|
+
files land under `proposals/` in the baseline checkout, nothing is committed
|
|
153
|
+
there, and no branch is touched. The operator reviews and commits in that repo
|
|
154
|
+
himself.
|
|
155
|
+
|
|
156
|
+
### 2.3 Invoke `runReconcile`
|
|
126
157
|
|
|
127
158
|
```javascript
|
|
128
159
|
import { runReconcile } from '$PLUGIN_ROOT/scripts/lib/reconcile/engine.mjs';
|
|
@@ -243,12 +274,12 @@ For each batch (proposals sliced into groups of 4):
|
|
|
243
274
|
```
|
|
244
275
|
AskUserQuestion({
|
|
245
276
|
questions: [{
|
|
246
|
-
question: "
|
|
247
|
-
header: "
|
|
277
|
+
question: "Batch K of N — which rule proposals should be written into .claude/rules/?",
|
|
278
|
+
header: "Regeln",
|
|
248
279
|
options: [
|
|
249
280
|
{
|
|
250
281
|
label: "<slug>.md (confidence: 0.72)",
|
|
251
|
-
description: "
|
|
282
|
+
description: "From learning <learningKey>. Becomes a file under .claude/rules/ — where this repo keeps its rules. Text: <first 100 chars of rendered content>"
|
|
252
283
|
},
|
|
253
284
|
...up to 4 options per batch...
|
|
254
285
|
{
|
|
@@ -282,12 +313,21 @@ const { written, archived, errors } = await writeApprovedRules({
|
|
|
282
313
|
approved: approved, // proposals the operator approved
|
|
283
314
|
rejected: rejected_by_operator, // proposals the operator declined
|
|
284
315
|
repoRoot,
|
|
316
|
+
baselineRoot, // from Phase 2.2; omit/undefined ⇒ baseline is a no-op
|
|
317
|
+
targets, // from Phase 2.2; omitted ⇒ ['repo-local']
|
|
285
318
|
sessionId: currentSessionId, // informational; from STATE.md or 'manual'
|
|
286
319
|
});
|
|
287
320
|
```
|
|
288
321
|
|
|
289
322
|
`writeApprovedRules` NEVER throws — per-item failures are collected in `errors[]`.
|
|
290
323
|
|
|
324
|
+
`written` is a FILE count, not a proposal count: one proposal approved with both
|
|
325
|
+
targets in effect writes two files and counts 2, while stamping the idempotency
|
|
326
|
+
sidecar exactly once. A baseline root that does not exist on disk (the
|
|
327
|
+
fresh-clone / CI case) skips that target with an `errors[]` entry — it is NEVER
|
|
328
|
+
created, because a typo'd path that silently mints a directory tree looks
|
|
329
|
+
exactly like a successful write.
|
|
330
|
+
|
|
291
331
|
### 6.2 Handle Errors
|
|
292
332
|
|
|
293
333
|
If `errors.length > 0`, surface each error to the operator:
|
|
@@ -141,7 +141,8 @@ For every `SPIRAL` or `FAILED` agent surfaced in the walk above, ALSO append a c
|
|
|
141
141
|
```js
|
|
142
142
|
import { appendWhatNotToRetryOnDisk } from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
|
|
143
143
|
|
|
144
|
-
// `parsed` = parseStateMd(STATE.md); session
|
|
144
|
+
// `parsed` = parseStateMd(STATE.md); `session:` is an attribution/history label.
|
|
145
|
+
// It records this entry's provenance only and never authorizes lock ownership.
|
|
145
146
|
const sessionId = parsed.frontmatter.session ?? 'unknown-session';
|
|
146
147
|
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
147
148
|
|
|
@@ -646,7 +647,7 @@ import { planTailPhases } from '${PLUGIN_ROOT}/scripts/lib/session-end/phase-ski
|
|
|
646
647
|
const { plan, skippedReport } = await planTailPhases({
|
|
647
648
|
repoRoot: process.cwd(),
|
|
648
649
|
config, // parsed Session Config (from $CONFIG)
|
|
649
|
-
sessionId, // session.lock `session_id`
|
|
650
|
+
sessionId, // physical session.lock `session_id` only (or null), never STATE.md `session`
|
|
650
651
|
platform, // 'claude' | 'codex' | 'cursor'
|
|
651
652
|
});
|
|
652
653
|
// plan: Array<{ phase, run, reason, inputSource }>, already in ascending phase order.
|
|
@@ -733,20 +734,21 @@ After STATE.md is finalized with `status: completed` (Phase 3.4) and Recommendat
|
|
|
733
734
|
|
|
734
735
|
```javascript
|
|
735
736
|
import { release } from 'scripts/lib/session-lock.mjs';
|
|
736
|
-
// sessionId
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
const
|
|
737
|
+
// sessionId is the physical raw value established by session-start Phase 1.2
|
|
738
|
+
// and stored in .orchestrator/session.lock `session_id`. It is not STATE.md
|
|
739
|
+
// `session:` or `semantic_session_id`, both of which are attribution labels.
|
|
740
|
+
const rawSessionId = sessionId;
|
|
741
|
+
const result = release({ sessionId: rawSessionId, repoRoot: process.cwd() });
|
|
740
742
|
// result.ok is always true unless a filesystem error occurred.
|
|
741
743
|
// result.deleted === true → lock file removed successfully.
|
|
742
|
-
// result.deleted === false → lock was absent or
|
|
744
|
+
// result.deleted === false → lock was absent or had a different raw session_id.
|
|
743
745
|
```
|
|
744
746
|
|
|
745
|
-
If `result.deleted === false`, log `info: session-lock not released — already absent or session_id mismatch
|
|
747
|
+
If `result.deleted === false`, log `info: session-lock not released — already absent or raw session_id mismatch` and continue. An active lock whose raw id differs is ambiguous: do **not** retry release with an equal `semantic_session_id`, STATE.md `session`, or owner proof. Leave that live lock for its TTL/Reaper lifecycle.
|
|
746
748
|
|
|
747
749
|
If `result.ok === false` (rare filesystem error), log `⚠ session-lock: release failed — <result.reason>` and continue. Do NOT block the close for a lock-release failure — the TTL provides automatic expiry for the next session.
|
|
748
750
|
|
|
749
|
-
The lock is released here — AFTER all STATE.md writes are complete and BEFORE the commit is staged in Phase 4.1. This ordering ensures a clean handover: the lock file is absent from the working tree when the commit is assembled, so it is not accidentally staged.
|
|
751
|
+
The lock is released here — AFTER all STATE.md writes are complete and BEFORE the commit is staged in Phase 4.1. This ordering ensures a clean handover when the current raw owner releases it: the lock file is absent from the working tree when the commit is assembled, so it is not accidentally staged.
|
|
750
752
|
|
|
751
753
|
## Phase 4: Commit & Push
|
|
752
754
|
|
|
@@ -841,7 +843,12 @@ Auto-promoted sibling worktrees are created by `enterWorktree()` during the Phas
|
|
|
841
843
|
|
|
842
844
|
> **Authoritative impl:** `scripts/lib/session-end/worktree-cleanup.mjs` — `detectAutoPromotedWorktree(repoRoot, sessionId, opts)`. Import and call; do NOT re-implement from this doc.
|
|
843
845
|
>
|
|
844
|
-
>
|
|
846
|
+
> Two keys, tried in this order:
|
|
847
|
+
>
|
|
848
|
+
> 1. **Marker (primary).** Reads `<repoRoot>/.orchestrator/promoted-from.json` (`PROMOTION_MARKER_RELPATH`), written by `enterWorktree()` at creation time with `branch`, `source_session_id`, `source_root_hash`, `source_root_basename`, `promoted_at`. Accepted when the file parses, carries a non-empty `branch` + `source_session_id`, and the worktree's current branch (`git branch --show-current`) either matches the recorded one or cannot be read at all — an unverifiable branch never triggers auto-removal by itself, since that is gated separately by `isWorktreeClean()`, which fails closed on any git error. On match returns `{ wtPath, sessionId: marker.source_session_id, branch: marker.branch, source: 'marker' }`. This is the only key that survives the #1069 process boundary: since #1069 the session that RUNS in the promoted worktree is a brand-new session with its own id (see ADR-0013), so the current session's id appears in neither the worktree's directory name nor its branch — key 2 below can never match a #1069-promoted worktree.
|
|
849
|
+
> 2. **Basename (legacy fallback).** Parse `sessionId` via `parseSessionId()`; return `null` immediately for UUID-format sessions (never auto-promoted). Derive the MAIN checkout root from the first `worktree ` entry of `git worktree list --porcelain` (NOT `path.basename(repoRoot)` — the promoted worktree's basename IS the comparison target). If `repoRoot` resolves to the main checkout, return `null`. Otherwise compare `path.basename(repoRoot)` against `<main-repo-name>-<sessionId>` (the CURRENT session id); on match return `{ wtPath, sessionId, branch: parsed.branch, source: 'basename' }` — still correct for worktrees created before the marker existed. Returns `null` on no match.
|
|
850
|
+
>
|
|
851
|
+
> All git invocation is via the injection-safe `opts.execFileFn` (default `execFileSync` with an args array — #577 HARDEN-001).
|
|
845
852
|
|
|
846
853
|
### Clean-check
|
|
847
854
|
|
|
@@ -853,7 +860,7 @@ A worktree is clean iff ALL three conditions hold:
|
|
|
853
860
|
|
|
854
861
|
> **Authoritative impl:** `scripts/lib/session-end/worktree-cleanup.mjs` — `isWorktreeClean(wtPath, opts)`. Import and call; do NOT re-implement from this doc.
|
|
855
862
|
>
|
|
856
|
-
> Algorithm: run `git status --porcelain`;
|
|
863
|
+
> Algorithm: run `git status --porcelain`; filter blank lines, then discount EXACTLY the one untracked line the promotion marker itself produces (`?? .orchestrator/promoted-from.json` — in a repo where `.orchestrator/` is only partly gitignored, or on a worktree whose branch predates the ignore line, the marker `enterWorktree()` writes would otherwise make every promoted worktree read "dirty"; a modified/staged/renamed/conflicted marker still counts as dirty). If any lines remain → dirty (`false`). Else run `git status --short --branch`; if it matches `/\bahead\b/` → unpushed (`false`). Otherwise `true`. On ANY git error → `false` (conservative PSA-003 default: never auto-remove a worktree we could not verify). Git invocation is via the injection-safe `opts.execFileFn` (default `execFileSync` with an args array — #577 HARDEN-001).
|
|
857
864
|
|
|
858
865
|
### Clean path: auto-remove + WARN (PRD §3 P3 Gherkin row 2)
|
|
859
866
|
|
|
@@ -890,15 +897,24 @@ if (!promoted) {
|
|
|
890
897
|
When the worktree is dirty (uncommitted, untracked, OR unpushed), render this AUQ via the coordinator's `AskUserQuestion` tool. The AUQ is coordinator-only — per `.claude/rules/ask-via-tool.md` AUQ-004, dispatched agents cannot call AUQ. Calling `git worktree remove --force` without explicit operator confirmation would violate PSA-003 (destructive action safeguards) — the dirty state may contain another session's work-in-progress or unmerged commits.
|
|
891
898
|
|
|
892
899
|
```js
|
|
900
|
+
// What is actually at stake, shown beside the options via `preview` (AUQ-006):
|
|
901
|
+
// the operator must see WHICH changes he would lose before he authorises the delete.
|
|
902
|
+
// Capped at 10 lines so the preview never outgrows the option list next to it.
|
|
903
|
+
const dirtyDetail = execFileSync('git', ['-C', promoted.wtPath, 'status', '--short', '--branch'], { encoding: 'utf8' })
|
|
904
|
+
.trim()
|
|
905
|
+
.split('\n')
|
|
906
|
+
.slice(0, 10)
|
|
907
|
+
.join('\n');
|
|
908
|
+
|
|
893
909
|
AskUserQuestion({
|
|
894
910
|
questions: [{
|
|
895
911
|
question: `Auto-promoted worktree at ${promoted.wtPath} has uncommitted/untracked/unpushed changes. How should I proceed?`,
|
|
896
|
-
header: "Worktree
|
|
912
|
+
header: "Worktree",
|
|
897
913
|
multiSelect: false,
|
|
898
914
|
options: [
|
|
899
|
-
{ label: "Behalten (Recommended)", description: "
|
|
900
|
-
{ label: "Löschen", description: "I confirm the changes are handled or expendable. Run 'git worktree remove --force' on the worktree." },
|
|
901
|
-
{ label: "Manuell", description: "Exit /close. I will inspect the worktree before re-running /close." },
|
|
915
|
+
{ label: "Behalten (Recommended)", description: "Keeps the worktree exactly as it is — nothing is deleted, and you can still remove it by hand later.", preview: `Stays on disk:\n${dirtyDetail}` },
|
|
916
|
+
{ label: "Löschen", description: "I confirm the changes are handled or expendable. Run 'git worktree remove --force' on the worktree.", preview: `Deleted with the worktree:\n${dirtyDetail}` },
|
|
917
|
+
{ label: "Manuell", description: "Exit /close. I will inspect the worktree before re-running /close.", preview: `You would inspect this first:\n${dirtyDetail}` },
|
|
902
918
|
],
|
|
903
919
|
}],
|
|
904
920
|
});
|
|
@@ -907,8 +923,8 @@ AskUserQuestion({
|
|
|
907
923
|
**Codex CLI / Cursor IDE fallback** (numbered Markdown list):
|
|
908
924
|
|
|
909
925
|
```
|
|
910
|
-
Worktree cleanup options:
|
|
911
|
-
1. **Behalten (Recommended)** —
|
|
926
|
+
Worktree cleanup options (the changes at stake are the `git status --short --branch` lines printed above):
|
|
927
|
+
1. **Behalten (Recommended)** — Keeps the worktree exactly as it is; nothing is deleted, and you can still remove it by hand later.
|
|
912
928
|
2. **Löschen** — I confirm the changes are handled or expendable. Run 'git worktree remove --force'.
|
|
913
929
|
3. **Manuell** — Exit /close. I will inspect the worktree before re-running /close.
|
|
914
930
|
Reply with the number of your choice.
|
|
@@ -1021,24 +1037,54 @@ if (sweep) {
|
|
|
1021
1037
|
|
|
1022
1038
|
Fail-open: a `markOpenQuestionAnsweredOnDisk` failure is non-fatal — log a WARN and proceed with the close; the question simply stays `- [ ]` and roundtrips to the next session.
|
|
1023
1039
|
|
|
1024
|
-
3b. **Drain the issue-budget overflow — exactly ONE collector artefact (issue-budget):** when `.orchestrator/runtime/issue-budget
|
|
1040
|
+
3b. **Drain the issue-budget overflow — exactly ONE collector artefact (issue-budget):** when this session's budget file (`budgetStatePath(repoRoot, accountingSessionId)` → `.orchestrator/runtime/issue-budget/<hash>.json`, #1141) has a non-empty `overflow[]`, the session hit its `issue-budget.max-per-session` cap and every over-cap creation was PARKED rather than filed. Fold the whole list into a single artefact so nothing is silently dropped.
|
|
1025
1041
|
|
|
1026
1042
|
**Ordering (load-bearing):** run this as the LAST issue-creating action of Phase 5 — after step 3, after "Discovery Issue Creation", after step 4 — and re-read the counter file at that moment. Those steps can themselves push new entries into `overflow[]`; draining early would leave them unfiled.
|
|
1027
1043
|
|
|
1028
1044
|
```js
|
|
1029
|
-
import {
|
|
1030
|
-
|
|
1045
|
+
import { readFileSync } from 'node:fs';
|
|
1046
|
+
import {
|
|
1047
|
+
readBudgetState,
|
|
1048
|
+
budgetStatePath,
|
|
1049
|
+
resolveIssueBudgetSessionId,
|
|
1050
|
+
} from '${PLUGIN_ROOT}/scripts/lib/issue-budget.mjs';
|
|
1051
|
+
|
|
1052
|
+
// `sessionId` is the physical raw lock/registry identity from session-start.
|
|
1053
|
+
const rawSessionId = sessionId;
|
|
1054
|
+
let currentSession = null;
|
|
1055
|
+
try {
|
|
1056
|
+
currentSession = JSON.parse(
|
|
1057
|
+
readFileSync(`${repoRoot}/.orchestrator/current-session.json`, 'utf8'),
|
|
1058
|
+
);
|
|
1059
|
+
} catch { /* no verified semantic accounting bridge */ }
|
|
1060
|
+
const accountingSessionId = resolveIssueBudgetSessionId(rawSessionId, currentSession);
|
|
1061
|
+
const state = readBudgetState(repoRoot, accountingSessionId);
|
|
1062
|
+
// { sessionId, count, exempt, overflow: [...] }
|
|
1031
1063
|
```
|
|
1032
1064
|
|
|
1065
|
+
`accountingSessionId` may be semantic only after
|
|
1066
|
+
`currentSession.session_id === rawSessionId`; this is budget accounting, not
|
|
1067
|
+
lock/registry ownership. When that proof is absent it remains the raw id.
|
|
1068
|
+
A host rotation that changes both raw and semantic values has no guaranteed
|
|
1069
|
+
budget continuity.
|
|
1070
|
+
|
|
1033
1071
|
- **`issue-budget.overflow: collect-issue` (default)** — create exactly ONE issue:
|
|
1034
|
-
- Title: `[Backlog-Sammel] <
|
|
1072
|
+
- Title: `[Backlog-Sammel] <accountingSessionId>, <N> zurückgestellte Punkte`
|
|
1035
1073
|
- Labels: `type::backlog`, `priority::low`
|
|
1036
1074
|
- Body: a Markdown checklist with one `- [ ]` line per `overflow[]` entry (`title` when present, otherwise the truncated `command`, plus its `at` timestamp).
|
|
1037
1075
|
- This collector issue is itself EXEMPT from the cap (`[Backlog-Sammel]` is in the exemption list in `scripts/lib/issue-budget.mjs`), so it always lands even at count == max.
|
|
1038
|
-
- **`issue-budget.overflow: vault-note`** — create NO issue. Write one Markdown file `vault/00-inbox/<
|
|
1076
|
+
- **`issue-budget.overflow: vault-note`** — create NO issue. Write one Markdown file `vault/00-inbox/<accountingSessionId>-backlog-sammel.md` (path relative to `vault-integration.vault-dir`) with valid vault frontmatter and the same checklist body.
|
|
1039
1077
|
- After the artefact exists, reset `overflow` to `[]` in the counter file and record the collector issue ID / note path in the Phase 6 Final Report under `### Zurückgestellt (issue-budget)`.
|
|
1040
1078
|
- **Never exempt-by-accident:** the cap never applied to `priority::critical`, the carryover class (`[Carryover]`, SPIRAL/FAILED, `type::carryover`), or `broken-window` closure issues, so nothing on the Phase 1.65 carry-list can ever appear in `overflow[]`. The promises at Phase 1.8 ("SPIRAL / FAILED agent carryover … non-deselectable") and the Critical Rule "ALWAYS create issues for unfinished PLANNED work" stay intact by construction.
|
|
1041
1079
|
- Fail-open: a missing or malformed counter file means "no overflow" — log a WARN and continue the close.
|
|
1080
|
+
- **Then reap stale counter files (#1151):** the per-session split (#1141) writes one file per accounting session and nothing ever deleted them, so `.orchestrator/runtime/issue-budget/` grew without bound in every working copy. After the drain, sweep files older than 14 days; THIS session's file is exempt regardless of age, and the call is best-effort (it never throws, so it can never abort the close).
|
|
1081
|
+
|
|
1082
|
+
```js
|
|
1083
|
+
import { reapStaleBudgetFiles } from '${PLUGIN_ROOT}/scripts/lib/issue-budget.mjs';
|
|
1084
|
+
|
|
1085
|
+
const { removed } = reapStaleBudgetFiles({ repoRoot, sessionId: accountingSessionId });
|
|
1086
|
+
if (removed.length) console.log(`issue-budget: reaped ${removed.length} stale counter file(s) (> 14 d)`);
|
|
1087
|
+
```
|
|
1042
1088
|
|
|
1043
1089
|
#### Discovery Issue Creation (if discovery ran in Phase 1.5)
|
|
1044
1090
|
|
|
@@ -1128,13 +1174,13 @@ Present to the user:
|
|
|
1128
1174
|
| `learning-patterns.md` | Phases 3.5a + 3.6 extraction heuristics, confidence updates, passive decay, and JSONL write procedure |
|
|
1129
1175
|
| `phase-3-6-tail.md` | Phase 3.6.x tail — full unabridged detail procedures for all six tail phases: 3.6.3 Memory-Proposals Collection (`collectProposals` + AUQ multiSelect + `promoteAndClear`, composing `writeApproved` + `clearProposalsJsonl` behind a mechanical write-before-clear guard, #828), 3.6.4 Expired-Learnings Sweep (Epic #723 B4), 3.6.5 Auto-Dream nudge (`shouldDispatchAutoDream`, #614), 3.6.6 Skill-Applied Judge (#645 L3 — `runSkillJudge`, coordinator-writes), 3.6.7 Auto-Dialectic nudge (`shouldDispatchAutoDialectic`, #614), 3.6.8 Reconciliation Rule Proposals (#696 FA3 — `runReconcile` + AUQ + `writeApprovedRules`). Loaded on demand by the SKILL.md skip-plan dispatcher (#724) — only phases with `run: true` in the `planTailPhases()` plan execute |
|
|
1130
1176
|
| `scripts/lib/session-end/phase-skip.mjs` | Phase 3.6.x tail skip-plan aggregator (#724) — `planTailPhases({repoRoot, config, sessionId, platform})` → `{plan, skippedReport}`; side-effect-free (reconcile/sweep via dry-run — no writes), never-throws (per-phase probe error fail-opens to `run: true`); wraps the six existing signal helpers with config gates first, then input detection |
|
|
1131
|
-
| (inline) Phase 3.45 | Telemetry Flush (advisory, #844) — `flush()` from `scripts/lib/telemetry/sync.mjs` drains the host-local send-queue fire-and-forget; no config key (send-gate is `resolveConsent()` inside the module, fail-closed); skip when `persistence: false`; never-throw + ~3s-bounded, offline → bounded oldest-dropped queue, optional `Telemetry: sent/queued/gated` close-summary line, NEVER an error banner; runs late in the close after Phase 3.7 |
|
|
1177
|
+
| (inline) Phase 3.45 | Telemetry Flush (advisory, #844; MECHANICAL since #1138 — `hooks/on-session-end.mjs` calls `flush()` itself at the end of every teardown and emits an `orchestrator.telemetry.flush` breadcrumb, so this phase is the DESCRIPTION and the fallback, never the trigger; a coordinator that skips it changes nothing) — `flush()` from `scripts/lib/telemetry/sync.mjs` drains the host-local send-queue fire-and-forget; no config key (send-gate is `resolveConsent()` inside the module, fail-closed); skip when `persistence: false`; never-throw + ~3s-bounded, offline → bounded oldest-dropped queue, optional `Telemetry: sent/queued/gated` close-summary line, NEVER an error banner; runs late in the close after Phase 3.7 |
|
|
1132
1178
|
| `session-metrics-write.md` | Phase 3.7 JSONL append, vault-mirror invocation, durable narrative mirror (`mirrorNarrative`, #675), and behavior matrix |
|
|
1133
1179
|
| `phase-3-7a-recommendations.md` | Phase 3.7a full procedural body — computeV0Recommendation call, STATE.md field write, data source guarantee, error mode |
|
|
1134
1180
|
| `phase-3-7a-recommendations.md` § 3.7b | Phase 3.7b full procedural body — `withDurableCommit` invocation for `sessions.jsonl` + `STATE.md` (#490 AC2), `enabled:false` local no-op, autopilot.jsonl exclusion note |
|
|
1135
1181
|
| (inline) Phase 3.7c | Vault Board → Closed (#674) — `mirrorBoard({ explicitStatus: 'closed' })` transitions this repo's board row to `closed`; gated on `vault-integration.enabled`, generator-marked + idempotent, non-blocking, ordered after 3.7b and before 3.7d/3.4/3.8 |
|
|
1136
1182
|
| (inline) Phase 3.7d | Session-Eval (opt-in — #803) — `node scripts/eval-session.mjs --json` scores the just-closed session; gated on `eval.enabled` + `eval.mode != off` (parsed by `scripts/lib/config/eval.mjs`), optional `eval-judge` dispatch + `writeEvalReport`, advisory/never-blocks-close, ordered after 3.7 (record must exist) and before 3.4/Phase 4 (record committed with the session). Full flow in `skills/eval/SKILL.md` |
|
|
1137
|
-
| (inline) Phase 3.8 | Session Lock Release — `release()`
|
|
1183
|
+
| (inline) Phase 3.8 | Session Lock Release — `release()` uses the physical raw `session_id`; raw mismatch/absent is non-fatal but never repaired with semantic labels or proof (live ambiguity remains for TTL/Reaper); fs-errors are non-fatal; runs after STATE.md writes and before Phase 4 commit staging |
|
|
1138
1184
|
|
|
1139
1185
|
## Anti-Patterns
|
|
1140
1186
|
|
|
@@ -36,7 +36,8 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
36
36
|
From the `stagnation` array, aggregate into `stagnation_events`:
|
|
37
37
|
- `total`: count of entries in the array
|
|
38
38
|
- `by_pattern`: count by `pattern` value (omit zero-valued keys)
|
|
39
|
-
- `by_error_class`: count by `error_class` value (omit zero-valued keys; omit entire sub-object if all entries lack `error_class`)
|
|
39
|
+
- `by_error_class`: count by `error_class` value (omit zero-valued keys; omit entire sub-object if all entries lack `error_class` — only `error-echo` records carry one)
|
|
40
|
+
- `by_source`: count by `source` value — `coordinator` (post-wave review) vs `tail` (the `wave-transcript-tail` monitor, #1114). Same rule: omit zero-valued keys; omit the entire sub-object when no entry carries `source` (pre-#1114 records do not).
|
|
40
41
|
- `files`: unique list of non-null `file` values (deduplicated)
|
|
41
42
|
- **Omit the entire `stagnation_events` field if `total == 0`** (keeps historical entries clean).
|
|
42
43
|
|
|
@@ -67,7 +68,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
67
68
|
total_agents: <N>,
|
|
68
69
|
total_files_changed: <N>,
|
|
69
70
|
agent_summary: {complete: <N>, partial: <N>, failed: <N>, spiral: <N>},
|
|
70
|
-
waves: [/* {wave, role, agent_count, files_changed, quality, planned_files_count?, over_delivery_ratio?} */],
|
|
71
|
+
waves: [/* {wave, role, agent_count, files_changed, quality, agent_count_planned?, agent_count_started?, agent_count_completed?, planned_files_count?, over_delivery_ratio?} */],
|
|
71
72
|
// effectiveness is CONSTRUCTED EXPLICITLY (#773) — NOT left as an optional
|
|
72
73
|
// field for the coordinator to remember. Leaving it optional is exactly how
|
|
73
74
|
// the carryover=0 blind spot recurred (41/41 records read carryover:0).
|
|
@@ -110,7 +111,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
110
111
|
"total_files_changed": N,
|
|
111
112
|
"agent_summary": {"complete": N, "partial": N, "failed": N, "spiral": N},
|
|
112
113
|
"waves": [
|
|
113
|
-
{"wave": 1, "role": "Discovery", "agent_count": N, "files_changed": N, "quality": "pass|fail|skip", "planned_files_count": N, "over_delivery_ratio": 0.0},
|
|
114
|
+
{"wave": 1, "role": "Discovery", "agent_count": N, "files_changed": N, "quality": "pass|fail|skip", "agent_count_planned": N, "agent_count_started": N, "agent_count_completed": N, "planned_files_count": N, "over_delivery_ratio": 0.0},
|
|
114
115
|
...
|
|
115
116
|
],
|
|
116
117
|
"discovery_stats": {
|
|
@@ -152,7 +153,8 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
152
153
|
},
|
|
153
154
|
"stagnation_events": {
|
|
154
155
|
"total": N,
|
|
155
|
-
"by_pattern": {"error-echo": N, "turn-key-repetition": N, "pagination-spiral": N},
|
|
156
|
+
"by_pattern": {"error-echo": N, "turn-key-repetition": N, "pagination-spiral": N, "psa007-git-write": N, "status-partial": N},
|
|
157
|
+
"by_source": {"coordinator": N, "tail": N},
|
|
156
158
|
"by_error_class": {"edit-format-friction": N, "scope-denied": N, "command-blocked": N, "other": N},
|
|
157
159
|
"files": ["<relative path>", "..."]
|
|
158
160
|
}
|
|
@@ -168,6 +170,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
168
170
|
> - `review_stats`: populated ONLY when Phase 1.8 dispatched the session-reviewer agent AND it returned findings. Source: the session-reviewer's output summary.
|
|
169
171
|
> - `effectiveness`: ALWAYS populated from Phase 1 plan verification results, and CONSTRUCTED EXPLICITLY in the METRICS_ENTRY snippet (#773) — never deferred to a "remember to add" optional step (that omission is how `carryover: 0` slipped past 41 records). `completion_rate` = `completed / planned_issues` (0.0-1.0, where 0.0 means nothing was completed). **`carryover` counting rule (#773):** `carryover` is the **length of the Phase 1.65 gate carry-list** — `autoCarry` ∪ the middle-band `ask` items the operator LEFT SELECTED ∪ the answered-question `impliesWork: true` candidates — NOT the raw Phase 1.2+1.3 candidate count. On the fail-open skip (gate disabled / headless / AUQ unavailable), EVERY candidate carries, so `carryover` = the full candidate-list length. Count the gate's OUTPUT (what reaches Phase 5 Step 3 filing), not its INPUT.
|
|
170
172
|
> - `effectiveness.override_ratio` (#730/H5): OPTIONAL nested field = `overridden_findings / max(total_findings_surfaced, 1)` (float 0.0-1.0). Populate ONLY when Phase 2.6 (Broken-Window Budget) ran this session (`broken-window-budget.enabled: true`). OMIT (do NOT write null/0) otherwise — **absent = "not measured"**, `0.0` = "measured, nothing overridden". `overridden_findings` = the summed `count` of the `orchestrator.finding.overridden` events emitted this session; `total_findings_surfaced` = every MED/LOW+ finding surfaced across Phase 1.8 + wave reviewers.
|
|
173
|
+
> - `waves[].agent_count_planned` / `waves[].agent_count_started` / `waves[].agent_count_completed` (#724/#1115): OPTIONAL per-wave fields, sourced from `wave-loop.md` § Capture wave metrics step 7 — mirror its definitions exactly, do not re-derive them here. `agent_count_planned` = agents named in the session plan for this wave. `agent_count_started` = distinct agents whose `agent-<id>.meta.json` sidecar is present, after any silent-drop re-dispatch — NOT "produced a tool-result" (under background dispatch the launch ack is a result and would count an agent that never ran). `agent_count_completed` = distinct agents whose task-notification (`<status>completed</status>`) arrived. Omit each field when the wave did not measure it — **absent = "not measured"**, never zero-fill; `0` would read as "measured, no agent started", which is the opposite of an unmeasured wave. The two gaps carry the diagnosis: `agent_count_planned > agent_count_started` after re-dispatch is a persistent silent drop, `agent_count_started > agent_count_completed` at wave end is an agent that started and never returned. Both are also logged to STATE.md `## Deviations` by wave-loop.md, so a record and a deviation entry should agree.
|
|
171
174
|
> - `waves[].planned_files_count` / `waves[].over_delivery_ratio` (#730/H4): OPTIONAL per-wave fields, populated from STATE.md Wave History headers of the form `(planned <P> files → actual <A>, over-delivery <R>)` (written by wave-executor §3a since #730/H4); omit when absent (pre-#730 sessions / grounding-check: false).
|
|
172
175
|
> - `waves[].suite_passed` / `waves[].suite_failed` / `waves[].suite_platform` (#944): OPTIONAL per-wave fields. Omit all three when absent — absent = "not measured", `suite_failed: 0` = "measured, zero failures".
|
|
173
176
|
> **`suite_passed` / `suite_failed`: read the event FIRST, the STATE.md header only as fallback (#966 step 3).** Since #954/#967 the between-waves gate wrapper `scripts/run-quality-gate.mjs` emits `orchestrator.quality_gate.{passed,failed}` with a machine-measured `counts: {passed, failed, total}` AND the `wave_number` it resolved from the `wave-scope.json` sidecar, so per-wave attribution needs no wall-clock window join. Payload fields are flat at the record's top level; for each wave `N` of this session:
|
|
@@ -81,7 +81,25 @@ The proposals queue is populated mid-session by wave-executor agents calling `no
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Then iterate `batches` and emit one `AskUserQuestion` per batch
|
|
84
|
+
Then iterate `batches` and emit one `AskUserQuestion` per batch. The verbatim template is `agents/memory-proposal-collector.md` § AUQ Question Template — keep the two in step:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
AskUserQuestion({
|
|
88
|
+
questions: [{
|
|
89
|
+
header: "Memory",
|
|
90
|
+
question: "Batch <N> of <M> — which of these learnings should be stored permanently?",
|
|
91
|
+
options: [
|
|
92
|
+
// one entry per proposal in this batch (max 4)
|
|
93
|
+
// label + description formats are LOCKED by D3 — see that file, do not restate them here
|
|
94
|
+
{ label: "[type ] | subject(40) | conf=X.XX", description: "evidence: <first 60 chars of insight>" },
|
|
95
|
+
...
|
|
96
|
+
],
|
|
97
|
+
multiSelect: true
|
|
98
|
+
}]
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The batch counter moved out of `header` and into the question because `header` is cut off after 12 characters — `Memory — Confirm Proposals (Batch N of M)` reached the operator as `Memory — Con`.
|
|
85
103
|
|
|
86
104
|
5. After all batches answered, partition the queue into `approved` (any option selected across all batches) and `rejected` (all unselected).
|
|
87
105
|
|
|
@@ -134,9 +152,10 @@ After learnings are written (Phase 3.6), determine whether to emit a **manual-ca
|
|
|
134
152
|
```javascript
|
|
135
153
|
import { shouldDispatchAutoDream } from '${PLUGIN_ROOT}/scripts/lib/auto-dream.mjs';
|
|
136
154
|
import { resolveMemoryDir } from '${PLUGIN_ROOT}/scripts/lib/memory-paths.mjs';
|
|
137
|
-
const
|
|
155
|
+
const repoRoot = process.cwd();
|
|
156
|
+
const memoryDir = resolveMemoryDir(repoRoot);
|
|
138
157
|
const decision = await shouldDispatchAutoDream({
|
|
139
|
-
repoRoot
|
|
158
|
+
repoRoot,
|
|
140
159
|
memoryDir,
|
|
141
160
|
threshold: config['memory-cleanup-threshold'] ?? 5,
|
|
142
161
|
softLimit: config['memory-cleanup-soft-limit'] ?? 180,
|
|
@@ -337,7 +356,24 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
337
356
|
}
|
|
338
357
|
```
|
|
339
358
|
|
|
340
|
-
Iterate `batches` and emit one `AskUserQuestion` per batch
|
|
359
|
+
Iterate `batches` and emit one `AskUserQuestion` per batch:
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
AskUserQuestion({
|
|
363
|
+
questions: [{
|
|
364
|
+
header: "Regeln",
|
|
365
|
+
question: "Batch <N> of <M> — which rule proposals should be written into .claude/rules/?",
|
|
366
|
+
options: [
|
|
367
|
+
// one entry per proposal in this batch (max 4)
|
|
368
|
+
{ label: "<slug-40>", description: "Confidence <confidence>. First 80 chars of the rendered rule text: <…>" },
|
|
369
|
+
...
|
|
370
|
+
],
|
|
371
|
+
multiSelect: true
|
|
372
|
+
}]
|
|
373
|
+
})
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The batch counter moved out of `header` and into the question because `header` is cut off after 12 characters — `Reconciliation — Confirm Rule Proposals (Batch N of M)` reached the operator as `Reconciliati`. The rendered `content` shown in the description is the rule prose that will land on disk.
|
|
341
377
|
|
|
342
378
|
6. After all batches are answered, partition proposals into `approved` (any option selected across all batches) and `rejected` (all unselected). Proposals the operator rejected join the engine's `rejected` array for archival.
|
|
343
379
|
|
|
@@ -349,12 +385,19 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
349
385
|
approved,
|
|
350
386
|
rejected: [...rejected, ...operatorRejected],
|
|
351
387
|
repoRoot: process.cwd(),
|
|
388
|
+
// #1099 — FORWARD BOTH. `decideReconcile()` already resolved them onto its
|
|
389
|
+
// RUN decision (`scripts/lib/session-end/phase-skip.mjs`, `targets` +
|
|
390
|
+
// `baselineRoot`); dropping them here silently pins every session to
|
|
391
|
+
// repo-local writes no matter what `reconcile.targets` says. Absent
|
|
392
|
+
// `baselineRoot` is the documented no-op path, not an error.
|
|
393
|
+
targets: decision.targets,
|
|
394
|
+
baselineRoot: decision.baselineRoot,
|
|
352
395
|
sessionId,
|
|
353
396
|
});
|
|
354
397
|
// writeResult = { written: number, archived: number, errors: string[] }
|
|
355
398
|
```
|
|
356
399
|
|
|
357
|
-
`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.
|
|
400
|
+
`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.
|
|
358
401
|
|
|
359
402
|
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)` : ''}`.
|
|
360
403
|
|
|
@@ -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
|
|