session-orchestrator 5.3.0 → 5.5.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 (260) hide show
  1. package/.agents/skills/claude-md-drift-check/SKILL.md +1 -1
  2. package/.agents/skills/test-audit/SKILL.md +21 -0
  3. package/.agents/skills/test-audit/agents/openai.yaml +5 -0
  4. package/.claude-plugin/marketplace.json +1 -1
  5. package/.claude-plugin/plugin.json +1 -1
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
  8. package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
  9. package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
  10. package/.cursor/commands/test-audit.md +14 -0
  11. package/.cursor/rules/060-evolve.mdc +28 -13
  12. package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
  13. package/.cursor/skills/test-audit/SKILL.md +12 -0
  14. package/.cursor-plugin/plugin.json +1 -1
  15. package/.orchestrator/policy/blocked-commands.json +1 -1
  16. package/AGENTS.md +9 -31
  17. package/CHANGELOG.md +162 -0
  18. package/README.md +14 -10
  19. package/docs/agent-authoring.md +2 -2
  20. package/docs/baseline.md +10 -1
  21. package/docs/ci-setup.md +154 -4
  22. package/docs/components.md +11 -11
  23. package/docs/events-schema.md +25 -19
  24. package/docs/install.md +14 -2
  25. package/docs/migration-v4.md +24 -17
  26. package/docs/persona-panel.md +5 -4
  27. package/docs/plugin-architecture-v3.md +1 -1
  28. package/docs/rule-authoring.md +27 -12
  29. package/docs/scope-collision-guard.md +40 -16
  30. package/docs/session-config-reference.md +61 -12
  31. package/docs/session-config-template.md +11 -2
  32. package/docs/vault-docs-architecture.md +8 -4
  33. package/hooks/_lib/hook-import-set.json +42 -29
  34. package/hooks/_lib/lock-bootstrap.mjs +118 -26
  35. package/hooks/config-protection.mjs +1 -1
  36. package/hooks/enforce-commands.mjs +28 -13
  37. package/hooks/enforce-scope.mjs +201 -23
  38. package/hooks/hooks-codex.json +1 -1
  39. package/hooks/hooks-cursor.json +5 -0
  40. package/hooks/hooks-pi.json +5 -0
  41. package/hooks/hooks.json +6 -1
  42. package/hooks/on-session-start.mjs +57 -2
  43. package/hooks/on-stop.mjs +70 -168
  44. package/hooks/post-bash-write-verify.mjs +98 -17
  45. package/hooks/post-edit-validate.mjs +15 -5
  46. package/hooks/post-tool-batch-wave-signal.mjs +32 -152
  47. package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
  48. package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
  49. package/hooks/pre-bash-staging-fence.mjs +29 -8
  50. package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
  51. package/hooks/pre-task-scope-disjoint.mjs +1143 -279
  52. package/hooks/subagent-telemetry.mjs +96 -19
  53. package/hooks/wave-scope-commit-guard.mjs +168 -72
  54. package/package.json +2 -6
  55. package/pi/prompts/test-audit.md +12 -0
  56. package/rules/README.md +6 -1
  57. package/rules/_index.md +3 -3
  58. package/rules/always-on/bash-harness-pitfalls.md +7 -1
  59. package/rules/opt-in-domain/prompt-caching.md +5 -1
  60. package/rules/opt-in-stack/backend-data.md +9 -1
  61. package/rules/opt-in-stack/backend.md +7 -1
  62. package/rules/opt-in-stack/frontend.md +6 -1
  63. package/rules/opt-in-stack/security-web.md +7 -1
  64. package/rules/opt-in-stack/swift.md +5 -1
  65. package/scripts/apply-session-learnings.mjs +513 -0
  66. package/scripts/archive-closed-prds.mjs +40 -12
  67. package/scripts/autopilot.mjs +57 -54
  68. package/scripts/backfill-abandoned-sessions.mjs +81 -6
  69. package/scripts/backfill-learnings-from-vault.mjs +49 -22
  70. package/scripts/baseline-archetypes.mjs +1 -1
  71. package/scripts/check-package-manager.mjs +62 -5
  72. package/scripts/codex-install.mjs +3 -0
  73. package/scripts/cursor-install.mjs +47 -6
  74. package/scripts/emit-session.mjs +298 -2
  75. package/scripts/export-hw-learnings.mjs +40 -10
  76. package/scripts/fleet-checkin.mjs +170 -0
  77. package/scripts/lib/agent-frontmatter.mjs +3 -3
  78. package/scripts/lib/auto-dream.mjs +7 -6
  79. package/scripts/lib/autopilot/flags.mjs +6 -1
  80. package/scripts/lib/autopilot/loop.mjs +32 -13
  81. package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
  82. package/scripts/lib/baseline-archetypes.mjs +41 -22
  83. package/scripts/lib/blocked-commands-policy.mjs +40 -0
  84. package/scripts/lib/common.mjs +41 -0
  85. package/scripts/lib/config/host-paths.mjs +79 -0
  86. package/scripts/lib/config/persona-gate-wave.mjs +3 -3
  87. package/scripts/lib/config/reaper.mjs +7 -6
  88. package/scripts/lib/config/vault-integration.mjs +151 -16
  89. package/scripts/lib/config-schema.mjs +13 -0
  90. package/scripts/lib/config.mjs +75 -10
  91. package/scripts/lib/eval/engine.mjs +13 -0
  92. package/scripts/lib/events-retention-banner.mjs +393 -0
  93. package/scripts/lib/events-rotation.mjs +21 -6
  94. package/scripts/lib/events.mjs +72 -5
  95. package/scripts/lib/fleet-protocol.mjs +288 -0
  96. package/scripts/lib/gates/gate-baseline.mjs +13 -3
  97. package/scripts/lib/gates/gate-full.mjs +7 -2
  98. package/scripts/lib/gates/gate-helpers.mjs +93 -2
  99. package/scripts/lib/gates/gate-incremental.mjs +23 -22
  100. package/scripts/lib/gates/gate-per-file.mjs +16 -7
  101. package/scripts/lib/hardening.mjs +1 -0
  102. package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
  103. package/scripts/lib/instruction-budget-guard.mjs +77 -11
  104. package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
  105. package/scripts/lib/learnings/io.mjs +274 -37
  106. package/scripts/lib/learnings/schema.mjs +163 -15
  107. package/scripts/lib/learnings/sizing-subject.mjs +2 -2
  108. package/scripts/lib/lock-reaper.mjs +38 -14
  109. package/scripts/lib/maintenance-due-banner.mjs +23 -8
  110. package/scripts/lib/memory-proposals/schema.mjs +11 -5
  111. package/scripts/lib/named-baseline-resolver.mjs +53 -28
  112. package/scripts/lib/named-vault-resolver.mjs +147 -5
  113. package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
  114. package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
  115. package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
  116. package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
  117. package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
  118. package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
  119. package/scripts/lib/orphan-reaper/scan.mjs +510 -0
  120. package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
  121. package/scripts/lib/orphan-reaper.mjs +47 -1315
  122. package/scripts/lib/owner-yaml.mjs +104 -6
  123. package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
  124. package/scripts/lib/platform.mjs +206 -2
  125. package/scripts/lib/plugin-update-banner.mjs +3 -1
  126. package/scripts/lib/process-group.mjs +134 -22
  127. package/scripts/lib/project-hygiene.mjs +21 -1
  128. package/scripts/lib/quality-gate.mjs +45 -5
  129. package/scripts/lib/resource-probe.mjs +1 -1
  130. package/scripts/lib/rule-loader.mjs +100 -62
  131. package/scripts/lib/rules-sync.mjs +27 -12
  132. package/scripts/lib/scope-baseline.mjs +119 -29
  133. package/scripts/lib/scope-echo.mjs +7 -0
  134. package/scripts/lib/scope-gate.mjs +194 -27
  135. package/scripts/lib/session-close-backfill.mjs +129 -4
  136. package/scripts/lib/session-end/tail-runner.mjs +10 -5
  137. package/scripts/lib/session-lock-shape.mjs +6 -3
  138. package/scripts/lib/session-lock.mjs +320 -67
  139. package/scripts/lib/session-record-repair.mjs +183 -26
  140. package/scripts/lib/session-registry.mjs +67 -17
  141. package/scripts/lib/session-schema/constants.mjs +30 -4
  142. package/scripts/lib/session-schema/validator.mjs +28 -1
  143. package/scripts/lib/session-start-probes.mjs +242 -68
  144. package/scripts/lib/session-token-rollup.mjs +252 -54
  145. package/scripts/lib/sessions-canonical.mjs +100 -9
  146. package/scripts/lib/sessions-staleness-banner.mjs +3 -1
  147. package/scripts/lib/sh-quote.mjs +29 -0
  148. package/scripts/lib/subagents-schema.mjs +30 -3
  149. package/scripts/lib/tail-window.mjs +27 -3
  150. package/scripts/lib/telemetry/pricing.mjs +121 -71
  151. package/scripts/lib/telemetry/sync.mjs +17 -0
  152. package/scripts/lib/tests-src-ratio.mjs +215 -23
  153. package/scripts/lib/tmux-layout/layouts.mjs +1 -11
  154. package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
  155. package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
  156. package/scripts/lib/ux-grill/reconcile.mjs +1 -1
  157. package/scripts/lib/validate/check-agents.mjs +1 -1
  158. package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
  159. package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
  160. package/scripts/lib/validate/check-test-value-bans.mjs +388 -27
  161. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  162. package/scripts/lib/validate/check-unwired-features.mjs +1 -1
  163. package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
  164. package/scripts/lib/validate-vendored-rules.mjs +206 -81
  165. package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
  166. package/scripts/lib/vault-mirror/namespace.mjs +47 -26
  167. package/scripts/lib/vault-mirror/process.mjs +350 -62
  168. package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
  169. package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
  170. package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
  171. package/scripts/lib/vault-mirror/telemetry.mjs +25 -11
  172. package/scripts/lib/vault-status/board-writer.mjs +53 -12
  173. package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
  174. package/scripts/lib/vcs-repo-spec.mjs +8 -0
  175. package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
  176. package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
  177. package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
  178. package/scripts/lib/wave-resource-gate.mjs +34 -8
  179. package/scripts/materialize-wave-scope.mjs +7 -4
  180. package/scripts/memory-propose.mjs +18 -5
  181. package/scripts/release-session-lock.mjs +2 -1
  182. package/scripts/release.mjs +11 -3
  183. package/scripts/repair-invalid-sessions.mjs +37 -4
  184. package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
  185. package/scripts/run-quality-gate.mjs +331 -307
  186. package/scripts/self-update.mjs +229 -0
  187. package/scripts/site-numbers.mjs +163 -5
  188. package/scripts/sweep-expired-learnings.mjs +603 -20
  189. package/scripts/sweep-expired-rules.mjs +2 -1
  190. package/scripts/validate-plugin.mjs +7 -0
  191. package/scripts/validate-wave-scope.mjs +21 -2
  192. package/scripts/vault-backfill.mjs +50 -37
  193. package/scripts/vault-integration-watcher.mjs +2 -1
  194. package/scripts/vault-mirror.mjs +238 -75
  195. package/scripts/wave-scope-binding.mjs +2 -1
  196. package/skills/_shared/config-reading.md +3 -3
  197. package/skills/_shared/fleet-protocol.md +134 -0
  198. package/skills/_shared/state-ownership.md +41 -12
  199. package/skills/autopilot/SKILL.md +25 -11
  200. package/skills/bootstrap/SKILL.md +6 -4
  201. package/skills/bootstrap/_shared-template.md +97 -2
  202. package/skills/bootstrap/fast-template.md +4 -1
  203. package/skills/bootstrap/private-contract.md +2 -1
  204. package/skills/bootstrap/public-fallback.md +6 -3
  205. package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
  206. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
  207. package/skills/bootstrap/standard-template.md +2 -0
  208. package/skills/claude-md-drift-check/SKILL.md +10 -10
  209. package/skills/claude-md-drift-check/checker.mjs +26 -18
  210. package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
  211. package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
  212. package/skills/discovery/probes/vault-staleness.mjs +9 -3
  213. package/skills/docs-orchestrator/SKILL.md +12 -5
  214. package/skills/docs-orchestrator/audience-mapping.md +5 -3
  215. package/skills/evolve/SKILL.md +13 -4
  216. package/skills/evolve/references/evolve-analyze-mode.md +42 -18
  217. package/skills/memory-cleanup/SKILL.md +1 -1
  218. package/skills/persona-panel/SKILL.md +2 -2
  219. package/skills/persona-panel/persona-format.md +2 -2
  220. package/skills/persona-panel/presets/designer-lens.md +1 -1
  221. package/skills/persona-panel/presets/engineer-lens.md +1 -1
  222. package/skills/persona-panel/presets/pm-lens.md +1 -1
  223. package/skills/plan/mode-retro.md +19 -4
  224. package/skills/quality-gates/SKILL.md +6 -4
  225. package/skills/remote-offload/SKILL.md +4 -2
  226. package/skills/session-end/learning-patterns.md +23 -13
  227. package/skills/session-end/metrics-collection.md +6 -7
  228. package/skills/session-end/phase-3-2-docs-verification.md +7 -6
  229. package/skills/session-end/plan-verification.md +25 -9
  230. package/skills/session-end/references/phase-3-documentation-updates.md +13 -3
  231. package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
  232. package/skills/session-end/references/session-summary-template.md +1 -1
  233. package/skills/session-end/session-metrics-write.md +65 -68
  234. package/skills/session-plan/SKILL.md +0 -3
  235. package/skills/session-start/SKILL.md +8 -2
  236. package/skills/session-start/references/operations-contract.md +6 -0
  237. package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
  238. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  239. package/skills/session-start/references/phase-4-ssot-environment-check.md +10 -3
  240. package/skills/test-audit/SKILL.md +152 -0
  241. package/skills/test-audit/references/campaign.md +111 -0
  242. package/skills/test-audit/references/mutate.sh +50 -0
  243. package/skills/vault-mirror/SKILL.md +21 -11
  244. package/skills/wave-executor/SKILL.md +2 -2
  245. package/skills/wave-executor/circuit-breaker.md +1 -1
  246. package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
  247. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
  248. package/skills/wave-executor/references/wave-loop-review.md +4 -6
  249. package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
  250. package/templates/personas/README.md +5 -5
  251. package/templates/personas/accounting-compliance.v1.md +1 -1
  252. package/templates/personas/accounting-tax-advisor.v1.md +1 -1
  253. package/templates/personas/buyer-p1-cto.v1.md +1 -1
  254. package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
  255. package/templates/personas/buyer-p3-build.v1.md +1 -1
  256. package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
  257. package/templates/personas/buyer-p5-solo.v1.md +1 -1
  258. package/templates/personas/buyer-p6-ld.v1.md +1 -1
  259. package/templates/personas/klima-ai-expert.v1.md +1 -1
  260. package/templates/personas/klima-physicist.v1.md +1 -1
@@ -29,6 +29,7 @@
29
29
  * per-file error on the `--apply` path)
30
30
  */
31
31
 
32
+ import path from 'node:path';
32
33
  import { emitEvent } from './lib/events.mjs';
33
34
  import { isMainModule } from './lib/is-main-module.mjs';
34
35
  import {
@@ -100,7 +101,7 @@ export function parseArgs(argv) {
100
101
  } else if (a === '--repo-root') {
101
102
  const raw = argv[++i];
102
103
  if (typeof raw !== 'string' || raw.length === 0) usageError('--repo-root requires a path');
103
- args.repoRoot = raw;
104
+ args.repoRoot = path.resolve(raw);
104
105
  } else if (a === '--learnings') {
105
106
  const raw = argv[++i];
106
107
  if (typeof raw !== 'string' || raw.length === 0) usageError('--learnings requires a path');
@@ -513,6 +513,13 @@ runStrictShAdvisory();
513
513
  // session-end tail phases from their dispatcher.
514
514
  if (runCheck('check-skill-links.mjs') !== 0) checkFailed = 1;
515
515
 
516
+ // BLOCKING (#1446 / GH#69): instruction prose that tells an agent to write
517
+ // `learnings.jsonl` with a shell `>` / `>>`. The store is gitignored, so a bad
518
+ // write has no VCS restore; the sanctioned writers (`apply-session-learnings.mjs`,
519
+ // `sweep-expired-learnings.mjs --prune`) validate, snapshot `.bak` and archive.
520
+ process.stdout.write('\n');
521
+ if (runCheck('check-learnings-shell-writes.mjs') !== 0) checkFailed = 1;
522
+
516
523
  // WARN-only: a state-mutating `git` call in tests/ that names no target resolves
517
524
  // its destination from the ambient cwd (or an inherited GIT_DIR) — the 2026-08-19
518
525
  // incident, where fixture commits, a fixture remote and a fixture identity landed
@@ -35,6 +35,9 @@
35
35
  * `git ls-files` — spawned HERE, in the CLI layer,
36
36
  * because scripts/lib/scope-gate.mjs is hook-safe and
37
37
  * must not spawn a process (see its module header).
38
+ * Runs even when the manifest fails schema validation
39
+ * (#1026.4): it reads only the sidecar, so schema
40
+ * errors and collisions are reported together (exit 1).
38
41
  * --union <path> #1020. QUERY MODE. Read the same sidecar, compute
39
42
  * `expandTestSiblings(unionFileScopes(scopes), {role})`
40
43
  * using the MANIFEST'S OWN `role`, and print the
@@ -638,11 +641,24 @@ function knownRepoFiles() {
638
641
  * @param {string} sidecarPath
639
642
  */
640
643
  function assertDisjointOrDie(sidecarPath) {
644
+ if (!reportScopeCollisions(sidecarPath)) process.exit(1);
645
+ }
646
+
647
+ /**
648
+ * Run the #1020 collision check and write one stderr line per finding.
649
+ * Reads ONLY the sidecar, never the manifest — which is why {@link validate}
650
+ * can still run it when the manifest fails schema validation (#1026.4).
651
+ * Sidecar I/O and shape defects exit via {@link readAgentScopesOrDie}.
652
+ *
653
+ * @param {string} sidecarPath
654
+ * @returns {boolean} true when the wave is disjoint
655
+ */
656
+ function reportScopeCollisions(sidecarPath) {
641
657
  const agentScopes = readAgentScopesOrDie(sidecarPath, '--assert-disjoint');
642
658
  const { ok, collisions, duplicateIds } = findScopeCollisions(agentScopes, {
643
659
  knownFiles: knownRepoFiles(),
644
660
  });
645
- if (ok) return;
661
+ if (ok) return true;
646
662
 
647
663
  // Duplicate ids FIRST: they are a malformed plan, and a reader who fixes them
648
664
  // may well change which collisions remain.
@@ -659,7 +675,7 @@ function assertDisjointOrDie(sidecarPath) {
659
675
  process.stderr.write(
660
676
  `ERROR: ${collisions.length} scope collision(s), ${duplicateIds.length} duplicate id(s) — every file must belong to exactly ONE agent per wave (#1020; .claude/rules/parallel-sessions.md § Decision Tree)\n`,
661
677
  );
662
- process.exit(1);
678
+ return false;
663
679
  }
664
680
 
665
681
  /**
@@ -709,6 +725,9 @@ function validate(
709
725
  for (const e of errors) {
710
726
  process.stderr.write(`ERROR: ${e}\n`);
711
727
  }
728
+ // #1026.4 — the collision check reads only the sidecar, so a schema error
729
+ // must not hide its findings: report both, exit 1 either way.
730
+ if (assertDisjointPath) reportScopeCollisions(assertDisjointPath);
712
731
  process.exit(1);
713
732
  }
714
733
 
@@ -38,6 +38,10 @@ import {
38
38
  } from './lib/vault-backfill/template.mjs';
39
39
 
40
40
  import { validateManifest, SLUG_RE } from './lib/vault-backfill/manifest.mjs';
41
+ import { _parseVaultIntegration } from './lib/config/vault-integration.mjs';
42
+ import { loadHostPaths, resolveVaultIntegrationHost } from './lib/config/host-paths.mjs';
43
+ import { resolveVaultDir } from './lib/config.mjs';
44
+ import { expandTilde } from './lib/common.mjs';
41
45
 
42
46
  // ── CLI argument parsing ──────────────────────────────────────────────────────
43
47
 
@@ -85,9 +89,29 @@ let hadWriteError = false;
85
89
 
86
90
  if (flagVerbose) setGlabVerbose(true);
87
91
 
88
- // ── Session Config reader (inline) ───────────────────────────────────────────
92
+ // ── Session Config reader ─────────────────────────────────────────────────────
89
93
 
90
- /** Read vault-integration.{gitlab-groups,vault-dir} from CLAUDE.md / AGENTS.md in CWD. */
94
+ /**
95
+ * Read vault-integration.{gitlab-groups,vault-dir} from CLAUDE.md / AGENTS.md in
96
+ * CWD through the canonical Session Config parser (#1094), so every form it
97
+ * accepts (block, inline object, dash/bold bullet) reaches this CLI too.
98
+ *
99
+ * `vault-dir` resolves HOST-LOCALLY like every other vault writer:
100
+ * SO_VAULT_DIR > owner.yaml `vault-dirs:` cwd match > owner.yaml
101
+ * `paths.vault-dir` > the committed value (`resolveVaultDir`). The committed
102
+ * value alone would put `--apply` stubs into whatever vault the committed file
103
+ * names, even on a host whose owner.yaml points elsewhere. Not
104
+ * `parseSessionConfig`: it throws on an invalid value in any unrelated block.
105
+ *
106
+ * `enabled` is the switch the other vault writers obey (board-writer,
107
+ * narrative-mirror: `enabled !== true` → skip), after the same host-local
108
+ * lowering `parseSessionConfig` applies — env `SO_VAULT_INTEGRATION` >
109
+ * owner.yaml `vault-integration:` > committed (#1496: stubs were created with
110
+ * the integration off, from an owner.yaml `paths.vault-dir` alone).
111
+ *
112
+ * @returns {{'gitlab-groups': string[]|null, 'vault-dir': string|null, enabled: boolean,
113
+ * 'host-override': string|null}}
114
+ */
91
115
  function readVaultIntegrationConfig() {
92
116
  const candidates = [join(process.cwd(), 'CLAUDE.md'), join(process.cwd(), 'AGENTS.md')];
93
117
 
@@ -98,45 +122,24 @@ function readVaultIntegrationConfig() {
98
122
  }
99
123
  }
100
124
 
101
- if (!content) return { 'gitlab-groups': null, 'vault-dir': null };
102
-
103
- let inBlock = false;
104
- let gitlabGroups = null;
105
- let vaultDir = null;
106
-
107
- for (const rawLine of content.split(/\r?\n/)) {
108
- const line = rawLine.replace(/\r$/, '');
109
-
110
- if (!inBlock && /^vault-integration:\s*$/.test(line)) { inBlock = true; continue; }
111
- if (inBlock) {
112
- if (line.length > 0 && !/^\s/.test(line)) break;
113
-
114
- const m = line.match(/^\s+([a-zA-Z_-]+):\s*(.*)/);
115
- if (!m) continue;
116
-
117
- const k = m[1].trim();
118
- let v = m[2].trim().replace(/\s*#.*$/, '').trim();
119
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
120
- v = v.slice(1, -1);
121
- }
122
-
123
- if (k === 'gitlab-groups' && v) {
124
- gitlabGroups = v.replace(/^\[/, '').replace(/\]$/, '').trim() || null;
125
- }
126
- if (k === 'vault-dir' && v && v !== 'none' && v !== 'null') {
127
- vaultDir = v;
128
- }
129
- }
130
- }
131
-
132
- return { 'gitlab-groups': gitlabGroups, 'vault-dir': vaultDir };
125
+ const hostCtx = loadHostPaths();
126
+ const vi = resolveVaultIntegrationHost(_parseVaultIntegration(content ?? ''), hostCtx);
127
+ const vaultDir = resolveVaultDir(vi['vault-dir'], hostCtx).value ?? null;
128
+ return {
129
+ 'gitlab-groups': vi['gitlab-groups'],
130
+ 'vault-dir': vaultDir,
131
+ enabled: vi.enabled === true,
132
+ 'host-override': vi['host-override'],
133
+ };
133
134
  }
134
135
 
135
136
  // ── Vault-dir folder stub ─────────────────────────────────────────────────────
136
137
 
137
138
  function createVaultFolderStub(vaultDir, slug) {
138
139
  if (!vaultDir) return;
139
- const stubDir = join(resolve(vaultDir), '01-projects', slug);
140
+ // `~/…` is the documented vault-dir shape; resolve() alone does not expand it
141
+ // and would put the stub under `<cwd>/~/…`.
142
+ const stubDir = join(resolve(expandTilde(vaultDir)), '01-projects', slug);
140
143
  try {
141
144
  if (!existsSync(stubDir)) {
142
145
  if (applyWrites) {
@@ -399,10 +402,20 @@ async function main() {
399
402
  if (flagGroups) {
400
403
  groups = flagGroups.split(',').map((g) => g.trim()).filter(Boolean);
401
404
  } else if (cfgConfig['gitlab-groups']) {
402
- groups = cfgConfig['gitlab-groups'].split(',').map((g) => g.trim()).filter(Boolean);
405
+ groups = cfgConfig['gitlab-groups'];
403
406
  }
404
407
 
405
- const vaultDir = flagVaultDir ?? cfgConfig['vault-dir'] ?? null;
408
+ // The configured vault-dir counts only while vault-integration is on; an
409
+ // explicit --vault-dir is this run's own request and still wins.
410
+ let vaultDir = flagVaultDir ?? null;
411
+ if (vaultDir === null && cfgConfig['vault-dir'] !== null) {
412
+ if (cfgConfig.enabled) {
413
+ vaultDir = cfgConfig['vault-dir'];
414
+ } else {
415
+ const why = cfgConfig['host-override'] ? `switched off by ${cfgConfig['host-override']}` : 'not enabled';
416
+ log(`vault-integration is ${why} — no vault folder stubs (pass --vault-dir to create them anyway)`);
417
+ }
418
+ }
406
419
 
407
420
  if (!flagYes && (!groups || groups.length === 0)) {
408
421
  die(1, 'no GitLab groups specified — pass --groups <CSV> or set vault-integration.gitlab-groups in CLAUDE.md');
@@ -35,6 +35,7 @@ import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-rep
35
35
  import { validatePathInsideProject } from './lib/path-utils.mjs';
36
36
  import { resolveRepoSpec, redactUrlCredentials } from './lib/vcs-repo-spec.mjs';
37
37
  import { expandTilde } from './lib/common.mjs';
38
+ import { shellQuote } from './lib/sh-quote.mjs';
38
39
 
39
40
  // ── Argument parsing ──────────────────────────────────────────────────────────
40
41
 
@@ -285,7 +286,7 @@ function buildTickBody(issueMap, depIds, verdict, streak, date) {
285
286
  function buildFlipBody(repos) {
286
287
  // Defense-in-depth: regex+confinement upstream already filter, but shell-quote here
287
288
  // makes the embedded bash safe under any future bypass.
288
- const repoList = repos.map(r => `'${r.replace(/'/g, "'\\''")}'`).join(' ');
289
+ const repoList = repos.map(shellQuote).join(' ');
289
290
  // The sed/add/commit one-liner targets the project-instruction file. Most
290
291
  // ecosystem repos use CLAUDE.md, but Codex-CLI repos use AGENTS.md as a
291
292
  // transparent alias — see skills/_shared/instruction-file-resolution.md.