session-orchestrator 5.2.0 → 5.3.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/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/session.md +1 -1
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +136 -0
- package/README.md +9 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/baseline.md +12 -6
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +7 -5
- package/docs/events-schema.md +56 -9
- package/docs/rule-authoring.md +58 -6
- package/docs/session-config-reference.md +100 -7
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +85 -8
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +168 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +200 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +233 -2
- package/hooks/subagent-telemetry.mjs +15 -19
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +1 -1
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/session.md +1 -1
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +117 -15
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +75 -29
- package/scripts/eval-session.mjs +65 -3
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +524 -5
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +146 -4
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/rule-loader.mjs +41 -12
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +4 -3
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +30 -5
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/sweep-expired-rules.mjs +14 -3
- package/scripts/validate-plugin.mjs +12 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-mirror.mjs +9 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/autopilot/SKILL.md +22 -7
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +1 -1
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +17 -5
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/references/phase-4-ssot-environment-check.md +2 -1
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +8 -4
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +5 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +2 -1
- package/.codex-plugin/skills/convergence-monitoring/agents/openai.yaml +0 -5
- package/.codex-plugin/skills/npm-publish/agents/openai.yaml +0 -5
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
|
@@ -531,15 +531,23 @@ function synthesizeRecord({ recordId, synthetic, gathered, nowMs, status = 'aban
|
|
|
531
531
|
const BACKFILL_STUB_STATUSES = new Set(['abandoned']);
|
|
532
532
|
|
|
533
533
|
/**
|
|
534
|
-
* True when a sessions.jsonl record is a backfilled
|
|
535
|
-
* provenance (`_backfill_source`) AND a stub status.
|
|
536
|
-
* hand-written `abandoned` record with no backfill
|
|
537
|
-
* deliberate statement and is never superseded on our
|
|
534
|
+
* True when a sessions.jsonl record is a backfilled stub this module may
|
|
535
|
+
* SUPERSEDE — reconstructed provenance (`_backfill_source`) AND a stub status.
|
|
536
|
+
* Both are required: a hand-written `abandoned` record with no backfill
|
|
537
|
+
* provenance is somebody's deliberate statement and is never superseded on our
|
|
538
|
+
* own initiative.
|
|
539
|
+
*
|
|
540
|
+
* Named for the decision it makes, not for the record class, because
|
|
541
|
+
* `sessions-staleness-banner.mjs` asks a DIFFERENT question about the same
|
|
542
|
+
* records — `isNonAnchorStub()` there accepts EITHER marker, since a record
|
|
543
|
+
* only needs to be doubtful to be disqualified as a time anchor, where this
|
|
544
|
+
* one must be proven reconstructed before it may be overwritten. Both are
|
|
545
|
+
* module-local on purpose; they must not be shared.
|
|
538
546
|
*
|
|
539
547
|
* @param {unknown} record
|
|
540
548
|
* @returns {boolean}
|
|
541
549
|
*/
|
|
542
|
-
function
|
|
550
|
+
function isSupersedableStub(record) {
|
|
543
551
|
if (!record || typeof record !== 'object' || Array.isArray(record)) return false;
|
|
544
552
|
if (typeof record._backfill_source !== 'string') return false;
|
|
545
553
|
return BACKFILL_STUB_STATUSES.has(record.status);
|
|
@@ -636,7 +644,7 @@ function classifyExisting(readFileSync, sessionsPath, { recordId, sessionId, sem
|
|
|
636
644
|
const sessionRecords = readJsonlSafe(readFileSync, sessionsPath);
|
|
637
645
|
const newest = findRecordedSession(sessionRecords, { sessionId, semanticSessionId, startedAt });
|
|
638
646
|
if (!newest) return { kind: 'absent' };
|
|
639
|
-
if (
|
|
647
|
+
if (isSupersedableStub(newest)) {
|
|
640
648
|
return { kind: 'stub', matchedId: newest.session_id, stubId: newest.session_id };
|
|
641
649
|
}
|
|
642
650
|
return { kind: 'canonical', matchedId: newest.session_id };
|
|
@@ -1034,6 +1042,9 @@ export async function backfillAbandonedSession({
|
|
|
1034
1042
|
* { action: 'skipped-not-completed', status } — STATE.md status isn't 'completed'
|
|
1035
1043
|
* { action: 'skipped-no-session-id' } — completed but no `session:` field
|
|
1036
1044
|
* { action: 'skipped-already-recorded', sessionId } — sessions.jsonl already has it
|
|
1045
|
+
* { action: 'skipped-key-occupied', sessionId } — identity absent, but a canonical
|
|
1046
|
+
* non-stub record of a DIFFERENT session
|
|
1047
|
+
* already holds this key (#1388 P8)
|
|
1037
1048
|
* { action: 'skipped-marker-exists', sessionId } — lost the TOCTOU claim
|
|
1038
1049
|
* { action: 'error', error, sessionId? } — any failure, swallowed
|
|
1039
1050
|
*
|
|
@@ -1118,6 +1129,22 @@ export async function backfillCompletedFromStateMd({
|
|
|
1118
1129
|
if (existing.kind === 'canonical') {
|
|
1119
1130
|
return { action: 'skipped-already-recorded', sessionId: existing.matchedId };
|
|
1120
1131
|
}
|
|
1132
|
+
// Key-occupancy guard (#1380 P6) — the counterpart of the abandoned
|
|
1133
|
+
// path's occupied-key check. Identity can say `absent` (label drift
|
|
1134
|
+
// veto, conflicting native join) while a canonical, authoritative record
|
|
1135
|
+
// already holds `recordId`; canonical readers collapse by session_id
|
|
1136
|
+
// newest-wins, so appending here would displace that record with this
|
|
1137
|
+
// zero-counter one. `recordId` is already the native id whenever STATE.md
|
|
1138
|
+
// carries one, so there is no alternate key to fall back to: skip.
|
|
1139
|
+
if (existing.kind === 'absent'
|
|
1140
|
+
&& canonicalizeSessions(readJsonlSafe(readFileSync, sessionsPath))
|
|
1141
|
+
.some((record) => record?.session_id === recordId && !isSupersedableStub(record))) {
|
|
1142
|
+
// Distinct action (#1388 P8): `skipped-already-recorded` above means
|
|
1143
|
+
// "this very session is already on file"; here identity said ABSENT and
|
|
1144
|
+
// a DIFFERENT session's record occupies the key. Sharing one string made
|
|
1145
|
+
// the two causes indistinguishable in the backfill_completed telemetry.
|
|
1146
|
+
return { action: 'skipped-key-occupied', sessionId: recordId };
|
|
1147
|
+
}
|
|
1121
1148
|
const supersedes = existing.kind === 'stub' ? existing.stubId : null;
|
|
1122
1149
|
// Preserve a matched stub's key for its append-only replacement. Without
|
|
1123
1150
|
// event timestamps or a raw join on a legacy stub, a new UUID key would
|
|
@@ -39,10 +39,11 @@
|
|
|
39
39
|
* - Production code is silent: no console.log, no console.warn.
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
import {
|
|
42
|
+
import { readFile } from 'node:fs/promises';
|
|
43
43
|
import path from 'node:path';
|
|
44
44
|
|
|
45
45
|
import { readCanonicalSessions } from './sessions-canonical.mjs';
|
|
46
|
+
import { readTailWindow } from './tail-window.mjs';
|
|
46
47
|
import { withStateMdLock } from './session-lock.mjs';
|
|
47
48
|
import { resolveStateMdPath } from './state-md/frontmatter-mutators.mjs';
|
|
48
49
|
import { parseStateMd } from './state-md/yaml-parser.mjs';
|
|
@@ -208,31 +209,19 @@ const EVENTS_MAX_AGE_MS = 2 * 24 * 60 * 60 * 1000;
|
|
|
208
209
|
async function readSessionIdsFromEvents(repoRoot) {
|
|
209
210
|
const filePath = path.join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl');
|
|
210
211
|
|
|
211
|
-
let
|
|
212
|
-
// Assigned in the try below; every path that reaches its read site has
|
|
213
|
-
// passed through that assignment (the catch returns early).
|
|
214
|
-
let windowIsPartial;
|
|
215
|
-
let handle = null;
|
|
212
|
+
let window;
|
|
216
213
|
try {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const length = size - start;
|
|
222
|
-
if (length > 0) {
|
|
223
|
-
const buf = Buffer.alloc(length);
|
|
224
|
-
const { bytesRead } = await handle.read(buf, 0, length, start);
|
|
225
|
-
text = buf.subarray(0, bytesRead).toString('utf8');
|
|
226
|
-
}
|
|
214
|
+
// Sync read inside an async function: 1 MiB ceiling, one call per n-claim —
|
|
215
|
+
// the shared primitive (tail-window.mjs) owns the byte window, this reader
|
|
216
|
+
// keeps its own fail-open mapping.
|
|
217
|
+
window = readTailWindow(filePath, EVENTS_TAIL_BYTES);
|
|
227
218
|
} catch {
|
|
228
219
|
return [];
|
|
229
|
-
} finally {
|
|
230
|
-
if (handle !== null) await handle.close().catch(() => {});
|
|
231
220
|
}
|
|
232
221
|
|
|
233
|
-
const lines = text.split(/\r?\n/);
|
|
222
|
+
const lines = window.text.split(/\r?\n/);
|
|
234
223
|
// The window started mid-file, so line 0 may be a truncated record.
|
|
235
|
-
if (
|
|
224
|
+
if (window.cut) lines.shift();
|
|
236
225
|
|
|
237
226
|
const cutoff = Date.now() - EVENTS_MAX_AGE_MS;
|
|
238
227
|
const ids = [];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the leading /session mode independently of the user's task context.
|
|
3
|
+
* This is a text boundary, not a shell/option parser: quotes and command syntax
|
|
4
|
+
* in the context remain data. `unknown` is a ledger value, never a live mode.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} [argumentsText] Complete text following /session.
|
|
7
|
+
* @returns {{sessionType: string, context: string, profile?: string, invalidMode?: string}}
|
|
8
|
+
*/
|
|
9
|
+
export function resolveSessionInvocation(argumentsText = '') {
|
|
10
|
+
if (typeof argumentsText !== 'string') {
|
|
11
|
+
throw new TypeError('session invocation arguments must be a string');
|
|
12
|
+
}
|
|
13
|
+
const [_, mode, context = ''] = argumentsText.trimStart().match(/^(\S+)(?:\s+([\s\S]*))?$/u) ?? [];
|
|
14
|
+
if (mode === undefined) return { sessionType: 'deep', context: '' };
|
|
15
|
+
if (mode === 'ultradeep') return { sessionType: 'deep', profile: 'ultradeep', context };
|
|
16
|
+
if (['housekeeping', 'feature', 'deep'].includes(mode)) {
|
|
17
|
+
return { sessionType: mode, context };
|
|
18
|
+
}
|
|
19
|
+
return { sessionType: 'deep', context, invalidMode: mode };
|
|
20
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Leaf module — no imports from siblings or parent.
|
|
6
6
|
*
|
|
7
7
|
* Exports: CURRENT_SESSION_SCHEMA_VERSION, SESSION_KEY_ALIASES,
|
|
8
|
-
* VALID_SESSION_TYPES, VALID_SESSION_PROFILES,
|
|
9
|
-
* AGENT_SUMMARY_FIELDS
|
|
8
|
+
* WAVE_KEY_ALIASES, VALID_SESSION_TYPES, VALID_SESSION_PROFILES,
|
|
9
|
+
* REQUIRED_FIELDS, AGENT_SUMMARY_FIELDS, OPTIONAL_FIELDS
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
@@ -53,6 +53,24 @@ export const SESSION_KEY_ALIASES = Object.freeze({
|
|
|
53
53
|
isolation_override: 'isolation',
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Safe per-wave key aliases (legacy -> canonical) — the `waves[]` counterpart of
|
|
58
|
+
* SESSION_KEY_ALIASES, same-shape renames only. Applied by normalizeWaveKeys
|
|
59
|
+
* (normalizer.mjs) on read AND by scripts/emit-session.mjs on write (#1390 P1).
|
|
60
|
+
*
|
|
61
|
+
* The canonical spelling is `agent_count_*` (skills/session-end/
|
|
62
|
+
* metrics-collection.md). The ledger carried 23 sessions in that spelling and
|
|
63
|
+
* exactly one, `main-2026-09-18-session-1` (5 waves), in the de-underscored
|
|
64
|
+
* `agents_*` spelling (measured 2026-09-19 @ 8f6ac022 over
|
|
65
|
+
* .orchestrator/metrics/sessions.jsonl) — a consumer reading
|
|
66
|
+
* `w.agent_count_completed` got `undefined` for every one of its waves.
|
|
67
|
+
*/
|
|
68
|
+
export const WAVE_KEY_ALIASES = Object.freeze({
|
|
69
|
+
agents_planned: 'agent_count_planned',
|
|
70
|
+
agents_started: 'agent_count_started',
|
|
71
|
+
agents_completed: 'agent_count_completed',
|
|
72
|
+
});
|
|
73
|
+
|
|
56
74
|
// ---------------------------------------------------------------------------
|
|
57
75
|
// Enums / required field lists
|
|
58
76
|
// ---------------------------------------------------------------------------
|
|
@@ -198,4 +216,14 @@ export const OPTIONAL_FIELDS = Object.freeze([
|
|
|
198
216
|
// lacking the field validates unchanged. Value set + the full rationale for
|
|
199
217
|
// why it is NOT a VALID_SESSION_TYPES member: VALID_SESSION_PROFILES above.
|
|
200
218
|
'session_profile',
|
|
219
|
+
// #1339 P8 — HEAD sha at session start. Gives `/evolve analyze` an
|
|
220
|
+
// attributable commit range instead of a time window that also catches
|
|
221
|
+
// parallel sessions' commits. Absent = not measured. NOT guaranteed to be a
|
|
222
|
+
// full sha: scripts/emit-session.mjs adopts STATE.md `session-start-ref`
|
|
223
|
+
// only when it is a full 40/64-hex sha, but an explicit key already on the
|
|
224
|
+
// record wins unchecked (even `null`), and every record before that
|
|
225
|
+
// derivation was hand-composed — 19 with a 40-char and 9 with a 7-char
|
|
226
|
+
// value (measured 2026-09-19 @ 8f6ac022, jq over sessions.jsonl).
|
|
227
|
+
// Consumers must accept a short sha or `null`, never assume a full one.
|
|
228
|
+
'session_start_ref',
|
|
201
229
|
]);
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* Imports: constants.mjs. No imports from siblings (validator, timestamps,
|
|
6
6
|
* aliases) or parent barrel.
|
|
7
7
|
*
|
|
8
|
-
* Exports: normalizeSession
|
|
8
|
+
* Exports: normalizeSession, normalizeWaveKeys
|
|
9
9
|
* Module-private: _warnedMissingSchemaVersion (Set, per-process dedupe),
|
|
10
|
-
* isPlainObject, _canonicalizeExpressPath
|
|
10
|
+
* isPlainObject, _canonicalizeExpressPath, _aliasWaves
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { SESSION_KEY_ALIASES } from './constants.mjs';
|
|
13
|
+
import { SESSION_KEY_ALIASES, WAVE_KEY_ALIASES } from './constants.mjs';
|
|
14
14
|
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
// Module-level state
|
|
@@ -79,12 +79,61 @@ function _canonicalizeExpressPath(next) {
|
|
|
79
79
|
next.express_path = raw.activated;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Apply WAVE_KEY_ALIASES to every plain-object wave, mirroring the top-level
|
|
84
|
+
* SESSION_KEY_ALIASES rule: the legacy key is preserved, and an existing
|
|
85
|
+
* canonical key is never clobbered. Copy-on-write — the caller's wave objects
|
|
86
|
+
* are never mutated (normalizeSession's `{ ...entry }` is shallow, so `waves`
|
|
87
|
+
* is still the caller's array).
|
|
88
|
+
*
|
|
89
|
+
* @param {any[]} waves
|
|
90
|
+
* @returns {any[]} the same array reference when no wave needed an alias
|
|
91
|
+
*/
|
|
92
|
+
function _aliasWaves(waves) {
|
|
93
|
+
let changed = false;
|
|
94
|
+
const out = waves.map((w) => {
|
|
95
|
+
if (!isPlainObject(w)) return w;
|
|
96
|
+
let copy = null;
|
|
97
|
+
for (const [oldKey, newKey] of Object.entries(WAVE_KEY_ALIASES)) {
|
|
98
|
+
if (oldKey in w && !(newKey in w)) {
|
|
99
|
+
copy ??= { ...w };
|
|
100
|
+
copy[newKey] = w[oldKey];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (copy === null) return w;
|
|
104
|
+
changed = true;
|
|
105
|
+
return copy;
|
|
106
|
+
});
|
|
107
|
+
return changed ? out : waves;
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
// ---------------------------------------------------------------------------
|
|
83
111
|
// Public API
|
|
84
112
|
// ---------------------------------------------------------------------------
|
|
85
113
|
|
|
86
114
|
/**
|
|
87
|
-
*
|
|
115
|
+
* Apply ONLY the per-wave key aliases (WAVE_KEY_ALIASES) to a session entry —
|
|
116
|
+
* the subset of normalizeSession that is safe on the WRITE path
|
|
117
|
+
* (scripts/emit-session.mjs, #1390 P1). The rest of normalizeSession is not:
|
|
118
|
+
* it tags a missing `schema_version` as 0 where validateSession stamps the
|
|
119
|
+
* current version, and applies top-level aliases the write-path contract
|
|
120
|
+
* explicitly excludes.
|
|
121
|
+
*
|
|
122
|
+
* Never throws. Non-objects and entries without a `waves` array are returned
|
|
123
|
+
* unchanged; so is an entry none of whose waves needs an alias (same reference).
|
|
124
|
+
*
|
|
125
|
+
* @param {any} entry
|
|
126
|
+
* @returns {any} a new entry with aliased waves, or the input unchanged
|
|
127
|
+
*/
|
|
128
|
+
export function normalizeWaveKeys(entry) {
|
|
129
|
+
if (!isPlainObject(entry) || !Array.isArray(entry.waves)) return entry;
|
|
130
|
+
const waves = _aliasWaves(entry.waves);
|
|
131
|
+
return waves === entry.waves ? entry : { ...entry, waves };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Normalize a session entry read from disk. Applies SAFE key aliases (top-level
|
|
136
|
+
* SESSION_KEY_ALIASES and per-wave WAVE_KEY_ALIASES), collapses
|
|
88
137
|
* the legacy object form of `express_path` onto its canonical boolean, and tags
|
|
89
138
|
* legacy entries without `schema_version` as 0 (distinct from
|
|
90
139
|
* CURRENT_SESSION_SCHEMA_VERSION=2 which is stamped on new writes; bumped
|
|
@@ -112,6 +161,9 @@ export function normalizeSession(entry) {
|
|
|
112
161
|
}
|
|
113
162
|
}
|
|
114
163
|
|
|
164
|
+
// Per-wave aliases (#1390 P1) — same non-clobber rule, one level down.
|
|
165
|
+
if (Array.isArray(next.waves)) next.waves = _aliasWaves(next.waves);
|
|
166
|
+
|
|
115
167
|
// express_path — same key, two shapes. Collapse onto the canonical boolean.
|
|
116
168
|
_canonicalizeExpressPath(next);
|
|
117
169
|
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* SESSION_KEY_ALIASES, VALID_SESSION_TYPES,
|
|
60
60
|
* REQUIRED_FIELDS, AGENT_SUMMARY_FIELDS
|
|
61
61
|
* session-schema/validator.mjs — ValidationError, validateSession
|
|
62
|
-
* session-schema/normalizer.mjs — normalizeSession
|
|
62
|
+
* session-schema/normalizer.mjs — normalizeSession, normalizeWaveKeys
|
|
63
63
|
* session-schema/timestamps.mjs — clampTimestampsMonotonic
|
|
64
64
|
* session-schema/aliases.mjs — aliasLegacyEndedAt
|
|
65
65
|
*/
|
|
@@ -67,7 +67,12 @@
|
|
|
67
67
|
export { CURRENT_SESSION_SCHEMA_VERSION, SESSION_KEY_ALIASES } from './session-schema/constants.mjs';
|
|
68
68
|
export { ValidationError, validateSession } from './session-schema/validator.mjs';
|
|
69
69
|
export { serializeSessionLineChecked } from './session-schema/serializer.mjs';
|
|
70
|
-
export { normalizeSession } from './session-schema/normalizer.mjs';
|
|
70
|
+
export { normalizeSession, normalizeWaveKeys } from './session-schema/normalizer.mjs';
|
|
71
71
|
export { clampTimestampsMonotonic } from './session-schema/timestamps.mjs';
|
|
72
72
|
export { aliasLegacyEndedAt } from './session-schema/aliases.mjs';
|
|
73
|
-
export {
|
|
73
|
+
export {
|
|
74
|
+
isRealSession,
|
|
75
|
+
filterRealSessions,
|
|
76
|
+
tailRealSessions,
|
|
77
|
+
isCoordinatorDirectHousekeeping,
|
|
78
|
+
} from './session-schema/filters.mjs';
|
|
@@ -70,11 +70,22 @@ const local = (rel) => pathToFileURL(path.join(import.meta.dirname, rel)).href;
|
|
|
70
70
|
const CI_UNKNOWN_HINT_DEFAULT = 'run `glab ci status` on demand';
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* `ci-status` "no
|
|
74
|
-
* worth asking about: GitLab's `no-pipeline-for-head-sha
|
|
75
|
-
* `no-check-runs-for-head` (
|
|
73
|
+
* `ci-status` "no usable reading for HEAD" reasons after which the last PUSHED
|
|
74
|
+
* commit is worth asking about: GitLab's `no-pipeline-for-head-sha`, GitHub's
|
|
75
|
+
* `no-check-runs-for-head`, and GitLab's `pipeline-unmatched-ref` (all emitted
|
|
76
|
+
* by `checkCiStatus`).
|
|
77
|
+
*
|
|
78
|
+
* `pipeline-unmatched-ref` (#857) is not a "no data" state — pipelines EXIST for
|
|
79
|
+
* the sha, they just all sit on a foreign ref, so the reading stays `unknown`.
|
|
80
|
+
* For this consumer that is the same DARK outcome: the operator learns nothing
|
|
81
|
+
* about HEAD. Routing it to the pushed-commit re-query at least yields the
|
|
82
|
+
* pushed sha's own verdict instead of an unactionable line.
|
|
76
83
|
*/
|
|
77
|
-
const PUSHED_FOLLOW_UP_REASONS = new Set([
|
|
84
|
+
const PUSHED_FOLLOW_UP_REASONS = new Set([
|
|
85
|
+
'no-pipeline-for-head-sha',
|
|
86
|
+
'no-check-runs-for-head',
|
|
87
|
+
'pipeline-unmatched-ref',
|
|
88
|
+
]);
|
|
78
89
|
|
|
79
90
|
/**
|
|
80
91
|
* Full SHAs for `refs`, in order, from ONE `git rev-parse` — or `null` when
|
|
@@ -190,6 +201,22 @@ function pushedVerdictText(v) {
|
|
|
190
201
|
return `CI ${v.status}${parts.length > 0 ? ` (${parts.join(', ')})` : ''}`;
|
|
191
202
|
}
|
|
192
203
|
|
|
204
|
+
/**
|
|
205
|
+
* On-demand command listing every CI run of ONE commit, in the CLI that read
|
|
206
|
+
* CI — a glab command cannot answer in a GitHub repo (#1339 P1).
|
|
207
|
+
*
|
|
208
|
+
* Takes the FULL sha. Measured 2026-09-19, glab 1.117.0: `glab ci status` has
|
|
209
|
+
* no `--ref` flag ("Unknown flag: --ref.", exit 1), and `glab ci list --sha`
|
|
210
|
+
* matches exactly, so a short sha lists nothing.
|
|
211
|
+
*
|
|
212
|
+
* @param {boolean} gh The reading came from the gh CLI
|
|
213
|
+
* @param {string} sha Full commit sha
|
|
214
|
+
* @returns {string}
|
|
215
|
+
*/
|
|
216
|
+
function commitRunsCommand(gh, sha) {
|
|
217
|
+
return gh ? `gh run list --commit ${sha}` : `glab ci list --sha ${sha}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
193
220
|
/**
|
|
194
221
|
* Hint text for a `status: 'unknown'` ci-status reading.
|
|
195
222
|
*
|
|
@@ -212,8 +239,7 @@ function ciUnknownHint(result, ctx) {
|
|
|
212
239
|
if (pushed?.sameAsHead) return `last pushed: ${short} = HEAD — ${onDemand}`;
|
|
213
240
|
const verdict = pushedVerdictText(pushed?.verdict);
|
|
214
241
|
if (verdict) return `last pushed: ${short} — ${verdict}`;
|
|
215
|
-
|
|
216
|
-
return `last pushed: ${short} (pipeline not checked; run \`${check}\`)`;
|
|
242
|
+
return `last pushed: ${short} (pipeline not checked; run \`${commitRunsCommand(gh, sha)}\`)`;
|
|
217
243
|
}
|
|
218
244
|
|
|
219
245
|
/**
|
|
@@ -229,6 +255,53 @@ function hasFailedAllowFailureJobs(r) {
|
|
|
229
255
|
return Array.isArray(r?.allowFailureJobs) && r.allowFailureJobs.length > 0;
|
|
230
256
|
}
|
|
231
257
|
|
|
258
|
+
/**
|
|
259
|
+
* How many same-commit pipelines the ref preference SET ASIDE ended `failed`
|
|
260
|
+
* (#1390 P5) — the ONE predicate the `ci-status` renderer and its `severityOf`
|
|
261
|
+
* share, for the reason {@link hasFailedAllowFailureJobs} records (#1333).
|
|
262
|
+
* Only `failed` counts: a set-aside `canceled`/`skipped` run says nothing about
|
|
263
|
+
* the commit, and a line that fires on those would teach the operator to skip
|
|
264
|
+
* the banner (HR-101).
|
|
265
|
+
*
|
|
266
|
+
* @param {*} r
|
|
267
|
+
* @returns {number}
|
|
268
|
+
*/
|
|
269
|
+
function droppedFailedCount(r) {
|
|
270
|
+
const statuses = r?.details?.droppedStatuses;
|
|
271
|
+
return Array.isArray(statuses) ? statuses.filter((s) => s === 'failed').length : 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Banner line for a `green` reading beside a FAILED pipeline of the same
|
|
276
|
+
* commit that the ref preference set aside (#1390 P5). The verdict stays the
|
|
277
|
+
* #857 ref-preference result; this only stops the failure from vanishing
|
|
278
|
+
* behind a silent green.
|
|
279
|
+
*
|
|
280
|
+
* NAMED CEILING (BV-004): one `git rev-parse HEAD` (2s timeout), run only on
|
|
281
|
+
* this branch — a green reading WITH a set-aside `failed` run, behind the
|
|
282
|
+
* network opt-in. The reading carries no sha, and the GitLab path read the
|
|
283
|
+
* local HEAD, so HEAD is the commit it spoke for. REVISIT TRIGGER: once
|
|
284
|
+
* `checkCiStatus` publishes the sha it read, take it from the reading.
|
|
285
|
+
*
|
|
286
|
+
* Only the GitLab path publishes `droppedStatuses` today (`candidateEvidence`
|
|
287
|
+
* is called from `checkGitlab` alone); the gh form exists so a GitHub producer
|
|
288
|
+
* can never print a glab command (#1339 P1 class) — see {@link commitRunsCommand}.
|
|
289
|
+
*
|
|
290
|
+
* @param {*} r A green reading with `droppedFailedCount(r) > 0`
|
|
291
|
+
* @param {{repoRoot?: string}} [ctx]
|
|
292
|
+
* @returns {string}
|
|
293
|
+
*/
|
|
294
|
+
function droppedFailedLine(r, ctx) {
|
|
295
|
+
const sha = revParseShas(ctx?.repoRoot, ['HEAD'])?.[0] ?? null;
|
|
296
|
+
const gh = r.details.cliUsed === 'gh';
|
|
297
|
+
let check;
|
|
298
|
+
if (sha) check = commitRunsCommand(gh, sha);
|
|
299
|
+
else check = gh ? 'gh run list' : 'glab ci list --status=failed';
|
|
300
|
+
const ref = r.details.matchedRef ?? 'HEAD';
|
|
301
|
+
const on = sha ? `${ref} for ${sha.slice(0, 8)}` : ref;
|
|
302
|
+
return `⚠ CI green on ${on}, but ${droppedFailedCount(r)} other pipeline(s) for the same commit FAILED (set-aside statuses: ${r.details.droppedStatuses.join(', ')}) — check \`${check}\``;
|
|
303
|
+
}
|
|
304
|
+
|
|
232
305
|
// ---------------------------------------------------------------------------
|
|
233
306
|
// Budget
|
|
234
307
|
// ---------------------------------------------------------------------------
|
|
@@ -476,9 +549,17 @@ export const PROBES = [
|
|
|
476
549
|
const job = r.failingJobName ? ` Failing job: ${r.failingJobName}` : '';
|
|
477
550
|
return `🚨 CI RED on HEAD (pipeline #${pid})${green}.${job}`;
|
|
478
551
|
}
|
|
479
|
-
if (r.status === 'green'
|
|
480
|
-
const
|
|
481
|
-
|
|
552
|
+
if (r.status === 'green') {
|
|
553
|
+
const lines = [];
|
|
554
|
+
if (hasFailedAllowFailureJobs(r)) {
|
|
555
|
+
const names = r.allowFailureJobs.map((j) => j?.name ?? String(j)).join(', ');
|
|
556
|
+
lines.push(`⚠ CI green on HEAD, but ${r.allowFailureJobs.length} allow_failure job(s) FAILED: ${names}. A pipeline reports success regardless of these.`);
|
|
557
|
+
}
|
|
558
|
+
// #1390 P5: a failed same-commit pipeline the ref preference set aside.
|
|
559
|
+
if (droppedFailedCount(r) > 0) lines.push(droppedFailedLine(r, ctx));
|
|
560
|
+
// Both findings can hold at once; `pushBanner` takes a multi-line
|
|
561
|
+
// string verbatim, so neither swallows the other.
|
|
562
|
+
return lines.length > 0 ? lines.join('\n') : null;
|
|
482
563
|
}
|
|
483
564
|
return null;
|
|
484
565
|
},
|
|
@@ -493,7 +574,11 @@ export const PROBES = [
|
|
|
493
574
|
if (!r || typeof r !== 'object') return 'ok';
|
|
494
575
|
if (r.degraded) return 'warn';
|
|
495
576
|
if (r.status === 'red') return 'alert';
|
|
496
|
-
|
|
577
|
+
// A set-aside failed run (#1390 P5) is `warn`, never `alert`: the reading
|
|
578
|
+
// itself is green, and `alert` is what a red pipeline scores.
|
|
579
|
+
if (r.status === 'green') {
|
|
580
|
+
return hasFailedAllowFailureJobs(r) || droppedFailedCount(r) > 0 ? 'warn' : 'ok';
|
|
581
|
+
}
|
|
497
582
|
// #1337: HEAD undetermined but the pushed commit's pipeline is red —
|
|
498
583
|
// the code on origin is broken, so this is an alert, not a warning.
|
|
499
584
|
if (r.pushed?.verdict?.status === 'red') return 'alert';
|
|
@@ -399,6 +399,29 @@ export function countSessionsInJsonl(raw) {
|
|
|
399
399
|
* is skipped rather than aborting the whole read (same posture as the readers
|
|
400
400
|
* in `session-close-backfill.mjs` and `backfill-abandoned-sessions.mjs`).
|
|
401
401
|
*
|
|
402
|
+
* NOT EVERY RAW READ OF `sessions.jsonl` IS A GAP TO CLOSE (#1221 P2). Five
|
|
403
|
+
* more readers bypass this module's collapse on purpose — verified via
|
|
404
|
+
* `rg -n "sessions.jsonl|SESSIONS"` against each file — and none of them
|
|
405
|
+
* should be "migrated" to `readCanonicalSessions`:
|
|
406
|
+
* - `scripts/lib/sessions-integrity-banner.mjs` reads every raw line
|
|
407
|
+
* (`readJsonlLines`) because it EXISTS to count malformed/mirror-invalid
|
|
408
|
+
* records; canonicalizing first would hide the corruption it reports on.
|
|
409
|
+
* - `scripts/lib/session-record-repair.mjs` is the repair tool itself — it
|
|
410
|
+
* must see every raw record, malformed and duplicate alike, to fix them;
|
|
411
|
+
* a canonical read would collapse or drop the very rows it needs to repair.
|
|
412
|
+
* - `scripts/migrate-sessions-jsonl.mjs` rewrites legacy-shaped records one
|
|
413
|
+
* raw line at a time; a canonicalized read could silently skip a
|
|
414
|
+
* superseded or duplicate legacy record before the migration ever sees it.
|
|
415
|
+
* - `scripts/gc-stale-worktrees.mjs` (`loadRecentSessionRefs`) scans every
|
|
416
|
+
* raw line's string content for worktree references within a 7-day
|
|
417
|
+
* window; a canonical read could drop a duplicate/superseded stub that
|
|
418
|
+
* still names a worktree, making a live worktree look unreferenced and
|
|
419
|
+
* eligible for garbage collection.
|
|
420
|
+
* - `readTailSession()` in `scripts/autopilot.mjs` parses only the LAST raw
|
|
421
|
+
* line to project the record this run's own session just appended,
|
|
422
|
+
* verbatim; canonicalizing could return a different record than the tail
|
|
423
|
+
* line if a collapse rule folded it away.
|
|
424
|
+
*
|
|
402
425
|
* @param {object} [args]
|
|
403
426
|
* @param {string} [args.repoRoot] project root; the ledger is resolved as
|
|
404
427
|
* `<repoRoot>/.orchestrator/metrics/sessions.jsonl`. Defaults to
|
|
@@ -103,7 +103,13 @@ import {
|
|
|
103
103
|
} from './vault-mirror/render-sessions.mjs';
|
|
104
104
|
|
|
105
105
|
/** Repo-relative path to the session ledger (one record per closed session). */
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Ledger path, relative to the repo root. EXPORTED (#1417) so the CLI front-end
|
|
108
|
+
* `scripts/check-sessions-integrity.mjs` addresses the same file this banner
|
|
109
|
+
* judges instead of re-typing the literal — two spellings of one path is how a
|
|
110
|
+
* guard ends up measuring a file nobody writes.
|
|
111
|
+
*/
|
|
112
|
+
export const SESSIONS_PATH = '.orchestrator/metrics/sessions.jsonl';
|
|
107
113
|
|
|
108
114
|
/** Max session_ids listed inline before the message collapses to "+N more". */
|
|
109
115
|
export const MAX_LISTED_IDS = 5;
|