session-orchestrator 5.9.0 → 5.10.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +33 -2
- package/README.md +10 -9
- package/docs/USER-GUIDE.md +2 -0
- package/docs/ci-setup.md +46 -1
- package/docs/events-schema.md +35 -5
- package/docs/owner-config-schema.md +18 -1
- package/docs/owner-privacy-patterns.md +85 -0
- package/docs/scope-collision-guard.md +12 -2
- package/hooks/enforce-commands.mjs +9 -19
- package/hooks/enforce-scope.mjs +23 -42
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/post-bash-write-verify.mjs +38 -21
- package/package.json +2 -2
- package/scripts/emit-session.mjs +15 -0
- package/scripts/lib/gates/gate-full.mjs +6 -0
- package/scripts/lib/gates/gate-helpers.mjs +2 -0
- package/scripts/lib/named-vault-resolver.mjs +1 -1
- package/scripts/lib/owner-yaml.mjs +33 -3
- package/scripts/lib/platform.mjs +70 -1
- package/scripts/lib/pre-push-remote-gate.mjs +107 -0
- package/scripts/lib/quality-gate.mjs +6 -1
- package/scripts/lib/redact-spans.mjs +8 -17
- package/scripts/lib/scope-gate.mjs +68 -5
- package/scripts/lib/validate/check-owner-leakage.mjs +198 -1015
- package/scripts/lib/vault-backfill/template.mjs +1 -1
- package/scripts/lib/vault-mirror/render-sessions.mjs +15 -3
- package/scripts/lib/vault-status/board-writer.mjs +15 -7
- package/scripts/migrate-vault-paths.mjs +57 -60
- package/scripts/release.mjs +103 -36
- package/scripts/vault-consolidate.mjs +19 -4
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +14 -3
- package/skills/session-end/metrics-collection.md +26 -2
- package/skills/wave-executor/references/wave-loop-review.md +26 -2
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { readFileSync, readdirSync, existsSync, statSync, lstatSync, realpathSync } from 'node:fs';
|
|
34
|
-
import { join, relative, resolve } from 'node:path';
|
|
34
|
+
import { join, relative, resolve, posix } from 'node:path';
|
|
35
35
|
import { execFileSync } from 'node:child_process';
|
|
36
36
|
import { createRequire } from 'node:module';
|
|
37
37
|
import { resolveInstructionFile } from '../../scripts/lib/common.mjs';
|
|
@@ -41,6 +41,7 @@ import { isSessionConfigHeading } from '../../scripts/lib/config/section-extract
|
|
|
41
41
|
import { FRONTMATTER_NOT_AT_TOP, isRuleExpired, parseGlobsFrontmatter } from '../../scripts/lib/rule-loader.mjs';
|
|
42
42
|
import { resolveRepoSpec } from '../../scripts/lib/vcs-repo-spec.mjs';
|
|
43
43
|
import { userInvocableSkills } from '../../scripts/lib/user-invocable-skills.mjs';
|
|
44
|
+
import { loadOwnerConfig } from '../../scripts/lib/owner-yaml.mjs';
|
|
44
45
|
import { learningKeyOf } from '../../scripts/lib/learnings/kebab.mjs';
|
|
45
46
|
|
|
46
47
|
const FORWARD_HEADING_RE =
|
|
@@ -1016,6 +1017,9 @@ function main() {
|
|
|
1016
1017
|
if (!args.skipSessionFiles) checksRun.push('session-file-existence');
|
|
1017
1018
|
for (const s of activeSurfaces) checksRun.push(s.id);
|
|
1018
1019
|
|
|
1020
|
+
const foreignHostPrefixes = args.skipPathResolver ? [] :
|
|
1021
|
+
(loadOwnerConfig().config['drift-check']?.['foreign-host-prefixes'] ?? [])
|
|
1022
|
+
.map((prefix) => prefix.replace(/\/+$/, ''));
|
|
1019
1023
|
const errors = [];
|
|
1020
1024
|
const warnings = [];
|
|
1021
1025
|
// Informational findings — never block, never count as a warning. Currently
|
|
@@ -1731,9 +1735,16 @@ function main() {
|
|
|
1731
1735
|
while ((m = pathRegex.exec(line)) !== null) {
|
|
1732
1736
|
const p = m[0].replace(/[.,;:)\]`"']+$/, '');
|
|
1733
1737
|
if (!existsSync(p)) {
|
|
1734
|
-
|
|
1738
|
+
// Lexical containment must use the effective POSIX path: '..' can
|
|
1739
|
+
// escape an allowed foreign directory. Keep p for diagnostics.
|
|
1740
|
+
const normalizedPath = posix.normalize(p);
|
|
1741
|
+
const foreign = foreignHostPrefixes.some((prefix) =>
|
|
1742
|
+
normalizedPath === prefix || normalizedPath.startsWith(`${prefix}/`));
|
|
1743
|
+
(foreign ? warnings : errors).push({
|
|
1735
1744
|
check: 'path-resolver', file: rel, line: lineNum,
|
|
1736
|
-
message:
|
|
1745
|
+
message: foreign
|
|
1746
|
+
? `Known foreign-host path does not exist on this host: ${p}`
|
|
1747
|
+
: `Absolute path does not exist: ${p}`,
|
|
1737
1748
|
extracted: p,
|
|
1738
1749
|
});
|
|
1739
1750
|
}
|
|
@@ -184,9 +184,33 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
184
184
|
> ' .orchestrator/metrics/events.jsonl | tail -1
|
|
185
185
|
> ```
|
|
186
186
|
>
|
|
187
|
-
> `WAVE` and `SEMANTIC_SESSION_ID` come from the existing own session/wave identity; never construct either for this lookup. Filtering by both is mandatory: the ledger accumulates across sessions, and a different session or wave must not supply these counts. `counts.passed` → `suite_passed`, `counts.failed` → `suite_failed`. `tail -1` selects the last matching gate run of the wave, including auto-fix. `-R` with `fromjson? | objects` skips an unreadable ledger line (a torn tail from a killed writer, #1401) instead of letting one parse error end `jq` and
|
|
187
|
+
> `WAVE` and `SEMANTIC_SESSION_ID` come from the existing own session/wave identity; never construct either for this lookup. Filtering by both is mandatory: the ledger accumulates across sessions, and a different session or wave must not supply these counts. `counts.passed` → `suite_passed`, `counts.failed` → `suite_failed`. `tail -1` selects the last matching gate run of the wave, including auto-fix. `-R` with `fromjson? | objects` skips an unreadable ledger line (a torn tail from a killed writer, #1401) instead of letting one parse error end `jq`. The old pipeline could emit an earlier matching value before the parse error; `tail -1` retained that stale value and could hide the jq failure. Known limit: an interrupted `appendFile` can leave a truncated record that `fromjson?` discards completely; `tail -1` can still select an older matching run, which alone is not evidence of the current gate result. The library emits one record per `runQualityGateWithRetry` call, not per retry; `attempts` is not a record count.
|
|
188
188
|
>
|
|
189
|
-
>
|
|
189
|
+
> An empty counts selector cannot distinguish a missing event, missing wave attribution,
|
|
190
|
+
> or an attributed event without counts after fail-fast. Inspect the own session's
|
|
191
|
+
> records without the counts filter before deciding whether a documented count
|
|
192
|
+
> fallback case applies:
|
|
193
|
+
>
|
|
194
|
+
> ```bash
|
|
195
|
+
> jq -cR --argjson w "$WAVE" --arg s "$SEMANTIC_SESSION_ID" '
|
|
196
|
+
> fromjson? | objects
|
|
197
|
+
> | select((.event | type) == "string" and (.event | startswith("orchestrator.quality_gate.")))
|
|
198
|
+
> | select(.semantic_session_id == $s)
|
|
199
|
+
> | select(.wave_number == $w or .wave_number == null)
|
|
200
|
+
> | {event, semantic_session_id, wave_number, counts}
|
|
201
|
+
> ' .orchestrator/metrics/events.jsonl
|
|
202
|
+
> ```
|
|
203
|
+
>
|
|
204
|
+
> This is a diagnostic, not a replacement counts source. No rows means no readable
|
|
205
|
+
> matching event; a row without `wave_number` is not proof of this wave's run;
|
|
206
|
+
> a row for this wave without `counts` supplies no measurement. Keep the session
|
|
207
|
+
> filter and never copy counts from another session or wave, or from a row without
|
|
208
|
+
> wave attribution. An event without `semantic_session_id` is not attributable by
|
|
209
|
+
> this query either; diagnose missing session attribution from independent evidence
|
|
210
|
+
> of the own gate run, not by adopting an unrelated unattributed event. Missing
|
|
211
|
+
> attribution or an empty result alone does not prove a fallback source exists.
|
|
212
|
+
>
|
|
213
|
+
> **Count fallback is limited to three cases:** pre-#954 sessions, a gate run outside the event wrapper, or missing wave attribution (including an auto-fix call without a readable own wave scope). An event without `semantic_session_id` cannot match the selector — emitters set it only from the session lock via `sessionAttribution()` — and is treated like a gate run outside the wrapper. In those cases only, read the STATE.md Wave History header `— suite <passed>/<failed> on <platform>`. An auto-fix call with a known wave uses the event selector above. An empty selector alone is not a fallback case: an event without `counts`, for example after fail-fast before the test suite, supplies no replacement zeros and does not by itself permit count fallback.
|
|
190
214
|
>
|
|
191
215
|
> If no counts are available and none of those fallback cases applies, omit `suite_passed` and `suite_failed`. Absent means "not measured"; a present `suite_failed: 0` means "measured, zero failures". Never zero-fill missing measurements.
|
|
192
216
|
>
|
|
@@ -386,9 +386,33 @@ If the commit itself fails (e.g., nothing to commit, pre-commit hook rejects), d
|
|
|
386
386
|
' .orchestrator/metrics/events.jsonl | tail -1
|
|
387
387
|
```
|
|
388
388
|
|
|
389
|
-
`WAVE` and `SEMANTIC_SESSION_ID` come from the existing own session/wave identity; never construct either for this lookup. Filtering by both is mandatory: the ledger accumulates across sessions, and a different session or wave must not supply these counts. `counts.passed` → `suite_passed`, `counts.failed` → `suite_failed`. `tail -1` selects the last matching gate run of the wave, including auto-fix. `-R` with `fromjson? | objects` skips an unreadable ledger line (a torn tail from a killed writer, #1401) instead of letting one parse error end `jq` and
|
|
389
|
+
`WAVE` and `SEMANTIC_SESSION_ID` come from the existing own session/wave identity; never construct either for this lookup. Filtering by both is mandatory: the ledger accumulates across sessions, and a different session or wave must not supply these counts. `counts.passed` → `suite_passed`, `counts.failed` → `suite_failed`. `tail -1` selects the last matching gate run of the wave, including auto-fix. `-R` with `fromjson? | objects` skips an unreadable ledger line (a torn tail from a killed writer, #1401) instead of letting one parse error end `jq`. The old pipeline could emit an earlier matching value before the parse error; `tail -1` retained that stale value and could hide the jq failure. Known limit: an interrupted `appendFile` can leave a truncated record that `fromjson?` discards completely; `tail -1` can still select an older matching run, which alone is not evidence of the current gate result. The library emits one record per `runQualityGateWithRetry` call, not per retry; `attempts` is not a record count.
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
An empty counts selector cannot distinguish a missing event, missing wave attribution,
|
|
392
|
+
or an attributed event without counts after fail-fast. Inspect the own session's
|
|
393
|
+
records without the counts filter before deciding whether a documented count
|
|
394
|
+
fallback case applies:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
jq -cR --argjson w "$WAVE" --arg s "$SEMANTIC_SESSION_ID" '
|
|
398
|
+
fromjson? | objects
|
|
399
|
+
| select((.event | type) == "string" and (.event | startswith("orchestrator.quality_gate.")))
|
|
400
|
+
| select(.semantic_session_id == $s)
|
|
401
|
+
| select(.wave_number == $w or .wave_number == null)
|
|
402
|
+
| {event, semantic_session_id, wave_number, counts}
|
|
403
|
+
' .orchestrator/metrics/events.jsonl
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
This is a diagnostic, not a replacement counts source. No rows means no readable
|
|
407
|
+
matching event; a row without `wave_number` is not proof of this wave's run;
|
|
408
|
+
a row for this wave without `counts` supplies no measurement. Keep the session
|
|
409
|
+
filter and never copy counts from another session or wave, or from a row without
|
|
410
|
+
wave attribution. An event without `semantic_session_id` is not attributable by
|
|
411
|
+
this query either; diagnose missing session attribution from independent evidence
|
|
412
|
+
of the own gate run, not by adopting an unrelated unattributed event. Missing
|
|
413
|
+
attribution or an empty result alone does not prove a fallback source exists.
|
|
414
|
+
|
|
415
|
+
**Count fallback is limited to three cases:** pre-#954 sessions, a gate run outside the event wrapper, or missing wave attribution (including an auto-fix call without a readable own wave scope). An event without `semantic_session_id` cannot match the selector — emitters set it only from the session lock via `sessionAttribution()` — and is treated like a gate run outside the wrapper. In those cases only, fall back to the gate output you read for this wave — the same run whose counts go into the § 3a header — and say so in the progress update. Do not read the STATE.md Wave History header here: this step runs before § 3a writes this wave's header, so the newest header line there belongs to the previous wave. An auto-fix call with a known wave uses the event selector above. An empty selector alone is not a fallback case: an event without `counts`, for example after fail-fast before the test suite, supplies no replacement zeros and does not by itself permit count fallback.
|
|
392
416
|
|
|
393
417
|
If no counts are available and none of those fallback cases applies, omit `suite_passed` and `suite_failed`. Absent means "not measured"; a present `suite_failed: 0` means "measured, zero failures". Never zero-fill missing measurements.
|
|
394
418
|
|