session-orchestrator 4.1.0 → 5.0.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.
Files changed (230) hide show
  1. package/.agents/skills/session-plan/SKILL.md +1 -1
  2. package/.agents/skills/session-start/SKILL.md +1 -1
  3. package/.agents/skills/ux-grill/SKILL.md +22 -0
  4. package/.claude-plugin/marketplace.json +1 -1
  5. package/.claude-plugin/plugin.json +3 -2
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.codex-plugin/skills/session-plan/SKILL.md +1 -1
  8. package/.codex-plugin/skills/session-start/SKILL.md +1 -1
  9. package/.codex-plugin/skills/ux-grill/SKILL.md +21 -0
  10. package/.codex-plugin/skills/ux-grill/agents/openai.yaml +5 -0
  11. package/.cursor/commands/ux-grill.md +14 -0
  12. package/.cursor/skills/session-plan/SKILL.md +1 -1
  13. package/.cursor/skills/session-start/SKILL.md +1 -1
  14. package/.cursor/skills/ux-grill/SKILL.md +13 -0
  15. package/.cursor-plugin/plugin.json +1 -1
  16. package/AGENTS.md +2 -1
  17. package/CHANGELOG.md +128 -1
  18. package/README.md +98 -86
  19. package/agents/dialectic-deriver.md +11 -0
  20. package/agents/ux-evaluator.md +1 -1
  21. package/commands/close.md +3 -3
  22. package/commands/go.md +2 -0
  23. package/commands/memory-cleanup.md +4 -3
  24. package/commands/persona-panel.md +1 -1
  25. package/commands/session.md +3 -2
  26. package/commands/ux-grill.md +51 -0
  27. package/docs/README.md +4 -4
  28. package/docs/USER-GUIDE.md +117 -50
  29. package/docs/agent-authoring.md +2 -2
  30. package/docs/baseline.md +55 -1
  31. package/docs/ci-setup.md +1 -1
  32. package/docs/codex-setup.md +9 -0
  33. package/docs/components.md +9 -9
  34. package/docs/cursor-setup.md +1 -0
  35. package/docs/events-schema.md +13 -6
  36. package/docs/github-mirror-protection.md +61 -20
  37. package/docs/instruction-delivery.md +1 -1
  38. package/docs/memory-proposal-flow.md +3 -3
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/migration-v5.md +62 -0
  41. package/docs/owner-config-schema.md +74 -90
  42. package/docs/persona-panel.md +4 -4
  43. package/docs/pi-setup.md +1 -0
  44. package/docs/rule-authoring.md +13 -6
  45. package/docs/scope-collision-guard.md +16 -0
  46. package/docs/session-config-reference.md +55 -22
  47. package/docs/session-config-template.md +9 -5
  48. package/docs/vault-docs-architecture.md +4 -2
  49. package/hooks/_lib/hook-import-set.json +70 -3
  50. package/hooks/_lib/lock-bootstrap.mjs +84 -1
  51. package/hooks/_lib/vcs-create-matcher.mjs +401 -16
  52. package/hooks/enforce-scope.mjs +201 -0
  53. package/hooks/hooks-codex.json +1 -1
  54. package/hooks/hooks-cursor.json +5 -0
  55. package/hooks/hooks.json +7 -2
  56. package/hooks/on-session-start.mjs +171 -49
  57. package/hooks/post-bash-issue-budget-refund.mjs +375 -0
  58. package/hooks/pre-auq-clarity.mjs +70 -18
  59. package/hooks/pre-bash-issue-budget.mjs +170 -26
  60. package/hooks/subagent-telemetry.mjs +106 -20
  61. package/package.json +5 -4
  62. package/pi/prompts/ux-grill.md +12 -0
  63. package/scripts/baseline-archetypes.mjs +28 -0
  64. package/scripts/ci/assert-vitest-green.mjs +4 -2
  65. package/scripts/dialectic-deriver.mjs +32 -8
  66. package/scripts/emit-session.mjs +72 -1
  67. package/scripts/lib/agent-status.mjs +441 -9
  68. package/scripts/lib/auq/schema.mjs +10 -3
  69. package/scripts/lib/auto-dialectic.mjs +0 -68
  70. package/scripts/lib/baseline-archetypes.mjs +439 -0
  71. package/scripts/lib/build-live-signals.mjs +5 -6
  72. package/scripts/lib/ci-status-banner.mjs +29 -6
  73. package/scripts/lib/claude-md-budget-lint.mjs +52 -2
  74. package/scripts/lib/config/issue-budget.mjs +68 -8
  75. package/scripts/lib/config/private-config-dir.mjs +3 -2
  76. package/scripts/lib/config/remote-hosts.mjs +2 -2
  77. package/scripts/lib/config-schema.mjs +79 -0
  78. package/scripts/lib/config.mjs +12 -1
  79. package/scripts/lib/eval/engine.mjs +7 -1
  80. package/scripts/lib/file-lock.mjs +151 -8
  81. package/scripts/lib/git-porcelain.mjs +113 -0
  82. package/scripts/lib/instruction-budget-guard.mjs +415 -47
  83. package/scripts/lib/io.mjs +29 -4
  84. package/scripts/lib/issue-budget-reconcile.mjs +392 -0
  85. package/scripts/lib/issue-budget.mjs +412 -9
  86. package/scripts/lib/learnings/evolve-telemetry.mjs +1 -2
  87. package/scripts/lib/learnings/sizing-subject.mjs +44 -0
  88. package/scripts/lib/locks/staging-fence-lock.mjs +19 -38
  89. package/scripts/lib/locks/state-md-lock.mjs +19 -41
  90. package/scripts/lib/maintenance-due-banner.mjs +450 -0
  91. package/scripts/lib/owner-config.example.yaml +29 -46
  92. package/scripts/lib/owner-yaml.mjs +14 -13
  93. package/scripts/lib/peer-cards/merger.mjs +143 -0
  94. package/scripts/lib/pre-dispatch-check.mjs +20 -14
  95. package/scripts/lib/project-hygiene.mjs +81 -30
  96. package/scripts/lib/quality-gate.mjs +27 -71
  97. package/scripts/lib/reconcile/engine.mjs +19 -1
  98. package/scripts/lib/reconcile/writer.mjs +278 -11
  99. package/scripts/lib/resource-probe/evaluate.mjs +19 -21
  100. package/scripts/lib/rules-sync.mjs +34 -4
  101. package/scripts/lib/scope-echo.mjs +346 -0
  102. package/scripts/lib/session-close-backfill.mjs +182 -40
  103. package/scripts/lib/session-end/phase-skip.mjs +85 -86
  104. package/scripts/lib/session-end/tail-runner.mjs +178 -0
  105. package/scripts/lib/session-lock.mjs +62 -2
  106. package/scripts/lib/session-record-repair.mjs +91 -0
  107. package/scripts/lib/session-schema/constants.mjs +6 -0
  108. package/scripts/lib/session-schema/filters.mjs +26 -1
  109. package/scripts/lib/session-schema/validator.mjs +20 -0
  110. package/scripts/lib/session-shape.mjs +558 -0
  111. package/scripts/lib/session-start-probes.mjs +429 -56
  112. package/scripts/lib/session-token-rollup.mjs +95 -10
  113. package/scripts/lib/state-md/frontmatter-mutators.mjs +22 -34
  114. package/scripts/lib/state-md.mjs +1 -0
  115. package/scripts/lib/subagents-schema.mjs +77 -9
  116. package/scripts/lib/telemetry/pricing.mjs +197 -0
  117. package/scripts/lib/telemetry/sync.mjs +50 -1
  118. package/scripts/lib/test-runner/artifact-paths.mjs +30 -5
  119. package/scripts/lib/test-runner/issue-reconcile.mjs +45 -8
  120. package/scripts/lib/tmux-layout/layouts.mjs +62 -4
  121. package/scripts/lib/ux-grill/collect.mjs +1163 -0
  122. package/scripts/lib/ux-grill/compare.mjs +285 -0
  123. package/scripts/lib/ux-grill/manifest.mjs +618 -0
  124. package/scripts/lib/ux-grill/measures.mjs +431 -0
  125. package/scripts/lib/ux-grill/paths.mjs +224 -0
  126. package/scripts/lib/ux-grill/pencil-coverage.mjs +284 -0
  127. package/scripts/lib/ux-grill/reconcile.mjs +344 -0
  128. package/scripts/lib/ux-grill/run-record.mjs +316 -0
  129. package/scripts/lib/ux-grill/schema.mjs +321 -0
  130. package/scripts/lib/validate/check-skill-script-paths.mjs +33 -10
  131. package/scripts/lib/validate/check-untracked-test-deps.mjs +33 -19
  132. package/scripts/lib/validate/check-unwired-features.mjs +56 -27
  133. package/scripts/lib/vault-mirror/process.mjs +2 -1
  134. package/scripts/lib/vault-status/board-lock.mjs +18 -0
  135. package/scripts/lib/vault-status/board-writer.mjs +8 -0
  136. package/scripts/lib/vault-status/narrative-mirror.mjs +4 -4
  137. package/scripts/lib/wave-resource-gate.mjs +23 -27
  138. package/scripts/lib/wave-sizing.mjs +10 -3
  139. package/scripts/materialize-wave-scope.mjs +68 -14
  140. package/scripts/mcp-server.sh +16 -1
  141. package/scripts/print-applicable-rules.mjs +7 -6
  142. package/scripts/print-learnings-index.mjs +3 -2
  143. package/scripts/release.mjs +7 -2
  144. package/scripts/session-shape.mjs +266 -0
  145. package/skills/_shared/config-reading.md +15 -9
  146. package/skills/_shared/private-capability-context.md +89 -0
  147. package/skills/bootstrap/SKILL.md +60 -209
  148. package/skills/bootstrap/_shared-template.md +99 -14
  149. package/skills/bootstrap/deep-template.md +36 -26
  150. package/skills/bootstrap/fast-template.md +44 -8
  151. package/skills/bootstrap/intensity-heuristic.md +10 -4
  152. package/skills/bootstrap/private-contract.md +119 -0
  153. package/skills/bootstrap/public-fallback.md +30 -18
  154. package/skills/bootstrap/references/bootstrap-ecosystem-health-flow.md +48 -0
  155. package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +37 -0
  156. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +108 -0
  157. package/skills/bootstrap/references/bootstrap-rules-fetch-bridge.md +64 -0
  158. package/skills/bootstrap/standard-template.md +39 -24
  159. package/skills/claude-md-drift-check/SKILL.md +9 -2
  160. package/skills/claude-md-drift-check/checker.mjs +213 -21
  161. package/skills/discovery/SKILL.md +6 -173
  162. package/skills/discovery/probes/vault-staleness.mjs +35 -5
  163. package/skills/discovery/probes-docs.md +8 -4
  164. package/skills/discovery/probes-supply-chain.md +4 -2
  165. package/skills/discovery/probes-ui.md +8 -4
  166. package/skills/discovery/probes-vault.md +12 -4
  167. package/skills/discovery/references/discovery-interactive-triage.md +139 -0
  168. package/skills/discovery/references/discovery-triage-state.md +54 -0
  169. package/skills/docs-orchestrator/audience-mapping.md +1 -1
  170. package/skills/eval/rubric-v1.md +13 -0
  171. package/skills/evolve/SKILL.md +2 -458
  172. package/skills/evolve/references/evolve-analyze-mode.md +360 -0
  173. package/skills/evolve/references/evolve-dialectic-mode.md +139 -0
  174. package/skills/gitlab-ops/SKILL.md +3 -3
  175. package/skills/grill/SKILL.md +1 -1
  176. package/skills/memory-cleanup/SKILL.md +2 -2
  177. package/skills/plan/mode-new.md +9 -0
  178. package/skills/plan/mode-retro.md +4 -3
  179. package/skills/reconcile/SKILL.md +11 -1
  180. package/skills/session-end/SKILL.md +3 -2
  181. package/skills/session-end/drift-operations.md +20 -5
  182. package/skills/session-end/metrics-collection.md +1 -0
  183. package/skills/session-end/phase-3-2-docs-verification.md +1 -1
  184. package/skills/session-end/phase-3-6-tail.md +27 -67
  185. package/skills/session-end/phase-3-7a-recommendations.md +2 -2
  186. package/skills/session-end/references/phase-2-quality-gate.md +3 -3
  187. package/skills/session-end/references/phase-3-documentation-updates.md +8 -6
  188. package/skills/session-end/references/phase-5-issue-cleanup.md +32 -1
  189. package/skills/session-end/session-metrics-write.md +33 -12
  190. package/skills/session-plan/SKILL.md +46 -180
  191. package/skills/session-plan/references/session-plan-task-classification.md +152 -0
  192. package/skills/session-plan/wave-template.md +8 -15
  193. package/skills/session-start/SKILL.md +41 -7
  194. package/skills/session-start/phase-2-5-docs-planning.md +1 -1
  195. package/skills/session-start/phase-8-5-express-path.md +12 -9
  196. package/skills/session-start/references/operations-contract.md +114 -0
  197. package/skills/session-start/references/phase-1-5-session-continuity.md +2 -0
  198. package/skills/session-start/references/phase-4-ssot-environment-check.md +42 -24
  199. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +3 -1
  200. package/skills/session-start/soul.md +2 -2
  201. package/skills/test-runner/SKILL.md +1 -1
  202. package/skills/test-runner/rubric-v1.md +2 -2
  203. package/skills/tmux-layout/SKILL.md +3 -1
  204. package/skills/ux-grill/SKILL.md +211 -0
  205. package/skills/ux-grill/rubric-v2.md +201 -0
  206. package/skills/ux-grill/soul.md +76 -0
  207. package/skills/wave-executor/SKILL.md +32 -127
  208. package/skills/wave-executor/circuit-breaker.md +3 -1
  209. package/skills/wave-executor/references/wave-executor-quality-gate.md +61 -0
  210. package/skills/wave-executor/references/wave-executor-state-init.md +86 -0
  211. package/skills/wave-executor/references/wave-loop-dispatch.md +12 -2
  212. package/skills/wave-executor/references/wave-loop-review.md +19 -6
  213. package/skills/wave-executor/references/wave-loop-scope-manifest.md +6 -2
  214. package/templates/_shared/ux-manifest.template.md +149 -0
  215. package/templates/nextjs-minimal/package.json +1 -1
  216. package/templates/node-minimal/package.json +1 -1
  217. package/scripts/lib/multi-provider-build/providers.mjs +0 -64
  218. package/scripts/lib/multi-provider-build/templating.mjs +0 -130
  219. package/scripts/lib/owner-config/coerce.mjs +0 -29
  220. package/scripts/lib/owner-config/constants.mjs +0 -21
  221. package/scripts/lib/owner-config/defaults.mjs +0 -50
  222. package/scripts/lib/owner-config/error.mjs +0 -19
  223. package/scripts/lib/owner-config/index.mjs +0 -13
  224. package/scripts/lib/owner-config/merge.mjs +0 -52
  225. package/scripts/lib/owner-config/validate.mjs +0 -259
  226. package/scripts/lib/owner-config-loader.mjs +0 -170
  227. package/scripts/lib/owner-config.mjs +0 -28
  228. package/scripts/lib/soul-resolve.mjs +0 -130
  229. package/scripts/lib/vault-mirror/render.mjs +0 -8
  230. package/templates/_shared/journey-manifest.md +0 -114
@@ -5,13 +5,19 @@
5
5
  >
6
6
  > Issue [#174](../../../-/issues/174) — D1 of Sub-Epic [#161](../../../-/issues/161) (Owner Persona Layer).
7
7
  > Parent epic: [#157](../../../-/issues/157) (v3.1.0 Environment-Aware Sessions).
8
+ >
9
+ > **SSOT is code, not this file:** `scripts/lib/owner-yaml.mjs` (schema,
10
+ > validator, loader, writer) + `scripts/lib/config/private-config-dir.mjs`
11
+ > (path resolution). This page describes that module; when the two disagree,
12
+ > the module wins.
8
13
 
9
14
  ## Path Resolution
10
15
 
11
- The loader (`scripts/lib/owner-config-loader.mjs`) resolves the canonical path as:
16
+ The loader (`scripts/lib/owner-yaml.mjs`, via `resolvePrivateConfigDir()` in
17
+ `scripts/lib/config/private-config-dir.mjs`) resolves the canonical path as:
12
18
 
13
19
  ```
14
- ${XDG_CONFIG_HOME ?? <homedir>/.config}/session-orchestrator/owner.yaml
20
+ ${SO_CONFIG_HOME ?? XDG_CONFIG_HOME ?? <homedir>/.config}/session-orchestrator/owner.yaml
15
21
  ```
16
22
 
17
23
  | Platform | Default path |
@@ -21,37 +27,40 @@ ${XDG_CONFIG_HOME ?? <homedir>/.config}/session-orchestrator/owner.yaml
21
27
  | Windows | `%USERPROFILE%\.config\session-orchestrator\owner.yaml` |
22
28
 
23
29
  A missing file is **not an error** — it signals the user has not opted in.
24
- The loader returns `{ok: false, source: 'missing'}` in that case and consumers
25
- fall back to plugin defaults.
26
-
27
- ## Schema (`schema-version: 1`)
28
-
29
- | Section / Field | Type | Required | Default | Notes |
30
- |------------------------------------------|-------------------------------|----------|---------------|----------------------------------------------------|
31
- | `schema-version` | integer | yes | — | Must equal `1`. |
32
- | `owner.name` | string (1-100) | yes | — | Display name. |
33
- | `owner.email-hash` | hex (64 chars) or `null` | no | `null` | SHA256 of email; cross-host identity correlation. |
34
- | `owner.language` | ISO-639-1 (`de`, `en`, ...) | yes | | Drives soul.md default language. |
35
- | `tone.style` | `direct\|neutral\|friendly` | no | `neutral` | |
36
- | `tone.tonality` | string (≤200) or `null` | no | `null` | Free-form descriptor. |
37
- | `efficiency.output-level` | `lite\|full\|ultra` | no | `full` | Caveman-inspired token dial. |
38
- | `efficiency.preamble` | `minimal\|verbose` | no | `minimal` | Pre-tool-call narration. |
39
- | `efficiency.comments-in-code` | `minimal\|full` | no | `minimal` | Inline code comments. |
40
- | `hardware-sharing.enabled` | boolean | no | `false` | Consent gate for hardware-pattern export. |
41
- | `hardware-sharing.hash-salt` | hex (64 chars) or `null` | no | `null` | Per-host random salt. Required when `enabled=true`.|
42
- | `defaults.preferred-test-command` | string (≤200) or `null` | no | `null` | Override CLAUDE.md `test-command`. |
43
- | `defaults.preferred-editor` | string (≤50) or `null` | no | `null` | |
44
- | `metadata.created_at` | ISO 8601 string or `null` | no | `null` | Auto-set on first write. |
45
- | `metadata.updated_at` | ISO 8601 string or `null` | no | `null` | Auto-bumped on every save. |
30
+ `loadOwnerConfig()` then returns `getDefaults()` with `source: 'defaults'`.
31
+
32
+ ## Schema
33
+
34
+ Four **required** sections (`owner`, `tone`, `efficiency`, `hardware-sharing`)
35
+ and the optional sections below. An invalid required section discards the whole
36
+ file (defaults are returned, errors reported); an invalid optional object
37
+ section is replaced by its default and reported via `droppedSections`
38
+ (`source: 'partial'`).
39
+
40
+ | Section / Field | Type / enum | Required | Default | Notes |
41
+ |-------------------------------------|--------------------------------------|----------|-----------|----------------------------------------------------|
42
+ | `owner.name` | non-empty string | yes | `''` | Display name. |
43
+ | `owner.language` | `de` \| `en` | yes | `en` | Drives soul.md default language. |
44
+ | `tone.style` | `direct` \| `neutral` \| `friendly` | yes | `neutral` | |
45
+ | `tone.tonality` | string or absent | no | `''` | Free-form descriptor. |
46
+ | `efficiency.output-level` | `lite` \| `full` \| `ultra` | yes | `full` | Token dial. |
47
+ | `efficiency.preamble` | `minimal` \| `verbose` | yes | `minimal` | Pre-tool-call narration. |
48
+ | `hardware-sharing.enabled` | boolean | yes | `false` | Consent gate for hardware-pattern export. |
49
+ | `hardware-sharing.hash-salt` | string | no | `''` | Required (non-empty) when `enabled = true`. |
50
+ | `paths.vault-dir` | string | no | `''` | `''` = no override; beats the committed `vault-integration.vault-dir`. |
51
+ | `paths.baseline-path` | string | no | `''` | Host-local `plan-baseline-path` override (#653). |
52
+ | `paths.namespace-map-path` | string | no | `''` | Host-local repo-pseudonym JSON map (#725 D5). |
53
+ | `paths.confidential-names-file` | string | no | `''` | Host-local JSON array of names for the CP11 leakage rule (#728a). Names live in that file only, never inline here. |
54
+ | `dispatcher.autonomy` | `off` \| `advisory` \| `autonomous-gated` \| `''` | no | `''` | `''` = no override (#679). |
55
+ | `vaults`, `baselines` | lists | no | absent | Passed through untouched; parsed leniently at point of use. |
46
56
 
47
57
  ### Privacy Contract
48
58
 
49
- - `hardware-sharing.enabled = true` **requires** `hardware-sharing.hash-salt`
50
- to be a valid 64-char hex string. The validator rejects `enabled=true` with
51
- `hash-salt=null` so consent is never recorded without the means to
52
- anonymise per-host learnings.
53
- - `owner.email-hash` is the only field carrying potential cross-host identity;
54
- it is hashed (never plaintext email) and remains optional.
59
+ - `hardware-sharing.enabled = true` **requires** a non-empty
60
+ `hardware-sharing.hash-salt`. The validator rejects consent without the means
61
+ to anonymise per-host learnings.
62
+ - Only PATHS live in `owner.yaml`; the host-local data they point at
63
+ (confidential names, namespace map) is never inlined and never committed.
55
64
 
56
65
  ## Example
57
66
 
@@ -60,95 +69,70 @@ for an annotated, copyable example.
60
69
 
61
70
  ## API Surface
62
71
 
63
- `scripts/lib/owner-config.mjs` (pure, no I/O):
72
+ `scripts/lib/owner-yaml.mjs` the live SSOT. **Synchronous**; every call site
73
+ consumes it without `await`. `js-yaml` is imported lazily, so the module is
74
+ safe on the hook import graph.
64
75
 
65
76
  ```js
66
- import { validate, coerce, defaults, merge } from './scripts/lib/owner-config.mjs';
67
-
68
- // Defensive, never throws. Use this in skill code paths.
69
- const result = validate(rawObj);
70
- // → { ok: true, value: <normalized>, errors: [] }
71
- // → { ok: false, value: null, errors: ["owner.name must be ..."] }
72
-
73
- // Strict mode: throws OwnerConfigError on failure. Use in tests / CLI.
74
- const value = coerce(rawObj);
75
-
76
- // Default-filled config. owner.name and owner.language remain blank
77
- // (the bootstrap interview in D2 fills them in).
78
- const def = defaults();
77
+ import { loadOwnerConfig, resolveOwnerYamlPath, validateOwnerConfig, getDefaults }
78
+ from './scripts/lib/owner-yaml.mjs';
79
79
 
80
- // Deep-merge two configs. `override` wins on every defined leaf.
81
- const merged = merge(baseFromOwnerYaml, perSessionOverrides);
82
- ```
83
-
84
- `scripts/lib/owner-config-loader.mjs` (filesystem I/O):
85
-
86
- ```js
87
- import { loadOwnerConfig, resolveOwnerConfigPath } from './scripts/lib/owner-config-loader.mjs';
88
-
89
- const result = await loadOwnerConfig();
80
+ const result = loadOwnerConfig();
90
81
  // {
91
- // ok: true|false,
92
- // value: <normalized> | null,
93
- // errors: string[],
94
- // source: 'file' | 'missing' | 'parse-error' | 'validation-error',
82
+ // config: <merged with getDefaults()>,
83
+ // source: 'file' | 'partial' | 'defaults' | ...,
95
84
  // path: '<absolute path>',
85
+ // errors: string[], warnings: string[], droppedSections: string[],
96
86
  // }
97
87
 
98
88
  // Test override
99
- const result2 = await loadOwnerConfig({ path: '/tmp/test-owner.yaml' });
89
+ const result2 = loadOwnerConfig({ path: '/tmp/test-owner.yaml' });
90
+
91
+ // Pure validation, no I/O, never throws.
92
+ const { valid, errors } = validateOwnerConfig(rawObj);
100
93
  ```
101
94
 
95
+ Also exported: `validateOwnerSections()` (per-section buckets),
96
+ `writeOwnerConfig()`, `OPTIONAL_OBJECT_SECTIONS`, `resolvePrivateConfigDir`.
97
+
102
98
  ## Where this gets read
103
99
 
104
- D1 only ships the schema + validator + loader. The downstream tasks consume
105
- these primitives:
106
-
107
- - **[#175 D2](../../../-/issues/175) — bootstrap interview**: writes the file via
108
- `coerce()` then a YAML serializer, stamping `metadata.created_at` /
109
- `metadata.updated_at`.
110
- - **[#176 D3](../../../-/issues/176) `soul.md` runtime-merge**: calls
111
- `loadOwnerConfig()` from the session-start hook, then `merge()` with any
112
- per-session overrides, then resolves `{{tone.style}}` /
113
- `{{efficiency.output-level}}` / `{{owner.language}}` template slots.
114
- - **#177 D4 — projects-baseline propagation**: stamps a
115
- reference to the canonical path into every consumer repo's CLAUDE.md
116
- frontmatter (path reference only — never the content).
117
- - **[#168 C4](../../../-/issues/168) — hardware-sharing consent prompt**: writes
118
- `hardware-sharing.enabled = true` + a freshly-generated `hash-salt` after
119
- the user accepts the consent dialogue.
100
+ `loadOwnerConfig()` has 10 live consumers outside its own module
101
+ (`rg -ln "loadOwnerConfig" scripts hooks`, 2026-09-09) — two hooks
102
+ (`hooks/on-session-start.mjs`, `hooks/skill-invocation-telemetry.mjs`), the
103
+ session-start owner-config banner (`scripts/lib/owner-config-banner.mjs`), the
104
+ host-path + dispatcher-autonomy resolvers (`scripts/lib/config/host-paths.mjs`,
105
+ `scripts/lib/config/dispatcher-autonomy.mjs`), the named-vault resolver, the
106
+ owner-leakage scanner, telemetry, and vault-mirror. For the behavioural
107
+ contract of the persona layer itself see
108
+ [`.claude/rules/owner-persona.md`](../.claude/rules/owner-persona.md).
120
109
 
121
110
  ## Validation Rules (rejection examples)
122
111
 
123
112
  ```yaml
124
- # REJECTED — schema-version missing
125
- owner: { name: x, language: en }
126
-
127
- # REJECTED — schema-version mismatch (refuses to load future versions)
128
- schema-version: 2
129
- owner: { name: x, language: en }
130
-
131
- # REJECTED — name empty
132
- schema-version: 1
113
+ # REJECTED — owner.name empty
133
114
  owner: { name: "", language: en }
134
115
 
135
- # REJECTED — language not ISO-639-1
136
- schema-version: 1
116
+ # REJECTED — language outside the de|en enum
137
117
  owner: { name: x, language: "english" }
138
118
 
139
119
  # REJECTED — enum violation
140
- schema-version: 1
141
120
  owner: { name: x, language: en }
142
121
  tone: { style: "snarky" }
143
122
 
144
123
  # REJECTED — privacy contract: enabled=true without hash-salt
145
- schema-version: 1
146
124
  owner: { name: x, language: en }
147
125
  hardware-sharing: { enabled: true }
148
126
 
149
- # ACCEPTED — minimal valid config
150
- schema-version: 1
127
+ # ACCEPTED — all four required sections present and valid
151
128
  owner:
152
129
  name: Bernhard
153
130
  language: de
131
+ tone:
132
+ style: direct
133
+ efficiency:
134
+ output-level: full
135
+ preamble: minimal
136
+ hardware-sharing:
137
+ enabled: false
154
138
  ```
@@ -70,8 +70,8 @@ written to `.orchestrator/persona-panel/`.
70
70
  **Scenario:** A climate-research brief (`wfk-2.1.5.md`) needs sign-off from a domain physicist
71
71
  and an AI/ML expert before it is submitted. Both must agree.
72
72
 
73
- **Catalog files:** `.claude/personas/klima-physicist.md`,
74
- `.claude/personas/klima-ai-expert.md`
73
+ **Catalog files:** `.claude/personas/klima-physicist.md`, <!-- path-check: example -->
74
+ `.claude/personas/klima-ai-expert.md` <!-- path-check: example -->
75
75
 
76
76
  ```bash
77
77
  /persona-panel docs/research/wfk-2.1.5.md \
@@ -182,8 +182,8 @@ The command exits with code 1. CI pipelines and wave-executor hooks can gate on
182
182
  **Scenario:** An AI accountant (Sophie) produces invoice analysis JSON. A tax-advisor persona
183
183
  and a DSGVO-compliance persona must both approve before the output is delivered to the client.
184
184
 
185
- **Catalog files:** `.claude/personas/accounting-tax-advisor.md`,
186
- `.claude/personas/accounting-compliance.md`
185
+ **Catalog files:** `.claude/personas/accounting-tax-advisor.md`, <!-- path-check: example -->
186
+ `.claude/personas/accounting-compliance.md` <!-- path-check: example -->
187
187
 
188
188
  ```bash
189
189
  /persona-panel sophie-outputs/2026-05-19/invoice-12345.json \
package/docs/pi-setup.md CHANGED
@@ -118,5 +118,6 @@ After reload, use the same commands:
118
118
  - **Prompts or skills not visible**: verify the project is trusted in Pi, then restart or reload.
119
119
  - **Package not loaded**: confirm `.pi/settings.json` or `~/.pi/agent/settings.json` contains the local checkout path under `packages`.
120
120
  - **Hooks not firing**: confirm `package.json` still has the `pi.extensions` entry and `hooks/hooks-pi.json` exists.
121
+ - **No import-probe warning after an edit**: Pi wires the probe through `tool_result`; file eligibility, ESLint resolution, and `no-undef` configuration still apply. See [import-probe coverage and ESLint troubleshooting](USER-GUIDE.md#import-probe-warnings-and-missing-eslint).
121
122
  - **Config ignored**: ensure the file has a `## Session Config` header in `AGENTS.md` or `CLAUDE.md`.
122
123
  - **Runtime error from hooks**: run `npm install` in the Session Orchestrator checkout so hook dependencies are present.
@@ -285,12 +285,19 @@ them silently loses a learning or regenerates it:
285
285
  TTL past what its type registry granted it.) State the rule in the file
286
286
  itself, so the next editor does not "fix" it upward.
287
287
 
288
- 3. **Keep `globs:` only, and take the UNION.** The merged file loads for any
289
- path any of its parts covered, so its `globs:` is the union of theirs.
290
- `rule-loader.mjs` resolves `globs:` and `paths:` with `globs:` winning
291
- SILENTLY when both are present (issue #795, `parseGlobsFrontmatter`) — so a
292
- file carrying both duplicate keys ships the `paths:` block as dead bytes.
293
- Write `globs:` alone.
288
+ 3. **Keep BOTH `globs:` and `paths:`, as byte-identical mirrors, each the
289
+ UNION of the parts.** The merged file loads for any path any of its parts
290
+ covered, so both lists are the union of theirs — never `globs:` alone.
291
+ `rule-loader.mjs` resolves `globs:` for wave-time injection, while Claude
292
+ Code's OWN native rule loader reads ONLY `paths:` and treats a rule
293
+ lacking it as unconditional, always-on (`check-rules.mjs` check #1108,
294
+ measured this session) — a `globs:`-only merged file is scoped everywhere
295
+ it is inspected and loads everywhere it is used, exactly the
296
+ instruction-budget failure consolidation exists to prevent. `rule-loader.mjs`
297
+ still resolves `globs:` and `paths:` with `globs:` winning SILENTLY when
298
+ both are present (issue #795, `parseGlobsFrontmatter`), but keeping the two
299
+ lists identical makes that precedence moot — there is no divergent value
300
+ left for it to pick between.
294
301
 
295
302
  4. **Substance in, boilerplate out.** Each absorbed learning becomes an `###`
296
303
  heading carrying its original rule sentence, plus its evidence line. What is
@@ -25,6 +25,8 @@ Two things follow, and both are the point of #1020:
25
25
 
26
26
  `<state-dir>` is the first of `.pi` / `.cursor` / `.codex` / `.claude` that carries a `wave-scope.json` — the same precedence `findScopeFile()` and the hook's `waveKeyOf()` use.
27
27
 
28
+ Step 1 also offers an opt-in path diagnostic: run `materialize-wave-scope.mjs` from the project root with `--warn-missing` to name absent concrete paths and their agents on stderr (#1235). Relative paths resolve from the working directory, not `<state-dir>`. A warning does not fail the command or change its stdout or either declaration shape. For a file the wave deliberately creates, repeat `--new-file PATH` as needed; every exception must exactly match a declared path and pass scope validation before any writes begin. Grants containing `*` or ending in `/` are skipped under the existing glob/prefix grammar; `?` and braces are literal. The canonical invocation and handling steps are in `skills/wave-executor/references/wave-loop-scope-manifest.md` § 3.1.
29
+
28
30
  ### 2.1 Why `--union` runs last
29
31
 
30
32
  A union computed over colliding scopes **launders the defect into the artefact meant to prevent it**: `allowedPaths` then grants the contested file, and every later gate — `--assert-subset`, `enforce-scope` Gate 7, the commit guard — sees a perfectly legal write. `validate()` in `validate-wave-scope.mjs` enforces the order in code: `--assert-subset` → `--assert-disjoint` → `--union`, and `--union` returns early because it is a QUERY MODE that replaces the manifest echo on stdout.
@@ -62,6 +64,14 @@ Five properties are choices, not omissions — and every one of them points the
62
64
 
63
65
  The event is what keeps the skip countable rather than silent: `orchestrator.scope.foreign_session_ignored` carries `hook`, `manifest`, `manifest_session`, `own_session`, `wave` and `file_path`. It is deliberately an event and not an `emitWarn` — the branch is hit on *every* Edit of the non-owning session, so a stderr line per write would be noise the operator learns to ignore.
64
66
 
67
+ ### 2.4 The one out-of-repo territory: harness auto-memory (#1295)
68
+
69
+ `hooks/enforce-scope.mjs` Gate 5c allows writes under **this repo's** Claude Code auto-memory directory, `~/.claude/projects/<encoded-repo-path>/memory/` (`<encoded-repo-path>` = the repo root with every `/` and `.` replaced by `-`, via the one repo encoder `encodeProjectDir()` in `scripts/lib/wave-transcript-tail.mjs`). The harness writes `MEMORY.md` and the per-fact files there, outside the working copy, so those paths can never collide with any wave file scope — yet Gate 6 denied every one of them while a manifest existed, and six sessions in a consumer repo re-documented the same tear-down-the-manifest workaround (bewerbungs-assistent#307). One `orchestrator.scope.memory_dir_allowed` event per allow makes it countable.
70
+
71
+ The grant is exactly one directory: containment is exact-prefix on the realpath-resolved candidate (both the raw and the canonical project root are encoded, since Claude Code names the directory after the path it was launched with), so a SIBLING repo's memory dir, an adjacent-prefix sibling directory (`<memory>-evil/`), a `..` traversal out of it, and every other out-of-repo path stay denied. One honest caveat: the harness name is not unique — `encodeProjectDir()` maps both `/` and `.` to `-`, so two repo roots differing only in `.` vs `-` (`/x/a.b` and `/x/a-b`) share ONE memory dir. That collision is the harness's, inherited here and not narrowable by this gate — and in-repo enforcement, including Discovery's `allowedPaths: []` deny-all, is untouched. The encoder is bound lazily and a load failure falls through to the deny (fail-closed). **Who the grant is for (#1352): the COORDINATOR only.** The carve-out opens a directory whose content is auto-injected as trusted project context into every later session, outside `git diff`, CI review, owner-leakage and gitleaks — so a dispatched wave agent must not reach it. The discriminator is the payload's **`agent_id`**, and it is measured, not assumed (Claude Code 2.1.270, `strings` over the shipped binary, 2026-09-13): the base hook-input builder every event shares is `{session_id, transcript_path: yf(session.id), cwd, …, agent_id: toolUseContext?.agentId, agent_type}`, which PreToolUse spreads verbatim, and the harness's own schema says of `agent_id` — *"Present only when the hook fires from within a subagent… Absent for the main thread, even in `--agent` sessions. Use this field (not `agent_type`) to distinguish subagent calls from main-thread calls."* The issue's original candidate is refuted by the same source: `transcript_path` is derived from the SESSION id, so coordinator and subagent carry the identical value; so is `CLAUDE_CODE_SESSION_ID`, and `CLAUDE_CODE_CHILD_SESSION=1` was measured in a main-thread `env` dump (`isSidechain:false`).
72
+
73
+ `classifyCaller()` therefore returns `subagent` (non-empty `agent_id` → **no carve-out**; the write falls through to the normal gates, which is a DENY under Discovery's `allowedPaths: []`), `coordinator` (no `agent_id` but a non-empty `agent_type` — per the schema that combination is the main thread of an `--agent` session), or `absent` (neither field). **`absent` keeps the pre-#1352 ALLOW, deliberately fail-OPEN:** fail-closed on ambiguity would deny the coordinator's own memory writes and break `/close`, and a harness that sends no marker is indistinguishable from a main thread. The outcome rides on the existing `orchestrator.scope.memory_dir_allowed` event as `discriminator`, so the fail-open case is countable rather than silent (HR-105) — revisit if `absent` ever dominates the records on a harness that does dispatch subagents.
74
+
65
75
  ## 3. The collision algorithm
66
76
 
67
77
  `findScopeCollisions(agentScopes, { knownFiles })` compares every cross-agent entry pair through `classifyEntryCollision()`, in three binding stages:
@@ -141,6 +151,12 @@ Why it exists: the in-ledger `scopeSignals` counter (§ 4.1, rows 5/6) is a **wa
141
151
 
142
152
  **Payload discipline.** Counts and closed enums only, plus `agent_id` (the coordinator's own dispatch description, clamped) and the optional session attribution: no prompt body, no declared path, no glob. Issue #1092's acceptance criterion 3 is the rule, and the reason is concrete — this record also travels over the optional Clank Event-Bus webhook with no redaction, and paths under `01-projects/` carry private project slugs.
143
153
 
154
+ **The receive side now has a SOFT self-report signal (#1092).** Beside the fenced `FILE-SCOPE` block the coordinator injects one line naming the `scopeDigest` of that agent's own scope file (`scripts/lib/scope-echo.mjs --instruction`); post-wave, `checkScopeEcho` compares the digest the agent echoed in its report and emits one `orchestrator.wave_dispatch.scope_echo_checked` record per agent (`skills/wave-executor/references/wave-loop-review.md` step 3d-bis). It is informational — a missing or mismatched echo blocks nothing.
155
+
156
+ **Its ceiling, named (BV-004).** The digest is *handed to the agent in the prompt*, so an agent that never read a single path can still copy the line. The signal therefore proves only that the injected line survived the round trip into the agent's context and back — strictly more than the zero receive-side signal that preceded it, and strictly less than obedience, which stays measured at write time (`enforce-scope.mjs`) and by the W5 pass. Revisit when the platform exposes a prompt-assembly boundary, at which point the digest can be computed against the assembled prompt rather than echoed by its reader.
157
+
158
+ A cryptographic proof still needs that platform boundary: no mechanism inside this repo can distinguish a copied digest from a read one.
159
+
144
160
  **Revisit-Trigger** (verbatim from issue #1092, for the transport half this section deliberately does NOT close):
145
161
 
146
162
  > Implement when the platform exposes a stable prompt-assembly hook or when a coordinator-owned digest event can be proven against the real dispatched transcript.
@@ -76,12 +76,33 @@ Some sub-configs live in dedicated policy files under `.orchestrator/policy/`:
76
76
 
77
77
  | Field | Type | Default | Description |
78
78
  |-------|------|---------|-------------|
79
- | `agents-per-wave` | integer or integer with overrides | `6` | Maximum parallel subagents per wave. Supports session-type overrides: `6 (deep: 18)` outputs `{"default": 6, "deep": 18}`. The override key set is OPEN — `_coerceInteger` (`scripts/lib/config/coercers.mjs`) parses whatever keys the parentheses contain, so `6 (deep: 18, ultradeep: 18)` outputs `{"default": 6, "deep": 18, "ultradeep": 18}` with no code change (see § Session Profile below). Plain integers remain plain. The override key names a session type but does **not** create one: there is no `session-type:` Session Config key — `parseSessionConfig()` emits none, so writing one into a repo's `## Session Config` block is inert prose. The session type comes from the `/session` argument (default `deep`, see `commands/session.md`) and is persisted to STATE.md frontmatter as `session-type:`, which is the only live read (`scripts/print-applicable-rules.mjs` rule mode-gating). |
79
+ | `agents-per-wave` | integer or integer with overrides | `6` | Maximum parallel subagents per wave. Supports session-type overrides: `6 (deep: 18)` outputs `{"default": 6, "deep": 18}`. The override key set is OPEN — `_coerceInteger` (`scripts/lib/config/coercers.mjs`) parses whatever keys the parentheses contain, so `6 (deep: 18, ultradeep: 18)` outputs `{"default": 6, "deep": 18, "ultradeep": 18}` with no code change (see § Session Profile below). Plain integers remain plain. The override key names a session type but does **not** create one: there is no `session-type:` Session Config key — `parseSessionConfig()` emits none, so writing one into a repo's `## Session Config` block is inert prose. The session type comes from the `/session` argument (default `deep`, see `commands/session.md`) and is persisted to STATE.md frontmatter as `session-type:`, which is the only live read (`scripts/print-applicable-rules.mjs` rule mode-gating). **Resolution for wave shaping** is `resolveAgentCap(cap, sessionType)` in `scripts/lib/session-shape.mjs` — the one EXPORTED resolver. `wave-resource-gate.mjs` and `resource-probe/evaluate.mjs` both import and call this same function for their resource-ceiling checks, but each does so through a local `MODE_BLIND_SESSION_TYPE` constant (`undefined`) instead of the session's real type — two mode-blind CALL SITES, not separate resolvers — so `resolveAgentCap` sees no type and takes the `.default` fallback unconditionally; a difference between their answer and the wave-shape answer is expected, not a bug. Ultradeep looks up `ultradeep` → `deep` → `default`, in that order (`resolveUltradeepCap()`), so a repo that only configured `6 (deep: 18)` still gets 18 under the profile. See § Session Shapes below. |
80
80
  | `agent-mapping` | object | null | Optional mapping of role keys to agent names for explicit agent binding. Keys: `impl`, `test`, `db`, `ui`, `security`, `compliance`, `docs`, `perf`. Example: `{ impl: code-editor, test: test-specialist }`. Overrides auto-discovery when present. Values may carry a channel prefix — see § `agent-mapping` values below. |
81
- | `waves` | integer | `5` | Number of execution waves for feature and deep sessions. |
81
+ | `waves` | integer | `5` | Base wave count, read by `resolveSessionShape()` (`scripts/lib/session-shape.mjs`) as the value it reports back in `wavesConfigHonored` / `wavesConfigIgnoredValue` — it is **not** a free dial per session type. `feature` and `deep` (no profile) each have exactly one natural wave count and IGNORE a `waves` value that disagrees with it (recorded in the shape's `notes`, never used to re-combine roles — the former 3/4/6+ role-combination tables are RETIRED as of 2026-09-09). The `ultradeep` profile ignores `waves` outright regardless of its value (`wavesConfigHonored: false`) — PRD `2026-09-06-ultradeep-session-profile.md` AC-9 ("`waves < 7` is an error") was dropped in favour of this explicit ignore-and-report. See § Session Shapes below for the authoritative per-shape wave counts. |
82
82
  | `recent-commits` | integer | `20` | Number of recent commits to display during session start git analysis. |
83
83
  | `special` | string | none | Repo-specific instructions. Freeform text that the orchestrator reads and follows during sessions. |
84
84
 
85
+ ### Session Shapes
86
+
87
+ `scripts/lib/session-shape.mjs` is the one place a `/session` mode + optional `--profile` becomes an execution shape (wave count, roles, agent caps, Discovery on/off, per-wave `max-turns`), driven via the CLI wrapper `scripts/session-shape.mjs`:
88
+
89
+ ```
90
+ node scripts/session-shape.mjs --repo-root <path> --session-type <housekeeping|feature|deep> \
91
+ [--profile ultradeep] [--known-scope true|false] [--task-count <n>] [--no-event]
92
+ ```
93
+
94
+ It prints one JSON line (`resolveSessionShape()`'s return value) and, unless `--no-event`, records `orchestrator.session.shape_resolved` to `.orchestrator/metrics/events.jsonl`. Four shapes, resolved 2026-09-09 (decided by the operator, superseding the prose it replaces — see the module's own header for the full rationale):
95
+
96
+ | Shape | Waves | Roles (agent cap) | Discovery | Coordinator-direct |
97
+ |---|---|---|---|---|
98
+ | `housekeeping` | **1** | Housekeeping (0 — coordinator runs the maintenance loop directly: drift-check, sweep, evolve, reconcile, dialectic, memory-cleanup) | n/a | yes, the whole wave |
99
+ | `feature` | **3** | Impl-Core (4) → Impl-Polish+Quality (4) → Finalization (2) | no | no |
100
+ | `deep` (unknown scope) | **5** | Discovery (8) → Impl-Core (10) → Impl-Polish (8) → Quality (6) → Finalization (4) | yes | no |
101
+ | `deep` (`--known-scope true`) | **4** | Impl-Core (10) → Impl-Polish (8) → Quality (6) → Finalization (4) | no (dropped, rest renumbered) | no |
102
+ | `deep` + `--profile ultradeep` | **7**, fixed — ignores `waves` | Research+Code-Discovery (18) → **Synthesis-Gate (0, coordinator-direct, blocking AskUserQuestion)** → Impl-Core (8) → Impl-Polish (8) → Review-Panel (3, read-only) → Quality (6) → Release/Finalization (4) | yes (wave 1) | wave 2 only |
103
+
104
+ Raw caps are the `agents-per-wave` value BEFORE the session-type override resolves (`agentCapRaw` in the JSON); `agentCap` is the resolved number. `max-turns` (`auto`) expands per shape — see the `max-turns` row below. This table is descriptive of the module's committed defaults; the module itself, not this table, is the SSOT — re-run the CLI with `--no-event` to confirm before citing a number from here in an automated check.
105
+
85
106
  ### Session Profile — `session-profile` (NOT a Session Config key)
86
107
 
87
108
  `session-profile` names a WAVE-SHAPE variant on top of an unchanged `session-type`. It is listed here because it is easy to look for in the wrong place: **it is not a Session Config key and `parseSessionConfig()` does not emit one.** Writing `session-profile:` into a repo's `## Session Config` block is inert prose, exactly like `session-type:` (see the `agents-per-wave` row above).
@@ -270,15 +291,19 @@ issue-budget:
270
291
 
271
292
  | Field | Type | Default | Description |
272
293
  |-------|------|---------|-------------|
273
- | `issue-budget.max-per-session` | integer | `12` | Non-exempt issues one session may create before the cap bites. `0` is valid (blocks everything non-exempt). Malformed or negative values fall back to `12`. |
294
+ | `issue-budget.max-per-session` | integer or integer with overrides | `12` | Non-exempt issues one session may create before the cap bites. `0` is valid (blocks everything non-exempt). Malformed or negative values fall back to `12`. **Accepts the same session-type override syntax `agents-per-wave` uses**: `12 (feature: 6)` parses via `_coerceInteger` into `{default: 12, feature: 6}`. The override key set is OPEN — any session-type label the operator writes, because the vocabulary lives in session-start, not in this parser. |
274
295
  | `issue-budget.mode` | string | `strict` | `strict` blocks over-cap creations (exit 2 from the hook) and parks them as overflow; `warn` allows them with a stderr notice; `off` disables the gate entirely (no counter is written). |
275
296
  | `issue-budget.overflow` | string | `collect-issue` | Where session-end drains parked creations. `collect-issue` files exactly ONE `[Backlog-Sammel] <session-id>, N zurückgestellte Punkte` issue (`type::backlog`, `priority::low`) whose body is a checklist of the parked items; `vault-note` writes a single Markdown file under `vault/00-inbox/` instead. |
276
297
 
298
+ **Override resolution (#1163-adjacent, load-bearing split).** `_parseIssueBudget()` (`scripts/lib/config/issue-budget.mjs`) returns TWO keys: `"max-per-session"` (always a plain number — the resolved `.default`, so the three existing consumers that treat it as a number never see `[object Object]`) and `"max-per-session-raw"` (the full parsed value, number or override object). `loadIssueBudgetConfig()` (`scripts/lib/issue-budget.mjs`) resolves the effective cap for the CURRENT session by reading `session-type:` off the active STATE.md frontmatter (`readSessionTypeFromStateMd()`, never throws — a missing/unparseable STATE.md yields `null`, read as "use the default") and calling `resolveMaxPerSession(cfg, sessionType)`, whose precedence is `raw[sessionType] ?? raw.default ?? cfg['max-per-session']`. The resolved session type is also returned as `"session-type-resolved"` (`null` when STATE.md carries none) so a caller can tell the resolution actually happened rather than fallen through.
299
+
277
300
  **Exemptions (load-bearing).** `priority::critical`, the carryover class (`[Carryover]`, `[SPIRAL]`/`[FAILED]`, `type::carryover`, a bare `carryover` label) and `broken-window` closure issues bypass the cap unconditionally. Without those exemptions the cap would break the standing session-end promises in `skills/session-end/SKILL.md` (Phase 1.8 "non-deselectable" SPIRAL/FAILED carryover, and the Critical Rule "ALWAYS create issues for unfinished PLANNED work"). Exempt creations are counted in the state file's `exempt` field for observability but never blocked.
278
301
 
279
302
  **Counter file:** `.orchestrator/runtime/issue-budget/<sha256(sessionId)[0..16]>.json` — `{ sessionId, count, exempt, overflow: [...] }`, ONE file per session (#1141: the former single `issue-budget.json` slot was reset by whichever session wrote last, so two sessions in one working copy silently disabled each other's cap). Identity-less callers still use the legacy flat path; `budgetStatePath(repoRoot, sessionId)` in `scripts/lib/issue-budget.mjs` is the resolver.
280
303
 
281
- **Used by:** `hooks/pre-bash-issue-budget.mjs` (shell path, PreToolUse/Bash), `scripts/lib/spiral-carryover.mjs` `runCli()` (programmatic path), `scripts/lib/issue-budget.mjs` (shared decision core), `skills/session-end/SKILL.md` Phase 5 Step 3b (overflow drain). Parser: `scripts/lib/config/issue-budget.mjs`.
304
+ **Close-time reconcile (#1163).** The cap is enforced by a PreToolUse hook, which only sees the shell routes it pattern-matches — every unmatched creation route (a GUI-created issue, an unmatched CLI form) is a silent zero: no ledger line, no error, indistinguishable from "created nothing". `scripts/lib/issue-budget-reconcile.mjs` closes that gap at session-end by comparing two independently-produced numbers: `recorded` (`record.issues_created.length` from the session record) against `charged + exempt` (summed over BOTH the semantic-id and raw-id ledger keys — which key a given session's counter file used depends on a condition, `resolveIssueBudgetSessionId`, that can change mid-session). It emits `orchestrator.issue_budget.reconciled` with a `verdict`: `match` (recorded charged + exempt), `escaped` (`recorded > charged + exempt` — some creations bypassed the hook), `no-ledger` (`recorded > 0` and no counter file existed under either key — the hook never ran once), or `stale-record` (a ledger exists but the record shows 0 recorded with a positive charge). Fail-open by contract: nothing in this module throws, and a close-time cross-check that could abort the close would be strictly worse than one that reports `no-ledger`.
305
+
306
+ **Used by:** `hooks/pre-bash-issue-budget.mjs` (shell path, PreToolUse/Bash, now charges **per statement** rather than per whole command — see `hooks/_lib/vcs-create-matcher.mjs`, which also matches `gh|glab api … POST …/issues`), `scripts/lib/spiral-carryover.mjs` `runCli()` (programmatic path), `scripts/lib/issue-budget.mjs` (shared decision core + `resolveMaxPerSession`/`readSessionTypeFromStateMd`), `scripts/lib/issue-budget-reconcile.mjs` (close-time cross-check), `skills/session-end/SKILL.md` Phase 5 Step 3b (overflow drain). Parser: `scripts/lib/config/issue-budget.mjs`.
282
307
 
283
308
  ## Slopcheck (Package Legitimacy Gate) (#520)
284
309
 
@@ -306,6 +331,8 @@ slopcheck:
306
331
  | `persistence` | boolean | `true` | Enable session resumption via STATE.md and session memory files. |
307
332
  | `memory-cleanup-threshold` | integer | `5` | Recommend `/memory-cleanup` after N accumulated session memory files. |
308
333
  | `memory-cleanup-soft-limit` | integer | `180` | Hard ceiling on accumulated memory files before the cleanup nudge escalates from a soft suggestion to a strong recommendation. PRD F2.2 / issue #502. Used by `scripts/lib/auto-dream.mjs`. |
334
+
335
+ **Nudge retirement (2026-09-09, #1246-adjacent).** The two session-end nudges that used to read these thresholds — Phase 3.6.5 Auto-Dream and Phase 3.6.7 Auto-Dialectic — are RETIRED as standalone close-time prompts: measured across consumer repos, the auto-dialectic nudge recorded `decided: true` while nobody ever ran it, and memory-cleanup had run in only 1 of 3 repos, because both fired at the moment the operator is closing down, not the moment they can act. `shouldDispatchAutoDream()` and `shouldDispatchAutoDialectic()` are unchanged as pure signal functions and still consult these two keys — but they are now called from the session-START `maintenance-due` probe (`scripts/lib/maintenance-due-banner.mjs`, see § Reconcile below), which reads them as two of its six signals and reports a single `⚠ maintenance due: N of 6 …` banner at the one moment the operator can actually run `/session housekeeping`.
309
336
  | `learning-expiry-days` | integer | `30` | Legacy/default expiry window used by review/extend flows. New analyzer learnings preserve a candidate-supplied `expires_at` or derive expiry from `LEARNING_TTL_DAYS[type]` (for example, `autonomy-verdict` is 90 days). |
310
337
  | `learnings-surface-top-n` | integer | `15` | Cap on how many learnings the session-start Phase 5.6 and session-plan Step 0.5 sections surface, ranked by confidence descending. `0` = do not surface any learnings. Applies to Project Intelligence output. |
311
338
  | `learning-decay-rate` | float (0.0 ≤ x < 1.0) | `0.05` | Confidence decay applied to every untouched learning at session-end (after touched-set update, before prune). `0.0` = disable decay. A learning starting at `0.5` confidence survives ~10 untouched sessions with default decay. |
@@ -315,8 +342,8 @@ slopcheck:
315
342
  | `reasoning-output` | boolean | `false` | Enable STATE:/PLAN: structured reasoning markers in agent prompts. When true, agents emit short transparency lines before tool calls. Opt-in — adds prompt overhead. |
316
343
  | `grounding-check` | boolean | `true` | Enable file-level grounding verification in session-end Phase 1.1a (planned vs touched files). When true, session-end compares each agent's declared file scope against `git diff --name-only $SESSION_START_REF..HEAD` and reports scope creep + incomplete coverage. Informational — does not block session close. |
317
344
  | `grounding-injection-max-files` | integer | `3` | Max files with recent `edit-format-friction` stagnation history to inject as line-numbered GROUNDING blocks into each agent's prompt before dispatch (wave-executor pre-dispatch step). Per-agent scope; selects top N by recency. `0` disables the feature. Gated on `persistence: true`. (#85) |
318
- | `isolation` | string | `auto` | Agent isolation mode: `worktree`, `none`, or `auto`. `auto` resolves per-wave via the graduated default (#194): ≤2 agents → `none`, 3–4 agents on feature/deep → `worktree`, ≥5 agents → `worktree`, housekeeping 3–4 → `none`. Explicit `worktree` or `none` overrides the graduation. See [isolation graduation](#isolation-graduation) below. |
319
- | `max-turns` | integer or string | `auto` | Maximum agent turns before PARTIAL. Auto: housekeeping=8, feature=15, deep=25. |
345
+ | `isolation` | string | `auto` | Agent isolation mode: `worktree`, `none`, or `auto`. `auto` resolves per-wave via the graduated default (#194): ≤2 agents → `none`, 3–4 agents on feature/deep → `worktree`, ≥5 agents → `worktree`, housekeeping 3–4 → `none`. Explicit `worktree` or `none` overrides the graduation. The resolved value surfaces per wave as `waves[].isolation` in the session shape's JSON output (`scripts/session-shape.mjs`) — a coordinator-direct or read-only wave resolves `none` without consulting the graduation at all. See [isolation graduation](#isolation-graduation) below. |
346
+ | `max-turns` | integer or string | `auto` | Maximum agent turns before PARTIAL. Auto expands PER SESSION TYPE inside the resolved shape (`scripts/lib/session-shape.mjs` `MAX_TURNS_DEFAULT`, § Session Shapes above): housekeeping=8, feature=15, deep=25 (`maxTurnsDefault` in the shape JSON, applied to every wave). The `ultradeep` profile does **not** use one flat number — it sets `max-turns` PER WAVE: 40 for the Research+Code-Discovery wave, 25 for Impl-Core/Impl-Polish/Quality, 15 for Release/Finalization (the Synthesis-Gate wave is coordinator-direct and carries no `max-turns` at all). |
320
347
  | `auto-commit-per-wave` | boolean | `false` | Automatically commit each wave's work after the Quality-Lite gate passes. Checkpoint commits per wave reduce the risk of data loss from `git stash` collisions in parallel sessions (V3.3 RESCUE incident — see GitLab #214). When `false`, all work is committed at session-end via `/close`. Requires `persistence: true`; the flag is silently ignored when `persistence: false`. Trade-off: each wave produces an additional commit; git log shows N+1 commits instead of 1. Use `/simplify` or `git rebase -i --autosquash` before final close to squash if a clean history is desired. **Implementation note:** the procedural commit sequence (`scripts/lib/auto-commit.mjs`) is deferred to V3.6. Until then, setting this flag to `true` triggers a session-start warning that auto-commits are not yet active — the flag is a no-op but is validated so projects can opt in early. <!-- path-check: historical --> |
321
348
 
322
349
  ### enforcement-gates: the five gate keys (#800/#915)
@@ -588,7 +615,7 @@ This guard converts the manual post-copy `git diff` check (used to rescue the 07
588
615
 
589
616
  ### Heavy-Repo Preflight (HR-003/HR-004, baseline #60)
590
617
 
591
- `templates/shared/.claude/rules/heavy-repo.md` documents two Session Config fields for repos large enough that default parallelism risks resource pressure (HR-001 indicators: checkout > 50 MB, DB surface > 100 tables, prior parallel agent count > 15, build time > 90s, generated artifacts > 200 MB). Both fields are now wired end-to-end (previously documented but silently dropped by the parser):
618
+ The two Session Config fields below apply to repos large enough that default parallelism risks resource pressure (HR-001 indicators: checkout > 50 MB, DB surface > 100 tables, prior parallel agent count > 15, build time > 90s, generated artifacts > 200 MB). The parser accepts both fields; their runtime effects and remaining limits are listed below:
592
619
 
593
620
  | Field | Type | Default | Description |
594
621
  |-------|------|---------|-------------|
@@ -636,7 +663,7 @@ Requires:
636
663
 
637
664
  If the fetch fails (network error, auth error, missing file), bootstrap continues without aborting — rules will arrive via the legacy Clank sync path. A warning is printed.
638
665
 
639
- See: session-orchestrator issue #110, projects-baseline `docs/REPO-STATUS.md`.
666
+ See: session-orchestrator issue #110, projects-baseline `docs/REPO-STATUS.md`. <!-- path-check: example -->
640
667
 
641
668
  ### baseline-project-id
642
669
 
@@ -740,7 +767,7 @@ vault-integration:
740
767
 
741
768
  > **Host-local override (#653; extended #819).** `vault-dir` resolves host-locally with precedence: env-var (`SO_VAULT_DIR`) > `owner.yaml` `paths.vault-dir` > the committed default. `plan-baseline-path` resolves with an extra per-context tier in between: `SO_BASELINE_PATH` env > `owner.yaml` `baselines:` directory-prefix match against cwd > `owner.yaml` `paths.baseline-path` (legacy scalar) > the committed default. This keeps maintainer-specific absolute paths out of version control. Resolvers: `scripts/lib/config/host-paths.mjs` (both keys) and `scripts/lib/named-baseline-resolver.mjs` (the `baselines:` match tier).
742
769
 
743
- > **`SO_CONFIG_HOME` — the host-private config directory itself.** A sibling override, one layer below `owner.yaml`'s own contents rather than a key inside it: `scripts/lib/host-identity.mjs` `_privateDir()` resolves the directory holding `owner.yaml`, `host-private.json`, and the host-alias ledger (`SO_HOST_ALIASES_FILE`, see `host-identity.mjs`) with precedence env-var (`SO_CONFIG_HOME`, names the private dir ITSELF) > `XDG_CONFIG_HOME` (names its PARENT — `owner-config-loader.mjs` uses the same variable the same way) > the homedir default `~/.config/session-orchestrator`. Both env vars are read with `.trim() || fallback`, not a bare `||` (`.claude/rules/development.md` § Error Handling env-var-fallback-whitespace trap).
770
+ > **`SO_CONFIG_HOME` — the host-private config directory itself.** A sibling override, one layer below `owner.yaml`'s own contents rather than a key inside it: `scripts/lib/config/private-config-dir.mjs` (`resolvePrivateConfigDir()`) is THE resolver (#1223) for the directory holding `owner.yaml`, `host-private.json`, and the host-alias ledger (`SO_HOST_ALIASES_FILE`) `scripts/lib/host-identity.mjs`'s `_privateDir()` delegates to it — with precedence env-var (`SO_CONFIG_HOME`, names the private dir ITSELF) > `XDG_CONFIG_HOME` (names its PARENT) > the homedir default `~/.config/session-orchestrator`. Both env vars are read with `.trim() || fallback`, not a bare `||` (`.claude/rules/development.md` § Error Handling env-var-fallback-whitespace trap).
744
771
 
745
772
  > **Parser accepts three key-line renderings (#823).** The `vault-integration:` key line is recognized in plain form (`vault-integration:`), dash-bullet form (`- vault-integration:`), and bold-bullet form (`- **vault-integration:**`) — each paired with either the inline-object shape (`{ enabled: true, ... }` on the same line) or the indented block shape shown above. Parser: `scripts/lib/config/vault-integration.mjs` (`_parseVaultIntegration`).
746
773
 
@@ -968,7 +995,7 @@ broken-window-budget:
968
995
 
969
996
  ## Dialectic-Deriver (#506)
970
997
 
971
- Opt-in mode for `/evolve --dialectic` and session-end Phase 3.6.7 auto-trigger. When `cadence > 0`, session-end auto-dispatches `/evolve --dialectic --dry-run` after every N sessions to produce a proposed update to USER.md/AGENT.md peer cards (#503). The dry-run writes a sidecar at `.orchestrator/dialectic-pending.md`; the operator applies via `/evolve --dialectic --apply` in a subsequent session. Set `cadence: 0` as a kill-switch.
998
+ Opt-in mode for `/evolve --dialectic`. When `cadence > 0`, the session-START `maintenance-due` probe reports a due dialectic after every N sessions, and the housekeeping loop runs `/evolve --dialectic --dry-run` to produce a proposed update to USER.md/AGENT.md peer cards (#503). The dry-run writes a sidecar at `.orchestrator/dialectic-pending.md`; the operator applies via `/evolve --dialectic --apply` in a subsequent session. Set `cadence: 0` as a kill-switch. <!-- path-check: example -->
972
999
 
973
1000
  All fields live under a top-level `dialectic` object in your Session Config (CLAUDE.md or AGENTS.md):
974
1001
 
@@ -981,15 +1008,15 @@ dialectic:
981
1008
 
982
1009
  | Field | Type | Default | Description |
983
1010
  |-------|------|---------|-------------|
984
- | `dialectic.cadence` | integer | `5` | Number of sessions between auto-dialectic dispatches. Set to `0` to disable all dispatches (kill-switch). Non-integer and negative values silently fall back to default. |
1011
+ | `dialectic.cadence` | integer | `5` | Number of sessions between auto-dialectic dispatches. Set to `0` to disable all dispatches (kill-switch). Non-integer and negative values silently fall back to default. **The dispatch moment moved (2026-09-09):** `shouldDispatchAutoDialectic()` is still the decision function this key feeds, but session-end Phase 3.6.7's own auto-trigger nudge is gone — retired as a standalone close-time prompt, and its recording wrapper (with the `orchestrator.dialectic.nudge_decided` event) removed in #1288 — see § Persistence & Safety above "Nudge retirement". The session-start `maintenance-due` probe now calls it as one of six signals, so a due dialectic surfaces where the operator can act on it (session start), not where they are closing down. |
985
1012
  | `dialectic.model` | string | `haiku` | Model tier for the critique call. Must be one of `haiku`, `sonnet`, `opus`. **Fail-fast**: unknown values cause parse-config.mjs to exit 1 at startup — NOT silently ignored. |
986
1013
  | `dialectic.budget-tokens` | integer | `8000` | Input token budget per call. Output budget is fixed at 4000 (per #506). Non-integer and negative values fall back to default. |
987
1014
 
988
- **Used by:** `skills/evolve/SKILL.md` Phase 6, `skills/session-end/SKILL.md` Phase 3.6.7, `scripts/dialectic-deriver.mjs`, `scripts/lib/auto-dialectic.mjs`.
1015
+ **Used by:** `skills/evolve/SKILL.md` Phase 6, `scripts/dialectic-deriver.mjs`, `scripts/lib/auto-dialectic.mjs`, and the session-start `maintenance-due` probe (`scripts/lib/maintenance-due-banner.mjs`). Session-end Phase 3.6.7 no longer reads it (retired 2026-09-09).
989
1016
 
990
1017
  **Cross-reference:** PRD F2.5 (#506) — Honcho's "reasoning at consolidation time" insight, adopted without SaaS/AGPL/per-message-LLM-cost.
991
1018
 
992
- **Auto-trigger behavior:** When `cadence > 0` AND sessions-since-last-dialectic ≥ cadence AND (≥1 new session OR ≥1 new learning since last run), session-end Phase 3.6.7 dispatches the deriver in dry-run mode. The diff sidecar lands at `.orchestrator/dialectic-pending.md` (gitignored, vault-mirror-excluded). When `cadence: 0`, the auto-trigger is permanently skipped; manual `/evolve --dialectic` always works.
1019
+ **Trigger behavior:** When `cadence > 0` AND sessions-since-last-dialectic ≥ cadence AND (≥1 new session OR ≥1 new learning since last run), `shouldDispatchAutoDialectic()` returns `trigger: true` and the session-start `maintenance-due` probe reports it; the housekeeping session then runs the deriver in dry-run mode (session-end Phase 3.6.7 is retired — it dispatches nothing). The diff sidecar lands at `.orchestrator/dialectic-pending.md` (gitignored, vault-mirror-excluded). When `cadence: 0`, the signal never fires; manual `/evolve --dialectic` always works. <!-- path-check: example -->
993
1020
 
994
1021
  **Token cost:** With defaults (cadence: 5, budget-tokens: 8000, output 4000, model haiku), ~12k tokens every 5 sessions. At haiku pricing this is ~$0.02/run. Surfaced in Final Report.
995
1022
 
@@ -997,7 +1024,9 @@ dialectic:
997
1024
 
998
1025
  ## Eval (#803)
999
1026
 
1000
- Opt-in configuration for the Standard v1 evaluation harness (aiat-llm-eval PRD, `docs/prd/2026-07-16-aiat-llm-eval.md` §S6) and the forthcoming `/eval` skill (Session-Prozess-Eval — lands in a later wave of Epic #803). This section documents the config surface only; the skill that reads it is not yet shipped as of this parser's introduction.
1027
+ Opt-in configuration for the Standard v1 evaluation harness and the `/eval` skill (Session-Prozess-Eval). The current workflow is documented in `skills/eval/SKILL.md`.
1028
+
1029
+ The original config proposal is in the archived PRD `docs/prd/2026-07-16-aiat-llm-eval.md` §S6 (Epic #803). <!-- path-check: historical -->
1001
1030
 
1002
1031
  All fields live under a top-level `eval` object in your Session Config host file (`CLAUDE.md` or `AGENTS.md`), for example:
1003
1032
 
@@ -1314,6 +1343,8 @@ Read by: `scripts/lib/config/evolve.mjs` (parser), `skills/evolve/SKILL.md` Step
1314
1343
 
1315
1344
  Opt-in configuration for the learning→conditional-rule reconciliation engine (Epic #693). When enabled, the reconciliation engine runs at session-end Phase 3.6.8 and proposes new `.claude/rules/` entries derived from accumulated learnings. The proposal is always operator-AUQ-gated — rules are **never** auto-applied. FA3 (#696) delivers proposals via `AskUserQuestion`; FA4 (#697) adds the guardrail config block documented here. When `enabled: false` (the default), Phase 3.6.8 is a silent no-op and the engine never runs.
1316
1345
 
1346
+ **The former `reconcile-nudge` session-start probe is SUBSUMED (2026-09-09), not replaced 1:1.** `reconcile-nudge-banner.mjs`'s `computeReconcileNudge()` function is unchanged and still the reconcile-specific signal — but it is no longer surfaced as its own standalone banner. `scripts/lib/maintenance-due-banner.mjs` now calls it wholesale as one of six ANDed maintenance signals (`reconcile: computeReconcileNudge().nudge === true`) behind the single session-start `maintenance-due` probe, alongside `evolve`, `sweep`, `dialectic`, `memory-cleanup` and `pending-sidecar`. The reconcile signal is therefore not lost — it is reported as `⚠ maintenance due: N of 6 (…) — run /session housekeeping.` rather than as its own line, with a 7-day cooldown after any housekeeping session (`HOUSEKEEPING_COOLDOWN_DAYS`) so a repo that just ran the loop stays silent instead of re-nagging.
1347
+
1317
1348
  All fields live under a top-level `reconcile` object in your Session Config host file (`CLAUDE.md` or `AGENTS.md`), for example:
1318
1349
 
1319
1350
  ```yaml
@@ -1447,7 +1478,7 @@ frontend-slop-hook:
1447
1478
 
1448
1479
  **Used by:** `hooks/post-tooluse-frontend-slop.mjs` (parser/loader: `scripts/lib/config/frontend-slop-hook.mjs`).
1449
1480
 
1450
- **Cross-reference:** detector rule markers (`<!-- rule:<id> -->`) live in `.claude/rules/frontend.md` (Absolute Bans / Motion / Layout sections). Mirrors the opt-in / default-on contrast against `loop-guard`.
1481
+ **Cross-reference:** detector rule markers (`<!-- rule:<id> -->`) live in `rules/opt-in-stack/frontend.md` (Absolute Bans / Motion / Layout sections). Mirrors the opt-in / default-on contrast against `loop-guard`.
1451
1482
 
1452
1483
  ## Loop Guard (#619)
1453
1484
 
@@ -1593,7 +1624,9 @@ Both variables must be set for the fire-and-forget POST to fire. Setting only `C
1593
1624
 
1594
1625
  ## Express Path (#214)
1595
1626
 
1596
- Codified coordinator-direct flow for housekeeping and simple single-issue sessions. When the express path activates, session-start Phase 8.5 skips the full 5-wave plan decomposition and runs all tasks directly as the coordinator — no subagents dispatched, no inter-wave checkpoints.
1627
+ Codified coordinator-direct flow for housekeeping and simple single-issue sessions. When the express path activates, session-start Phase 8.5 skips session-plan's full wave decomposition and runs all tasks directly as the coordinator — no subagents dispatched, no inter-wave checkpoints.
1628
+
1629
+ **Relationship to § Session Shapes (2026-09-09).** The 1-wave `coordinator-direct` plan session-plan emits when the express path is active is the SAME shape `resolveSessionShape({sessionType: 'housekeeping'})` now resolves for EVERY housekeeping session, express-path gate or not — a single coordinator-direct "Housekeeping" wave running the maintenance loop (drift-check, sweep, evolve, reconcile, dialectic, memory-cleanup). Express path is the ACTIVATION GATE that decides whether the coordinator runs those tasks inline right now (its 3 conditions below); the housekeeping shape decides the wave STRUCTURE, and is unconditional for the type since the former 3/4/6+-wave role-combination mapping was retired 2026-09-09 for every session type. Housekeeping therefore has no "full 5-wave flow" left to fall back to — see the corrected condition-matrix note below.
1597
1630
 
1598
1631
  > **Historical context:** The 13 coordinator-direct sessions documented in the project `CLAUDE.md` (2026-04 series: vault-mirror GH#31, phased-rollout #307, v3.2.0 release, Architecture-DDD-Trio, etc.) were running this pattern implicitly without a codified path. Issue #214 codifies it so that future housekeeping sessions gain the express path automatically without needing to know to opt in manually.
1599
1632
 
@@ -1601,12 +1634,12 @@ All fields live under a top-level `express-path` object in your Session Config h
1601
1634
 
1602
1635
  ```yaml
1603
1636
  express-path:
1604
- enabled: true # default true; set false to always use the full 5-wave flow
1637
+ enabled: true # default true; set false to always run session-plan's full decomposition flow
1605
1638
  ```
1606
1639
 
1607
1640
  | Field | Type | Default | Description |
1608
1641
  |-------|------|---------|-------------|
1609
- | `express-path.enabled` | boolean | `true` | When `true`, session-start Phase 8.5 evaluates the express-path activation conditions. When `false`, the evaluation is skipped and the full session-plan 5-wave flow always runs. |
1642
+ | `express-path.enabled` | boolean | `true` | When `true`, session-start Phase 8.5 evaluates the express-path activation conditions. When `false`, the evaluation is skipped and session-plan always runs its full decomposition flow — for a housekeeping session that flow still resolves to the same 1-wave shape Session Shapes), since `express-path.enabled` only gates whether the coordinator runs it INLINE right now, not the wave count. |
1610
1643
 
1611
1644
  ### Activation conditions
1612
1645
 
@@ -1616,7 +1649,7 @@ All three conditions must be true simultaneously for the express path to activat
1616
1649
  2. Session type is `housekeeping` (confirmed in session-start Phase 8 Q&A)
1617
1650
  3. Agreed issue scope is ≤ 3 issues AND no parallel agents are required
1618
1651
 
1619
- When any condition is false, the full 5-wave flow runs as before — the check is a transparent no-op.
1652
+ **When any condition is false, session-plan runs its full decomposition flow as before** — the express-path check is a transparent no-op on the PLAN, not on the wave shape. For `feature` and `deep` sessions this was always their own resolved shape (3 and 5 waves respectively, per § Session Shapes) and is unaffected by this gate. For a `housekeeping` session that fails the gate (≥4 issues, or parallel agents required), the older documentation here described a fallback to "the full 5-wave flow" — that fallback no longer exists: `resolveSessionShape({sessionType: 'housekeeping'})` returns the same 1-wave coordinator-direct maintenance-loop shape unconditionally, so the practical difference the gate still makes is whether the coordinator executes that one wave INLINE (gate passed) or session-plan emits it as a normal — still 1-wave, still coordinator-direct — plan for wave-executor to run (gate failed).
1620
1653
 
1621
1654
  ### What changes when express path is active
1622
1655
 
@@ -1629,9 +1662,9 @@ When any condition is false, the full 5-wave flow runs as before — the check i
1629
1662
 
1630
1663
  Set `express-path.enabled: false` when:
1631
1664
 
1632
- - You want all housekeeping sessions to go through the standard quality-gate pipeline (Discovery + Quality waves).
1665
+ - You want session-plan to emit housekeeping's maintenance-loop wave as a normal wave-executor plan instead of running it inline as the coordinator the wave STRUCTURE is unchanged either way (§ Session Shapes), only whether it runs inline or through the wave-executor dispatch/checkpoint machinery.
1633
1666
  - The session involves ≥ 4 issues (the scope check already prevents activation, but disabling makes the intent explicit).
1634
- - You are running an automated `/autopilot` loop and want predictable wave counts across session types.
1667
+ - You are running an automated `/autopilot` loop and want a predictable inline-vs-dispatched execution path across session types.
1635
1668
 
1636
1669
  ### Condition matrix
1637
1670
 
@@ -1653,7 +1686,7 @@ Set `express-path.enabled: false` when:
1653
1686
 
1654
1687
  The `autopilot` block and its single field `autopilot.bg-isolation` are **gone**, not
1655
1688
  deprecated. Their only reader was `scripts/autopilot-multi.mjs`, retired together with <!-- path-check: historical -->
1656
- `commands/autopilot-multi.md` by the 2026-09-06 360°-Audit (§ 5A: 0 telemetry, 0 fleet
1689
+ `commands/autopilot-multi.md` by the 2026-09-06 360°-Audit (§ 5A: 0 telemetry, 0 fleet <!-- path-check: historical -->
1657
1690
  invocations in 90 days, no runtime consumer).
1658
1691
 
1659
1692
  Verified 2026-09-06 at `e4674109`: