session-orchestrator 3.21.0 → 3.22.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/rules/000-session-orchestrator.mdc +3 -2
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +8 -8
- package/CHANGELOG.md +101 -0
- package/README.md +10 -10
- package/agents/memory-proposal-collector.md +6 -4
- package/commands/eli5.md +33 -0
- package/commands/release.md +5 -3
- package/commands/test.md +2 -2
- package/docs/components.md +6 -5
- package/docs/scope-collision-guard.md +3 -3
- package/docs/session-config-reference.md +31 -8
- package/hooks/_lib/lock-bootstrap.mjs +19 -13
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +11 -1
- package/hooks/on-session-end.mjs +24 -92
- package/hooks/on-session-start.mjs +195 -104
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +17 -18
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +7 -8
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/ci-status-banner.mjs +63 -57
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/gates/gate-full.mjs +3 -3
- package/scripts/lib/gates/gate-helpers.mjs +17 -6
- package/scripts/lib/io.mjs +239 -0
- package/scripts/lib/issue-budget.mjs +63 -9
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/project-hygiene.mjs +64 -4
- package/scripts/lib/reconcile/renderer.mjs +17 -4
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/spiral-carryover.mjs +23 -2
- package/scripts/lib/state-md/mission-status.mjs +147 -50
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +30 -0
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vcs-repo-spec.mjs +182 -13
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/materialize-wave-scope.mjs +281 -0
- package/scripts/release.mjs +443 -122
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/validate-plugin.mjs +3 -0
- package/scripts/validate-wave-scope.mjs +6 -1
- package/scripts/vault-backfill.mjs +32 -5
- package/skills/_shared/parallel-aware-auq.md +30 -24
- package/skills/_shared/parallel-aware-preamble.md +31 -2
- package/skills/_shared/state-ownership.md +32 -6
- package/skills/bootstrap/SKILL.md +2 -1
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/discovery/SKILL.md +28 -24
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/gitlab-ops/SKILL.md +30 -26
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/npm-publish/SKILL.md +4 -4
- package/skills/peekaboo-driver/SKILL.md +3 -3
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +35 -23
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +3 -3
- package/skills/session-end/SKILL.md +53 -20
- package/skills/session-end/phase-3-6-tail.md +37 -2
- package/skills/session-start/SKILL.md +69 -35
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/soul.md +110 -0
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/wave-executor/wave-loop.md +27 -5
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Materialize both wave file-scope declaration shapes from one canonical array.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/materialize-wave-scope.mjs --state-dir <dir> --wave <positive-int> [--json] < scopes.json
|
|
7
|
+
*
|
|
8
|
+
* The stdin document is an array of `{ id, files }` records. The command writes
|
|
9
|
+
* each bare `files` array first, then writes the complete record array as the
|
|
10
|
+
* aggregate sidecar consumed by validate-wave-scope's --assert-disjoint and
|
|
11
|
+
* --union modes.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, unlinkSync } from 'node:fs';
|
|
15
|
+
import { resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { writeJsonAtomicSync } from './lib/io.mjs';
|
|
18
|
+
|
|
19
|
+
const HELP = `Usage: node scripts/materialize-wave-scope.mjs --state-dir <dir> --wave <positive-int> [--json]
|
|
20
|
+
|
|
21
|
+
Read one JSON array of {id, files} records from stdin and materialize both
|
|
22
|
+
wave-scope declaration shapes from that canonical record array.
|
|
23
|
+
|
|
24
|
+
Required:
|
|
25
|
+
--state-dir <dir> State directory that owns filescopes/.
|
|
26
|
+
--wave <positive-int>
|
|
27
|
+
Positive wave number used in filescopes/wave-N/.
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--json Emit {ok, aggregatePath, perAgentPaths} to stdout.
|
|
31
|
+
-h, --help Show this help and exit 0.
|
|
32
|
+
|
|
33
|
+
Output:
|
|
34
|
+
Human mode prints only the aggregate sidecar path. --json prints one JSON
|
|
35
|
+
object. Diagnostics are written only to stderr.
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
printf '%s' '[{"id":"W1-I1","files":["scripts/example.mjs"]},{"id":"coordinator","files":[]}]' | \\
|
|
39
|
+
node scripts/materialize-wave-scope.mjs --state-dir .claude --wave 1
|
|
40
|
+
node scripts/materialize-wave-scope.mjs --state-dir .claude --wave 1 --json < scopes.json
|
|
41
|
+
|
|
42
|
+
Writes:
|
|
43
|
+
<state-dir>/filescopes/wave-N/<id>.json Bare string[] for each record
|
|
44
|
+
<state-dir>/filescopes/wave-N.scopes.json Aggregate [{id, files}, ...]
|
|
45
|
+
|
|
46
|
+
Exit codes:
|
|
47
|
+
0 All declaration files and the aggregate sidecar were written.
|
|
48
|
+
1 Usage or input validation error; no write was attempted.
|
|
49
|
+
2 Filesystem or write error; earlier per-agent writes are retained and any
|
|
50
|
+
previous aggregate is invalidated before per-agent writes begin.
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
class InputError extends Error {}
|
|
54
|
+
class WriteError extends Error {}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string[]} argv
|
|
58
|
+
* @returns {{ stateDir: string, wave: number, json: boolean, help: boolean }}
|
|
59
|
+
*/
|
|
60
|
+
export function parseCliArgs(argv) {
|
|
61
|
+
let stateDir;
|
|
62
|
+
let waveRaw;
|
|
63
|
+
let json = false;
|
|
64
|
+
let help = false;
|
|
65
|
+
|
|
66
|
+
for (let index = 0; index < argv.length; index++) {
|
|
67
|
+
const arg = argv[index];
|
|
68
|
+
if (arg === '--json') {
|
|
69
|
+
json = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (arg === '--help' || arg === '-h') {
|
|
73
|
+
help = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (arg === '--state-dir' || arg === '--wave') {
|
|
77
|
+
const value = argv[index + 1];
|
|
78
|
+
if (value === undefined || value.startsWith('--')) {
|
|
79
|
+
throw new InputError(`${arg} requires a value`);
|
|
80
|
+
}
|
|
81
|
+
if (arg === '--state-dir') {
|
|
82
|
+
if (stateDir !== undefined) throw new InputError('--state-dir may be specified only once');
|
|
83
|
+
stateDir = value;
|
|
84
|
+
} else {
|
|
85
|
+
if (waveRaw !== undefined) throw new InputError('--wave may be specified only once');
|
|
86
|
+
waveRaw = value;
|
|
87
|
+
}
|
|
88
|
+
index++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
throw new InputError(`unknown argument: ${arg}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (help) return { stateDir: '', wave: 0, json, help: true };
|
|
95
|
+
if (stateDir === undefined) throw new InputError('--state-dir is required');
|
|
96
|
+
if (waveRaw === undefined) throw new InputError('--wave is required');
|
|
97
|
+
if (stateDir.length === 0 || /[\0\r\n]/.test(stateDir)) {
|
|
98
|
+
throw new InputError('--state-dir must be a non-empty path without NUL or newline characters');
|
|
99
|
+
}
|
|
100
|
+
if (!/^[1-9]\d*$/.test(waveRaw)) {
|
|
101
|
+
throw new InputError('--wave must be a positive integer');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const wave = Number(waveRaw);
|
|
105
|
+
if (!Number.isSafeInteger(wave)) throw new InputError('--wave must be a safe positive integer');
|
|
106
|
+
return { stateDir, wave, json, help: false };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {unknown} value
|
|
111
|
+
* @returns {value is Record<string, unknown>}
|
|
112
|
+
*/
|
|
113
|
+
function isRecord(value) {
|
|
114
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A path is scope data rather than an output target, so absolute paths remain
|
|
119
|
+
* valid for sanctioned out-of-repository grants. Traversal/control characters
|
|
120
|
+
* are rejected because no scope consumer can safely interpret them verbatim.
|
|
121
|
+
*
|
|
122
|
+
* @param {unknown} value
|
|
123
|
+
* @param {number} recordIndex
|
|
124
|
+
* @param {number} fileIndex
|
|
125
|
+
*/
|
|
126
|
+
function validateScopePath(value, recordIndex, fileIndex) {
|
|
127
|
+
if (typeof value !== 'string' || value.length === 0 || value.trim().length === 0) {
|
|
128
|
+
throw new InputError(`record #${recordIndex} files[${fileIndex}] must be a non-empty string`);
|
|
129
|
+
}
|
|
130
|
+
if (/[\0\r\n]/.test(value)) {
|
|
131
|
+
throw new InputError(`record #${recordIndex} files[${fileIndex}] must not contain NUL or newline characters`);
|
|
132
|
+
}
|
|
133
|
+
if (value.split(/[\\/]+/).includes('..')) {
|
|
134
|
+
throw new InputError(`record #${recordIndex} files[${fileIndex}] must not contain path traversal`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Validate the canonical record array completely before a write is attempted.
|
|
140
|
+
* The original values are returned without sorting, deduplication, or expansion.
|
|
141
|
+
*
|
|
142
|
+
* @param {unknown} value
|
|
143
|
+
* @returns {Array<{id: string, files: string[]}>}
|
|
144
|
+
*/
|
|
145
|
+
export function validateScopeRecords(value) {
|
|
146
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
147
|
+
throw new InputError('stdin must be a non-empty JSON array of {id, files} records');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const seenIds = new Set();
|
|
151
|
+
let coordinatorCount = 0;
|
|
152
|
+
for (let recordIndex = 0; recordIndex < value.length; recordIndex++) {
|
|
153
|
+
const record = value[recordIndex];
|
|
154
|
+
if (!isRecord(record)) {
|
|
155
|
+
throw new InputError(`record #${recordIndex} must be an object with id and files`);
|
|
156
|
+
}
|
|
157
|
+
if (typeof record.id !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(record.id)) {
|
|
158
|
+
throw new InputError(`record #${recordIndex} has an unsafe id`);
|
|
159
|
+
}
|
|
160
|
+
const idKey = record.id.toLowerCase();
|
|
161
|
+
if (seenIds.has(idKey)) {
|
|
162
|
+
throw new InputError(`record #${recordIndex} has duplicate id (case-insensitive): ${record.id}`);
|
|
163
|
+
}
|
|
164
|
+
seenIds.add(idKey);
|
|
165
|
+
if (idKey === 'coordinator') {
|
|
166
|
+
if (record.id !== 'coordinator') {
|
|
167
|
+
throw new InputError(`record #${recordIndex} id must be exactly coordinator (lowercase)`);
|
|
168
|
+
}
|
|
169
|
+
coordinatorCount++;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!Array.isArray(record.files)) {
|
|
173
|
+
throw new InputError(`record #${recordIndex} (${record.id}) must have a files string array`);
|
|
174
|
+
}
|
|
175
|
+
for (let fileIndex = 0; fileIndex < record.files.length; fileIndex++) {
|
|
176
|
+
validateScopePath(record.files[fileIndex], recordIndex, fileIndex);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (coordinatorCount !== 1) {
|
|
181
|
+
throw new InputError(`input must contain exactly one coordinator record; found ${coordinatorCount}`);
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Materialize validated declarations in their required write order.
|
|
188
|
+
*
|
|
189
|
+
* The optional writer is a narrow seam for deterministic write-failure tests;
|
|
190
|
+
* production always delegates to writeJsonAtomicSync. There is intentionally no
|
|
191
|
+
* rollback: a prior aggregate is invalidated before per-agent writes, then the
|
|
192
|
+
* aggregate publication marker is written only after every per-agent declaration
|
|
193
|
+
* has succeeded.
|
|
194
|
+
*
|
|
195
|
+
* @param {Array<{id: string, files: string[]}>} records
|
|
196
|
+
* @param {{ stateDir: string, wave: number, writeJson?: typeof writeJsonAtomicSync }} options
|
|
197
|
+
* @returns {{ aggregatePath: string, perAgentPaths: string[] }}
|
|
198
|
+
*/
|
|
199
|
+
export function materializeWaveScope(records, { stateDir, wave, writeJson = writeJsonAtomicSync }) {
|
|
200
|
+
const scopeDir = resolve(stateDir, 'filescopes', `wave-${wave}`);
|
|
201
|
+
const aggregatePath = resolve(stateDir, 'filescopes', `wave-${wave}.scopes.json`);
|
|
202
|
+
const perAgentPaths = records.map(({ id }) => resolve(scopeDir, `${id}.json`));
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
unlinkSync(aggregatePath);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error?.code !== 'ENOENT') {
|
|
208
|
+
throw new WriteError(`cannot invalidate aggregate declaration ${aggregatePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (let index = 0; index < records.length; index++) {
|
|
213
|
+
const result = writeJson(perAgentPaths[index], records[index].files, { tmpPrefix: '.materialize-wave-scope' });
|
|
214
|
+
if (!result?.ok) {
|
|
215
|
+
throw new WriteError(`cannot write per-agent declaration ${perAgentPaths[index]}: ${result?.error ?? 'unknown write failure'}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const aggregateResult = writeJson(aggregatePath, records, { tmpPrefix: '.materialize-wave-scope' });
|
|
220
|
+
if (!aggregateResult?.ok) {
|
|
221
|
+
throw new WriteError(`cannot write aggregate declaration ${aggregatePath}: ${aggregateResult?.error ?? 'unknown write failure'}`);
|
|
222
|
+
}
|
|
223
|
+
return { aggregatePath, perAgentPaths };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @param {string} message
|
|
228
|
+
* @param {number} code
|
|
229
|
+
*/
|
|
230
|
+
function fail(message, code) {
|
|
231
|
+
process.stderr.write(`materialize-wave-scope: ${message}\n`);
|
|
232
|
+
process.exitCode = code;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function readStdinJson() {
|
|
236
|
+
let raw;
|
|
237
|
+
try {
|
|
238
|
+
raw = readFileSync(0, 'utf8');
|
|
239
|
+
} catch (error) {
|
|
240
|
+
throw new WriteError(`cannot read stdin: ${error instanceof Error ? error.message : String(error)}`);
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse(raw);
|
|
244
|
+
} catch {
|
|
245
|
+
throw new InputError('stdin is not valid JSON');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Execute the CLI, mapping usage/input and I/O failures to its exit contract. */
|
|
250
|
+
export function main() {
|
|
251
|
+
let args;
|
|
252
|
+
try {
|
|
253
|
+
args = parseCliArgs(process.argv.slice(2));
|
|
254
|
+
if (args.help) {
|
|
255
|
+
process.stdout.write(HELP);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const records = validateScopeRecords(readStdinJson());
|
|
259
|
+
const { aggregatePath, perAgentPaths } = materializeWaveScope(records, args);
|
|
260
|
+
process.stdout.write(
|
|
261
|
+
args.json
|
|
262
|
+
? `${JSON.stringify({ ok: true, aggregatePath, perAgentPaths })}\n`
|
|
263
|
+
: `${aggregatePath}\n`,
|
|
264
|
+
);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error instanceof InputError) {
|
|
267
|
+
fail(error.message, 1);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (error instanceof WriteError) {
|
|
271
|
+
fail(error.message, 2);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
fail(`unexpected system error: ${error instanceof Error ? error.message : String(error)}`, 2);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : null;
|
|
279
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
280
|
+
main();
|
|
281
|
+
}
|