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.
- package/.agents/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +28 -13
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +9 -31
- package/CHANGELOG.md +162 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +154 -4
- package/docs/components.md +11 -11
- package/docs/events-schema.md +25 -19
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/persona-panel.md +5 -4
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +40 -16
- package/docs/session-config-reference.md +61 -12
- package/docs/session-config-template.md +11 -2
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +42 -29
- package/hooks/_lib/lock-bootstrap.mjs +118 -26
- package/hooks/config-protection.mjs +1 -1
- package/hooks/enforce-commands.mjs +28 -13
- package/hooks/enforce-scope.mjs +201 -23
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/on-session-start.mjs +57 -2
- package/hooks/on-stop.mjs +70 -168
- package/hooks/post-bash-write-verify.mjs +98 -17
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +32 -152
- package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +1143 -279
- package/hooks/subagent-telemetry.mjs +96 -19
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +6 -1
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +5 -1
- package/rules/opt-in-stack/backend-data.md +9 -1
- package/rules/opt-in-stack/backend.md +7 -1
- package/rules/opt-in-stack/frontend.md +6 -1
- package/rules/opt-in-stack/security-web.md +7 -1
- package/rules/opt-in-stack/swift.md +5 -1
- package/scripts/apply-session-learnings.mjs +513 -0
- package/scripts/archive-closed-prds.mjs +40 -12
- package/scripts/autopilot.mjs +57 -54
- package/scripts/backfill-abandoned-sessions.mjs +81 -6
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +47 -6
- package/scripts/emit-session.mjs +298 -2
- package/scripts/export-hw-learnings.mjs +40 -10
- package/scripts/fleet-checkin.mjs +170 -0
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +6 -1
- package/scripts/lib/autopilot/loop.mjs +32 -13
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +7 -6
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +75 -10
- package/scripts/lib/eval/engine.mjs +13 -0
- package/scripts/lib/events-retention-banner.mjs +393 -0
- package/scripts/lib/events-rotation.mjs +21 -6
- package/scripts/lib/events.mjs +72 -5
- package/scripts/lib/fleet-protocol.mjs +288 -0
- package/scripts/lib/gates/gate-baseline.mjs +13 -3
- package/scripts/lib/gates/gate-full.mjs +7 -2
- package/scripts/lib/gates/gate-helpers.mjs +93 -2
- package/scripts/lib/gates/gate-incremental.mjs +23 -22
- package/scripts/lib/gates/gate-per-file.mjs +16 -7
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +77 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
- package/scripts/lib/learnings/io.mjs +274 -37
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +147 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
- package/scripts/lib/orphan-reaper/scan.mjs +510 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +47 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +134 -22
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +45 -5
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +100 -62
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +119 -29
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +194 -27
- package/scripts/lib/session-close-backfill.mjs +129 -4
- package/scripts/lib/session-end/tail-runner.mjs +10 -5
- package/scripts/lib/session-lock-shape.mjs +6 -3
- package/scripts/lib/session-lock.mjs +320 -67
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +67 -17
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +252 -54
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +121 -71
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +215 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/ux-grill/reconcile.mjs +1 -1
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +388 -27
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
- package/scripts/lib/vault-mirror/namespace.mjs +47 -26
- package/scripts/lib/vault-mirror/process.mjs +350 -62
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
- package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
- package/scripts/lib/vault-mirror/telemetry.mjs +25 -11
- package/scripts/lib/vault-status/board-writer.mjs +53 -12
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +11 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
- package/scripts/run-quality-gate.mjs +331 -307
- package/scripts/self-update.mjs +229 -0
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +603 -20
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +238 -75
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +134 -0
- package/skills/_shared/state-ownership.md +41 -12
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +6 -4
- package/skills/bootstrap/_shared-template.md +97 -2
- package/skills/bootstrap/fast-template.md +4 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
- package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
- package/skills/discovery/probes/vault-staleness.mjs +9 -3
- package/skills/docs-orchestrator/SKILL.md +12 -5
- package/skills/docs-orchestrator/audience-mapping.md +5 -3
- package/skills/evolve/SKILL.md +13 -4
- package/skills/evolve/references/evolve-analyze-mode.md +42 -18
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/learning-patterns.md +23 -13
- package/skills/session-end/metrics-collection.md +6 -7
- package/skills/session-end/phase-3-2-docs-verification.md +7 -6
- package/skills/session-end/plan-verification.md +25 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +13 -3
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +65 -68
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +8 -2
- package/skills/session-start/references/operations-contract.md +6 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +10 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +2 -2
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
- package/skills/wave-executor/references/wave-loop-review.md +4 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
|
@@ -13,6 +13,17 @@
|
|
|
13
13
|
* 3. committed Session Config default (the value the committed CLAUDE.md —
|
|
14
14
|
* AGENTS.md on Codex CLI — produced)
|
|
15
15
|
*
|
|
16
|
+
* `vault-dir` has one more tier (agents/vault#319): an owner.yaml `vault-dirs:`
|
|
17
|
+
* cwd match between env and paths.vault-dir. That tier lives in
|
|
18
|
+
* `resolveVaultDir` in scripts/lib/config.mjs, NOT here: this module is one of
|
|
19
|
+
* the three direct CP11 helpers check-owner-leakage.mjs imports, and a standalone
|
|
20
|
+
* vendored copy carries only those three files. A new import edge from here
|
|
21
|
+
* (e.g. to named-baseline-resolver.mjs) makes CP11 fail closed in that copy.
|
|
22
|
+
*
|
|
23
|
+
* `resolveVaultIntegrationHost` is the host-local switch for the
|
|
24
|
+
* `vault-integration` gate itself (SO#1448). It may only LOWER the committed
|
|
25
|
+
* level (strict → warn → off), never raise it.
|
|
26
|
+
*
|
|
16
27
|
* SYNCHRONOUS by design: `parseSessionConfig` in scripts/lib/config.mjs is sync,
|
|
17
28
|
* so this layer reuses the SYNC owner loader (`loadOwnerConfig`) and exposes only
|
|
18
29
|
* sync functions. An empty/whitespace value at any tier is treated as "unset" and
|
|
@@ -90,3 +101,71 @@ export function resolveHostPath(key, committedDefault, { env = process.env, owne
|
|
|
90
101
|
|
|
91
102
|
return committedDefault;
|
|
92
103
|
}
|
|
104
|
+
|
|
105
|
+
/** vault-integration levels, weakest first. The index order IS the ordering. */
|
|
106
|
+
const VAULT_INTEGRATION_LEVELS = /** @type {readonly string[]} */ (['off', 'warn', 'strict']);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {unknown} v
|
|
110
|
+
* @returns {'off'|'warn'|'strict'|undefined} a valid level, or undefined for unset/invalid
|
|
111
|
+
*/
|
|
112
|
+
function coerceVaultIntegrationLevel(v) {
|
|
113
|
+
if (typeof v !== 'string') return undefined;
|
|
114
|
+
const lower = v.trim().toLowerCase();
|
|
115
|
+
return VAULT_INTEGRATION_LEVELS.includes(lower)
|
|
116
|
+
? /** @type {'off'|'warn'|'strict'} */ (lower)
|
|
117
|
+
: undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Overlay the host-local `vault-integration` switch onto the committed block
|
|
122
|
+
* (SO#1448). Precedence: env `SO_VAULT_INTEGRATION=off|warn|strict` > owner.yaml
|
|
123
|
+
* `vault-integration: { enabled: false | mode: off|warn|strict }` > committed.
|
|
124
|
+
* An invalid value at a tier counts as unset and falls through, mirroring
|
|
125
|
+
* resolveDispatcherAutonomy in dispatcher-autonomy.mjs.
|
|
126
|
+
*
|
|
127
|
+
* LOWER-ONLY: the host's level is applied only when it is weaker than the
|
|
128
|
+
* committed one. A host that could raise it would mirror repos into the vault
|
|
129
|
+
* that never opted in, so `enabled: true` or a stronger mode is ignored.
|
|
130
|
+
* Applying `off` sets both `enabled: false` and `mode: 'off'`, so either
|
|
131
|
+
* consumer check (`enabled`, `mode != off`) sees the integration as off.
|
|
132
|
+
*
|
|
133
|
+
* @param {{ enabled?: boolean, mode?: string } & Record<string, unknown>} vi — parsed committed block
|
|
134
|
+
* @param {{ env?: Record<string, string|undefined>, ownerConfig?: object }} [ctx] — from loadHostPaths()
|
|
135
|
+
* @returns {Record<string, unknown> & { enabled?: boolean, mode?: string, 'host-override': 'env:SO_VAULT_INTEGRATION'|'owner.yaml'|null }}
|
|
136
|
+
* a copy of `vi`; `host-override` names the tier that lowered it, `null` when none did
|
|
137
|
+
*/
|
|
138
|
+
export function resolveVaultIntegrationHost(vi, { env = process.env, ownerConfig } = {}) {
|
|
139
|
+
/** @type {Record<string, unknown> & { enabled?: boolean, mode?: string, 'host-override': 'env:SO_VAULT_INTEGRATION'|'owner.yaml'|null }} */
|
|
140
|
+
const result = { ...vi, 'host-override': null };
|
|
141
|
+
|
|
142
|
+
/** @type {'off'|'warn'|'strict'|undefined} */
|
|
143
|
+
let requested = coerceVaultIntegrationLevel(env?.SO_VAULT_INTEGRATION);
|
|
144
|
+
/** @type {'env:SO_VAULT_INTEGRATION'|'owner.yaml'} */
|
|
145
|
+
let source = 'env:SO_VAULT_INTEGRATION';
|
|
146
|
+
if (requested === undefined) {
|
|
147
|
+
const owner = ownerConfig?.['vault-integration'];
|
|
148
|
+
if (owner !== null && typeof owner === 'object' && !Array.isArray(owner)) {
|
|
149
|
+
requested = owner.enabled === false ? 'off' : coerceVaultIntegrationLevel(owner.mode);
|
|
150
|
+
source = 'owner.yaml';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (requested === undefined) return result;
|
|
154
|
+
|
|
155
|
+
const committedLevel =
|
|
156
|
+
vi?.enabled === true ? (coerceVaultIntegrationLevel(vi.mode) ?? 'warn') : 'off';
|
|
157
|
+
if (
|
|
158
|
+
VAULT_INTEGRATION_LEVELS.indexOf(requested) >= VAULT_INTEGRATION_LEVELS.indexOf(committedLevel)
|
|
159
|
+
) {
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (requested === 'off') {
|
|
164
|
+
result.enabled = false;
|
|
165
|
+
result.mode = 'off';
|
|
166
|
+
} else {
|
|
167
|
+
result.mode = requested;
|
|
168
|
+
}
|
|
169
|
+
result['host-override'] = source;
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* after — 'quality' | 'impl-polish' (default: 'quality')
|
|
11
11
|
* threshold — string spec parsed via parseThreshold (default: 'all')
|
|
12
12
|
* personas — string[] (each matches SAFE_PERSONA_NAME_RE) (default: [])
|
|
13
|
-
* dispatch-model — model alias or full ID (default: '
|
|
13
|
+
* dispatch-model — model alias or full ID (default: 'opus' — tracks the current Opus)
|
|
14
14
|
* mode — 'off' | 'warn' | 'strict' (default: 'off')
|
|
15
15
|
*
|
|
16
16
|
* Used by skills/wave-executor/wave-loop.md § 3b: Persona-Gate Hook to decide
|
|
@@ -32,7 +32,7 @@ const DEFAULTS = Object.freeze({
|
|
|
32
32
|
threshold: 'all',
|
|
33
33
|
threshold_parsed: null, // cached ParsedThreshold result; populated by _normalizePersonaGateWave
|
|
34
34
|
personas: [],
|
|
35
|
-
'dispatch-model': '
|
|
35
|
+
'dispatch-model': 'opus',
|
|
36
36
|
mode: 'off',
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -138,7 +138,7 @@ export function _normalizePersonaGateWave(parsed) {
|
|
|
138
138
|
}
|
|
139
139
|
if (!ALLOWED_MODEL_ALIASES.has(m) && !MODEL_ID_RE.test(m)) {
|
|
140
140
|
throw new Error(
|
|
141
|
-
`persona-gate-wave.dispatch-model must be one of ${[...ALLOWED_MODEL_ALIASES].map((a) => JSON.stringify(a)).join('|')} or a full model ID like "claude-opus-
|
|
141
|
+
`persona-gate-wave.dispatch-model must be one of ${[...ALLOWED_MODEL_ALIASES].map((a) => JSON.stringify(a)).join('|')} or a full model ID like "claude-opus-5-5" (got ${JSON.stringify(m)})`,
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
out['dispatch-model'] = m;
|
|
@@ -27,18 +27,19 @@ import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
|
27
27
|
* Startwerte für Stufe 1 — NOT calibrated. Every number carries its provenance
|
|
28
28
|
* from the PRD §4 parameter table ("keine Zahl ohne ihre Population").
|
|
29
29
|
*
|
|
30
|
-
* RUNTIME SSOT is `REAPER_DEFAULTS` in `scripts/lib/orphan-reaper.mjs`; these
|
|
31
|
-
* literals mirror it
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* RUNTIME SSOT is `REAPER_DEFAULTS` in `scripts/lib/orphan-reaper/defaults.mjs`; these
|
|
31
|
+
* literals mirror it rather than importing it. The original reason — the
|
|
32
|
+
* single-file `orphan-reaper.mjs` pulled `process-group.mjs` +
|
|
33
|
+
* `resource-probe/parsers.mjs` into the config import graph — no longer holds
|
|
34
|
+
* since the #1437 split (`defaults.mjs` imports only `node:path`); revisit the
|
|
35
|
+
* mirror there. The parity is held by a test, not by an import
|
|
35
36
|
* (`tests/lib/config/reaper.test.mjs` § defaults mirror REAPER_DEFAULTS).
|
|
36
37
|
*/
|
|
37
38
|
/** 300 s — DevWatchdog's hard limit for `tsgo`; the 2026-09-20 orphans were 7–17 min old. */
|
|
38
39
|
const DEFAULT_MIN_AGE_SECONDS = 300;
|
|
39
40
|
/** 30 s — DevWatchdog's normal scan cadence; keeps a PostToolBatch storm from taxing every tool call. */
|
|
40
41
|
const DEFAULT_MIN_SCAN_INTERVAL_SECONDS = 30;
|
|
41
|
-
/** 10 000 ms — `DEFAULT_KILL_GRACE_MS` from `
|
|
42
|
+
/** 10 000 ms — `DEFAULT_KILL_GRACE_MS` from `process-group.mjs`; repo convention, not newly invented. */
|
|
42
43
|
const DEFAULT_KILL_GRACE_MS = 10_000;
|
|
43
44
|
/** 500 ms — wait before reading the effect back; without it the 2026-09-20 check falsely reported "still alive". */
|
|
44
45
|
const DEFAULT_VERIFY_WAIT_MS = 500;
|
|
@@ -57,16 +57,31 @@ const MODE_ALLOWED = ['warn', 'strict', 'off'];
|
|
|
57
57
|
* value portion (including any literal `**` inside it) is untouched.
|
|
58
58
|
*
|
|
59
59
|
* Defaults:
|
|
60
|
-
* enabled:
|
|
61
|
-
* vault-dir:
|
|
62
|
-
* mode:
|
|
63
|
-
* vault-name:
|
|
60
|
+
* enabled: false
|
|
61
|
+
* vault-dir: null
|
|
62
|
+
* mode: "warn" (invalid values silently fall back to "warn")
|
|
63
|
+
* vault-name: null (absent = downstream callers use deriveRepo() default)
|
|
64
|
+
* gitlab-groups: null (#1094 — non-empty string[] or null; see _groupsFromScalar)
|
|
65
|
+
*
|
|
66
|
+
* `gitlab-groups` accepts a comma string (`a, b`), a flow array (`[a, b]`) and,
|
|
67
|
+
* in block form only, a YAML block list (`gitlab-groups:` + indented `- a`
|
|
68
|
+
* lines — the shape docs/session-config-template.md documents). Inside the
|
|
69
|
+
* inline-object form more than one group needs the `[a, b]` array, because the
|
|
70
|
+
* literal is split on commas. YAML semantics throughout: `null` / `~` / empty
|
|
71
|
+
* mean unset (also as a list element), and each element loses one pair of
|
|
72
|
+
* matching quotes.
|
|
64
73
|
*
|
|
65
74
|
* @param {string} content — full file contents
|
|
66
|
-
* @returns {{enabled: boolean, "vault-dir": string|null, mode: string, "vault-name": string|null}}
|
|
75
|
+
* @returns {{enabled: boolean, "vault-dir": string|null, mode: string, "vault-name": string|null, "gitlab-groups": string[]|null}}
|
|
67
76
|
*/
|
|
68
77
|
export function _parseVaultIntegration(content) {
|
|
69
|
-
const defaults = {
|
|
78
|
+
const defaults = {
|
|
79
|
+
enabled: false,
|
|
80
|
+
'vault-dir': null,
|
|
81
|
+
mode: 'warn',
|
|
82
|
+
'vault-name': null,
|
|
83
|
+
'gitlab-groups': null,
|
|
84
|
+
};
|
|
70
85
|
if (typeof content !== 'string' || content === '') return defaults;
|
|
71
86
|
|
|
72
87
|
// #1162: NOT a raw split. `preprocessBlockLines` drops HTML-commented lines
|
|
@@ -106,21 +121,36 @@ export function _parseVaultIntegration(content) {
|
|
|
106
121
|
let vaultDir = null;
|
|
107
122
|
let mode = 'warn';
|
|
108
123
|
let vaultName = null;
|
|
124
|
+
let gitlabGroups = null;
|
|
125
|
+
// Items of a YAML block list under a value-less `gitlab-groups:` line; null
|
|
126
|
+
// while no such list is open.
|
|
127
|
+
let groupItems = null;
|
|
109
128
|
|
|
110
129
|
for (const rawLine of blockLines) {
|
|
111
130
|
const clean = rawLine.replace(/\s*#.*$/, '').replace(/\s+$/, '');
|
|
112
131
|
if (!clean.trim()) continue;
|
|
113
132
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
if (groupItems !== null) {
|
|
134
|
+
const item = clean.match(/^\s+-(?:\s+(.*))?$/);
|
|
135
|
+
if (item) {
|
|
136
|
+
const value = _groupName((item[1] ?? '').trim());
|
|
137
|
+
if (value !== null) groupItems.push(value);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Sub-keys at any indent. A list-item line (`- x`) never matches, so list
|
|
143
|
+
// items under a key this parser does not own are skipped here.
|
|
117
144
|
const kvMatch = clean.match(/^\s+([a-zA-Z_-]+):\s*(.*)$/);
|
|
118
145
|
if (!kvMatch) continue;
|
|
119
146
|
|
|
147
|
+
if (groupItems !== null) {
|
|
148
|
+
if (groupItems.length > 0) gitlabGroups = groupItems;
|
|
149
|
+
groupItems = null;
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
const k = kvMatch[1];
|
|
121
|
-
|
|
122
|
-
if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) v = v.slice(1, -1);
|
|
123
|
-
else if (v.startsWith("'") && v.endsWith("'") && v.length >= 2) v = v.slice(1, -1);
|
|
153
|
+
const v = _stripQuotes(kvMatch[2].trim());
|
|
124
154
|
|
|
125
155
|
switch (k) {
|
|
126
156
|
case 'enabled':
|
|
@@ -140,21 +170,118 @@ export function _parseVaultIntegration(content) {
|
|
|
140
170
|
if (v === '' || v === 'none' || v === 'null') vaultName = null;
|
|
141
171
|
else vaultName = v;
|
|
142
172
|
break;
|
|
173
|
+
case 'gitlab-groups': {
|
|
174
|
+
// A value replaces any earlier one; an empty value opens a block list,
|
|
175
|
+
// which replaces it only once it has collected an item. The value goes
|
|
176
|
+
// to `_groupsFromScalar` WITH its quotes, as the inline form's does —
|
|
177
|
+
// stripping them here first read `"null"` as unset in this form only (#1496).
|
|
178
|
+
const raw = kvMatch[2].trim();
|
|
179
|
+
if (raw === '') groupItems = [];
|
|
180
|
+
else gitlabGroups = _groupsFromScalar(raw);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
143
183
|
}
|
|
144
184
|
}
|
|
185
|
+
if (groupItems !== null && groupItems.length > 0) gitlabGroups = groupItems;
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
enabled,
|
|
189
|
+
'vault-dir': vaultDir,
|
|
190
|
+
mode,
|
|
191
|
+
'vault-name': vaultName,
|
|
192
|
+
'gitlab-groups': gitlabGroups,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
145
195
|
|
|
146
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Strip ONE pair of matching surrounding quotes (`"x"` / `'x'`). A lone quote
|
|
198
|
+
* character is returned unchanged.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} v — an already-trimmed value
|
|
201
|
+
* @returns {string}
|
|
202
|
+
*/
|
|
203
|
+
function _stripQuotes(v) {
|
|
204
|
+
if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) {
|
|
205
|
+
return v.slice(1, -1);
|
|
206
|
+
}
|
|
207
|
+
return v;
|
|
147
208
|
}
|
|
148
209
|
|
|
210
|
+
/**
|
|
211
|
+
* The YAML 1.2 core-schema null scalars, plus the empty value. `none` is NOT
|
|
212
|
+
* one: YAML reads it as a string, so it stays a literal group name.
|
|
213
|
+
*/
|
|
214
|
+
const YAML_NULL_RE = /^(?:|null|Null|NULL|~)$/;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* One `gitlab-groups` element as YAML reads it: an unquoted null scalar (or an
|
|
218
|
+
* empty element) is no group at all → `null`; otherwise ONE pair of matching
|
|
219
|
+
* quotes is stripped, so `"null"` stays the string `null` exactly as in YAML.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} raw — an already-trimmed element
|
|
222
|
+
* @returns {string|null}
|
|
223
|
+
*/
|
|
224
|
+
function _groupName(raw) {
|
|
225
|
+
if (YAML_NULL_RE.test(raw)) return null;
|
|
226
|
+
const name = _stripQuotes(raw).trim();
|
|
227
|
+
return name === '' ? null : name;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Normalise a scalar `gitlab-groups` value to a group list with YAML semantics:
|
|
232
|
+
* a null scalar (`null`, `~`, empty) is unset → `null`; otherwise drop one
|
|
233
|
+
* leading `[` and one trailing `]`, split on commas, trim, and read each element
|
|
234
|
+
* through `_groupName` (null elements dropped, one quote pair stripped). An
|
|
235
|
+
* empty result is `null`, the documented default. Before this, `null` scanned a
|
|
236
|
+
* GitLab group named "null" and `["a"]` one named `"a"` with the quotes.
|
|
237
|
+
*
|
|
238
|
+
* The ONE normalisation for both forms, so both pass the value with its quotes
|
|
239
|
+
* (#1496). A wholly quoted value is a YAML string, never a null: it loses that
|
|
240
|
+
* quote pair, is split on commas, and its elements are taken literally —
|
|
241
|
+
* `"null"` is the group "null", `"a, b"` the groups a and b.
|
|
242
|
+
*
|
|
243
|
+
* @param {string} v
|
|
244
|
+
* @returns {string[]|null}
|
|
245
|
+
*/
|
|
246
|
+
function _groupsFromScalar(v) {
|
|
247
|
+
const trimmed = v.trim();
|
|
248
|
+
if (YAML_NULL_RE.test(trimmed)) return null;
|
|
249
|
+
const unquoted = _stripQuotes(trimmed);
|
|
250
|
+
const readElement = unquoted === trimmed ? _groupName : (g) => (g === '' ? null : g);
|
|
251
|
+
const groups = unquoted
|
|
252
|
+
.replace(/^\[/, '')
|
|
253
|
+
.replace(/\]$/, '')
|
|
254
|
+
.split(',')
|
|
255
|
+
.map((g) => readElement(g.trim()))
|
|
256
|
+
.filter((g) => g !== null);
|
|
257
|
+
return groups.length > 0 ? groups : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* `gitlab-groups: [ ... ]` inside an inline object literal (braces already
|
|
262
|
+
* removed). Taken out BEFORE the comma split, which would otherwise cut the
|
|
263
|
+
* array apart; every other pair is split exactly as before.
|
|
264
|
+
*/
|
|
265
|
+
const INLINE_GROUPS_ARRAY_RE = /(^|,)\s*gitlab-groups\s*:\s*(\[[^\]]*\])\s*(?=,|$)/;
|
|
266
|
+
|
|
149
267
|
/**
|
|
150
268
|
* Parse an inline YAML object literal `{ key: val, key: val }` into the
|
|
151
269
|
* vault-integration return shape. Supports unquoted values including `~/` paths.
|
|
152
270
|
*
|
|
153
271
|
* @param {string} raw — full literal including braces
|
|
154
|
-
* @returns {{enabled: boolean, "vault-dir": string|null, mode: string}}
|
|
272
|
+
* @returns {{enabled: boolean, "vault-dir": string|null, mode: string, "vault-name": string|null, "gitlab-groups": string[]|null}}
|
|
155
273
|
*/
|
|
156
274
|
function _parseInlineObject(raw) {
|
|
157
|
-
|
|
275
|
+
let stripped = raw.replace(/^\s*\{/, '').replace(/\}\s*$/, '').trim();
|
|
276
|
+
let groupsArray = null;
|
|
277
|
+
const arrayMatch = stripped.match(INLINE_GROUPS_ARRAY_RE);
|
|
278
|
+
if (arrayMatch) {
|
|
279
|
+
groupsArray = arrayMatch[2];
|
|
280
|
+
stripped =
|
|
281
|
+
stripped.slice(0, arrayMatch.index) +
|
|
282
|
+
arrayMatch[1] +
|
|
283
|
+
stripped.slice(arrayMatch.index + arrayMatch[0].length);
|
|
284
|
+
}
|
|
158
285
|
const kv = new Map();
|
|
159
286
|
if (stripped !== '') {
|
|
160
287
|
for (const pair of stripped.split(',')) {
|
|
@@ -180,7 +307,15 @@ function _parseInlineObject(raw) {
|
|
|
180
307
|
vaultNameRaw === undefined || vaultNameRaw === '' || vaultNameRaw === 'none' || vaultNameRaw === 'null'
|
|
181
308
|
? null
|
|
182
309
|
: vaultNameRaw;
|
|
183
|
-
|
|
310
|
+
const groupsRaw = groupsArray ?? kv.get('gitlab-groups');
|
|
311
|
+
const gitlabGroups = groupsRaw === undefined ? null : _groupsFromScalar(groupsRaw);
|
|
312
|
+
return {
|
|
313
|
+
enabled,
|
|
314
|
+
'vault-dir': vaultDir,
|
|
315
|
+
mode,
|
|
316
|
+
'vault-name': vaultName,
|
|
317
|
+
'gitlab-groups': gitlabGroups,
|
|
318
|
+
};
|
|
184
319
|
}
|
|
185
320
|
|
|
186
321
|
// ---------------------------------------------------------------------------
|
|
@@ -120,6 +120,19 @@ export function validateSessionConfig(config) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// #1465 cross-field: offload-first with no declared host can never place a
|
|
124
|
+
// wave anywhere — advisory (warn-level), it never flips `ok`.
|
|
125
|
+
if (config['offload-first'] === true) {
|
|
126
|
+
const hosts = config['remote-hosts'];
|
|
127
|
+
if (!Array.isArray(hosts) || hosts.length === 0) {
|
|
128
|
+
warnings.push({
|
|
129
|
+
path: 'remote-hosts',
|
|
130
|
+
rule: 'offload-first-cross-field',
|
|
131
|
+
message: 'offload-first: true but no remote-hosts declared — every wave stays local',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
123
136
|
if (errors.length > 0) {
|
|
124
137
|
return { ok: false, errors, warnings };
|
|
125
138
|
}
|
package/scripts/lib/config.mjs
CHANGED
|
@@ -78,8 +78,61 @@ import { _parseRemoteHosts } from './config/remote-hosts.mjs';
|
|
|
78
78
|
import { _parseEvolve, _parseEvolveDecay } from './config/evolve.mjs';
|
|
79
79
|
import { _parseSkillEvolution } from './config/skill-evolution.mjs';
|
|
80
80
|
import { _parseDispatcherAutonomy, resolveDispatcherAutonomy } from './config/dispatcher-autonomy.mjs';
|
|
81
|
-
import { loadHostPaths, resolveHostPath } from './config/host-paths.mjs';
|
|
81
|
+
import { loadHostPaths, resolveHostPath, resolveVaultIntegrationHost } from './config/host-paths.mjs';
|
|
82
82
|
import { resolveNamedBaseline } from './named-baseline-resolver.mjs';
|
|
83
|
+
import { expandTilde } from './common.mjs';
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve `vault-dir` and report which tier produced it (agents/vault#319).
|
|
87
|
+
*
|
|
88
|
+
* Precedence (highest first): SO_VAULT_DIR env > owner.yaml `vault-dirs:`
|
|
89
|
+
* path-prefix match against the cwd > owner.yaml `paths.vault-dir` > committed.
|
|
90
|
+
* The env tier stays on top because tests/setup/vault-guard.mjs relies on it to
|
|
91
|
+
* shadow every host-local vault for the whole suite.
|
|
92
|
+
*
|
|
93
|
+
* Lives here, not in config/host-paths.mjs: host-paths.mjs is a direct CP11
|
|
94
|
+
* helper of check-owner-leakage.mjs, whose standalone vendored copy carries only
|
|
95
|
+
* its three direct helpers. The named-baseline-resolver import edge would make
|
|
96
|
+
* CP11 fail closed there (tests/husky/pre-commit-owner-leakage.test.mjs).
|
|
97
|
+
*
|
|
98
|
+
* The two owner.yaml tiers (`match`, `owner`) come back tilde-expanded: owner.yaml
|
|
99
|
+
* is host-local and `~/…` is the natural way to write a path there, but consumers
|
|
100
|
+
* such as vault-mirror.mjs `existsSync(resolve(vaultDir))` would read a literal
|
|
101
|
+
* `~` as `<cwd>/~/…`. The `env` and `committed` tiers keep their pass-through;
|
|
102
|
+
* vault-mirror.mjs expands its `--vault-dir` at entry for every tier anyway.
|
|
103
|
+
*
|
|
104
|
+
* `cwd` in the ctx is a test-only DI seam; production reads `process.cwd()`.
|
|
105
|
+
*
|
|
106
|
+
* Exported for vault writers that read only the `vault-integration` block and
|
|
107
|
+
* cannot afford `parseSessionConfig`, which throws on an invalid value in any
|
|
108
|
+
* unrelated block (scripts/vault-backfill.mjs, #1094).
|
|
109
|
+
*
|
|
110
|
+
* @param {string|null|undefined} committed — value the committed Session Config produced
|
|
111
|
+
* @param {{ env?: Record<string, string|undefined>, ownerConfig?: object, cwd?: string }} [ctx] — from loadHostPaths()
|
|
112
|
+
* @returns {{ value: string|null|undefined, source: 'env'|'match'|'owner'|'committed' }}
|
|
113
|
+
*/
|
|
114
|
+
export function resolveVaultDir(committed, { env = process.env, ownerConfig, cwd } = {}) {
|
|
115
|
+
const isNonBlank = (v) => typeof v === 'string' && v.trim() !== '';
|
|
116
|
+
|
|
117
|
+
const envVal = env?.SO_VAULT_DIR;
|
|
118
|
+
if (isNonBlank(envVal)) return { value: envVal, source: 'env' };
|
|
119
|
+
|
|
120
|
+
const matched = resolveNamedBaseline({
|
|
121
|
+
cwd: cwd ?? process.cwd(),
|
|
122
|
+
ownerConfig,
|
|
123
|
+
env,
|
|
124
|
+
section: 'vault-dirs',
|
|
125
|
+
envKey: 'SO_VAULT_DIR',
|
|
126
|
+
});
|
|
127
|
+
if (matched.source === 'match' && isNonBlank(matched.path)) {
|
|
128
|
+
return { value: expandTilde(matched.path), source: 'match' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const ownerVal = ownerConfig?.paths?.['vault-dir'];
|
|
132
|
+
if (isNonBlank(ownerVal)) return { value: expandTilde(ownerVal), source: 'owner' };
|
|
133
|
+
|
|
134
|
+
return { value: committed, source: 'committed' };
|
|
135
|
+
}
|
|
83
136
|
// express-path lives one level UP from config/ (see its module header for why):
|
|
84
137
|
// the parser is a sibling of config/state-md-lock.mjs in every respect except
|
|
85
138
|
// its directory. Only the pure parser is imported here — `evaluateExpressPath`
|
|
@@ -114,8 +167,9 @@ export { readConfigFile } from './config/io.mjs';
|
|
|
114
167
|
* hermetic ctx (e.g. `{ env: {}, ownerConfig: undefined }`) when asserting COMMITTED values:
|
|
115
168
|
* the default reads the real `owner.yaml`, so a host-local `paths:` override would otherwise
|
|
116
169
|
* bleed into fixture assertions (incident: 2026-07-03 Full-Gate red after the operator set
|
|
117
|
-
* `paths.baseline-path` host-locally). `cwd` feeds the `baselines:`
|
|
118
|
-
* test-only DI seam; production
|
|
170
|
+
* `paths.baseline-path` host-locally). `cwd` feeds the `baselines:` (#819) and
|
|
171
|
+
* `vault-dirs:` (agents/vault#319) match tiers — a test-only DI seam; production
|
|
172
|
+
* always resolves it from `process.cwd()`.
|
|
119
173
|
* @returns {object} config object with EXACT same shape as parse-config.sh stdout
|
|
120
174
|
* @throws if any enum value is invalid
|
|
121
175
|
*/
|
|
@@ -230,6 +284,9 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
|
|
|
230
284
|
// heavy-repo / worktree-cleanup: HR-003 preflight fields (templates/shared/.claude/rules/heavy-repo.md),
|
|
231
285
|
// documented but previously unwired — silently dropped by the parser (baseline issue #60).
|
|
232
286
|
const heavyRepo = _coerceBoolean(kv, 'heavy-repo', false);
|
|
287
|
+
// offload-first (#1465): route offloadable wave roles to a ready remote host
|
|
288
|
+
// even on a `proceed` resource verdict (consumed by wave-resource-gate.mjs).
|
|
289
|
+
const offloadFirst = _coerceBoolean(kv, 'offload-first', false);
|
|
233
290
|
|
|
234
291
|
// List fields
|
|
235
292
|
const crossRepos = _coerceList(kv, 'cross-repos', undefined);
|
|
@@ -331,18 +388,25 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
|
|
|
331
388
|
// vault-integration: parsed from full content (block-scoped, avoids the
|
|
332
389
|
// pre-#593 KV-name collision where `enabled:` was shared with 15+ other
|
|
333
390
|
// blocks like docs-orchestrator/vault-staleness/slopcheck).
|
|
334
|
-
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
|
|
391
|
+
// Host-local overrides — applied here, NOT inside _parseVaultIntegration, so
|
|
392
|
+
// claude-md-drift-check (which calls the parser directly) keeps seeing raw values.
|
|
393
|
+
// - the enabled/mode switch (SO#1448): env SO_VAULT_INTEGRATION > owner.yaml
|
|
394
|
+
// `vault-integration:` > committed, LOWER-ONLY; `host-override` names the tier.
|
|
395
|
+
// - vault-dir (#653, agents/vault#319): env > owner.yaml `vault-dirs:` cwd match >
|
|
396
|
+
// owner.yaml paths.vault-dir > committed; `vault-dir-source` names the tier.
|
|
397
|
+
const vaultIntegration = resolveVaultIntegrationHost(_parseVaultIntegration(mdContent), hostCtx);
|
|
398
|
+
const vaultIntegrationDir = resolveVaultDir(vaultIntegration['vault-dir'], hostCtx);
|
|
399
|
+
vaultIntegration['vault-dir'] = vaultIntegrationDir.value;
|
|
400
|
+
vaultIntegration['vault-dir-source'] = vaultIntegrationDir.source;
|
|
338
401
|
|
|
339
402
|
// resource-thresholds sub-keys (v3.1.0 env-aware — issue #166)
|
|
340
403
|
const resourceThresholds = _parseResourceThresholds(kv);
|
|
341
404
|
|
|
342
405
|
// vault-sync: parsed from full content (can live outside Session Config)
|
|
343
406
|
const vaultSync = _parseVaultSync(mdContent);
|
|
344
|
-
// Host-local override (issue #653) — same
|
|
345
|
-
|
|
407
|
+
// Host-local override (issue #653) — same resolver as vault-integration above, so the
|
|
408
|
+
// two blocks cannot disagree about which vault this repo writes to (agents/vault#319).
|
|
409
|
+
vaultSync['vault-dir'] = resolveVaultDir(vaultSync['vault-dir'], hostCtx).value;
|
|
346
410
|
|
|
347
411
|
// drift-check: parsed from full content (standalone top-level block)
|
|
348
412
|
const driftCheck = _parseDriftCheck(mdContent);
|
|
@@ -408,7 +472,7 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
|
|
|
408
472
|
// dispatcher-autonomy: opt-in cross-repo dispatcher autonomy block (Epic #673 / issue #679).
|
|
409
473
|
// Parser stays pure (raw committed value) for claude-md-drift-check raw-value parity;
|
|
410
474
|
// the host-local override (env > owner.yaml > committed > off) is overlaid onto the
|
|
411
|
-
// FINAL object only — mirroring the vault-dir
|
|
475
|
+
// FINAL object only — mirroring the vault-dir host-override pattern above. Reuses the
|
|
412
476
|
// already-loaded hostCtx.ownerConfig so owner.yaml is not read twice.
|
|
413
477
|
const dispatcherAutonomy = _parseDispatcherAutonomy(mdContent);
|
|
414
478
|
dispatcherAutonomy.autonomy = resolveDispatcherAutonomy({
|
|
@@ -552,6 +616,7 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
|
|
|
552
616
|
'resource-awareness': resourceAwareness,
|
|
553
617
|
'enable-host-banner': enableHostBanner,
|
|
554
618
|
'heavy-repo': heavyRepo,
|
|
619
|
+
'offload-first': offloadFirst,
|
|
555
620
|
'worktree-cleanup': worktreeCleanup,
|
|
556
621
|
'resource-thresholds': resourceThresholds,
|
|
557
622
|
'worktree-exclude': worktreeExclude,
|
|
@@ -840,6 +840,19 @@ function resolveModel(model, env, resolveModelFromEnv) {
|
|
|
840
840
|
// Public API
|
|
841
841
|
// ---------------------------------------------------------------------------
|
|
842
842
|
|
|
843
|
+
/**
|
|
844
|
+
* Events window this reader needs retained (#1401 part 3): NONE FIXED, hence
|
|
845
|
+
* `null` rather than a made-up number. `evaluateSession` reads the whole
|
|
846
|
+
* ledger through `readEventsWithRotations` to score ONE named past session,
|
|
847
|
+
* arbitrarily old (`computeWindow`, #1407) — the window it needs is that
|
|
848
|
+
* session's own age, which no finite retention can be sized for. A session
|
|
849
|
+
* older than the retained ledger surfaces through `ledgerGapNote` instead.
|
|
850
|
+
* Read by `scripts/lib/events-retention-banner.mjs`.
|
|
851
|
+
*
|
|
852
|
+
* @type {number|null}
|
|
853
|
+
*/
|
|
854
|
+
export const REQUIRED_EVENTS_WINDOW_DAYS = null;
|
|
855
|
+
|
|
843
856
|
/**
|
|
844
857
|
* Evaluate one completed session against the rubric-v2 dimensions.
|
|
845
858
|
*
|