session-orchestrator 3.20.0 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) 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/rules/000-session-orchestrator.mdc +3 -2
  5. package/.cursor/rules/030-wave-execution.mdc +10 -8
  6. package/.cursor/rules/040-discovery.mdc +6 -6
  7. package/.cursor/rules/050-plan.mdc +8 -8
  8. package/CHANGELOG.md +515 -0
  9. package/README.md +16 -11
  10. package/agents/analyst.md +1 -1
  11. package/agents/architect-reviewer.md +1 -1
  12. package/agents/code-implementer.md +4 -2
  13. package/agents/db-specialist.md +1 -1
  14. package/agents/dialectic-deriver.md +1 -1
  15. package/agents/docs-writer.md +1 -1
  16. package/agents/memory-proposal-collector.md +7 -5
  17. package/agents/qa-strategist.md +1 -1
  18. package/agents/security-reviewer.md +1 -1
  19. package/agents/session-reviewer.md +42 -1
  20. package/agents/skill-applied-judge.md +1 -1
  21. package/agents/test-writer.md +1 -1
  22. package/agents/ui-developer.md +1 -1
  23. package/agents/ux-evaluator.md +1 -1
  24. package/commands/eli5.md +33 -0
  25. package/commands/release.md +62 -0
  26. package/commands/test.md +2 -2
  27. package/docs/components.md +6 -5
  28. package/docs/migration-v3.md +9 -6
  29. package/docs/persona-panel.md +3 -1
  30. package/docs/scope-collision-guard.md +167 -0
  31. package/docs/session-config-reference.md +31 -8
  32. package/hooks/_lib/lock-bootstrap.mjs +19 -13
  33. package/hooks/enforce-scope.mjs +103 -3
  34. package/hooks/hooks-codex.json +1 -1
  35. package/hooks/hooks.json +21 -1
  36. package/hooks/on-session-end.mjs +76 -97
  37. package/hooks/on-session-start.mjs +195 -104
  38. package/hooks/on-stop.mjs +127 -12
  39. package/hooks/post-bash-write-verify.mjs +8 -32
  40. package/hooks/pre-auq-clarity.mjs +787 -0
  41. package/hooks/pre-bash-issue-budget.mjs +17 -18
  42. package/hooks/pre-task-scope-disjoint.mjs +1042 -0
  43. package/package.json +3 -1
  44. package/pi/prompts/eli5.md +12 -0
  45. package/pi/prompts/release.md +12 -0
  46. package/scripts/auq-audit.mjs +825 -0
  47. package/scripts/autopilot.mjs +10 -9
  48. package/scripts/emit-session.mjs +42 -0
  49. package/scripts/export-hw-learnings.mjs +61 -2
  50. package/scripts/lib/auq/clarity.mjs +1314 -0
  51. package/scripts/lib/auq/parse.mjs +1006 -0
  52. package/scripts/lib/auq/schema.mjs +1457 -0
  53. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  54. package/scripts/lib/backlog-scan.mjs +106 -15
  55. package/scripts/lib/build-live-signals.mjs +7 -3
  56. package/scripts/lib/ci-status-banner.mjs +267 -77
  57. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
  58. package/scripts/lib/config/vault-integration.mjs +12 -1
  59. package/scripts/lib/dispatcher/rank.mjs +4 -7
  60. package/scripts/lib/gates/gate-full.mjs +3 -3
  61. package/scripts/lib/gates/gate-helpers.mjs +17 -6
  62. package/scripts/lib/git-config-drift.mjs +471 -0
  63. package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
  64. package/scripts/lib/io.mjs +432 -7
  65. package/scripts/lib/issue-budget.mjs +63 -9
  66. package/scripts/lib/learnings/select.mjs +157 -3
  67. package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
  68. package/scripts/lib/mirror-issues-banner.mjs +266 -0
  69. package/scripts/lib/named-vault-resolver.mjs +105 -16
  70. package/scripts/lib/owner-interview.mjs +78 -32
  71. package/scripts/lib/peer-cards/schema.mjs +6 -2
  72. package/scripts/lib/peer-discovery.mjs +73 -22
  73. package/scripts/lib/project-hygiene.mjs +64 -4
  74. package/scripts/lib/reconcile/renderer.mjs +17 -4
  75. package/scripts/lib/reconcile/writer.mjs +69 -30
  76. package/scripts/lib/redact-spans.mjs +89 -0
  77. package/scripts/lib/resource-probe/evaluate.mjs +330 -149
  78. package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
  79. package/scripts/lib/resource-probe.mjs +18 -2
  80. package/scripts/lib/scope-baseline.mjs +77 -17
  81. package/scripts/lib/scope-gate.mjs +658 -0
  82. package/scripts/lib/secret-masker.mjs +262 -0
  83. package/scripts/lib/session-lock.mjs +34 -10
  84. package/scripts/lib/session-registry.mjs +9 -1
  85. package/scripts/lib/spiral-carryover.mjs +23 -2
  86. package/scripts/lib/state-md/mission-status.mjs +164 -58
  87. package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
  88. package/scripts/lib/validate/check-agents.mjs +77 -5
  89. package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
  90. package/scripts/lib/validate/check-commands.mjs +2 -20
  91. package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
  92. package/scripts/lib/validate/check-hooks-symmetry.mjs +48 -0
  93. package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
  94. package/scripts/lib/validate/check-rules.mjs +153 -9
  95. package/scripts/lib/validate/check-skills.mjs +191 -0
  96. package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
  97. package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
  98. package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
  99. package/scripts/lib/validate/check-unwired-features.mjs +219 -11
  100. package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
  101. package/scripts/lib/validate/frontmatter-block.mjs +61 -0
  102. package/scripts/lib/validate/tier-inference.mjs +46 -8
  103. package/scripts/lib/vault-backfill/glab.mjs +91 -58
  104. package/scripts/lib/vault-backfill/manifest.mjs +28 -8
  105. package/scripts/lib/vault-mirror/namespace.mjs +146 -1
  106. package/scripts/lib/vault-mirror/process.mjs +264 -31
  107. package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
  108. package/scripts/lib/vault-status/board-writer.mjs +300 -56
  109. package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
  110. package/scripts/lib/vcs-repo-spec.mjs +680 -30
  111. package/scripts/lib/wave-resource-gate.mjs +67 -73
  112. package/scripts/materialize-wave-scope.mjs +281 -0
  113. package/scripts/print-learnings-index.mjs +30 -3
  114. package/scripts/release.mjs +983 -107
  115. package/scripts/run-quality-gate.mjs +14 -0
  116. package/scripts/site-numbers.mjs +1049 -0
  117. package/scripts/validate-plugin.mjs +64 -0
  118. package/scripts/validate-wave-scope.mjs +286 -12
  119. package/scripts/vault-backfill.mjs +32 -5
  120. package/scripts/vault-mirror.mjs +26 -1
  121. package/skills/_shared/monitor-patterns.md +24 -4
  122. package/skills/_shared/parallel-aware-auq.md +30 -24
  123. package/skills/_shared/parallel-aware-preamble.md +31 -2
  124. package/skills/_shared/state-ownership.md +49 -6
  125. package/skills/bootstrap/SKILL.md +2 -1
  126. package/skills/brainstorm/SKILL.md +18 -18
  127. package/skills/brainstorm/soul.md +12 -0
  128. package/skills/claude-md-drift-check/SKILL.md +9 -1
  129. package/skills/debug/SKILL.md +4 -1
  130. package/skills/discovery/SKILL.md +28 -24
  131. package/skills/discovery/issue-templates.md +4 -4
  132. package/skills/discovery/probes-code.md +2 -2
  133. package/skills/discovery/probes-feature.md +6 -6
  134. package/skills/discovery/probes-infra.md +2 -2
  135. package/skills/discovery/probes-session.md +5 -5
  136. package/skills/dispatcher/SKILL.md +10 -1
  137. package/skills/eli5/SKILL.md +43 -0
  138. package/skills/evolve/SKILL.md +8 -9
  139. package/skills/frontmatter-guard/SKILL.md +9 -1
  140. package/skills/gitlab-ops/SKILL.md +73 -59
  141. package/skills/gitlab-portfolio/SKILL.md +10 -1
  142. package/skills/grill/SKILL.md +6 -6
  143. package/skills/grill/soul.md +16 -0
  144. package/skills/memory-cleanup/SKILL.md +20 -7
  145. package/skills/npm-publish/SKILL.md +23 -51
  146. package/skills/peekaboo-driver/SKILL.md +3 -3
  147. package/skills/persona-panel/SKILL.md +3 -1
  148. package/skills/plan/SKILL.md +18 -16
  149. package/skills/plan/mode-feature.md +1 -1
  150. package/skills/plan/mode-new.md +42 -12
  151. package/skills/plan/soul.md +12 -0
  152. package/skills/reconcile/SKILL.md +3 -3
  153. package/skills/repo-audit/SKILL.md +10 -1
  154. package/skills/session-end/SKILL.md +97 -22
  155. package/skills/session-end/metrics-collection.md +1 -1
  156. package/skills/session-end/phase-3-6-tail.md +37 -2
  157. package/skills/session-end/session-metrics-write.md +4 -10
  158. package/skills/session-plan/SKILL.md +2 -2
  159. package/skills/session-plan/wave-template.md +1 -1
  160. package/skills/session-start/SKILL.md +82 -36
  161. package/skills/session-start/phase-2-5-docs-planning.md +8 -8
  162. package/skills/session-start/phase-4-5-resource-health.md +82 -19
  163. package/skills/session-start/soul.md +110 -0
  164. package/skills/spinout/SKILL.md +5 -1
  165. package/skills/sunset-review/SKILL.md +11 -1
  166. package/skills/test-runner/SKILL.md +2 -2
  167. package/skills/tmux-layout/SKILL.md +7 -2
  168. package/skills/using-orchestrator/SKILL.md +1 -1
  169. package/skills/vault-mirror/SKILL.md +10 -1
  170. package/skills/vault-sync/SKILL.md +10 -1
  171. package/skills/vault-sync/validator.mjs +55 -6
  172. package/skills/wave-executor/wave-loop.md +64 -12
  173. package/skills/write-executable-plan/SKILL.md +6 -6
  174. package/scripts/lib/mission-status-schema.mjs +0 -114
  175. package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
  176. package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
  177. package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
  178. package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
  179. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
  180. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
  181. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
  182. package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
  183. package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
  184. package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
  185. package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
  186. package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
  187. package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
  188. package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
  189. package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
  190. package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
  191. package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
  192. package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
  193. package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
  194. package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
  195. package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
  196. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
  197. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
  198. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
  199. package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
  200. package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
  201. package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
  202. package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
@@ -67,7 +67,7 @@ When two or more candidates score ≥ 0.85 with delta < 0.15, call `AskUserQuest
67
67
  AskUserQuestion({
68
68
  questions: [{
69
69
  question: "Your message matches multiple workflows. Which one did you mean?",
70
- header: "Skill Dispatch",
70
+ header: "Dispatch",
71
71
  multiSelect: false,
72
72
  options: [
73
73
  { label: "<top-skill display name> (Recommended)", description: "<one-line description of what it does>" },
@@ -1,6 +1,15 @@
1
1
  ---
2
2
  name: vault-mirror
3
- description: Use when you need to populate the Meta-Vault with machine-generated notes derived from session-orchestrator JSONL records. Converts entries from `.orchestrator/metrics/sessions.jsonl` and `.orchestrator/metrics/learnings.jsonl` into vault-conformant Markdown under `50-sessions/` and `40-learnings/`. Called automatically at session-end Phase 3.7 and after evolve Phase 3.5 — only when `vault-integration.enabled=true` and `vault-integration.mode != "off"`. Idempotent: re-runs safely; skips hand-authored notes. Triggers: "mirror to vault", "sync session notes to vault", "write learning notes to vault", "vault-mirror failed at session close". <example>Context: session-end is finalizing, vault-integration.mode is "warn". user: "/close" assistant: "Running vault-mirror to write 50-sessions/session-2026-05-17.md from the closing session record — 1 created, 0 skipped."</example>
3
+ description: >
4
+ Use when you need to populate the Meta-Vault with machine-generated notes derived from
5
+ session-orchestrator JSONL records. Converts entries from `.orchestrator/metrics/sessions.jsonl` and
6
+ `.orchestrator/metrics/learnings.jsonl` into vault-conformant Markdown under `50-sessions/` and
7
+ `40-learnings/`. Called automatically at session-end Phase 3.7 and after evolve Phase 3.5 — only when
8
+ `vault-integration.enabled=true` and `vault-integration.mode != "off"`. Idempotent: re-runs safely;
9
+ skips hand-authored notes. Triggers: "mirror to vault", "sync session notes to vault", "write learning
10
+ notes to vault", "vault-mirror failed at session close". <example>Context: session-end is finalizing,
11
+ vault-integration.mode is "warn". user: "/close" assistant: "Running vault-mirror to write
12
+ 50-sessions/session-2026-05-17.md from the closing session record — 1 created, 0 skipped."</example>
4
13
  model: haiku
5
14
  ---
6
15
 
@@ -1,6 +1,15 @@
1
1
  ---
2
2
  name: vault-sync
3
- description: Use when you need to validate the Meta-Vault's Markdown frontmatter and wiki-link integrity before closing a session or after vault edits. Runs as a hard gate at session-end Phase 1 — blocks close if any `.md` file fails the Zod frontmatter schema or has dangling `[[wiki-links]]`. Supports three modes: `hard` (blocks on errors), `warn` (reports without blocking), `off` (skip). Reads `vault-sync.*` from Session Config; respects per-vault exclude globs from `CLAUDE.md`. Triggers: "vault validation failed at session close", "fix vault frontmatter errors", "check vault wiki-links", "why is session-end blocked by vault-sync". <example>Context: session-end Phase 1 quality gate, vault-sync.enabled=true, vault-sync.mode="hard". user: "/close" assistant: "vault-sync found 2 frontmatter errors in vault/40-learnings/ml-notes.md — missing required `id` field. Fixing before close."</example>
3
+ description: >
4
+ Use when you need to validate the Meta-Vault's Markdown frontmatter and wiki-link integrity before
5
+ closing a session or after vault edits. Runs as a hard gate at session-end Phase 1 — blocks close if any
6
+ `.md` file fails the Zod frontmatter schema or has dangling `[[wiki-links]]`. Supports three modes:
7
+ `hard` (blocks on errors), `warn` (reports without blocking), `off` (skip). Reads `vault-sync.*` from
8
+ Session Config; respects per-vault exclude globs from `CLAUDE.md`. Triggers: "vault validation failed at
9
+ session close", "fix vault frontmatter errors", "check vault wiki-links", "why is session-end blocked by
10
+ vault-sync". <example>Context: session-end Phase 1 quality gate, vault-sync.enabled=true,
11
+ vault-sync.mode="hard". user: "/close" assistant: "vault-sync found 2 frontmatter errors in
12
+ vault/40-learnings/ml-notes.md — missing required `id` field. Fixing before close."</example>
4
13
  model: haiku
5
14
  ---
6
15
 
@@ -254,6 +254,14 @@ for (let i = 0; i < args.length; i++) {
254
254
  // ? — any single character except `/`
255
255
  // literal path separators and characters otherwise
256
256
  // Operates on POSIX-style forward-slash relative paths.
257
+ // `**` compiles to a SEGMENT-ANCHORED alternative, not a free `(?:.*?)` (#1013).
258
+ // The free form ended anywhere, including mid-segment, so every `**/` pattern
259
+ // silently grew a suffix-match: `**/README.md` matched `MYREADME.md`, and
260
+ // `**/archive/**` matched `90-archive/...`. This matcher answers "is this path
261
+ // EXCLUDED from validation", so an over-approximation is not a harmless
262
+ // widening — it is a false negative: the file is never checked and the
263
+ // validator still reports 0 errors. Anchoring narrows exclusion, i.e. it can
264
+ // only ever add files to the checked set.
257
265
  function globToRegExp(glob) {
258
266
  // Normalise input
259
267
  const g = glob.replace(/\\/g, '/');
@@ -262,12 +270,18 @@ function globToRegExp(glob) {
262
270
  const c = g[i];
263
271
  if (c === '*') {
264
272
  if (g[i + 1] === '*') {
265
- // `**` — match across path segments
266
- // Also swallow a following `/` so that `**/foo` matches `foo` at root.
267
- const nextSlash = g[i + 2] === '/';
268
- re += '(?:.*?)';
269
- if (nextSlash) i += 2;
270
- else i += 1;
273
+ if (g[i + 2] === '/') {
274
+ // `**/` — zero or more WHOLE path segments. Zero repetitions is what
275
+ // makes `**/foo` match `foo` at the vault root, and it is why the
276
+ // trailing `/` is swallowed here rather than emitted literally.
277
+ re += '(?:[^/]+/)*';
278
+ i += 2;
279
+ } else {
280
+ // Trailing or standalone `**` (e.g. `a/**`) — everything below the
281
+ // preceding literal, segment boundaries included.
282
+ re += '.*';
283
+ i += 1;
284
+ }
271
285
  } else {
272
286
  re += '[^/]*';
273
287
  }
@@ -658,6 +672,21 @@ const errors = [];
658
672
  const warnings = [];
659
673
  let filesChecked = 0;
660
674
  let filesSkippedNoFrontmatter = 0;
675
+ // The paths behind the count (#1013). A bare count made "0 errors" ambiguous:
676
+ // it meant "everything CHECKED is valid", never "everything is valid", and the
677
+ // operator had no way to see WHICH files were never checked. The list is
678
+ // emitted only by the branches that actually ran the validation pass — so an
679
+ // ABSENT `files_skipped_no_frontmatter_paths` means "never inspected" (mode=off,
680
+ // no vault) while an EMPTY one means "inspected, nothing skipped". Same
681
+ // absence-preserving contract as `scripts/lib/reconcile-nudge-banner.mjs`
682
+ // (`skippedCandidates`), which distinguishes never-checked from checked-clean.
683
+ const filesSkippedNoFrontmatterPaths = [];
684
+ // Ceiling: 50 paths keeps the envelope bounded on a vault that has never been
685
+ // backfilled (real measurement 2026-08-15: 2 of 8574 files in the live vault).
686
+ // `paths.length < files_skipped_no_frontmatter` is itself the truncation
687
+ // signal, so no extra flag is needed. Revisit if a consuming vault routinely
688
+ // reports more than 50 — then the right fix is a backfill, not a bigger cap.
689
+ const SKIPPED_PATHS_CAP = 50;
661
690
  let excludedCount = 0;
662
691
  let archivedSkippedCount = 0;
663
692
 
@@ -686,6 +715,9 @@ for (const rec of records) {
686
715
 
687
716
  if (!fm.hasFrontmatter) {
688
717
  filesSkippedNoFrontmatter++;
718
+ if (filesSkippedNoFrontmatterPaths.length < SKIPPED_PATHS_CAP) {
719
+ filesSkippedNoFrontmatterPaths.push(rel);
720
+ }
689
721
  continue;
690
722
  }
691
723
 
@@ -737,6 +769,19 @@ for (const rec of records) {
737
769
 
738
770
  const hasErrors = errors.length > 0;
739
771
 
772
+ // Silence is not success: a skipped file contributes to neither `errors` nor
773
+ // `files_checked`, so a bare "0 errors" line would let an unvalidated file pass
774
+ // unremarked at the session-end hard gate (#1013). Surface the count on stderr
775
+ // too — stdout stays pure JSON per the caller contract.
776
+ if (filesSkippedNoFrontmatter > 0) {
777
+ const shown = filesSkippedNoFrontmatterPaths.slice(0, 5).join(', ');
778
+ const more = filesSkippedNoFrontmatter - Math.min(5, filesSkippedNoFrontmatterPaths.length);
779
+ process.stderr.write(
780
+ `WARN: ${filesSkippedNoFrontmatter} file(s) NOT validated — no frontmatter: ` +
781
+ `${shown}${more > 0 ? ` (+${more} more)` : ''}\n`,
782
+ );
783
+ }
784
+
740
785
  // ── mode=baseline ─────────────────────────────────────────────────────────
741
786
  // Serialize current errors + warnings as a snapshot, then exit 0.
742
787
  if (mode === 'baseline') {
@@ -774,6 +819,7 @@ if (mode === 'diff') {
774
819
  excluded_count: excludedCount,
775
820
  archived_skipped_count: archivedSkippedCount,
776
821
  files_skipped_no_frontmatter: filesSkippedNoFrontmatter,
822
+ files_skipped_no_frontmatter_paths: filesSkippedNoFrontmatterPaths,
777
823
  errors,
778
824
  warnings,
779
825
  });
@@ -794,6 +840,7 @@ if (mode === 'diff') {
794
840
  excluded_count: excludedCount,
795
841
  archived_skipped_count: archivedSkippedCount,
796
842
  files_skipped_no_frontmatter: filesSkippedNoFrontmatter,
843
+ files_skipped_no_frontmatter_paths: filesSkippedNoFrontmatterPaths,
797
844
  errors,
798
845
  warnings,
799
846
  });
@@ -816,6 +863,7 @@ if (mode === 'diff') {
816
863
  excluded_count: excludedCount,
817
864
  archived_skipped_count: archivedSkippedCount,
818
865
  files_skipped_no_frontmatter: filesSkippedNoFrontmatter,
866
+ files_skipped_no_frontmatter_paths: filesSkippedNoFrontmatterPaths,
819
867
  });
820
868
 
821
869
  process.stderr.write(
@@ -839,6 +887,7 @@ emit({
839
887
  excluded_count: excludedCount,
840
888
  archived_skipped_count: archivedSkippedCount,
841
889
  files_skipped_no_frontmatter: filesSkippedNoFrontmatter,
890
+ files_skipped_no_frontmatter_paths: filesSkippedNoFrontmatterPaths,
842
891
  errors,
843
892
  warnings,
844
893
  });
@@ -433,7 +433,7 @@ This replaces the older prose slot "Relevant patterns from `<state-dir>/rules/`"
433
433
 
434
434
  **Gate:** runs when `.orchestrator/metrics/learnings.jsonl` exists. When it does not — or when nothing clears the confidence floor, or the corpus is unreadable — the CLI prints nothing and exits 0. Same best-effort convention as every injector above (Grounding `:307`, Frontmatter-Guard `:386`, Path-Cousin-Guard `:208`): silent no-op on any failure, **never blocks dispatch**. Any non-zero exit means "inject nothing, continue".
435
435
 
436
- **Zero new coordinator obligations.** The per-agent file scope this needs is the SAME `$AGENT_FILESCOPE_JSON` temp file the Pre-Dispatch Scope-Union Assertion (#796, see `## Scope Manifest` § 3) already requires you to write for every agent in every `Agent()` batch. Reuse that file — do not write a second one.
436
+ **Zero new coordinator obligations.** The per-agent file scope this needs is the SAME `$AGENT_FILESCOPE_JSON` — `<state-dir>/filescopes/wave-<N>/<agent-id>.json` — that `## Scope Manifest` § 3.1 already requires you to write for every agent, and that the Scope-Union Assertion (#796) then consumes. Reuse that file — do not write a second one, and never a temp copy.
437
437
 
438
438
  **Invocation:** once per agent, immediately after that agent's `$AGENT_FILESCOPE_JSON` is written, capture stdout as `$LEARNINGS_INDEX`:
439
439
 
@@ -455,6 +455,21 @@ When it is empty (no corpus, no qualifying entries, or any CLI failure), dispatc
455
455
 
456
456
  **Instrumentation (why this one is measurable and its neighbours are not).** The rule injection above is a SHOULD and emits no signal either way, so "did the coordinator actually inject?" has been unanswerable after the fact — a gap the #1014 discovery wave had to leave open. This CLI emits `orchestrator.learnings.index.injected` to `.orchestrator/metrics/events.jsonl` (via `scripts/emit-event.mjs`, the canonical `emitEvent()` path — the same route `scripts/compute-grounding-injection.sh` uses for `orchestrator.grounding.injected`), carrying `count`, `scope_matched`, `global_count`, `candidates`, `truncated`, `bytes`, and `scope_source`. The before/after measurement is therefore a fact in the event log, not a matter of prose compliance. Emission is best-effort and suppressible with `--no-event`; a failed emit never blocks dispatch.
457
457
 
458
+ #### Pre-Dispatch: File-Scope Injection (#1020)
459
+
460
+ > **Read this first — this block is PER AGENT, unlike `#### Pre-Dispatch: Glob-Scoped Rule Injection (#336/#694)` above, which states "Per-wave scoping (not per-agent): the rule set is computed ONCE per wave".** Model it on **Pre-Dispatch Grounding Injection (#85)** — same cadence, same per-agent source. This injector legitimately has BOTH cadences (per-agent for the brief, per-wave for the § Scope Manifest union), which is exactly what makes the collapse tempting: reuse ONE agent's block for the whole batch and every agent reads the territory of every OTHER agent as its own. Deconfliction would then be **lifted rather than enforced**, and the double assignment § 3.2 exists to catch becomes invisible in the one channel where an agent could still notice it.
461
+
462
+ **Invocation:** for each agent, read `<state-dir>/filescopes/wave-<N>/<agent-id>.json` (= `$AGENT_FILESCOPE_JSON`) — the SAME file written in § Scope Manifest 3.1, not a re-derivation from the session plan and not a temp copy — and prepend its entries to that agent's prompt, one path per line:
463
+
464
+ FILE-SCOPE — exactly these:
465
+ ```
466
+ <one path or glob per line, verbatim from that agent's scope file>
467
+ ```
468
+
469
+ Marker line plus fenced block, in that order: `hooks/pre-task-scope-disjoint.mjs` extracts the scope from the prompt by finding the marker and taking the FIRST fenced block after it, so this shape is what makes an agent's declared territory machine-readable at dispatch time. An unparseable or absent block resolves to ALLOW there, so a malformed injection degrades to today's behaviour rather than blocking dispatch. When the scope file is missing or empty (Discovery waves), inject nothing and dispatch unchanged.
470
+
471
+ > **Registration note.** That hook was armed in `hooks/hooks.json` on 2026-08-14, after a green Full Gate. Its `PreToolUse` matcher is **`Agent`** — measured over 12 archived transcripts of this repo, `Agent` accounts for 147 of 147 dispatch `tool_use` blocks. A `Task` matcher would hit the unrelated todo family (`TaskCreate`/`TaskUpdate`/`TaskGet`/…) and never once fire on a dispatch: armed and inert, the failure mode that reads as done. It is deliberately absent from `hooks-codex.json` / `hooks-cursor.json` / `hooks-pi.json` — those platforms have no `Agent` dispatch tool, so the asymmetry is registered in `DOCUMENTED_ASYMMETRIES` rather than papered over with a matcher that can never fire.
472
+
458
473
  #### Structured Reasoning (STATE:/PLAN:) — opt-in via `reasoning-output: true` (#79)
459
474
 
460
475
  When `$CONFIG.reasoning-output` is `true`, append the following block to every agent prompt. The pattern is adapted from the BitGN PAC Agent's Soft-SGR: short structured transparency lines before tool invocations, without forcing structured output. Leave the block OUT when the flag is `false` (default) — this preserves exact legacy prompt behavior.
@@ -1099,19 +1114,56 @@ Before each wave dispatch:
1099
1114
  ```
1100
1115
  The `gates` field (optional) mirrors `enforcement-gates` from Session Config (#77). When present, hooks check each gate individually via `gate_enabled()`. Missing gate entries default to enabled, preserving default behavior.
1101
1116
  2. Validate by piping through `node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs"` (where `$PLUGIN_ROOT` is `$CLAUDE_PLUGIN_ROOT`, `$CODEX_PLUGIN_ROOT`, or `$CURSOR_RULES_DIR` per platform — see `skills/_shared/config-reading.md`). If validation fails (exit 1), fix the JSON based on stderr errors and retry.
1102
- 3. `allowedPaths` is the UNION of all agent file scopes for this wave
1103
- To compute `allowedPaths`: read each agent's specification from the session plan. Each agent lists its "Files:" scope (e.g., `skills/session-end/SKILL.md`, `scripts/*.sh`). Collect all file paths and glob patterns from all agents in this wave into a single flat array. Deduplicate entries. If an agent's scope uses globs (e.g., `scripts/*.sh`), include the glob pattern as-is — the enforcement hook resolves globs at check time.
1117
+ 3. **`allowedPaths` is COMPUTED from one canonical declaration array — never hand-transcribed (#1020/#1083).** Transcribing either declaration shape or the union by hand produced scope divergences. Globs stay verbatim (`scripts/*.sh`) — the enforcement hook resolves them at check time.
1104
1118
 
1105
- **Test-Sibling Expansion (#970):** an `allowedPaths` entry that names a production file but NOT its test sibling makes the wave's own regression test unwritable — the scope guard then mechanically enforces exactly the inconsistency the quality gate exists to catch. Cross-repo evidence, three occurrences in ONE session: a migrations glob without the SQL-test directory (the regression test could not be written); a lone `.actions.ts` file (the wave's cross-tenant security test stayed red); a dead-export deletion whose importing test lay outside every scope (the suite ended red). Do NOT hand-derive the sibling paths — expand through the shared helper so the hook, the validator and this prose state one rule:
1119
+ **3.1 — materialize both declaration shapes once.** Build one JSON array from the session plan, one `{id, files}` record for every agent plus exactly one `coordinator` record for the coordinator's planned direct edits. `files` arrays, their entries and their order are the plan's verbatim declarations. Materialize it ONCE and capture the aggregate-sidecar path:
1106
1120
 
1107
- ```js
1108
- import { expandTestSiblings } from '$PLUGIN_ROOT/scripts/lib/scope-gate.mjs';
1121
+ ```bash
1122
+ WAVE_SCOPE_RECORDS='[{"id":"W3-I1","files":["scripts/example.mjs"]},{"id":"coordinator","files":["skills/wave-executor/wave-loop.md"]}]'
1123
+ WAVE_SCOPES_SIDECAR="$(
1124
+ printf '%s' "$WAVE_SCOPE_RECORDS" | node "$PLUGIN_ROOT/scripts/materialize-wave-scope.mjs" \
1125
+ --state-dir "$STATE_DIR" --wave "$WAVE"
1126
+ )"
1127
+ [ -n "$WAVE_SCOPES_SIDECAR" ] || { echo "materialize-wave-scope produced no sidecar path" >&2; exit 1; }
1128
+ ```
1129
+
1130
+ The non-empty check is not decoration. The materializer sends every diagnostic
1131
+ to stderr, so a failure leaves `$WAVE_SCOPES_SIDECAR` empty, and an empty path
1132
+ is what step 3.2 would then pass to `--assert-disjoint`. That combination used
1133
+ to exit 0 with the collision gate never run — the same signal-free-ALLOW shape
1134
+ #1083 exists to close. `validate-wave-scope.mjs` now refuses an empty flag
1135
+ value as well, so this guard and that refusal are belt and braces.
1136
+
1137
+ `materialize-wave-scope.mjs` validates the COMPLETE input before writing; it writes `<state-dir>/filescopes/wave-<N>/<agent-id>.json` as each bare `files` array first, then writes `<state-dir>/filescopes/wave-<N>.scopes.json` as the unchanged aggregate record array last. Its human stdout is only that final sidecar path, so the command substitution above is the canonical `$WAVE_SCOPES_SIDECAR`. On error, do not continue with a partial declaration set; correct the plan and run the one command again.
1138
+
1139
+ The per-agent path IS `$AGENT_FILESCOPE_JSON` — the same file `--assert-subset` (#796 below), Grounding Injection (#85), the Learnings-Index (#1014) and File-Scope Injection (#1020) consume. Never write a `$TMPDIR` copy: it degrades to a signal-free allow when an injector cannot find the addressable wave-keyed file. The coordinator's record is materialized as `coordinator.json` and included in the aggregate, so its direct edits are covered by the two checks below.
1140
+
1141
+ > **`<state-dir>/filescopes/` is control state, like `wave-scope.json` itself — never a wave territory.** Step 3.1 necessarily runs before the union exists, so writing these files reports `bash-write-verify: N file(s) changed by a Bash call OUTSIDE the wave's allowedPaths` naming `filescopes/wave-<N>/*.json`. Expected once per wave rollover at this step; it is information, not a scope violation. Never widen `allowedPaths` to silence it — that would grant agents write access to the deconfliction record itself.
1109
1142
 
1110
- // unionScopes: the deduplicated flat array from the paragraph above.
1111
- // role: this wave's role, verbatim from the session plan — the helper GATES on it.
1112
- const allowedPaths = expandTestSiblings(unionScopes, { role });
1143
+ **3.2 — assert disjointness BEFORE computing the union.** The materialized aggregate is an ARRAY of `{id, files}` records (never an object map: a duplicated agent id must stay visible), including `coordinator.json`. Run:
1144
+
1145
+ ```bash
1146
+ node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs" \
1147
+ --assert-disjoint "$WAVE_SCOPES_SIDECAR" < <state-dir>/wave-scope.json
1113
1148
  ```
1114
1149
 
1150
+ Exit 1 (one stderr message per collision) means two agents were handed the same file: fix the session plan, re-materialize, re-assert. Never widen the union to make it pass. This runs **before** 3.3 because a union computed over colliding scopes launders the defect into the very artefact meant to prevent it — `allowedPaths` then grants the file and every later gate sees a legal write.
1151
+
1152
+ **3.3 — compute the union.** `--union` is a QUERY MODE that still requires a schema-valid manifest on stdin, so write the skeleton first with `"allowedPaths": []`, then:
1153
+
1154
+ ```bash
1155
+ node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs" \
1156
+ --union "$WAVE_SCOPES_SIDECAR" < <state-dir>/wave-scope.json
1157
+ ```
1158
+
1159
+ It prints the computed `allowedPaths` array as JSON on stdout **instead of** the manifest echo — one JSON document per run, the flag decides which. Insert that array as `allowedPaths`, then write the final `wave-scope.json`. It already applies the Test-Sibling Expansion below (`expandTestSiblings(unionFileScopes(scopes), { role })`, role read from the manifest), so do not also run the helper by hand.
1160
+
1161
+ **Artifact production, disjointness and union computation are mechanized. Native prompt injection is a separate follow-up.** The materializer creates the durable declarations; the validator proves disjointness and computes the union. It does not install or prove the platform's prompt-injection transport, which remains independently responsible for reading `$AGENT_FILESCOPE_JSON` before dispatch.
1162
+
1163
+ **The `--assert-subset` assertion (#796, below) stays unchanged and keeps running.** It checks a DIFFERENT property — each agent's scope ⊆ the union — and a double assignment is structurally invisible to it: a file claimed twice is a subset twice over. `--assert-disjoint` is an addition, never a replacement.
1164
+
1165
+ **Test-Sibling Expansion (#970):** an `allowedPaths` entry that names a production file but NOT its test sibling makes the wave's own regression test unwritable — the scope guard then mechanically enforces exactly the inconsistency the quality gate exists to catch. Cross-repo evidence, three occurrences in ONE session: a migrations glob without the SQL-test directory (the regression test could not be written); a lone `.actions.ts` file (the wave's cross-tenant security test stayed red); a dead-export deletion whose importing test lay outside every scope (the suite ended red). Do NOT hand-derive the sibling paths — step 3.3's `--union` runs `expandTestSiblings(…, { role })` for you, so the hook, the validator and this prose state one rule.
1166
+
1115
1167
  The helper is pure (same input → same output, no filesystem writes) and is also surfaced by `scripts/validate-wave-scope.mjs`. **The role decides, inside the helper** — `scripts/lib/scope-gate.mjs` `TEST_SIBLING_EXPANSION_ROLES` is THE list (currently `Impl-Core`, `Impl-Polish` — exactly where the incident occurred), and #5/#6 below describe that gate rather than restating it. Pass the role string; do not pre-filter by role in prose, and do not hand-roll the equivalent `{ enabled: … }`. Matching is trimmed + case-insensitive, so `impl-core` behaves as `Impl-Core`.
1116
1168
 
1117
1169
  > **Fail-closed:** an ABSENT or unrecognised `role` does **not** expand. Omitting it fails loudly (an agent's write to its own test is blocked, recoverable by one re-union); the opposite default would silently hand a Quality phase-1 simplification agent write access to the suite. `{ enabled: false }` is the unconditional opt-out and `{ enabled: true }` the explicit opt-in — both override the role.
@@ -1123,11 +1175,11 @@ Before each wave dispatch:
1123
1175
  **The sibling rule is repo-configurable, not a hardcoded layout.** THIS repo has zero `__tests__/` directories and no co-located tests; consumer-repo shapes (`<file>.test.*` beside the source, `<dir>/__tests__/**`, `supabase/migrations/** → supabase/tests/**`) are configured per repo and do not apply here.
1124
1176
 
1125
1177
  Three ordering constraints, all load-bearing:
1126
- - Expand each agent's `fileScope` **before** the overlap/deconfliction check, so a test file newly shared by two agents is visible to the check that exists to catch that collision.
1178
+ - The deconfliction check (3.2) runs on the DECLARED per-agent scopes, **before** the union expands anything. Named ceiling: two agents whose production files share a basename receive the same emitted sibling glob, which a declared-scope check cannot see — revisit if a wave is ever scoped by basename family instead of by directory.
1127
1179
  - Expand **before** `wave-scope.json` is written, in ONE pass. `hooks/post-bash-write-verify.mjs` fingerprints `allowedPaths` via `scopeSignature()` and fires a control notice on change, so a later mutation reads as tampering.
1128
1180
  - Skip **absolute** entries entirely — expanding a Gate-5b out-of-repo grant would sprout a synthetic `tests/**` sibling outside the repo.
1129
1181
 
1130
- **Pre-Dispatch Scope-Union Assertion (#796):** `wave-scope.json` is GLOBAL per wave — `hooks/enforce-scope.mjs` Gate 7 checks EVERY agent against the same `allowedPaths` union, so a union that (re)written for only ONE agent silently denies its siblings' legitimate writes. Before each `Agent()` batch, mechanically assert — for EVERY agent in the batch — that its fileScope ⊆ `wave-scope.allowedPaths`. Write the agent's "Files:" scope as a JSON array of strings to a temp file (`$AGENT_FILESCOPE_JSON`) and run:
1182
+ **Pre-Dispatch Scope-Union Assertion (#796):** `wave-scope.json` is GLOBAL per wave — `hooks/enforce-scope.mjs` Gate 7 checks EVERY agent against the same `allowedPaths` union, so a union that (re)written for only ONE agent silently denies its siblings' legitimate writes. Before each `Agent()` batch, mechanically assert — for EVERY agent in the batch — that its fileScope ⊆ `wave-scope.allowedPaths`. `$AGENT_FILESCOPE_JSON` is that agent's § 3.1 file — `<state-dir>/filescopes/wave-<N>/<agent-id>.json`, already written above and shared with every other consumer. Do not re-write it to a temp path here (§ 3.1 says why that degrades silently); just run:
1131
1183
 
1132
1184
  ```bash
1133
1185
  node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs" \
@@ -1158,4 +1210,4 @@ Before each wave dispatch:
1158
1210
  5. Delete `<state-dir>/wave-scope.json`
1159
1211
  6. Write Phase 2 wave-scope.json with test file allowedPaths (`**/*.test.*`, `**/*.spec.*`, `**/__tests__/**`)
1160
1212
  7. Dispatch test/review agents
1161
- 7. After the final wave completes, delete `<state-dir>/wave-scope.json` (cleanup)
1213
+ 7. After the final wave completes, delete `<state-dir>/wave-scope.json` (cleanup). Delete `<state-dir>/filescopes/` in the same step — the per-agent scope files (§ 3.1) are wave-local working state, and a stale `wave-<N>/` directory left behind is a scope claim nobody re-verified.
@@ -163,11 +163,11 @@ Any hit → surface to user via AUQ:
163
163
  ```
164
164
  AskUserQuestion({
165
165
  questions: [{
166
- question: "The placeholder linter found forbidden strings in the draft plan:\n\n[list each hit with Task number, Step number, and matched text]\n\nHow do you want to proceed?",
167
- header: "Placeholder Linter",
166
+ question: "The draft plan still contains forbidden placeholder strings. How do you want to proceed?",
167
+ header: "Platzhalter",
168
168
  options: [
169
- { label: "Fix automatically (Recommended)", description: "I will resolve each hit by filling in the missing specifics before writing the plan." },
170
- { label: "Show me each hit interactively", description: "Walk me through each one so I can provide the missing detail." }
169
+ { label: "Fix automatically (Recommended)", description: "I fill in the missing specifics myself, then write the plan — fastest, and you review the result at hand-off.", preview: "[each hit: Task number, Step number, matched text]" },
170
+ { label: "Show me each hit interactively", description: "Walk me through each one so I can provide the missing detail.", preview: "[the same hits, one at a time, starting with the first]" }
171
171
  ],
172
172
  multiSelect: false
173
173
  }]
@@ -202,8 +202,8 @@ Present the plan path and task count to the user via AUQ:
202
202
  ```
203
203
  AskUserQuestion({
204
204
  questions: [{
205
- question: "Executable plan written to docs/plans/YYYY-MM-DD-<slug>.md ([N] tasks, ~[total] min estimated).\n\nHow do you want to execute it?",
206
- header: "Plan Hand-off",
205
+ question: "Plan written to docs/plans/YYYY-MM-DD-<slug>.md — [N] tasks, ~[total] min. How to execute?",
206
+ header: "Plan fertig",
207
207
  options: [
208
208
  { label: "Dispatch via wave-executor (Recommended)", description: "Parallel agents execute all tasks simultaneously. Fastest for file-disjoint tasks." },
209
209
  { label: "Execute coordinator-direct", description: "One task at a time in this session. Safer for tasks with shared state." },
@@ -1,114 +0,0 @@
1
- /**
2
- * Mission-Status Enum + Transition Validator (issue #340).
3
- *
4
- * Pure ESM, no I/O, no external dependencies. All functions are deterministic.
5
- *
6
- * Five-state lifecycle for wave-plan items:
7
- *
8
- * brainstormed → validated → in-dev → testing → completed
9
- *
10
- * Rollbacks to `brainstormed` are permitted from any state (user can reset scope).
11
- * Idempotent self-transitions are always allowed.
12
- * All other transitions are rejected.
13
- */
14
-
15
- /**
16
- * Canonical ordered list of valid mission-status values.
17
- *
18
- * @type {readonly string[]}
19
- */
20
- export const MISSION_STATUS_VALUES = Object.freeze([
21
- 'brainstormed',
22
- 'validated',
23
- 'in-dev',
24
- 'testing',
25
- 'completed',
26
- ]);
27
-
28
- /**
29
- * Allowed forward transitions (excluding self-transitions and rollback to
30
- * 'brainstormed', which are handled separately in isValidMissionStatusTransition).
31
- *
32
- * @type {ReadonlyMap<string, string>}
33
- */
34
- const FORWARD_TRANSITIONS = new Map([
35
- ['brainstormed', 'validated'],
36
- ['validated', 'in-dev'],
37
- ['in-dev', 'testing'],
38
- ['testing', 'completed'],
39
- ]);
40
-
41
- /**
42
- * Returns true when `value` is one of the five canonical status strings.
43
- *
44
- * @param {unknown} value
45
- * @returns {boolean}
46
- */
47
- export function isValidMissionStatus(value) {
48
- return typeof value === 'string' && MISSION_STATUS_VALUES.includes(value);
49
- }
50
-
51
- /**
52
- * Returns true when the transition from `from` → `to` is permitted.
53
- *
54
- * Allowed transitions:
55
- * - Forward: brainstormed→validated, validated→in-dev, in-dev→testing, testing→completed
56
- * - Idempotent: any state → itself
57
- * - Rollback: any state → brainstormed
58
- *
59
- * All other transitions return false.
60
- *
61
- * @param {string} from - current status value
62
- * @param {string} to - target status value
63
- * @returns {boolean}
64
- */
65
- export function isValidMissionStatusTransition(from, to) {
66
- if (!isValidMissionStatus(from) || !isValidMissionStatus(to)) return false;
67
- // Idempotent self-transition
68
- if (from === to) return true;
69
- // Rollback to brainstormed from any state
70
- if (to === 'brainstormed') return true;
71
- // Forward transition
72
- return FORWARD_TRANSITIONS.get(from) === to;
73
- }
74
-
75
- /**
76
- * Validates a mission-status entry object.
77
- *
78
- * Expected shape:
79
- * { id: string, task: string, wave: number, status: <MISSION_STATUS_VALUES> }
80
- *
81
- * @param {unknown} obj
82
- * @returns {{ ok: boolean, errors: string[] }}
83
- */
84
- export function validateMissionStatusEntry(obj) {
85
- const errors = [];
86
- if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
87
- return { ok: false, errors: ['entry must be a non-null, non-array object'] };
88
- }
89
- const entry = /** @type {Record<string, unknown>} */ (obj);
90
-
91
- // id — non-empty string
92
- if (typeof entry.id !== 'string' || entry.id.trim() === '') {
93
- errors.push('id must be a non-empty string');
94
- }
95
-
96
- // task — non-empty string
97
- if (typeof entry.task !== 'string' || entry.task.trim() === '') {
98
- errors.push('task must be a non-empty string');
99
- }
100
-
101
- // wave — integer >= 1
102
- if (!Number.isInteger(entry.wave) || /** @type {number} */ (entry.wave) < 1) {
103
- errors.push('wave must be a positive integer');
104
- }
105
-
106
- // status — one of MISSION_STATUS_VALUES
107
- if (!isValidMissionStatus(entry.status)) {
108
- errors.push(
109
- `status must be one of [${MISSION_STATUS_VALUES.join(', ')}], got: ${JSON.stringify(entry.status)}`,
110
- );
111
- }
112
-
113
- return { ok: errors.length === 0, errors };
114
- }
@@ -1,8 +0,0 @@
1
- # Sample Baseline Rule (test fixture)
2
-
3
- This file is used by scripts/tests/fetch-baseline.bats as the mocked response body
4
- for the GitLab API raw-file endpoint. It has no real content — presence is enough
5
- to verify the fetch + cache path.
6
-
7
- - Rule ID: TEST-001
8
- - Purpose: end-to-end fetch verification
@@ -1,8 +0,0 @@
1
- ---
2
- id: bad-archived
3
- type: memo
4
- created: not-a-date
5
- updated: not-a-date
6
- ---
7
-
8
- Bad frontmatter — but inside 90-archive, so must be ignored.
@@ -1,8 +0,0 @@
1
- ---
2
- id: live-note
3
- type: note
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- ---
7
-
8
- Lives outside archive — must be validated.
@@ -1,8 +0,0 @@
1
- ---
2
- id: bad-type
3
- type: memo
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- ---
7
-
8
- `type: memo` is not in the enum.
@@ -1,8 +0,0 @@
1
- ---
2
- id: good-note
3
- type: note
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- ---
7
-
8
- OK.
@@ -1,8 +0,0 @@
1
- ---
2
- id: bogus
3
- type: memo
4
- created: not-a-date
5
- updated: not-a-date
6
- ---
7
-
8
- If the validator crawls .obsidian/, this file would FAIL. It must be excluded.
@@ -1,10 +0,0 @@
1
- ---
2
- id: projects-baseline
3
- type: project
4
- created: 2026-04-01
5
- updated: 2026-04-13
6
- status: active
7
- tags: [meta, infra]
8
- ---
9
-
10
- Project overview.
@@ -1,8 +0,0 @@
1
- ---
2
- id: daily-2026-04-13
3
- type: daily
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- ---
7
-
8
- Today: wrote vault-sync validator.
@@ -1,3 +0,0 @@
1
- # Clean Vault
2
-
3
- No frontmatter here — README-style files must be skipped, not errored.
@@ -1,11 +0,0 @@
1
- ---
2
- id: hello-world
3
- type: note
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- title: Hello World
7
- tags: [meta, intro]
8
- status: active
9
- ---
10
-
11
- A minimal valid note that links to [[daily-2026-04-13]] and [[projects-baseline]].
@@ -1,9 +0,0 @@
1
- ---
2
- id: has-dangling
3
- type: note
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- ---
7
-
8
- This note links to [[non-existent-target]] which should be flagged as a warning.
9
- It also links to [[real-target]] which exists.
@@ -1,8 +0,0 @@
1
- ---
2
- id: real-target
3
- type: note
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- ---
7
-
8
- Real.
@@ -1,7 +0,0 @@
1
- ---
2
- type: note
3
- created: 2026-04-13
4
- updated: 2026-04-13
5
- ---
6
-
7
- Missing `id`.
@@ -1,9 +0,0 @@
1
- ---
2
- id: daily-2026-04-13
3
- type: daily
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- tags: [area/infrastructure/cleanup, meta/session-log]
7
- ---
8
-
9
- Daily note using deep-nested Obsidian tags.
@@ -1,11 +0,0 @@
1
- ---
2
- id: nested-tags-note
3
- type: note
4
- created: 2026-04-13
5
- updated: 2026-04-13
6
- title: Nested Tags Note
7
- tags: [meta/schema, status/in-progress, area/infrastructure, session/2026-04-13]
8
- status: active
9
- ---
10
-
11
- A note that uses Obsidian nested-tag conventions (slash-separated hierarchy).
@@ -1,3 +0,0 @@
1
- # README only
2
-
3
- No frontmatter. Must be counted as `files_skipped_no_frontmatter` and not error.
@@ -1,3 +0,0 @@
1
- # Map of Content
2
-
3
- No frontmatter — this is a plain index.