session-orchestrator 5.7.0 → 5.9.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 -0
- package/.agents/skills/architecture/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +1 -0
- package/.agents/skills/eval/SKILL.md +1 -1
- package/.agents/skills/frontmatter-guard/SKILL.md +1 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +1 -0
- package/.agents/skills/hook-development/SKILL.md +1 -0
- package/.agents/skills/mcp-builder/SKILL.md +1 -0
- package/.agents/skills/tmux-layout/SKILL.md +3 -0
- package/.agents/skills/tmux-layout/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +1 -0
- package/.agents/skills/vault-sync/SKILL.md +1 -0
- package/.agents/skills/write-executable-plan/SKILL.md +3 -0
- package/.agents/skills/write-executable-plan/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/architecture/SKILL.md +3 -0
- package/.codex-plugin/skills/architecture/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -0
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +1 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +1 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +1 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +1 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +3 -0
- package/.codex-plugin/skills/tmux-layout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +1 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +1 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +3 -0
- package/.codex-plugin/skills/write-executable-plan/agents/openai.yaml +5 -0
- package/.cursor/commands/architecture.md +13 -0
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/tmux-layout.md +13 -0
- package/.cursor/commands/write-executable-plan.md +13 -0
- package/.cursor/skills/architecture/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/tmux-layout/SKILL.md +0 -1
- package/.cursor/skills/write-executable-plan/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +68 -0
- package/README.md +13 -9
- package/agents/eval-judge.md +5 -5
- package/docs/baseline.md +4 -3
- package/docs/codex-setup.md +6 -4
- package/docs/components.md +5 -4
- package/docs/events-schema.md +45 -4
- package/docs/github-mirror-protection.md +3 -2
- package/docs/install.md +39 -3
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +2 -2
- package/docs/session-config-reference.md +7 -5
- package/docs/session-config-template.md +3 -2
- package/evals/README.md +60 -0
- package/evals/instruction-ablation/README.md +147 -0
- package/evals/instruction-ablation/cases/psa-003-foreign-work.json +34 -0
- package/evals/instruction-ablation/cases/sec-007-sql-parameterisation.json +32 -0
- package/evals/instruction-ablation/cases/vbc-001-verify-before-claiming.json +41 -0
- package/evals/instruction-ablation/run.mjs +336 -0
- package/evals/psa-003-foreign-work/case.yaml +16 -0
- package/evals/psa-003-foreign-work/fixture.sh +12 -0
- package/evals/psa-003-foreign-work/graders/foreign-file-intact.md +7 -0
- package/evals/psa-003-foreign-work/graders/names-foreign-work.md +6 -0
- package/evals/psa-003-foreign-work/graders/no-destructive-git.md +8 -0
- package/evals/psa-003-foreign-work/graders/ran-git.md +7 -0
- package/evals/psa-003-foreign-work/prompt.md +6 -0
- package/hooks/_lib/hook-import-set.json +10 -2
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/post-tool-batch-wave-signal.mjs +1 -0
- package/hooks/post-tooluse-frontend-slop.mjs +11 -1
- package/hooks/pre-task-scope-disjoint.mjs +8 -6
- package/monitors/monitors.json +1 -1
- package/package.json +3 -1
- package/pi/prompts/architecture.md +11 -0
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/tmux-layout.md +11 -0
- package/pi/prompts/write-executable-plan.md +11 -0
- package/scripts/backfill-abandoned-sessions.mjs +2 -2
- package/scripts/backfill-sessions.mjs +9 -7
- package/scripts/codex-install.mjs +108 -23
- package/scripts/emit-session.mjs +15 -2
- package/scripts/fleet-auflagen.mjs +54 -0
- package/scripts/generate-agents-skills.mjs +9 -16
- package/scripts/generate-codex-skills.mjs +8 -6
- package/scripts/generate-cursor-adapter.mjs +9 -74
- package/scripts/generate-pi-prompts.mjs +8 -74
- package/scripts/github-protection-audit.mjs +8 -0
- package/scripts/lib/auto-dialectic.mjs +10 -9
- package/scripts/lib/auto-dream.mjs +8 -10
- package/scripts/lib/autopilot/telemetry.mjs +12 -6
- package/scripts/lib/bootstrap-lock-refresh.mjs +8 -12
- package/scripts/lib/ci-status-banner.mjs +38 -5
- package/scripts/lib/config/drift-check.mjs +1 -1
- package/scripts/lib/config/frontend-slop-hook.mjs +6 -0
- package/scripts/lib/config.mjs +5 -1
- package/scripts/lib/convergence-monitor.mjs +5 -3
- package/scripts/lib/ecosystem-health.mjs +5 -3
- package/scripts/lib/eval/engine.mjs +61 -27
- package/scripts/lib/eval/judge.mjs +14 -8
- package/scripts/lib/eval/session-resolve.mjs +7 -2
- package/scripts/lib/events-retention-banner.mjs +5 -2
- package/scripts/lib/events-schema.mjs +8 -0
- package/scripts/lib/events.mjs +19 -5
- package/scripts/lib/fleet-protocol.mjs +98 -1
- package/scripts/lib/instruction-budget-guard.mjs +25 -18
- package/scripts/lib/io.mjs +172 -35
- package/scripts/lib/learnings/candidates.mjs +7 -28
- package/scripts/lib/learnings/io.mjs +7 -2
- package/scripts/lib/learnings/judgment.mjs +5 -8
- package/scripts/lib/learnings/select.mjs +3 -3
- package/scripts/lib/maintenance-due-banner.mjs +2 -2
- package/scripts/lib/memory-proposals/sink.mjs +5 -6
- package/scripts/lib/memory-proposals/store.mjs +3 -5
- package/scripts/lib/mirror-issues-banner.mjs +11 -1
- package/scripts/lib/native-command.mjs +48 -0
- package/scripts/lib/orphan-reaper/scan.mjs +29 -3
- package/scripts/lib/peer-cards/writer.mjs +6 -18
- package/scripts/lib/platform.mjs +113 -4
- package/scripts/lib/plugin-package-stage.mjs +169 -0
- package/scripts/lib/plugin-surface.mjs +121 -0
- package/scripts/lib/quality-gate-wave.mjs +40 -0
- package/scripts/lib/quality-gate.mjs +36 -30
- package/scripts/lib/reconcile/idempotency.mjs +9 -10
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +48 -11
- package/scripts/lib/reconcile/writer.mjs +11 -17
- package/scripts/lib/rule-loader.mjs +32 -6
- package/scripts/lib/scope-echo.mjs +12 -2
- package/scripts/lib/scope-gate.mjs +2 -1
- package/scripts/lib/session-close-backfill.mjs +3 -4
- package/scripts/lib/session-id.mjs +2 -1
- package/scripts/lib/session-record-repair.mjs +28 -21
- package/scripts/lib/session-schema/constants.mjs +20 -5
- package/scripts/lib/session-schema/validator.mjs +12 -0
- package/scripts/lib/session-start-probes.mjs +22 -7
- package/scripts/lib/sessions-staleness-banner.mjs +2 -3
- package/scripts/lib/skill-evolution/engine.mjs +6 -9
- package/scripts/lib/skill-evolution/idempotency.mjs +8 -10
- package/scripts/lib/slopcheck.mjs +9 -7
- package/scripts/lib/sunset/walker.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +3 -1
- package/scripts/lib/telemetry-flush-health-banner.mjs +2 -2
- package/scripts/lib/test-sibling-coverage.mjs +167 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +5 -7
- package/scripts/lib/tmux-layout/telemetry.mjs +8 -10
- package/scripts/lib/user-invocable-skills.mjs +37 -10
- package/scripts/lib/ux-grill/run-record.mjs +5 -3
- package/scripts/lib/validate/check-banner-parity.mjs +1 -0
- package/scripts/lib/validate/check-skills.mjs +12 -2
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +3 -3
- package/scripts/lib/validate/check-test-value-bans.mjs +3 -2
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -1
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/vault-archive.mjs +2 -4
- package/scripts/lib/vault-mirror/process.mjs +207 -26
- package/scripts/lib/vault-mirror/utils.mjs +16 -1
- package/scripts/lib/vault-sync-baseline.mjs +9 -8
- package/scripts/lib/wave-executor/remote-dispatch.mjs +27 -9
- package/scripts/lib/wave-resource-gate.mjs +2 -1
- package/scripts/lib/wave-transcript-tail.mjs +8 -4
- package/scripts/lib/worktree/meta.mjs +7 -3
- package/scripts/mcp-server.mjs +158 -0
- package/scripts/migrate-sessions-jsonl.mjs +7 -5
- package/scripts/migrate-vault-paths.mjs +7 -4
- package/scripts/print-learnings-index.mjs +2 -1
- package/scripts/release.mjs +4 -1
- package/scripts/relocate-vault-corpus.mjs +5 -3
- package/scripts/run-migrate-v2-cross-repo.mjs +9 -7
- package/scripts/run-quality-gate.mjs +9 -39
- package/scripts/self-update.mjs +179 -39
- package/scripts/site-numbers.mjs +8 -3
- package/scripts/sweep-expired-learnings.mjs +6 -4
- package/scripts/sync-vault-schema.mjs +3 -7
- package/scripts/windows-setup.mjs +45 -0
- package/skills/_shared/fleet-protocol.md +10 -0
- package/skills/architecture/SKILL.md +1 -0
- package/skills/bootstrap/_shared-template.md +5 -2
- package/skills/claude-md-drift-check/SKILL.md +4 -1
- package/skills/claude-md-drift-check/checker.mjs +83 -10
- package/skills/discovery/probes/ssot-code-diff.mjs +3 -3
- package/skills/eval/SKILL.md +12 -13
- package/skills/eval/rubric-v3.md +521 -0
- package/skills/frontmatter-guard/SKILL.md +1 -0
- package/skills/gitlab-portfolio/SKILL.md +1 -0
- package/skills/hook-development/SKILL.md +1 -0
- package/skills/mcp-builder/SKILL.md +1 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/repo-audit/SKILL.md +2 -2
- package/skills/session-end/SKILL.md +32 -1
- package/skills/session-end/metrics-collection.md +15 -9
- package/skills/session-end/vault-operations.md +12 -2
- package/skills/session-start/SKILL.md +2 -2
- package/skills/session-start/phase-7-1-premise-check.md +2 -1
- package/skills/session-start/references/operations-contract.md +4 -1
- package/skills/tmux-layout/SKILL.md +1 -0
- package/skills/vault-mirror/SKILL.md +1 -0
- package/skills/vault-sync/SKILL.md +6 -6
- package/skills/vault-sync/validator.sh +29 -10
- package/skills/wave-executor/references/wave-loop-dispatch.md +1 -1
- package/skills/wave-executor/references/wave-loop-review.md +13 -7
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +4 -4
- package/skills/write-executable-plan/SKILL.md +1 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Native, dependency-free MCP stdio transport. No shell, WSL, or jq required. */
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { isSessionConfigHeading } from './lib/config/section-extractor.mjs';
|
|
8
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
9
|
+
|
|
10
|
+
const SERVER_INFO = { name: 'session-orchestrator', version: '2.0.0' };
|
|
11
|
+
const TOOLS = [
|
|
12
|
+
{
|
|
13
|
+
name: 'session_config',
|
|
14
|
+
description: 'Reads Session Config section from the project instruction file (CLAUDE.md or AGENTS.md alias)',
|
|
15
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'session_metrics',
|
|
19
|
+
description: 'Reads the last 5 REAL session metrics entries from .orchestrator/metrics/sessions.jsonl (abandoned phantom stubs excluded)',
|
|
20
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const textContent = (text) => ({ content: [{ type: 'text', text }] });
|
|
24
|
+
const errorResponse = (id, code, message) => ({ jsonrpc: '2.0', id, error: { code, message } });
|
|
25
|
+
|
|
26
|
+
/** Resolve the caller's repository without passing native paths through a shell. */
|
|
27
|
+
export function resolveProjectRoot(cwd = process.cwd()) {
|
|
28
|
+
try {
|
|
29
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
30
|
+
cwd, encoding: 'utf8', windowsHide: true, timeout: 10_000,
|
|
31
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
32
|
+
}).trim();
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isFile(path, nonempty = false) {
|
|
39
|
+
try {
|
|
40
|
+
const stat = statSync(path);
|
|
41
|
+
return stat.isFile() && (!nonempty || stat.size > 0);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return false;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Read the same raw config body as the legacy shell tool (including comments). */
|
|
49
|
+
export function readSessionConfig(root) {
|
|
50
|
+
const file = ['CLAUDE.md', 'AGENTS.md'].map((name) => join(root, name))
|
|
51
|
+
.find((path) => isFile(path, true));
|
|
52
|
+
if (!file) return `No CLAUDE.md or AGENTS.md found at ${root}`;
|
|
53
|
+
const body = [];
|
|
54
|
+
let inSection = false;
|
|
55
|
+
for (const line of readFileSync(file, 'utf8').split('\n')) {
|
|
56
|
+
if (isSessionConfigHeading(line)) {
|
|
57
|
+
inSection = true;
|
|
58
|
+
} else if (line.startsWith('## ')) {
|
|
59
|
+
inSection = false;
|
|
60
|
+
} else if (inSection) {
|
|
61
|
+
body.push(line);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Shell command substitution removes LF bytes, not spaces or CR bytes.
|
|
65
|
+
return body.join('\n').replace(/\n+$/, '') || `No '## Session Config' section found in ${file}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Read the last five real records, with whole-ledger stub count and token coverage. */
|
|
69
|
+
export function readSessionMetrics(root) {
|
|
70
|
+
const file = ['.orchestrator', '.claude'].map((dir) => join(root, dir, 'metrics', 'sessions.jsonl'))
|
|
71
|
+
.find((path) => isFile(path));
|
|
72
|
+
if (!file) return 'No metrics found (checked .orchestrator/metrics/ and .claude/metrics/)';
|
|
73
|
+
const entries = [];
|
|
74
|
+
let stubCount = 0;
|
|
75
|
+
for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
|
|
76
|
+
let record;
|
|
77
|
+
try { record = JSON.parse(line); } catch { continue; }
|
|
78
|
+
// Torn writes and non-record JSON must not hide subsequent valid sessions.
|
|
79
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) continue;
|
|
80
|
+
if (record.status === 'abandoned') { stubCount++; continue; }
|
|
81
|
+
entries.push(record);
|
|
82
|
+
if (entries.length > 5) entries.shift();
|
|
83
|
+
}
|
|
84
|
+
if (!entries.length) {
|
|
85
|
+
return stubCount ? `No real sessions (abandoned stubs excluded: ${stubCount})`
|
|
86
|
+
: 'No metrics found (file is empty)';
|
|
87
|
+
}
|
|
88
|
+
const record = entries.findLast((entry) => (entry.total_token_input !== null && entry.total_token_input !== undefined)
|
|
89
|
+
|| (entry.total_token_output !== null && entry.total_token_output !== undefined));
|
|
90
|
+
const coverage = record?.subagents_with_tokens ?? 0;
|
|
91
|
+
const summary = record
|
|
92
|
+
? `tokens: ${record.total_token_input ?? '?'} in / ${record.total_token_output ?? '?'} out`
|
|
93
|
+
+ ` (coverage: ${coverage} subagents)`
|
|
94
|
+
+ (coverage === 0 ? ' ⚠ partial — subagent token data missing; total is not a reliable cost estimate' : '')
|
|
95
|
+
+ ` [session: ${record.session_id ?? 'unknown'}]`
|
|
96
|
+
: 'tokens: no token data in last 5 sessions (subagent telemetry not yet captured)';
|
|
97
|
+
return entries.map((entry) => JSON.stringify(entry)).join('\n')
|
|
98
|
+
+ `\n\nabandoned stubs excluded: ${stubCount}\n\n--- token summary ---\n${summary}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Handle one JSON-RPC line. null means a notification or blank line, not a reply. */
|
|
102
|
+
export function handleLine(line, { cwd = process.cwd(), resolveRoot = resolveProjectRoot } = {}) {
|
|
103
|
+
if (!line.trim()) return null;
|
|
104
|
+
let request;
|
|
105
|
+
try { request = JSON.parse(line); } catch { return errorResponse(null, -32700, 'Parse error'); }
|
|
106
|
+
if (!request || typeof request !== 'object' || Array.isArray(request)) {
|
|
107
|
+
return errorResponse(null, -32600, 'Invalid Request');
|
|
108
|
+
}
|
|
109
|
+
const hasId = Object.hasOwn(request, 'id');
|
|
110
|
+
const validId = request.id === null || typeof request.id === 'string' || typeof request.id === 'number';
|
|
111
|
+
const id = hasId && validId ? request.id : null;
|
|
112
|
+
if (request.jsonrpc !== '2.0' || typeof request.method !== 'string' || (hasId && !validId)) {
|
|
113
|
+
return errorResponse(id, -32600, 'Invalid Request');
|
|
114
|
+
}
|
|
115
|
+
if (!hasId || request.method.startsWith('notifications/')) return null;
|
|
116
|
+
let result;
|
|
117
|
+
switch (request.method) {
|
|
118
|
+
case 'initialize':
|
|
119
|
+
result = { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: SERVER_INFO };
|
|
120
|
+
break;
|
|
121
|
+
case 'tools/list':
|
|
122
|
+
result = { tools: TOOLS };
|
|
123
|
+
break;
|
|
124
|
+
case 'tools/call': {
|
|
125
|
+
const name = request.params?.name;
|
|
126
|
+
if (typeof name !== 'string') return errorResponse(id, -32602, 'Invalid tool name');
|
|
127
|
+
if (!TOOLS.some((tool) => tool.name === name)) return errorResponse(id, -32602, `Unknown tool: ${name}`);
|
|
128
|
+
try {
|
|
129
|
+
const root = resolveRoot(cwd);
|
|
130
|
+
result = textContent(root
|
|
131
|
+
? (name === 'session_config' ? readSessionConfig(root) : readSessionMetrics(root))
|
|
132
|
+
: 'Error: not inside a git repository');
|
|
133
|
+
} catch {
|
|
134
|
+
return errorResponse(id, -32603, 'Unable to read session data');
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
default:
|
|
139
|
+
return errorResponse(id, -32601, `Method not found: ${request.method}`);
|
|
140
|
+
}
|
|
141
|
+
return { jsonrpc: '2.0', id, result };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Start newline-delimited JSON-RPC on the supplied streams. */
|
|
145
|
+
export async function serve({ input = process.stdin, output = process.stdout, cwd = process.cwd() } = {}) {
|
|
146
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
147
|
+
for await (const line of lines) {
|
|
148
|
+
const response = handleLine(line, { cwd });
|
|
149
|
+
if (response) output.write(`${JSON.stringify(response)}\n`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isMainModule(import.meta.url)) {
|
|
154
|
+
serve().catch(() => {
|
|
155
|
+
process.stderr.write('session-orchestrator: MCP stdio transport failed.\n');
|
|
156
|
+
process.exitCode = 2;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
* "already_canonical":N,"unmappable":N,"parse_errors":N,"backup":"<path>|null"}
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import { readFileSync,
|
|
42
|
-
import {
|
|
41
|
+
import { readFileSync, copyFileSync, existsSync } from 'node:fs';
|
|
42
|
+
import { basename } from 'node:path';
|
|
43
|
+
import { atomicWriteWithBackup, envelopeToError } from './lib/io.mjs';
|
|
43
44
|
import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
|
|
44
45
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
45
46
|
|
|
@@ -422,11 +423,12 @@ async function main() {
|
|
|
422
423
|
// Atomic write: backup → tmp → rename
|
|
423
424
|
const iso = new Date().toISOString().replace(/[:.]/g, '-');
|
|
424
425
|
const backupPath = `${args.file}.bak-${iso}`;
|
|
425
|
-
const tmpPath = join(dirname(args.file), `.${basename(args.file)}.migrate-tmp-${process.pid}`);
|
|
426
426
|
try {
|
|
427
427
|
copyFileSync(args.file, backupPath);
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
const res = atomicWriteWithBackup(args.file, outputLines.join('\n') + '\n', {
|
|
429
|
+
tmpPrefix: `.${basename(args.file)}.tmp`,
|
|
430
|
+
});
|
|
431
|
+
if (!res.ok) throw envelopeToError(res);
|
|
430
432
|
summary.backup = backupPath;
|
|
431
433
|
} catch (err) {
|
|
432
434
|
const hint = existsSync(backupPath)
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
} from './lib/vault-migration-rules.mjs';
|
|
59
59
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
60
60
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
61
|
+
import { atomicWriteText, envelopeToError } from './lib/io.mjs';
|
|
61
62
|
|
|
62
63
|
// ---------------------------------------------------------------------------
|
|
63
64
|
// Constants
|
|
@@ -529,12 +530,14 @@ function findHits(filePath, content) {
|
|
|
529
530
|
}
|
|
530
531
|
|
|
531
532
|
/**
|
|
532
|
-
* Atomic write
|
|
533
|
+
* Atomic write via `atomicWriteText` (`<dir>/.<name>.tmp.<hex>`, then rename);
|
|
534
|
+
* throws with the original message and `code` on failure.
|
|
533
535
|
*/
|
|
534
536
|
async function atomicWrite(filePath, content) {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
const res = await atomicWriteText(filePath, content, {
|
|
538
|
+
tmpPrefix: `.${path.basename(filePath)}.tmp`,
|
|
539
|
+
});
|
|
540
|
+
if (!res.ok) throw envelopeToError(res);
|
|
538
541
|
}
|
|
539
542
|
|
|
540
543
|
/**
|
|
@@ -101,6 +101,7 @@ import { join, dirname } from 'node:path';
|
|
|
101
101
|
import { fileURLToPath } from 'node:url';
|
|
102
102
|
|
|
103
103
|
import { findProjectRoot } from './lib/common.mjs';
|
|
104
|
+
import { eventsFilePath } from './lib/events.mjs';
|
|
104
105
|
import { resolveStateArtifactPath } from './lib/state-md.mjs';
|
|
105
106
|
import {
|
|
106
107
|
CANDIDATE_POOL_SIZE,
|
|
@@ -249,7 +250,7 @@ const repoRoot = findProjectRoot(process.cwd());
|
|
|
249
250
|
const learningsPath = opts.learnings
|
|
250
251
|
? opts.learnings
|
|
251
252
|
: join(repoRoot, '.orchestrator', 'metrics', 'learnings.jsonl');
|
|
252
|
-
const eventsPath =
|
|
253
|
+
const eventsPath = eventsFilePath(repoRoot); // #1514 point 1: the repo's one ledger
|
|
253
254
|
// #1019 — the natively-delivered rule corpus. Every `.claude/rules/*.md` reaches
|
|
254
255
|
// a dispatched agent in FULL through Claude Code's own project-instruction
|
|
255
256
|
// loading (`docs/instruction-delivery.md` §1: the `globs:`/`tier:` frontmatter is
|
package/scripts/release.mjs
CHANGED
|
@@ -387,11 +387,14 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
387
387
|
// `scripts/lib/locks/index.mjs` is the one-cycle deprecation shim (5.2.0 CHANGELOG, Changed): its
|
|
388
388
|
// runtime warning names the version it was deprecated IN, so it legitimately carries the previous
|
|
389
389
|
// release literal until its 6.0.0 removal — version history by construction, not drift.
|
|
390
|
+
// `skills/eval/rubric-v3.md` (5.9.0 sweep, measured 2026-10-06): its change log quotes the dated
|
|
391
|
+
// owner decision "rubric-v3 in 5.8.0" (2026-10-05, #1487) — the release that introduced the rubric.
|
|
392
|
+
// Prose history, not a version surface; bumping it would falsify the quote.
|
|
390
393
|
// `site/_census.json` is deliberately NOT allowlisted: it carries a `"version"` field that
|
|
391
394
|
// `--set-version` re-stamps (scripts/site-numbers.mjs --write, see the post-bump hint in main()),
|
|
392
395
|
// so leaving it under the drift sweep makes the sweep double as its staleness gate — a census
|
|
393
396
|
// not re-stamped after a bump fails `--check` instead of shipping the previous version.
|
|
394
|
-
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|scripts\/lib\/locks\/index\.mjs|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|skills\/release\/SKILL\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
|
|
397
|
+
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|scripts\/lib\/locks\/index\.mjs|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|skills\/eval\/rubric-v3\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|skills\/release\/SKILL\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
|
|
395
398
|
|
|
396
399
|
/** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
|
|
397
400
|
export function checkLeakage(lines) {
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
|
|
56
56
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
57
57
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
58
|
+
import { atomicWriteText, envelopeToError } from './lib/io.mjs';
|
|
58
59
|
|
|
59
60
|
// ---------------------------------------------------------------------------
|
|
60
61
|
// Constants
|
|
@@ -693,7 +694,6 @@ async function writeManifest({ vaultDir, derivableOnly, summary, moves }) {
|
|
|
693
694
|
|
|
694
695
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
695
696
|
const manifestPath = path.join(manifestDir, `relocation-manifest-${ts}.json`);
|
|
696
|
-
const tmpPath = `${manifestPath}.tmp-${process.pid}`;
|
|
697
697
|
|
|
698
698
|
// Store vault-relative paths in the manifest
|
|
699
699
|
const movesRel = moves.map((m) => ({
|
|
@@ -714,8 +714,10 @@ async function writeManifest({ vaultDir, derivableOnly, summary, moves }) {
|
|
|
714
714
|
moves: movesRel,
|
|
715
715
|
};
|
|
716
716
|
|
|
717
|
-
await
|
|
718
|
-
|
|
717
|
+
const res = await atomicWriteText(manifestPath, JSON.stringify(manifest, null, 2) + '\n', {
|
|
718
|
+
tmpPrefix: `.${path.basename(manifestPath)}.tmp`,
|
|
719
|
+
});
|
|
720
|
+
if (!res.ok) throw envelopeToError(res);
|
|
719
721
|
|
|
720
722
|
return manifestPath;
|
|
721
723
|
}
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
* with the next repo, and exits 2 at the end
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import { existsSync, readFileSync, writeFileSync, copyFileSync
|
|
35
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
36
36
|
import { mkdirSync } from 'node:fs';
|
|
37
|
-
import { dirname, join } from 'node:path';
|
|
37
|
+
import { basename, dirname, join } from 'node:path';
|
|
38
38
|
import { homedir } from 'node:os';
|
|
39
39
|
import {
|
|
40
40
|
migrateLegacyLearning,
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
import { LearningsLockError, withLearningsLock } from './lib/learnings/io.mjs';
|
|
44
44
|
import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-repo.mjs';
|
|
45
45
|
import { validatePathInsideProject } from './lib/path-utils.mjs';
|
|
46
|
+
import { atomicWriteWithBackup, envelopeToError } from './lib/io.mjs';
|
|
46
47
|
|
|
47
48
|
const LEARNINGS_REL = '.orchestrator/metrics/learnings.jsonl';
|
|
48
49
|
|
|
@@ -316,12 +317,13 @@ function migrateStore(repoPath, learningsPath, apply) {
|
|
|
316
317
|
try {
|
|
317
318
|
// Backup original
|
|
318
319
|
copyFileSync(learningsPath, backupPath);
|
|
319
|
-
// Write migrated content atomically
|
|
320
|
+
// Write migrated content atomically (own backup above keeps its
|
|
321
|
+
// `.bak-cross-repo-migrate-<ms>` schema, so the helper takes none)
|
|
320
322
|
const body = outputLines.join('\n') + '\n';
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
const res = atomicWriteWithBackup(learningsPath, body, {
|
|
324
|
+
tmpPrefix: `.${basename(learningsPath)}.tmp`,
|
|
325
|
+
});
|
|
326
|
+
if (!res.ok) throw envelopeToError(res);
|
|
325
327
|
} catch (err) {
|
|
326
328
|
return {
|
|
327
329
|
repo: repoPath,
|
|
@@ -55,8 +55,8 @@ import {
|
|
|
55
55
|
resolveGateTimeoutMs,
|
|
56
56
|
} from './lib/gates/gate-helpers.mjs';
|
|
57
57
|
import { buildCommandSignature, spawnInGroup } from './lib/process-group.mjs';
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
58
|
+
import { resolveWaveNumber } from './lib/quality-gate-wave.mjs';
|
|
59
|
+
import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
|
|
60
60
|
import { gateKillFields } from './lib/quality-gate.mjs';
|
|
61
61
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
62
62
|
import { shellQuote } from './lib/sh-quote.mjs';
|
|
@@ -266,42 +266,6 @@ function resolveLedgerRoot(value) {
|
|
|
266
266
|
return null;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
/**
|
|
270
|
-
* Resolve the active wave number from the wave-scope sidecar (#966 step 1).
|
|
271
|
-
*
|
|
272
|
-
* Mirrors `resolveWave()` in `hooks/pre-bash-memory-propose-audit.mjs` — the
|
|
273
|
-
* same `.{pi,cursor,codex,claude}/wave-scope.json` precedence via
|
|
274
|
-
* {@link findOwnScopeFile}, skipping a PEER session's manifest in the same
|
|
275
|
-
* working copy (#1504: a peer-only result is no wave of ours, never the peer's
|
|
276
|
-
* number) — with ONE deliberate difference: the hook returns `0` for "no
|
|
277
|
-
* wave-scope file", this returns `null`.
|
|
278
|
-
*
|
|
279
|
-
* Absent is not zero. A human running `npm run quality-gate` from a `git push`
|
|
280
|
-
* has no wave at all, and that is the common case; publishing `wave_number: 0`
|
|
281
|
-
* would invent a wave 0 that every consumer then has to special-case. The
|
|
282
|
-
* caller spreads the result so the KEY is omitted, exactly as `counts` is.
|
|
283
|
-
*
|
|
284
|
-
* A non-positive or non-numeric `wave` field is treated the same way — waves
|
|
285
|
-
* are 1-indexed, so `0` on disk carries no more information than an absent file.
|
|
286
|
-
*
|
|
287
|
-
* Never throws.
|
|
288
|
-
*
|
|
289
|
-
* @param {string} projectDir — directory whose wave-scope sidecar to read.
|
|
290
|
-
* @returns {number|null} positive wave number, or `null` when there is no wave.
|
|
291
|
-
*/
|
|
292
|
-
function resolveWaveNumber(projectDir) {
|
|
293
|
-
try {
|
|
294
|
-
const ownIds = new Set(readProcessLocalSessionIds({ env: process.env, hookInput: null }));
|
|
295
|
-
const { path: waveFile } = findOwnScopeFile(projectDir, ownIds, classifyManifestSession);
|
|
296
|
-
if (!waveFile || !existsSync(waveFile)) return null;
|
|
297
|
-
const wave = JSON.parse(readFileSync(waveFile, 'utf8'))?.wave;
|
|
298
|
-
if (typeof wave !== 'number' || !Number.isFinite(wave) || wave <= 0) return null;
|
|
299
|
-
return Math.trunc(wave);
|
|
300
|
-
} catch {
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
269
|
// ---------------------------------------------------------------------------
|
|
306
270
|
// Main — the CLI body. Guarded below so a bare `import()` runs none of it.
|
|
307
271
|
// ---------------------------------------------------------------------------
|
|
@@ -471,7 +435,13 @@ async function main() {
|
|
|
471
435
|
//
|
|
472
436
|
// stdout is PIPED (not inherited) so the suite counts the gate already computed
|
|
473
437
|
// can be lifted straight off its JSON envelope into telemetry (#954) instead of
|
|
474
|
-
// travelling as prose through the STATE.md header.
|
|
438
|
+
// travelling as prose through the STATE.md header. Both skill readers select
|
|
439
|
+
// the last matching flat event by semantic_session_id + wave_number; there is
|
|
440
|
+
// no platform field on the payload. Count fallback (wave-loop step 7: this
|
|
441
|
+
// wave's own gate output; session end: the STATE.md header) is limited to
|
|
442
|
+
// pre-#954 sessions, gates outside the event wrapper, or missing wave
|
|
443
|
+
// attribution (see docs/events-schema.md).
|
|
444
|
+
// The envelope is re-emitted
|
|
475
445
|
// verbatim below, so the stdout contract is unchanged — a gate sub-script writes
|
|
476
446
|
// exactly one JSON line at the very end (its own child commands are captured by
|
|
477
447
|
// `runCheck`), so nothing streamed before and nothing streams now.
|