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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/rules/000-session-orchestrator.mdc +3 -2
  5. package/.cursor/rules/040-discovery.mdc +6 -6
  6. package/.cursor/rules/050-plan.mdc +8 -8
  7. package/CHANGELOG.md +101 -0
  8. package/README.md +10 -10
  9. package/agents/memory-proposal-collector.md +6 -4
  10. package/commands/eli5.md +33 -0
  11. package/commands/release.md +5 -3
  12. package/commands/test.md +2 -2
  13. package/docs/components.md +6 -5
  14. package/docs/scope-collision-guard.md +3 -3
  15. package/docs/session-config-reference.md +31 -8
  16. package/hooks/_lib/lock-bootstrap.mjs +19 -13
  17. package/hooks/hooks-codex.json +1 -1
  18. package/hooks/hooks.json +11 -1
  19. package/hooks/on-session-end.mjs +24 -92
  20. package/hooks/on-session-start.mjs +195 -104
  21. package/hooks/pre-auq-clarity.mjs +787 -0
  22. package/hooks/pre-bash-issue-budget.mjs +17 -18
  23. package/package.json +3 -1
  24. package/pi/prompts/eli5.md +12 -0
  25. package/scripts/auq-audit.mjs +825 -0
  26. package/scripts/autopilot.mjs +7 -8
  27. package/scripts/lib/auq/clarity.mjs +1314 -0
  28. package/scripts/lib/auq/parse.mjs +1006 -0
  29. package/scripts/lib/auq/schema.mjs +1457 -0
  30. package/scripts/lib/ci-status-banner.mjs +63 -57
  31. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
  32. package/scripts/lib/config/vault-integration.mjs +12 -1
  33. package/scripts/lib/dispatcher/rank.mjs +4 -7
  34. package/scripts/lib/gates/gate-full.mjs +3 -3
  35. package/scripts/lib/gates/gate-helpers.mjs +17 -6
  36. package/scripts/lib/io.mjs +239 -0
  37. package/scripts/lib/issue-budget.mjs +63 -9
  38. package/scripts/lib/owner-interview.mjs +78 -32
  39. package/scripts/lib/peer-discovery.mjs +73 -22
  40. package/scripts/lib/project-hygiene.mjs +64 -4
  41. package/scripts/lib/reconcile/renderer.mjs +17 -4
  42. package/scripts/lib/resource-probe/evaluate.mjs +330 -149
  43. package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
  44. package/scripts/lib/resource-probe.mjs +18 -2
  45. package/scripts/lib/spiral-carryover.mjs +23 -2
  46. package/scripts/lib/state-md/mission-status.mjs +147 -50
  47. package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
  48. package/scripts/lib/validate/check-hooks-symmetry.mjs +30 -0
  49. package/scripts/lib/validate/check-rules.mjs +153 -9
  50. package/scripts/lib/vault-backfill/glab.mjs +91 -58
  51. package/scripts/lib/vault-backfill/manifest.mjs +28 -8
  52. package/scripts/lib/vcs-repo-spec.mjs +182 -13
  53. package/scripts/lib/wave-resource-gate.mjs +67 -73
  54. package/scripts/materialize-wave-scope.mjs +281 -0
  55. package/scripts/release.mjs +443 -122
  56. package/scripts/run-quality-gate.mjs +14 -0
  57. package/scripts/validate-plugin.mjs +3 -0
  58. package/scripts/validate-wave-scope.mjs +6 -1
  59. package/scripts/vault-backfill.mjs +32 -5
  60. package/skills/_shared/parallel-aware-auq.md +30 -24
  61. package/skills/_shared/parallel-aware-preamble.md +31 -2
  62. package/skills/_shared/state-ownership.md +32 -6
  63. package/skills/bootstrap/SKILL.md +2 -1
  64. package/skills/brainstorm/SKILL.md +18 -18
  65. package/skills/brainstorm/soul.md +12 -0
  66. package/skills/discovery/SKILL.md +28 -24
  67. package/skills/eli5/SKILL.md +43 -0
  68. package/skills/evolve/SKILL.md +8 -9
  69. package/skills/gitlab-ops/SKILL.md +30 -26
  70. package/skills/grill/SKILL.md +6 -6
  71. package/skills/grill/soul.md +16 -0
  72. package/skills/memory-cleanup/SKILL.md +2 -2
  73. package/skills/npm-publish/SKILL.md +4 -4
  74. package/skills/peekaboo-driver/SKILL.md +3 -3
  75. package/skills/plan/SKILL.md +18 -16
  76. package/skills/plan/mode-feature.md +1 -1
  77. package/skills/plan/mode-new.md +35 -23
  78. package/skills/plan/soul.md +12 -0
  79. package/skills/reconcile/SKILL.md +3 -3
  80. package/skills/session-end/SKILL.md +53 -20
  81. package/skills/session-end/phase-3-6-tail.md +37 -2
  82. package/skills/session-start/SKILL.md +69 -35
  83. package/skills/session-start/phase-2-5-docs-planning.md +8 -8
  84. package/skills/session-start/phase-4-5-resource-health.md +82 -19
  85. package/skills/session-start/soul.md +110 -0
  86. package/skills/test-runner/SKILL.md +2 -2
  87. package/skills/using-orchestrator/SKILL.md +1 -1
  88. package/skills/wave-executor/wave-loop.md +27 -5
  89. package/skills/write-executable-plan/SKILL.md +6 -6
  90. package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
  91. package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
  92. package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
  93. package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
  94. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
  95. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
  96. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
  97. package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
  98. package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
  99. package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
  100. package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
  101. package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
  102. package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
  103. package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
  104. package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
  105. package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
  106. package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
  107. package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
  108. package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
  109. package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
  110. package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
  111. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
  112. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
  113. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
  114. package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
  115. package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
  116. package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
  117. 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
+ }