session-orchestrator 3.22.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.
Files changed (268) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/commands/autopilot-multi.md +14 -0
  5. package/.cursor/commands/autopilot.md +14 -0
  6. package/.cursor/commands/bootstrap.md +14 -0
  7. package/.cursor/commands/brainstorm.md +14 -0
  8. package/.cursor/commands/close.md +13 -0
  9. package/.cursor/commands/contract-version-bump.md +14 -0
  10. package/.cursor/commands/debug.md +14 -0
  11. package/.cursor/commands/discovery.md +14 -0
  12. package/.cursor/commands/dispatcher.md +14 -0
  13. package/.cursor/commands/eli5.md +14 -0
  14. package/.cursor/commands/eval.md +14 -0
  15. package/.cursor/commands/evolve.md +14 -0
  16. package/.cursor/commands/go.md +14 -0
  17. package/.cursor/commands/grill.md +14 -0
  18. package/.cursor/commands/harness-audit.md +13 -0
  19. package/.cursor/commands/journey-audit.md +14 -0
  20. package/.cursor/commands/memory-cleanup.md +14 -0
  21. package/.cursor/commands/persona-panel.md +14 -0
  22. package/.cursor/commands/plan.md +14 -0
  23. package/.cursor/commands/portfolio.md +14 -0
  24. package/.cursor/commands/reconcile.md +14 -0
  25. package/.cursor/commands/release.md +14 -0
  26. package/.cursor/commands/repo-audit.md +13 -0
  27. package/.cursor/commands/session.md +14 -0
  28. package/.cursor/commands/spinout.md +14 -0
  29. package/.cursor/commands/sunset-review.md +14 -0
  30. package/.cursor/commands/templates-ack.md +14 -0
  31. package/.cursor/commands/test.md +14 -0
  32. package/.cursor/hooks.json +60 -0
  33. package/.cursor/rules/000-session-orchestrator.mdc +8 -0
  34. package/.cursor/rules/010-session-workflow.mdc +9 -1
  35. package/.cursor/rules/020-quality-gates.mdc +1 -1
  36. package/.cursor/rules/030-wave-execution.mdc +1 -1
  37. package/.cursor/rules/050-plan.mdc +2 -2
  38. package/.cursor/rules/070-gitlab-ops.mdc +73 -57
  39. package/.cursor/rules/080-ecosystem-health.mdc +7 -7
  40. package/.cursor/skills/architecture/SKILL.md +13 -0
  41. package/.cursor/skills/autopilot/SKILL.md +12 -0
  42. package/.cursor/skills/bootstrap/SKILL.md +12 -0
  43. package/.cursor/skills/brainstorm/SKILL.md +13 -0
  44. package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
  45. package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
  46. package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
  47. package/.cursor/skills/daily/SKILL.md +12 -0
  48. package/.cursor/skills/debug/SKILL.md +13 -0
  49. package/.cursor/skills/discovery/SKILL.md +13 -0
  50. package/.cursor/skills/dispatcher/SKILL.md +13 -0
  51. package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
  52. package/.cursor/skills/domain-model/SKILL.md +13 -0
  53. package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
  54. package/.cursor/skills/eli5/SKILL.md +13 -0
  55. package/.cursor/skills/eval/SKILL.md +12 -0
  56. package/.cursor/skills/evolve/SKILL.md +13 -0
  57. package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
  58. package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
  59. package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
  60. package/.cursor/skills/grill/SKILL.md +13 -0
  61. package/.cursor/skills/hook-development/SKILL.md +13 -0
  62. package/.cursor/skills/journey-audit/SKILL.md +13 -0
  63. package/.cursor/skills/mcp-builder/SKILL.md +13 -0
  64. package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
  65. package/.cursor/skills/mode-selector/SKILL.md +13 -0
  66. package/.cursor/skills/npm-publish/SKILL.md +12 -0
  67. package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
  68. package/.cursor/skills/persona-panel/SKILL.md +12 -0
  69. package/.cursor/skills/plan/SKILL.md +13 -0
  70. package/.cursor/skills/playwright-driver/SKILL.md +13 -0
  71. package/.cursor/skills/quality-gates/SKILL.md +13 -0
  72. package/.cursor/skills/reconcile/SKILL.md +12 -0
  73. package/.cursor/skills/repo-audit/SKILL.md +13 -0
  74. package/.cursor/skills/session-end/SKILL.md +13 -0
  75. package/.cursor/skills/session-plan/SKILL.md +13 -0
  76. package/.cursor/skills/session-start/SKILL.md +13 -0
  77. package/.cursor/skills/skill-creator/SKILL.md +13 -0
  78. package/.cursor/skills/spinout/SKILL.md +12 -0
  79. package/.cursor/skills/sunset-review/SKILL.md +13 -0
  80. package/.cursor/skills/test-runner/SKILL.md +13 -0
  81. package/.cursor/skills/tmux-layout/SKILL.md +13 -0
  82. package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
  83. package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
  84. package/.cursor/skills/vault-mirror/SKILL.md +13 -0
  85. package/.cursor/skills/vault-sync/SKILL.md +13 -0
  86. package/.cursor/skills/wave-executor/SKILL.md +13 -0
  87. package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
  88. package/.mcp.json +4 -1
  89. package/CHANGELOG.md +168 -0
  90. package/README.md +18 -15
  91. package/agents/AGENTS.md +23 -4
  92. package/agents/code-implementer.md +2 -1
  93. package/agents/db-specialist.md +2 -1
  94. package/agents/docs-writer.md +3 -1
  95. package/agents/eval-judge.md +1 -1
  96. package/agents/session-reviewer.md +7 -1
  97. package/agents/test-writer.md +2 -1
  98. package/agents/ui-developer.md +2 -1
  99. package/commands/bootstrap.md +2 -2
  100. package/commands/close.md +3 -1
  101. package/commands/go.md +1 -1
  102. package/commands/journey-audit.md +43 -0
  103. package/docs/USER-GUIDE.md +2 -2
  104. package/docs/ci-setup.md +14 -0
  105. package/docs/codex-setup.md +64 -0
  106. package/docs/components.md +6 -6
  107. package/docs/cursor-setup.md +26 -47
  108. package/docs/events-schema.md +76 -4
  109. package/docs/github-mirror-protection.md +197 -0
  110. package/docs/pi-setup.md +2 -0
  111. package/docs/rule-authoring.md +3 -1
  112. package/docs/scope-collision-guard.md +49 -2
  113. package/docs/session-config-reference.md +26 -4
  114. package/docs/session-config-template.md +4 -3
  115. package/docs/telemetry.md +22 -0
  116. package/hooks/_lib/lock-bootstrap.mjs +8 -4
  117. package/hooks/_lib/vcs-create-matcher.mjs +397 -38
  118. package/hooks/enforce-scope.mjs +64 -0
  119. package/hooks/hooks-codex.json +1 -1
  120. package/hooks/hooks-cursor.json +201 -20
  121. package/hooks/hooks-pi.json +1 -1
  122. package/hooks/hooks.json +2 -2
  123. package/hooks/on-session-end.mjs +211 -10
  124. package/hooks/on-session-start.mjs +214 -11
  125. package/hooks/on-stop.mjs +48 -9
  126. package/hooks/post-subagent-discovery-validator.mjs +34 -3
  127. package/hooks/post-tool-batch-wave-signal.mjs +11 -2
  128. package/hooks/pre-bash-issue-budget.mjs +117 -4
  129. package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
  130. package/hooks/pre-bash-staging-fence.mjs +4 -0
  131. package/hooks/pre-task-scope-disjoint.mjs +368 -35
  132. package/hooks/skill-invocation-telemetry.mjs +21 -10
  133. package/monitors/monitors.json +6 -0
  134. package/package.json +1 -1
  135. package/pi/prompts/journey-audit.md +12 -0
  136. package/rules/_index.md +9 -1
  137. package/rules/always-on/ask-via-tool.md +62 -0
  138. package/rules/always-on/bash-harness-pitfalls.md +168 -0
  139. package/rules/always-on/build-value.md +47 -0
  140. package/rules/always-on/cross-session-messaging.md +59 -0
  141. package/rules/always-on/loop-and-monitor.md +221 -0
  142. package/rules/always-on/parallel-sessions.md +142 -12
  143. package/rules/always-on/receiving-review.md +108 -0
  144. package/rules/always-on/test-value.md +40 -0
  145. package/rules/always-on/verification-before-completion.md +77 -0
  146. package/scripts/archive-closed-prds.mjs +258 -18
  147. package/scripts/autopilot.mjs +5 -0
  148. package/scripts/backfill-evidence-digest.mjs +376 -0
  149. package/scripts/cursor-install.mjs +89 -48
  150. package/scripts/export-hw-learnings.mjs +143 -2
  151. package/scripts/express-path.mjs +299 -0
  152. package/scripts/generate-cursor-adapter.mjs +253 -0
  153. package/scripts/github-protection-audit.mjs +358 -0
  154. package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
  155. package/scripts/lib/build-live-signals.mjs +24 -5
  156. package/scripts/lib/ci-status-banner.mjs +158 -11
  157. package/scripts/lib/command-blocker.mjs +70 -0
  158. package/scripts/lib/config/reconcile.mjs +79 -4
  159. package/scripts/lib/config/section-extractor.mjs +235 -36
  160. package/scripts/lib/config-schema.mjs +9 -1
  161. package/scripts/lib/config.mjs +57 -6
  162. package/scripts/lib/convergence-monitor.mjs +13 -2
  163. package/scripts/lib/cursor-hook-bridge.mjs +443 -0
  164. package/scripts/lib/dispatcher/cli.mjs +2 -2
  165. package/scripts/lib/express-path.mjs +327 -0
  166. package/scripts/lib/file-lock.mjs +22 -4
  167. package/scripts/lib/gates/gate-full.mjs +81 -8
  168. package/scripts/lib/gates/gate-helpers.mjs +76 -15
  169. package/scripts/lib/git-config-drift.mjs +134 -5
  170. package/scripts/lib/host-identity.mjs +247 -2
  171. package/scripts/lib/instruction-budget-guard.mjs +31 -1
  172. package/scripts/lib/issue-budget.mjs +229 -30
  173. package/scripts/lib/learnings/io.mjs +55 -10
  174. package/scripts/lib/learnings/schema.mjs +95 -28
  175. package/scripts/lib/lock-reaper.mjs +7 -1
  176. package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
  177. package/scripts/lib/locks/state-md-lock.mjs +8 -1
  178. package/scripts/lib/memory-banner.mjs +5 -2
  179. package/scripts/lib/memory-paths.mjs +15 -6
  180. package/scripts/lib/mode-selector/scoring.mjs +53 -6
  181. package/scripts/lib/platform.mjs +72 -9
  182. package/scripts/lib/plugin-root.mjs +143 -19
  183. package/scripts/lib/project-hygiene.mjs +43 -3
  184. package/scripts/lib/quality-gate.mjs +271 -13
  185. package/scripts/lib/reconcile/emitter.mjs +87 -19
  186. package/scripts/lib/reconcile/engine.mjs +281 -13
  187. package/scripts/lib/reconcile/idempotency.mjs +102 -1
  188. package/scripts/lib/reconcile/renderer.mjs +148 -3
  189. package/scripts/lib/reconcile/sanitize.mjs +40 -17
  190. package/scripts/lib/reconcile/writer.mjs +415 -84
  191. package/scripts/lib/rule-loader.mjs +37 -2
  192. package/scripts/lib/rules-sync.mjs +51 -8
  193. package/scripts/lib/scope-gate.mjs +90 -0
  194. package/scripts/lib/session-close-backfill.mjs +369 -28
  195. package/scripts/lib/session-discovery.mjs +13 -3
  196. package/scripts/lib/session-end/phase-skip.mjs +37 -4
  197. package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
  198. package/scripts/lib/session-id.mjs +30 -14
  199. package/scripts/lib/session-identity/own-session.mjs +159 -0
  200. package/scripts/lib/session-lock.mjs +85 -30
  201. package/scripts/lib/session-schema/normalizer.mjs +70 -3
  202. package/scripts/lib/session-schema/validator.mjs +40 -0
  203. package/scripts/lib/session-start-probes.mjs +608 -0
  204. package/scripts/lib/session-transition.mjs +277 -0
  205. package/scripts/lib/sessions-staleness-banner.mjs +124 -57
  206. package/scripts/lib/spiral-carryover.mjs +90 -9
  207. package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
  208. package/scripts/lib/state-md/mission-status.mjs +350 -52
  209. package/scripts/lib/state-md/yaml-parser.mjs +145 -16
  210. package/scripts/lib/state-md.mjs +12 -2
  211. package/scripts/lib/telemetry/sync.mjs +46 -8
  212. package/scripts/lib/validate/check-agents.mjs +66 -0
  213. package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
  214. package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
  215. package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
  216. package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -63
  217. package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
  218. package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
  219. package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
  220. package/scripts/lib/validate/check-unwired-features.mjs +340 -32
  221. package/scripts/lib/validate/repo-files.mjs +275 -0
  222. package/scripts/lib/validate-vendored-rules.mjs +229 -7
  223. package/scripts/lib/vault-mirror/process.mjs +99 -43
  224. package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
  225. package/scripts/lib/vault-staleness-banner.mjs +76 -6
  226. package/scripts/lib/vault-status/board-writer.mjs +211 -10
  227. package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
  228. package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
  229. package/scripts/lib/wave-transcript-tail.mjs +869 -0
  230. package/scripts/materialize-wave-scope.mjs +209 -12
  231. package/scripts/mcp-server.sh +11 -2
  232. package/scripts/parse-config.mjs +65 -0
  233. package/scripts/token-audit.sh +9 -2
  234. package/scripts/validate-plugin.mjs +3 -0
  235. package/scripts/validate-wave-scope.mjs +67 -0
  236. package/scripts/vault-mirror.mjs +203 -34
  237. package/skills/_shared/monitor-patterns.md +31 -5
  238. package/skills/_shared/parallel-aware-auq.md +1 -1
  239. package/skills/_shared/parallel-aware-preamble.md +4 -2
  240. package/skills/_shared/platform-tools.md +11 -5
  241. package/skills/_shared/state-ownership.md +29 -2
  242. package/skills/autopilot/SKILL.md +5 -1
  243. package/skills/bootstrap/SKILL.md +3 -3
  244. package/skills/bootstrap/_shared-template.md +18 -10
  245. package/skills/bootstrap/deep-template.md +10 -6
  246. package/skills/bootstrap/fast-template.md +15 -8
  247. package/skills/bootstrap/standard-template.md +10 -6
  248. package/skills/claude-md-drift-check/checker.mjs +39 -11
  249. package/skills/dispatcher/SKILL.md +1 -1
  250. package/skills/journey-audit/SKILL.md +269 -0
  251. package/skills/peekaboo-driver/SKILL.md +15 -3
  252. package/skills/persona-panel/SKILL.md +1 -1
  253. package/skills/reconcile/SKILL.md +41 -1
  254. package/skills/session-end/SKILL.md +17 -4
  255. package/skills/session-end/metrics-collection.md +7 -4
  256. package/skills/session-end/phase-3-6-tail.md +11 -3
  257. package/skills/session-end/phase-3-7a-recommendations.md +16 -2
  258. package/skills/session-plan/SKILL.md +6 -1
  259. package/skills/session-plan/wave-template.md +1 -0
  260. package/skills/session-start/SKILL.md +30 -16
  261. package/skills/session-start/phase-7-5-mode-selector.md +15 -3
  262. package/skills/session-start/phase-8-5-express-path.md +77 -12
  263. package/skills/vault-sync/validator.mjs +31 -0
  264. package/skills/wave-executor/SKILL.md +4 -2
  265. package/skills/wave-executor/circuit-breaker.md +34 -9
  266. package/skills/wave-executor/wave-loop.md +102 -19
  267. package/templates/_shared/journey-manifest.md +110 -0
  268. package/templates/_shared/rules/parallel-sessions.md +0 -77
@@ -11,25 +11,34 @@
11
11
 
12
12
  ---
13
13
 
14
- ## #parallel-sessions-rule — Step 3a: Install Parallel-Sessions Rule
14
+ ## #parallel-sessions-rule — Step 3a: Install Canonical Rules
15
15
 
16
- Write the vendored rule from `$PLUGIN_ROOT/templates/_shared/rules/parallel-sessions.md` to `$REPO_ROOT/.claude/rules/parallel-sessions.md`.
16
+ Vendor the canonical always-on rules from the plugin's `rules/` library into `$REPO_ROOT/.claude/rules/`. `rules/` is the single source of truth for every distributable rule — never `cp` a rule file from anywhere else (see "Why one writer" below).
17
17
 
18
- Idempotency:
18
+ Idempotency is handled by the writer itself:
19
19
  - Missing → create
20
- - Exists and byte-identical → skip silently
21
- - Exists and differs → overwrite (vendored is canonical)
20
+ - Exists, plugin-owned (first line is the `<!-- source: session-orchestrator plugin ... -->` header) and byte-identical → skip silently
21
+ - Exists, plugin-owned and stale → overwrite (the plugin copy is canonical)
22
+ - Exists WITHOUT that header → preserved untouched (a repo-private rule the operator authored)
22
23
 
23
24
  Shell:
24
25
  ```bash
25
- mkdir -p "$REPO_ROOT/.claude/rules"
26
- cp "$PLUGIN_ROOT/templates/_shared/rules/parallel-sessions.md" "$REPO_ROOT/.claude/rules/parallel-sessions.md"
26
+ mkdir -p "$REPO_ROOT/.claude"
27
+ node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$REPO_ROOT"
27
28
  cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"
28
29
  ```
29
30
 
30
- Why: PSA-003 destructive-command safeguards require every consumer repo to carry the rule. See issue #155. The `loop.md` vendor gives bare `/loop` a repo-aware maintenance prompt (issue #633 Hebel 3).
31
+ The command prints a JSON report (`written` / `skipped` / `preserved` / `errors` / `warnings` / `sanitizer`) and exits non-zero on any error. Surface `errors[]` to the operator; a non-empty `preserved[]` is normal and means a repo-private rule was left alone.
31
32
 
32
- Note: This step runs before the baseline-fetch step (S99/D99). If that step executes and fetches a newer version of `parallel-sessions.md` from the baseline, the baseline version wins (S99 overwrites by designacceptable).
33
+ Also surface `sanitizer[]` (issue #1098) — `{file, line, kind, text}` records for citations that read fine inside the plugin repo and dangle once vendored (`repo-local-path`, `unresolvable-see-also`). The CLI additionally prints each one to stderr as `rules-sync: sanitizer <kind> <file>:<line> — <text>`. **Report it to the operator; do not act on it automatically**the sanitizer never rewrites content and never changes the exit code, because silently stripping a citation would change a rule's meaning at vendoring time. A human decides whether the citation is a leak.
34
+
35
+ Archetype-scoped entries in `rules/_index.md` resolve from `.orchestrator/bootstrap.lock`, which does not exist yet at this step — they report `archetype-unknown` and are skipped. The always-on rules (including `parallel-sessions.md`) are universal and vendor regardless. Re-run `/bootstrap --sync-rules` after the lock is written to pick up the archetype-scoped ones.
36
+
37
+ Why: PSA-003 destructive-command safeguards require every consumer repo to carry the parallel-sessions rule. See issue #155. The `loop.md` vendor gives bare `/loop` a repo-aware maintenance prompt (issue #633 Hebel 3).
38
+
39
+ Why one writer (issue #1060): a literal `cp` from a second source directory bypasses the pre-write validator AND lands a file carrying no provenance header. On the next `--sync-rules` a headerless file is classified as a repo-private override and preserved forever — so the plugin can never update it again, and whichever rival copy is smaller silently wins. `rules/` is the only source with a manifest, archetype scoping, a basename-collision guard and a pre-write validator, so it is the only sanctioned writer to `.claude/rules/`.
40
+
41
+ Note: This step runs before the baseline-fetch step (S99/D99), and S99 must NOT overwrite a rule that `rules/` owns. The baseline's copy carries no provenance header, so letting it win would permanently mark the target as a repo-private override — the exact failure described above. `.claude/rules/parallel-sessions.md` has therefore been removed from the S99 manifest. Any other basename present in BOTH `rules/_index.md` and the S99 manifest has the same defect and needs the same treatment.
33
42
 
34
43
  ---
35
44
 
@@ -133,7 +142,6 @@ if [[ -n "$BASELINE_REF" && -n "${GITLAB_TOKEN:-}" && -n "${GITLAB_HOST:-}" && -
133
142
  .claude/rules/swift.md
134
143
  .claude/rules/mvp-scope.md
135
144
  .claude/rules/cli-design.md
136
- .claude/rules/parallel-sessions.md
137
145
  .claude/rules/ai-agent.md
138
146
  .claude/rules/claude-code-usage.md
139
147
  MANIFEST
@@ -492,14 +492,18 @@ If the call fails, log the structured message above and continue. Raw API respon
492
492
 
493
493
  Canonical implementation in [`_shared-template.md#parallel-sessions-rule`](_shared-template.md).
494
494
 
495
- Write the vendored rule from `$PLUGIN_ROOT/templates/_shared/rules/parallel-sessions.md` to
496
- `$REPO_ROOT/.claude/rules/parallel-sessions.md` (idempotent: missing→create, identical→skip,
497
- differs→overwrite). The shared partial also runs
495
+ Run `node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$REPO_ROOT"` — it vendors every
496
+ rule registered in `rules/_index.md` (idempotent: missing→create, identical→skip, differs→overwrite),
497
+ and it is the only writer that applies the pre-write validator, the basename-collision guard and the
498
+ copy-on-write branch that preserves repo-private rules. The shared partial also runs
498
499
  `cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"` so bare `/loop` gets a
499
- repo-aware maintenance prompt. See shared partial for full shell command. Issues #155, #633.
500
+ repo-aware maintenance prompt. See shared partial for full shell command. Issues #155, #633, #1060.
500
501
 
501
- Note: Runs before D99. If D99 (via inherited S99) fetches a newer `parallel-sessions.md` from
502
- the baseline, the baseline version wins (acceptable S99 is canonical).
502
+ Note: Runs before D99. D99 (via inherited S99) no longer fetches `parallel-sessions.md` from the
503
+ baseline that entry was removed from the S99 manifest in #1060. The baseline copy carries no
504
+ provenance header, so letting it win would make the next `--sync-rules` classify the target as a
505
+ repo-private override and preserve it permanently, i.e. the plugin could never update that rule
506
+ again. `rules/` is the single source for it.
503
507
 
504
508
  ## Step 3b: Initialize .orchestrator/metrics/ (Deep) (#185)
505
509
 
@@ -162,23 +162,30 @@ build/
162
162
 
163
163
  Note: `.orchestrator/` is NOT gitignored — `bootstrap.lock` must be committed. Only the platform state dirs (`.claude/`, `.codex/`, `.cursor/`) are excluded.
164
164
 
165
- ## Step 3a: Install Parallel-Sessions Rule
165
+ ## Step 3a: Install Canonical Rules
166
166
 
167
- Write the vendored rule from `$PLUGIN_ROOT/templates/_shared/rules/parallel-sessions.md` to `$REPO_ROOT/.claude/rules/parallel-sessions.md`.
167
+ Vendor the canonical always-on rules from the plugin's `rules/` library into `$REPO_ROOT/.claude/rules/`. `rules/` is the single source of truth for every distributable rule — never `cp` a rule file from anywhere else.
168
168
 
169
- Idempotency:
169
+ Idempotency is handled by the writer itself:
170
170
  - Missing → create
171
- - Exists and byte-identical → skip silently
172
- - Exists and differs → overwrite (vendored is canonical)
171
+ - Exists, plugin-owned (first line is the `<!-- source: session-orchestrator plugin ... -->` header) and byte-identical → skip silently
172
+ - Exists, plugin-owned and stale → overwrite (the plugin copy is canonical)
173
+ - Exists WITHOUT that header → preserved untouched (a repo-private rule the operator authored)
173
174
 
174
175
  Shell:
175
176
  ```bash
176
- mkdir -p "$REPO_ROOT/.claude/rules"
177
- cp "$PLUGIN_ROOT/templates/_shared/rules/parallel-sessions.md" "$REPO_ROOT/.claude/rules/parallel-sessions.md"
177
+ mkdir -p "$REPO_ROOT/.claude"
178
+ node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$REPO_ROOT"
178
179
  cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"
179
180
  ```
180
181
 
181
- Why: PSA-003 destructive-command safeguards require every consumer repo to carry the rule. See issue #155. The `loop.md` vendor gives bare `/loop` a repo-aware maintenance prompt (issue #633 Hebel 3).
182
+ The command prints a JSON report (`written` / `skipped` / `preserved` / `errors` / `warnings` / `sanitizer`) and exits non-zero on any error. At fast tier `.orchestrator/bootstrap.lock` does not exist yet (Step 5 writes it), so archetype-scoped entries report `archetype-unknown` and are skipped — the always-on rules vendor regardless.
183
+
184
+ Surface `errors[]` and `sanitizer[]` to the operator. `sanitizer[]` (issue #1098) carries `{file, line, kind, text}` records for citations that read fine inside the plugin repo and dangle once vendored (`repo-local-path`, `unresolvable-see-also`); the CLI also prints each to stderr as `rules-sync: sanitizer <kind> <file>:<line> — <text>`. **Report it, do not act on it automatically** — it never rewrites content and never changes the exit code, so a human decides whether the citation is a leak.
185
+
186
+ Why: PSA-003 destructive-command safeguards require every consumer repo to carry the parallel-sessions rule. See issue #155. The `loop.md` vendor gives bare `/loop` a repo-aware maintenance prompt (issue #633 Hebel 3).
187
+
188
+ Why one writer (issue #1060): a literal `cp` from a second source directory bypasses the pre-write validator AND lands a file carrying no provenance header. On the next `--sync-rules` a headerless file is classified as a repo-private override and preserved forever, so the plugin can never update it again — and whichever rival copy is smaller silently wins.
182
189
 
183
190
  ## Step 4: Generate README.md
184
191
 
@@ -592,14 +592,18 @@ indent_size = 2
592
592
 
593
593
  Canonical implementation in [`_shared-template.md#parallel-sessions-rule`](_shared-template.md).
594
594
 
595
- Write the vendored rule from `$PLUGIN_ROOT/templates/_shared/rules/parallel-sessions.md` to
596
- `$REPO_ROOT/.claude/rules/parallel-sessions.md` (idempotent: missing→create, identical→skip,
597
- differs→overwrite). The shared partial also runs
595
+ Run `node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$REPO_ROOT"` — it vendors every
596
+ rule registered in `rules/_index.md` (idempotent: missing→create, identical→skip, differs→overwrite),
597
+ and it is the only writer that applies the pre-write validator, the basename-collision guard and the
598
+ copy-on-write branch that preserves repo-private rules. The shared partial also runs
598
599
  `cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"` so bare `/loop` gets a
599
- repo-aware maintenance prompt. See shared partial for full shell command. Issues #155, #633.
600
+ repo-aware maintenance prompt. See shared partial for full shell command. Issues #155, #633, #1060.
600
601
 
601
- Note: Runs before S99. If S99 fetches a newer `parallel-sessions.md` from the baseline, the
602
- baseline version wins (acceptable S99 is canonical).
602
+ Note: Runs before S99. S99 no longer fetches `parallel-sessions.md` from the baseline — that entry
603
+ was removed from the S99 manifest in #1060. The baseline copy carries no provenance header, so
604
+ letting it win would make the next `--sync-rules` classify the target as a repo-private override and
605
+ preserve it permanently, i.e. the plugin could never update that rule again. `rules/` is the single
606
+ source for it.
603
607
 
604
608
  ## Step 3b: Initialize .orchestrator/metrics/ (#185)
605
609
 
@@ -910,11 +910,18 @@ function main() {
910
910
  // kebab(s) = s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
911
911
  //
912
912
  // WARN (never error) when:
913
- // - learnings.jsonl is present AND no entry's derived key matches the rule's
914
- // `learning-key` (absent learning), OR
913
+ // - NEITHER a learnings.jsonl entry NOR a valid `evidence-digest` frontmatter
914
+ // scalar is present (the rule's provenance is unresolvable), OR
915
915
  // - the matching entry's `expires_at` < now (expired learning).
916
- // When learnings.jsonl is absent, every key counts as absent — warn on each
917
- // generated rule's key.
916
+ //
917
+ // #1101 the digest branch. `.orchestrator/metrics/learnings.jsonl` is
918
+ // GITIGNORED, so in a fresh clone it does not exist and every generated rule
919
+ // used to warn (measured 2026-08-26: 23 of 23). A rule carrying a valid
920
+ // `evidence-digest: sha256-v1:<64 hex>` is SELF-CONTAINED — its `## Evidence`
921
+ // block plus its own `## Provenance` header fields re-derive the seal with no
922
+ // jsonl lookup — so its provenance is resolvable offline and it must not warn.
923
+ // The digest is EVIDENCE, not a second pointer; see
924
+ // `scripts/lib/reconcile/renderer.mjs` § computeEvidenceDigest.
918
925
  // The check is silently skipped (no id pushed) when .claude/rules/ is absent
919
926
  // or contains no .md files with auto-generated: true.
920
927
  if (!args.skipGeneratedRuleStaleness) {
@@ -926,15 +933,17 @@ function main() {
926
933
  // Reads the opening --- ... --- block from a markdown file.
927
934
  function extractFrontmatterFields(mdContent) {
928
935
  const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(stripLeadingRuleHeaderLines(mdContent));
929
- if (!m) return { autoGenerated: false, learningKey: null, expiresAt: null };
936
+ if (!m) return { autoGenerated: false, learningKey: null, expiresAt: null, evidenceDigest: null };
930
937
  const block = m[1];
931
938
  const autoGenM = /^auto-generated:\s*(.+)$/m.exec(block);
932
939
  const learningKeyM = /^learning-key:\s*(.+)$/m.exec(block);
933
940
  const expiresAtM = /^expires-at:\s*(.+)$/m.exec(block);
941
+ const evidenceDigestM = /^evidence-digest:\s*(.+)$/m.exec(block);
934
942
  return {
935
943
  autoGenerated: autoGenM ? autoGenM[1].trim() === 'true' : false,
936
944
  learningKey: learningKeyM ? learningKeyM[1].trim() : null,
937
945
  expiresAt: expiresAtM ? expiresAtM[1].trim() : null,
946
+ evidenceDigest: evidenceDigestM ? evidenceDigestM[1].trim() : null,
938
947
  };
939
948
  }
940
949
 
@@ -955,12 +964,20 @@ function main() {
955
964
  relPath: relative(vaultDir, absPath),
956
965
  learningKey: fields.learningKey,
957
966
  expiresAt: fields.expiresAt,
967
+ evidenceDigest: fields.evidenceDigest,
958
968
  });
959
969
  }
960
970
 
961
971
  // No generated rules found → silently skip (don't push the check id).
962
972
  if (generatedRules.length === 0) return;
963
973
 
974
+ // #1101 — a LITERAL COPY of `EVIDENCE_DIGEST_RE` from
975
+ // `scripts/lib/reconcile/renderer.mjs`. Copied, not imported: this checker
976
+ // is a standalone skill script, and importing across the skill/script
977
+ // boundary would add real coupling for one 30-character literal. The two
978
+ // copies are pinned equal by `tests/lib/reconcile/renderer.test.mjs`.
979
+ const EVIDENCE_DIGEST_RE = /^sha256-v1:[0-9a-f]{64}$/;
980
+
964
981
  // Slugify function mirroring emitter.mjs `kebab()`.
965
982
  const kebab = (s) =>
966
983
  String(s)
@@ -1001,26 +1018,37 @@ function main() {
1001
1018
  const nowMs = Date.now();
1002
1019
 
1003
1020
  for (const rule of generatedRules) {
1004
- const { relPath, learningKey, expiresAt } = rule;
1021
+ const { relPath, learningKey, expiresAt, evidenceDigest } = rule;
1005
1022
 
1006
1023
  // If we cannot evaluate the key (no learning-key frontmatter), skip
1007
1024
  // silently — avoid false positives on malformed rules.
1008
1025
  if (!learningKey) continue;
1009
1026
 
1010
- if (!learningsPresent || !knownKeys.has(learningKey)) {
1011
- // Learning is absent from learnings.jsonl (or the file is missing entirely).
1027
+ const hasJsonlEntry = learningsPresent && knownKeys.has(learningKey);
1028
+ const hasValidDigest =
1029
+ typeof evidenceDigest === 'string' && EVIDENCE_DIGEST_RE.test(evidenceDigest);
1030
+
1031
+ if (!hasJsonlEntry && !hasValidDigest) {
1032
+ // NEITHER resolution path is available: the learning is absent from
1033
+ // learnings.jsonl (or the file is missing entirely) AND the rule carries
1034
+ // no self-contained evidence digest, so nothing can vouch for it.
1012
1035
  warnings.push({
1013
1036
  check: 'generated-rule-staleness',
1014
1037
  file: relPath,
1015
1038
  line: 1,
1016
- message: `Auto-generated rule references learning-key '${learningKey}' which is absent from .orchestrator/metrics/learnings.jsonl`,
1039
+ message: `Auto-generated rule references learning-key '${learningKey}' which is absent from .orchestrator/metrics/learnings.jsonl, and the rule carries no valid evidence-digest to verify it offline`,
1017
1040
  extracted: learningKey,
1018
1041
  });
1019
1042
  continue;
1020
1043
  }
1021
1044
 
1022
- // Learning exists — check expiry.
1023
- const storedExpiresAt = knownKeys.get(learningKey);
1045
+ // Provenance resolves — check expiry.
1046
+ // `storedExpiresAt` is only meaningful when the jsonl entry actually
1047
+ // exists. A digest-only rule (fresh clone, or a learning aged out of the
1048
+ // store) is AUTHORITATIVE FOR ITS OWN EXPIRY via frontmatter; reading a
1049
+ // stored value that is not there would make `expiryStr` null and silently
1050
+ // skip the expiry gate for a rule that has a perfectly good `expires-at`.
1051
+ const storedExpiresAt = hasJsonlEntry ? knownKeys.get(learningKey) : null;
1024
1052
  // Prefer the frontmatter expires-at on the rule file; fall back to the
1025
1053
  // stored expires_at from the learning entry (both should agree, but the
1026
1054
  // rule file is authoritative for its own expiry).
@@ -113,7 +113,7 @@ const res = claimRepo({ repoRoot: R, sessionId, mode, ttlHours, semanticSessionI
113
113
  Or reuse the primitive directly: `acquire({ sessionId, mode, ttlHours, repoRoot, semanticSessionId })` from `scripts/lib/session-lock.mjs`. The claim is a `linkSync` create-or-fail = **atomic**.
114
114
 
115
115
  - **`ok: true`** → the claim is held. Proceed to Phase 4.
116
- - **`ok: false`** (race lost / busy — reasons: `active`, `stale-pid-alive`, `stale-pid-dead`, `fs-error`, …) → **exclude R**, re-rank the remaining free candidates (drop R from `free`, re-run Phase 1's rank step), and re-present Phase 2. Loop until a claim succeeds or no free candidate remains (then Phase 5).
116
+ - **`ok: false`** (race lost / busy — reasons: `active`, `stale-heartbeat`, `fs-error`, …) → **exclude R**, re-rank the remaining free candidates (drop R from `free`, re-run Phase 1's rank step), and re-present Phase 2. Loop until a claim succeeds or no free candidate remains (then Phase 5).
117
117
 
118
118
  Do NOT reinvent the claim — always go through `claimRepo`/`acquire`. The `ok:false` path is the load-bearing concurrency guard: two parallel dispatchers can both recommend R, but only one wins the `linkSync`; the loser must re-rank, never force.
119
119
 
@@ -0,0 +1,269 @@
1
+ ---
2
+ name: journey-audit
3
+ description: >
4
+ Use this skill when the user wants the PRODUCT audited from the outside in — what the website
5
+ promises vs what the code does vs what a real user experiences vs what actually arrives by mail
6
+ vs what the data says is used. Runs a 7-role read-only wave (R5 writes only under an explicit
7
+ SAFETY block) against a per-repo `.orchestrator/journey-manifest.md` and writes a dossier to
8
+ docs/audits/. Distinct from /discovery, which checks code quality inside-out — this checks
9
+ product truth outside-in; they complement, neither replaces the other. Triggers "journey audit",
10
+ "Nutzersicht-Audit", "audit the product from the user's side", "/journey-audit".
11
+ model: inherit
12
+ color: purple
13
+ tools: Read, Grep, Glob, Bash, Write, Task
14
+ ---
15
+
16
+ # Journey Audit
17
+
18
+ > Outside-in product audit as a repeatable deep session. Code review checks code against code;
19
+ > this checks the product against the user's experience. Quarterly, never a CI component.
20
+
21
+ ## Purpose
22
+
23
+ The most expensive defects of the first run (EventDrop `main-2026-08-28-a6`: a core feature with no
24
+ entry point anywhere in the UI, burned free quota, a plan gate that could be bypassed, a
25
+ claim-vs-code contradiction, EUR copy in a USD context) are **invisible to every code-centric
26
+ check**. They are not bugs in a function; they are gaps between five surfaces — marketing copy,
27
+ code constants, live UI, outbound mail, real usage data — that no single surface can reveal.
28
+
29
+ This skill dispatches seven roles that each own one surface, then makes the coordinator personally
30
+ re-verify every P0 before it is written down. The re-verification is the value, not the fan-out:
31
+ the first run caught 2 agent errors that way and confirmed 3 real P0s.
32
+
33
+ ## Phase 0: Gates
34
+
35
+ ### 0.1 Bootstrap Gate
36
+
37
+ Read `skills/_shared/bootstrap-gate.md` and execute the gate check. GATE_CLOSED → invoke
38
+ `skills/bootstrap/SKILL.md` first. GATE_OPEN → continue.
39
+
40
+ ### 0.2 Manifest HARD-GATE
41
+
42
+ <HARD-GATE>
43
+ Read `.orchestrator/journey-manifest.md` in the target repo (or the path passed as `$ARGUMENTS`).
44
+
45
+ **A — File missing or empty → REFUSE the whole run.** Say exactly this, then stop:
46
+
47
+ > `/journey-audit` needs `.orchestrator/journey-manifest.md` in this repo and it is not there.
48
+ > The audit is repo-specific: without personas, entry points, truth-SSOTs, the chat-interview key
49
+ > and the SAFETY block, seven agents would invent their own definition of "correct" and every
50
+ > finding would be unfalsifiable.
51
+ > Copy the template — `templates/_shared/journey-manifest.md` in the session-orchestrator plugin —
52
+ > to `.orchestrator/journey-manifest.md`, fill it in, then re-run `/journey-audit`.
53
+
54
+ **B — Manifest present, `## SAFETY` heading absent or its body empty → REFUSE R5 ONLY.** R5 is the
55
+ only role that touches production with a real account. Say:
56
+
57
+ > The manifest has no filled `## SAFETY` block, so R5 (real end-to-end run against production) is
58
+ > NOT dispatched. Running as dry-run: R1–R4 + R7 (R6 additionally needs the read-only DB
59
+ > credentials named in the manifest). Fill in allowed accounts/events, no-go actions, the checkout
60
+ > limit and the cleanup rule to enable R5.
61
+
62
+ Do NOT infer a SAFETY block from other sections, do NOT ask the operator to dictate one into chat,
63
+ do NOT dispatch R5 "read-only, just this once". A production write without a written, committed
64
+ safety envelope is the one thing this skill never does.
65
+ </HARD-GATE>
66
+
67
+ Both gates are cheap and mechanical:
68
+
69
+ ```bash
70
+ MANIFEST="${1:-.orchestrator/journey-manifest.md}"
71
+ [ -s "$MANIFEST" ] || { echo "GATE-A: no manifest"; exit 1; }
72
+ awk '/^## SAFETY/{f=1;next} /^## /{f=0} f&&NF{c++} END{exit c>0?0:1}' "$MANIFEST" \
73
+ || echo "GATE-B: SAFETY block absent or empty → R5 disabled"
74
+ ```
75
+
76
+ ### 0.3 Peer-session check (before dispatch, not after)
77
+
78
+ Run `ListAgents` / read `.orchestrator/` session locks and check for peer sessions in this working
79
+ copy per `.claude/rules/parallel-sessions.md`. A journey audit reads wide and R5 writes to
80
+ production — a peer holding the same files must know before the wave starts, not from the diff.
81
+ Announce the audit and the file scope you will touch; the audit itself claims almost no write
82
+ scope, which is exactly why it is easy to forget.
83
+
84
+ ## Phase 1: Read the manifest
85
+
86
+ Parse the manifest into `$MANIFEST` and derive per-role inputs. Every role prompt below is filled
87
+ from these fields — an empty field is reported to the operator, never guessed:
88
+
89
+ `personas` · `entry_points` · `truth_ssots` · `touchpoints` (template dir, send path, cron sources,
90
+ render idiom) · `chat_interview` (question → expected answer → SSOT) · `safety` · `credentials`
91
+ (env-var NAMES) · `realdata_queries` · `platform_expectation` · `known_exceptions`.
92
+
93
+ ## Phase 2: The wave — R1–R7
94
+
95
+ All roles are **read-only** except R5. All run in parallel in one wave; none depends on another's
96
+ output (the coordinator, not an agent, joins their findings).
97
+
98
+ | R | Rolle | Generisch | Aus dem Manifest |
99
+ |---|-------|-----------|------------------|
100
+ | R1 | Flow-Zensus | Trigger→Empfänger→Zeitpunkt→Dedupe map of every outbound touchpoint (mail/push/webhook) | Template dir, send path, cron sources |
101
+ | R2 | Artefakt-Rendering | Render templates to HTML, screenshots desktop/mobile/dark, consistency matrix | Render idiom, example props |
102
+ | R3 | Claim-vs-Code-Matrix | Every marketing/FAQ/chat claim against the SSOT constants; feature inventory × surfaces | SSOT files, i18n namespaces, chat fact source |
103
+ | R4 | Anonymer Live-Rundgang | agent-browser desktop+mobile, chat interview against the truth key, dead links, console | Route list, chat questions + expected answers |
104
+ | R5 | Echter E2E-Durchstich | Prod, real account, actually execute each core flow, DB counter-check, mail-log reconciliation | **SAFETY block (mandatory)** |
105
+ | R6 | Realdaten-Funnel | Usage funnel, never-fired flows, delivery defects, name what is not measurable | Read-only DB access, identity rules, mail-provider API |
106
+ | R7 | Plattform-Ausnutzung | Self-built vs platform matrix (hosting/DB/realtime/queues/CDN/WAF), limits at 10× | CLI logins, expected plan, open perf issues |
107
+
108
+ ### Shared prompt preamble (prepend to every role)
109
+
110
+ > You are role `<R#>` of a journey audit of `<repo>`. Manifest: `.orchestrator/journey-manifest.md`
111
+ > — read it first; it is your only definition of "correct". You are **read-only**: no `Edit`, no
112
+ > `Write` outside your own report, no git write operations (PSA-007), no production writes.
113
+ > Every distributional claim ("all N routes", "no template does X") carries the executed command
114
+ > and its output (PSA-006). A finding you cannot reproduce with a quoted command is a suspicion —
115
+ > label it as one. Findings listed under `Bekannte Ausnahmen` in the manifest are reported as
116
+ > `known-exception`, never as new. Severity: **P0** = money, data loss, or a core flow unreachable ·
117
+ > **P1** = a broken flow or a UX dead end · **P2** = content/platform. Return the FULL report as
118
+ > your last message.
119
+
120
+ ### Role prompt skeletons
121
+
122
+ Each is self-contained; fill the `<…>` from `$MANIFEST`.
123
+
124
+ **R1 — Flow-Zensus.** Inputs: template dir `<…>`, send path `<…>`, cron sources `<…>`.
125
+ Task: enumerate every outbound touchpoint and produce one row per flow: trigger (code location) →
126
+ recipient (which persona) → timing → dedupe/idempotency → is it reachable at all. Grep the send
127
+ path for callers; a template with zero callers is a P1 finding, a caller with no dedupe on a
128
+ money-relevant flow is P0. Output: Markdown table + a list of `never-fired` candidates with the
129
+ grep that proves zero callers.
130
+
131
+ **R2 — Artefakt-Rendering.** Inputs: render idiom `<…>`, example props `<…>`.
132
+ Task: render every template found by R1's directory to HTML, screenshot desktop + mobile + dark,
133
+ and build a consistency matrix (logo, sender, footer, legal block, CTA, language, currency).
134
+ Output: matrix table + the artifact paths under `.orchestrator/journey-audit/<date>/`, plus every
135
+ divergence as a finding. Do not fix a template; report it.
136
+
137
+ **R3 — Claim-vs-Code-Matrix.** Inputs: SSOT files `<…>`, i18n namespaces `<…>`, chat fact source `<…>`.
138
+ Task: extract every quantitative or capability claim from marketing pages, FAQ, pricing, chat facts
139
+ and mail copy; compare each against the SSOT constant. One row: claim · where it is said · SSOT
140
+ value · verdict (`match` / `drift` / `unbacked`). Then a feature inventory × surfaces grid: a
141
+ feature that exists in code but appears on no surface is a P0 candidate ("no entry point").
142
+ **i18n files are owned by R3 alone** — no other role opens them.
143
+ Output: two tables + findings.
144
+
145
+ **R4 — Anonymer Live-Rundgang.** Inputs: route list `<…>`, chat questions + expected answers `<…>`.
146
+ Task: `agent-browser`, logged OUT, desktop and mobile viewport. Walk every route: dead links,
147
+ console errors, layout breaks, CTA that goes nowhere. Then run the chat interview verbatim and
148
+ score each answer against the expected answer from the manifest (`match` / `drift` / `refused` /
149
+ `hallucinated`). Output: route table (status, console, findings), interview table, screenshots.
150
+ Read-only: never submit a form that creates data, never sign up.
151
+
152
+ **R5 — Echter E2E-Durchstich.** Inputs: SAFETY block `<…>`, credentials by env-var NAME `<…>`.
153
+ Task: with the allowed account only, actually execute each core flow end to end in production;
154
+ counter-check in the DB read-only that the expected records exist; reconcile against the mail log.
155
+ **Before every step, re-read the SAFETY block and state which rule permits this step.** Stop at the
156
+ first no-go, do not improvise around a block, honour the checkout limit exactly, and run the
157
+ cleanup rule at the end and prove it ran. Output: per-flow trace (step → observed → expected →
158
+ verdict), the DB counter-check output, the cleanup proof.
159
+
160
+ **R6 — Realdaten-Funnel.** Inputs: read-only queries `<…>`, identity rules `<…>`, mail-provider API `<…>`.
161
+ Task: run the manifest's `SELECT`s only. Build the funnel per persona, list flows that never fired
162
+ in production, and pull delivery defects from the mail provider (bounces, suppressions, a
163
+ permanently silenced owner address). Then name explicitly what the data CANNOT answer — an unnamed
164
+ blind spot reads as a zero. Output: funnel table, never-fired list, delivery-defect list,
165
+ `not-measurable` list. No writes, no schema changes, no query outside the manifest.
166
+
167
+ **R7 — Plattform-Ausnutzung.** Inputs: CLI logins `<…>`, expected plan/tier `<…>`, open perf issues `<…>`.
168
+ Task: self-built vs platform matrix across hosting, DB, realtime, queues, CDN, WAF, cron, mail:
169
+ what does the repo hand-roll that the paid platform already provides, and what breaks at 10× today's
170
+ load (quote the actual limit from the provider CLI/API, not from memory). Output: matrix
171
+ (capability · self-built? · platform feature · effort to switch · limit at 10×) + findings.
172
+
173
+ ## Phase 3: Koordinator-Disziplin
174
+
175
+ Numbered MUSTs. This phase is the skill; the wave is only its input.
176
+
177
+ 1. **Every P0 claim is re-verified by the coordinator, individually, with its OWN grep/curl/DB read,
178
+ before it enters the dossier.** Not "the agent quoted a command" — you run one yourself, and the
179
+ dossier carries YOUR command and output. An unreproduced P0 is downgraded to P1 with the note
180
+ `agent claim, not reproduced`, or dropped. First run: 2 of 5 P0 claims were agent errors
181
+ ("instantly unreachable" — it was selectable in the UI; "USD on prod" — a session artifact).
182
+ 2. **A finding is attributed.** Every dossier row names the role that found it and the command that
183
+ proves it — the reader must be able to re-run it without asking anyone.
184
+ 3. **i18n files are owned by exactly ONE agent (R3).** They were the known contention point in the
185
+ first run: many roles want to quote them, and a second reader turns into a second writer the
186
+ moment a fix is suggested. Same rule for any other file two roles both want.
187
+ 4. **Peer-session check before the wave starts** (Phase 0.3), not after the diff.
188
+ 5. **The coordinator never lets an agent write the dossier.** Agents return reports; the joining,
189
+ de-duplication and severity decision are the coordinator's, because contradictions between two
190
+ agents are only visible in one place.
191
+ 6. **Contradictions are reported as contradictions.** Two roles disagreeing is a finding about the
192
+ product, not a merge conflict to smooth over.
193
+ 7. **No fixes during the audit.** Findings only. The fix wave is Phase 5 and is a separate decision.
194
+
195
+ ## Phase 4: Dossier
196
+
197
+ Write `docs/audits/YYYY-MM-DD-user-journey-audit.md` with this fixed section order:
198
+
199
+ 1. `## P0 — Geld & Kern-Flows` (each: symptom · evidence (coordinator's own command + output) ·
200
+ impact · suggested fix · role)
201
+ 2. `## P1 — Flows`
202
+ 3. `## P1 — UX`
203
+ 4. `## P2 — Inhalt`
204
+ 5. `## P2 — Plattform`
205
+ 6. `## Realdaten` (funnel, never-fired flows, delivery defects, explicitly not-measurable)
206
+ 7. `## Marketing-Hebel` (what the product does well and says nowhere)
207
+
208
+ Header carries: date, repo, HEAD SHA, roles dispatched (and which were skipped, with the reason —
209
+ "R5 skipped: no SAFETY block" is a result, not a gap), manifest path, artifact directory.
210
+
211
+ ## Phase 5: Closing AskUserQuestion
212
+
213
+ One `AskUserQuestion` call, per `.claude/rules/ask-via-tool.md` (option 1 `(Recommended)`, every
214
+ description carries reason + cost + consequence, `header` ≤ 12 codepoints):
215
+
216
+ ```
217
+ AskUserQuestion({ questions: [
218
+ { question: "Which fix packages should the follow-up wave carry?",
219
+ header: "Fix-Wellen", multiSelect: true,
220
+ options: [
221
+ { label: "P0 money+core (Recommended)", description: "The <N> P0s I re-verified myself. Blocks revenue/core flow today; ~<X>h; freezes nothing else." },
222
+ { label: "P1 flows", description: "<N> broken/undedupliced flows. ~<X>h; needs the R1 map as input." },
223
+ { label: "P1 UX", description: "<N> dead ends. Cheap individually, touches i18n — one owner." },
224
+ { label: "P2 content+platform", description: "<N> items. No user impact today; do in cooldown." } ] },
225
+ { question: "Create issues for the selected packages?",
226
+ header: "Issues", multiSelect: false,
227
+ options: [
228
+ { label: "Yes, batch now (Recommended)", description: "One issue per finding via gitlab-ops; labels priority::critical|high|medium + area:* + type:bug/feature. ~<N> issues; respects issue-budget." },
229
+ { label: "Dossier only", description: "No issues; the dossier stays the record. Choose when the backlog is already over budget." } ] } ] })
230
+ ```
231
+
232
+ Issue creation follows `skills/gitlab-ops/SKILL.md` § Label Taxonomy: `priority::critical` for P0,
233
+ `priority::high` for P1, `priority::medium` for P2, plus the repo's `area:` and `type:` axes. Each
234
+ issue links back to the dossier section that produced it.
235
+
236
+ ## Offload note (R6/R7)
237
+
238
+ R6 and R7 are pure CLI/API roles — no browser, no repo writes — so they can run headless on a
239
+ second machine (`claude -p "<prompt>"`, see the `m5-offload` skill). Two rules from the first run,
240
+ both learned the hard way:
241
+
242
+ - **`claude -p` prints only the LAST message.** The prompt MUST end with: *"Return the complete
243
+ report in your last message — not a summary, not a pointer to a file. Nothing you say before the
244
+ last message will be read."*
245
+ - **No background processes.** The prompt MUST forbid `&`, `run_in_background`, and any "I'll keep
246
+ it running" pattern: the process is killed when `-p` returns, and a half-finished role looks
247
+ identical to a clean one.
248
+
249
+ ## Cadence & Abgrenzung
250
+
251
+ - **Quarterly, or after a large feature drop.** Deliberately **not a CI component** — it is
252
+ expensive and judgment-heavy, and a judgment-heavy gate that runs on every pipeline becomes a
253
+ rubber stamp.
254
+ - **Optional monthly light variant:** R3 + R4 only, as a scheduled cloud session.
255
+ - **vs `/discovery`:** discovery checks code quality inside-out (probes over the tree);
256
+ journey-audit checks product truth outside-in (five user-facing surfaces against each other).
257
+ Neither replaces the other; a repo needs both.
258
+ - **Follow-through:** after each run, freeze the mechanizable parts per repo — a `report:*` script,
259
+ a claim-drift test pinned to the SSOT constant — so the next run is cheaper. The audit's job is
260
+ to find what no test knows how to look for yet.
261
+
262
+ ## Anti-Patterns
263
+
264
+ - Dispatching R5 without a SAFETY block "because the operator said it's fine in chat" — the block
265
+ is a committed artifact for a reason.
266
+ - Copying an agent's P0 into the dossier because its command looked convincing (Phase 3.1).
267
+ - Two roles both editing/owning the i18n files (Phase 3.3).
268
+ - Running the audit as a CI job to "keep it honest" — it turns into a stamp nobody reads.
269
+ - Fixing findings mid-audit, so the later roles measure a moving product.
@@ -156,11 +156,15 @@ For SwiftUI 26+ targets (projects with `Package.swift` declaring `.iOS("26")` or
156
156
  ```bash
157
157
  # Gate: glass-modifiers emit is opt-in per the active profile's rubric_features flag (v1 rubric does not consume).
158
158
  PROFILES_FILE=".orchestrator/policy/test-profiles.json"
159
- HAS_GLASS_V2="false"
159
+ HAS_GLASS_V2="false" # preconditions unmet (no PROFILE / no file / no jq) = feature not requested
160
160
  if [ -n "${PROFILE:-}" ] && [ -f "$PROFILES_FILE" ] && command -v jq >/dev/null 2>&1; then
161
- HAS_GLASS_V2=$(jq -r --arg p "$PROFILE" '(.[$p].rubric_features // []) | contains(["glass-v2"])' "$PROFILES_FILE" 2>/dev/null || echo false)
161
+ # Fallback is `unknown`, NOT `false`: false is jq's legitimate "feature off" answer,
162
+ # so a jq crash on a malformed profiles file would read as a deliberate opt-out.
163
+ HAS_GLASS_V2=$(jq -r --arg p "$PROFILE" '(.[$p].rubric_features // []) | contains(["glass-v2"])' "$PROFILES_FILE" 2>/dev/null || echo unknown)
162
164
  fi
163
- if [ "$HAS_GLASS_V2" = "true" ]; then
165
+ if [ "$HAS_GLASS_V2" = "unknown" ]; then
166
+ echo "WARN: glass-v2 probe failed (jq could not read $PROFILES_FILE) — no conformance artifact emitted" >&2
167
+ elif [ "$HAS_GLASS_V2" = "true" ]; then
164
168
  cat > "${RUN_DIR}/ax-snapshots/glass-modifiers-$(date +%s%3N).json" <<EOF
165
169
  {
166
170
  "schema_version": "v1",
@@ -175,6 +179,14 @@ EOF
175
179
  fi
176
180
  ```
177
181
 
182
+ `HAS_GLASS_V2` has **three** states, and the third is the reason the fallback is not `false`:
183
+
184
+ | Value | Meaning | Driver action |
185
+ |---|---|---|
186
+ | `true` | The active profile lists `glass-v2` in `rubric_features` | Emit the conformance artifact |
187
+ | `false` | The profile does not list it — a real answer from a readable file, or preconditions unmet (no `PROFILE`, no profiles file, no `jq`) | Skip the artifact silently |
188
+ | `unknown` | The probe itself failed — `jq` could not read/parse `$PROFILES_FILE` | WARN on stderr, skip the artifact. **Never** collapsed into `false`: a crashed probe is not an opt-out, and ux-evaluator Check 4 would otherwise report screenshot-only fallback as if the profile had chosen it |
189
+
178
190
  The ux-evaluator Check 4 reads this file. `glassEffect_frames` = compliant (uses `.glassEffect()`). `legacy_material_frames` = non-compliant (uses `.background(.thinMaterial)` etc.). `blur_modifier_frames` = non-compliant (uses `.blur(radius:)` as background). In v1 the arrays are always empty — the evaluator falls back to screenshot-only analysis. Do NOT emit a bare `{}` — use the full schema structure with empty arrays.
179
191
 
180
192
  ## Composability Contract
@@ -334,7 +334,7 @@ result. If `final_verdict == "warn"`: exit 0 with a warning line on stderr. If
334
334
  - **NEVER** dispatch more than 20 personas per panel (security M2 cap).
335
335
  - **NEVER** write a sidecar that fails schema validation — validate BEFORE write (security M3).
336
336
  - **NEVER** skip `validatePathInsideProject` for the target path OR the sidecar output path (H1).
337
- - **NEVER** use `run_in_background: true` for persona agents — lose coordination ability.
337
+ - **NEVER** use `run_in_background: true` for persona agents — the panel synthesis needs every reviewer's findings before Phase 4; background dispatch would add turn-juggling with no consumer for early results.
338
338
  - **ALWAYS** validate `model:` fields from the catalog against `MODEL_ID_RE` + aliases (H2).
339
339
  - **ALWAYS** run `output_contract` structural pre-check before `ajv.compile()` (H3).
340
340
  - **ALWAYS** emit the `aggregator_warning` to BOTH stderr and sidecar when using