session-orchestrator 4.1.0 → 5.0.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/session-plan/SKILL.md +1 -1
- package/.agents/skills/session-start/SKILL.md +1 -1
- package/.agents/skills/ux-grill/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/session-plan/SKILL.md +1 -1
- package/.codex-plugin/skills/session-start/SKILL.md +1 -1
- package/.codex-plugin/skills/ux-grill/SKILL.md +21 -0
- package/.codex-plugin/skills/ux-grill/agents/openai.yaml +5 -0
- package/.cursor/commands/ux-grill.md +14 -0
- package/.cursor/skills/session-plan/SKILL.md +1 -1
- package/.cursor/skills/session-start/SKILL.md +1 -1
- package/.cursor/skills/ux-grill/SKILL.md +13 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +128 -1
- package/README.md +98 -86
- package/agents/dialectic-deriver.md +11 -0
- package/agents/ux-evaluator.md +1 -1
- package/commands/close.md +3 -3
- package/commands/go.md +2 -0
- package/commands/memory-cleanup.md +4 -3
- package/commands/persona-panel.md +1 -1
- package/commands/session.md +3 -2
- package/commands/ux-grill.md +51 -0
- package/docs/README.md +4 -4
- package/docs/USER-GUIDE.md +117 -50
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +55 -1
- package/docs/ci-setup.md +1 -1
- package/docs/codex-setup.md +9 -0
- package/docs/components.md +9 -9
- package/docs/cursor-setup.md +1 -0
- package/docs/events-schema.md +13 -6
- package/docs/github-mirror-protection.md +61 -20
- package/docs/instruction-delivery.md +1 -1
- package/docs/memory-proposal-flow.md +3 -3
- package/docs/migration-v4.md +2 -2
- package/docs/migration-v5.md +62 -0
- package/docs/owner-config-schema.md +74 -90
- package/docs/persona-panel.md +4 -4
- package/docs/pi-setup.md +1 -0
- package/docs/rule-authoring.md +13 -6
- package/docs/scope-collision-guard.md +16 -0
- package/docs/session-config-reference.md +55 -22
- package/docs/session-config-template.md +9 -5
- package/docs/vault-docs-architecture.md +4 -2
- package/hooks/_lib/hook-import-set.json +70 -3
- package/hooks/_lib/lock-bootstrap.mjs +84 -1
- package/hooks/_lib/vcs-create-matcher.mjs +401 -16
- package/hooks/enforce-scope.mjs +201 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks.json +7 -2
- package/hooks/on-session-start.mjs +171 -49
- package/hooks/post-bash-issue-budget-refund.mjs +375 -0
- package/hooks/pre-auq-clarity.mjs +70 -18
- package/hooks/pre-bash-issue-budget.mjs +170 -26
- package/hooks/subagent-telemetry.mjs +106 -20
- package/package.json +5 -4
- package/pi/prompts/ux-grill.md +12 -0
- package/scripts/baseline-archetypes.mjs +28 -0
- package/scripts/ci/assert-vitest-green.mjs +4 -2
- package/scripts/dialectic-deriver.mjs +32 -8
- package/scripts/emit-session.mjs +72 -1
- package/scripts/lib/agent-status.mjs +441 -9
- package/scripts/lib/auq/schema.mjs +10 -3
- package/scripts/lib/auto-dialectic.mjs +0 -68
- package/scripts/lib/baseline-archetypes.mjs +439 -0
- package/scripts/lib/build-live-signals.mjs +5 -6
- package/scripts/lib/ci-status-banner.mjs +29 -6
- package/scripts/lib/claude-md-budget-lint.mjs +52 -2
- package/scripts/lib/config/issue-budget.mjs +68 -8
- package/scripts/lib/config/private-config-dir.mjs +3 -2
- package/scripts/lib/config/remote-hosts.mjs +2 -2
- package/scripts/lib/config-schema.mjs +79 -0
- package/scripts/lib/config.mjs +12 -1
- package/scripts/lib/eval/engine.mjs +7 -1
- package/scripts/lib/file-lock.mjs +151 -8
- package/scripts/lib/git-porcelain.mjs +113 -0
- package/scripts/lib/instruction-budget-guard.mjs +415 -47
- package/scripts/lib/io.mjs +29 -4
- package/scripts/lib/issue-budget-reconcile.mjs +392 -0
- package/scripts/lib/issue-budget.mjs +412 -9
- package/scripts/lib/learnings/evolve-telemetry.mjs +1 -2
- package/scripts/lib/learnings/sizing-subject.mjs +44 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +19 -38
- package/scripts/lib/locks/state-md-lock.mjs +19 -41
- package/scripts/lib/maintenance-due-banner.mjs +450 -0
- package/scripts/lib/owner-config.example.yaml +29 -46
- package/scripts/lib/owner-yaml.mjs +14 -13
- package/scripts/lib/peer-cards/merger.mjs +143 -0
- package/scripts/lib/pre-dispatch-check.mjs +20 -14
- package/scripts/lib/project-hygiene.mjs +81 -30
- package/scripts/lib/quality-gate.mjs +27 -71
- package/scripts/lib/reconcile/engine.mjs +19 -1
- package/scripts/lib/reconcile/writer.mjs +278 -11
- package/scripts/lib/resource-probe/evaluate.mjs +19 -21
- package/scripts/lib/rules-sync.mjs +34 -4
- package/scripts/lib/scope-echo.mjs +346 -0
- package/scripts/lib/session-close-backfill.mjs +182 -40
- package/scripts/lib/session-end/phase-skip.mjs +85 -86
- package/scripts/lib/session-end/tail-runner.mjs +178 -0
- package/scripts/lib/session-lock.mjs +62 -2
- package/scripts/lib/session-record-repair.mjs +91 -0
- package/scripts/lib/session-schema/constants.mjs +6 -0
- package/scripts/lib/session-schema/filters.mjs +26 -1
- package/scripts/lib/session-schema/validator.mjs +20 -0
- package/scripts/lib/session-shape.mjs +558 -0
- package/scripts/lib/session-start-probes.mjs +429 -56
- package/scripts/lib/session-token-rollup.mjs +95 -10
- package/scripts/lib/state-md/frontmatter-mutators.mjs +22 -34
- package/scripts/lib/state-md.mjs +1 -0
- package/scripts/lib/subagents-schema.mjs +77 -9
- package/scripts/lib/telemetry/pricing.mjs +197 -0
- package/scripts/lib/telemetry/sync.mjs +50 -1
- package/scripts/lib/test-runner/artifact-paths.mjs +30 -5
- package/scripts/lib/test-runner/issue-reconcile.mjs +45 -8
- package/scripts/lib/tmux-layout/layouts.mjs +62 -4
- package/scripts/lib/ux-grill/collect.mjs +1163 -0
- package/scripts/lib/ux-grill/compare.mjs +285 -0
- package/scripts/lib/ux-grill/manifest.mjs +618 -0
- package/scripts/lib/ux-grill/measures.mjs +431 -0
- package/scripts/lib/ux-grill/paths.mjs +224 -0
- package/scripts/lib/ux-grill/pencil-coverage.mjs +284 -0
- package/scripts/lib/ux-grill/reconcile.mjs +344 -0
- package/scripts/lib/ux-grill/run-record.mjs +316 -0
- package/scripts/lib/ux-grill/schema.mjs +321 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +33 -10
- package/scripts/lib/validate/check-untracked-test-deps.mjs +33 -19
- package/scripts/lib/validate/check-unwired-features.mjs +56 -27
- package/scripts/lib/vault-mirror/process.mjs +2 -1
- package/scripts/lib/vault-status/board-lock.mjs +18 -0
- package/scripts/lib/vault-status/board-writer.mjs +8 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +4 -4
- package/scripts/lib/wave-resource-gate.mjs +23 -27
- package/scripts/lib/wave-sizing.mjs +10 -3
- package/scripts/materialize-wave-scope.mjs +68 -14
- package/scripts/mcp-server.sh +16 -1
- package/scripts/print-applicable-rules.mjs +7 -6
- package/scripts/print-learnings-index.mjs +3 -2
- package/scripts/release.mjs +7 -2
- package/scripts/session-shape.mjs +266 -0
- package/skills/_shared/config-reading.md +15 -9
- package/skills/_shared/private-capability-context.md +89 -0
- package/skills/bootstrap/SKILL.md +60 -209
- package/skills/bootstrap/_shared-template.md +99 -14
- package/skills/bootstrap/deep-template.md +36 -26
- package/skills/bootstrap/fast-template.md +44 -8
- package/skills/bootstrap/intensity-heuristic.md +10 -4
- package/skills/bootstrap/private-contract.md +119 -0
- package/skills/bootstrap/public-fallback.md +30 -18
- package/skills/bootstrap/references/bootstrap-ecosystem-health-flow.md +48 -0
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +37 -0
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +108 -0
- package/skills/bootstrap/references/bootstrap-rules-fetch-bridge.md +64 -0
- package/skills/bootstrap/standard-template.md +39 -24
- package/skills/claude-md-drift-check/SKILL.md +9 -2
- package/skills/claude-md-drift-check/checker.mjs +213 -21
- package/skills/discovery/SKILL.md +6 -173
- package/skills/discovery/probes/vault-staleness.mjs +35 -5
- package/skills/discovery/probes-docs.md +8 -4
- package/skills/discovery/probes-supply-chain.md +4 -2
- package/skills/discovery/probes-ui.md +8 -4
- package/skills/discovery/probes-vault.md +12 -4
- package/skills/discovery/references/discovery-interactive-triage.md +139 -0
- package/skills/discovery/references/discovery-triage-state.md +54 -0
- package/skills/docs-orchestrator/audience-mapping.md +1 -1
- package/skills/eval/rubric-v1.md +13 -0
- package/skills/evolve/SKILL.md +2 -458
- package/skills/evolve/references/evolve-analyze-mode.md +360 -0
- package/skills/evolve/references/evolve-dialectic-mode.md +139 -0
- package/skills/gitlab-ops/SKILL.md +3 -3
- package/skills/grill/SKILL.md +1 -1
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/plan/mode-new.md +9 -0
- package/skills/plan/mode-retro.md +4 -3
- package/skills/reconcile/SKILL.md +11 -1
- package/skills/session-end/SKILL.md +3 -2
- package/skills/session-end/drift-operations.md +20 -5
- package/skills/session-end/metrics-collection.md +1 -0
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +27 -67
- package/skills/session-end/phase-3-7a-recommendations.md +2 -2
- package/skills/session-end/references/phase-2-quality-gate.md +3 -3
- package/skills/session-end/references/phase-3-documentation-updates.md +8 -6
- package/skills/session-end/references/phase-5-issue-cleanup.md +32 -1
- package/skills/session-end/session-metrics-write.md +33 -12
- package/skills/session-plan/SKILL.md +46 -180
- package/skills/session-plan/references/session-plan-task-classification.md +152 -0
- package/skills/session-plan/wave-template.md +8 -15
- package/skills/session-start/SKILL.md +41 -7
- package/skills/session-start/phase-2-5-docs-planning.md +1 -1
- package/skills/session-start/phase-8-5-express-path.md +12 -9
- package/skills/session-start/references/operations-contract.md +114 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +2 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +42 -24
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +3 -1
- package/skills/session-start/soul.md +2 -2
- package/skills/test-runner/SKILL.md +1 -1
- package/skills/test-runner/rubric-v1.md +2 -2
- package/skills/tmux-layout/SKILL.md +3 -1
- package/skills/ux-grill/SKILL.md +211 -0
- package/skills/ux-grill/rubric-v2.md +201 -0
- package/skills/ux-grill/soul.md +76 -0
- package/skills/wave-executor/SKILL.md +32 -127
- package/skills/wave-executor/circuit-breaker.md +3 -1
- package/skills/wave-executor/references/wave-executor-quality-gate.md +61 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +86 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +12 -2
- package/skills/wave-executor/references/wave-loop-review.md +19 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +6 -2
- package/templates/_shared/ux-manifest.template.md +149 -0
- package/templates/nextjs-minimal/package.json +1 -1
- package/templates/node-minimal/package.json +1 -1
- package/scripts/lib/multi-provider-build/providers.mjs +0 -64
- package/scripts/lib/multi-provider-build/templating.mjs +0 -130
- package/scripts/lib/owner-config/coerce.mjs +0 -29
- package/scripts/lib/owner-config/constants.mjs +0 -21
- package/scripts/lib/owner-config/defaults.mjs +0 -50
- package/scripts/lib/owner-config/error.mjs +0 -19
- package/scripts/lib/owner-config/index.mjs +0 -13
- package/scripts/lib/owner-config/merge.mjs +0 -52
- package/scripts/lib/owner-config/validate.mjs +0 -259
- package/scripts/lib/owner-config-loader.mjs +0 -170
- package/scripts/lib/owner-config.mjs +0 -28
- package/scripts/lib/soul-resolve.mjs +0 -130
- package/scripts/lib/vault-mirror/render.mjs +0 -8
- package/templates/_shared/journey-manifest.md +0 -114
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* `allowedPaths` never grants a peer's territory to this wave's agents.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
25
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
26
26
|
import { resolve } from 'node:path';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
28
|
import { writeJsonAtomicSync } from './lib/io.mjs';
|
|
@@ -49,6 +49,12 @@ Options:
|
|
|
49
49
|
it, orphans are reported and RETAINED, never deleted.
|
|
50
50
|
--json Emit {ok, aggregatePath, perAgentPaths, removedOrphans,
|
|
51
51
|
retainedOrphans} to stdout.
|
|
52
|
+
--warn-missing Warn on stderr about absent concrete scope paths, resolved
|
|
53
|
+
from the project working directory (not --state-dir).
|
|
54
|
+
Glob/prefix grants containing * or ending / are skipped.
|
|
55
|
+
--new-file <path> Suppress a missing-path warning for an intended new file.
|
|
56
|
+
Repeatable; each path must exactly match a declared scope
|
|
57
|
+
path and pass scope validation, even without --warn-missing.
|
|
52
58
|
-h, --help Show this help and exit 0.
|
|
53
59
|
|
|
54
60
|
Output:
|
|
@@ -85,13 +91,16 @@ class WriteError extends Error {}
|
|
|
85
91
|
|
|
86
92
|
/**
|
|
87
93
|
* @param {string[]} argv
|
|
88
|
-
* @returns {{ stateDir: string, wave: number, session: string|null, json: boolean,
|
|
94
|
+
* @returns {{ stateDir: string, wave: number, session: string|null, json: boolean,
|
|
95
|
+
* warnMissing: boolean, newFiles: string[], help: boolean }}
|
|
89
96
|
*/
|
|
90
97
|
export function parseCliArgs(argv) {
|
|
91
98
|
let stateDir;
|
|
92
99
|
let waveRaw;
|
|
93
100
|
let session = null;
|
|
94
101
|
let json = false;
|
|
102
|
+
let warnMissing = false;
|
|
103
|
+
const newFiles = [];
|
|
95
104
|
let help = false;
|
|
96
105
|
|
|
97
106
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -100,16 +109,22 @@ export function parseCliArgs(argv) {
|
|
|
100
109
|
json = true;
|
|
101
110
|
continue;
|
|
102
111
|
}
|
|
112
|
+
if (arg === '--warn-missing') {
|
|
113
|
+
warnMissing = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
103
116
|
if (arg === '--help' || arg === '-h') {
|
|
104
117
|
help = true;
|
|
105
118
|
continue;
|
|
106
119
|
}
|
|
107
|
-
if (arg === '--state-dir' || arg === '--wave' || arg === '--session') {
|
|
120
|
+
if (arg === '--state-dir' || arg === '--wave' || arg === '--session' || arg === '--new-file') {
|
|
108
121
|
const value = argv[index + 1];
|
|
109
122
|
if (value === undefined || value.startsWith('--')) {
|
|
110
123
|
throw new InputError(`${arg} requires a value`);
|
|
111
124
|
}
|
|
112
|
-
if (arg === '--
|
|
125
|
+
if (arg === '--new-file') {
|
|
126
|
+
newFiles.push(value);
|
|
127
|
+
} else if (arg === '--state-dir') {
|
|
113
128
|
if (stateDir !== undefined) throw new InputError('--state-dir may be specified only once');
|
|
114
129
|
stateDir = value;
|
|
115
130
|
} else if (arg === '--session') {
|
|
@@ -128,7 +143,7 @@ export function parseCliArgs(argv) {
|
|
|
128
143
|
throw new InputError(`unknown argument: ${arg}`);
|
|
129
144
|
}
|
|
130
145
|
|
|
131
|
-
if (help) return { stateDir: '', wave: 0, session, json, help: true };
|
|
146
|
+
if (help) return { stateDir: '', wave: 0, session, json, warnMissing, newFiles, help: true };
|
|
132
147
|
if (stateDir === undefined) throw new InputError('--state-dir is required');
|
|
133
148
|
if (waveRaw === undefined) throw new InputError('--wave is required');
|
|
134
149
|
if (stateDir.length === 0 || /[\0\r\n]/.test(stateDir)) {
|
|
@@ -140,7 +155,7 @@ export function parseCliArgs(argv) {
|
|
|
140
155
|
|
|
141
156
|
const wave = Number(waveRaw);
|
|
142
157
|
if (!Number.isSafeInteger(wave)) throw new InputError('--wave must be a safe positive integer');
|
|
143
|
-
return { stateDir, wave, session, json, help: false };
|
|
158
|
+
return { stateDir, wave, session, json, warnMissing, newFiles, help: false };
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
/**
|
|
@@ -157,18 +172,17 @@ function isRecord(value) {
|
|
|
157
172
|
* are rejected because no scope consumer can safely interpret them verbatim.
|
|
158
173
|
*
|
|
159
174
|
* @param {unknown} value
|
|
160
|
-
* @param {
|
|
161
|
-
* @param {number} fileIndex
|
|
175
|
+
* @param {string} description
|
|
162
176
|
*/
|
|
163
|
-
function validateScopePath(value,
|
|
177
|
+
function validateScopePath(value, description) {
|
|
164
178
|
if (typeof value !== 'string' || value.length === 0 || value.trim().length === 0) {
|
|
165
|
-
throw new InputError(
|
|
179
|
+
throw new InputError(`${description} must be a non-empty string`);
|
|
166
180
|
}
|
|
167
181
|
if (/[\0\r\n]/.test(value)) {
|
|
168
|
-
throw new InputError(
|
|
182
|
+
throw new InputError(`${description} must not contain NUL or newline characters`);
|
|
169
183
|
}
|
|
170
184
|
if (value.split(/[\\/]+/).includes('..')) {
|
|
171
|
-
throw new InputError(
|
|
185
|
+
throw new InputError(`${description} must not contain path traversal`);
|
|
172
186
|
}
|
|
173
187
|
}
|
|
174
188
|
|
|
@@ -210,7 +224,7 @@ export function validateScopeRecords(value) {
|
|
|
210
224
|
throw new InputError(`record #${recordIndex} (${record.id}) must have a files string array`);
|
|
211
225
|
}
|
|
212
226
|
for (let fileIndex = 0; fileIndex < record.files.length; fileIndex++) {
|
|
213
|
-
validateScopePath(record.files[fileIndex], recordIndex
|
|
227
|
+
validateScopePath(record.files[fileIndex], `record #${recordIndex} files[${fileIndex}]`);
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
|
|
@@ -220,6 +234,42 @@ export function validateScopeRecords(value) {
|
|
|
220
234
|
return value;
|
|
221
235
|
}
|
|
222
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Validate exceptions before any published declarations can be invalidated.
|
|
239
|
+
* Keep exact spelling: these are diagnostic exceptions, never scope rewrites.
|
|
240
|
+
*
|
|
241
|
+
* @param {string[]} newFiles
|
|
242
|
+
* @param {Array<{id: string, files: string[]}>} records
|
|
243
|
+
* @returns {Set<string>}
|
|
244
|
+
*/
|
|
245
|
+
function validateNewFiles(newFiles, records) {
|
|
246
|
+
const declared = new Set(records.flatMap(({ files }) => files));
|
|
247
|
+
for (const file of newFiles) {
|
|
248
|
+
validateScopePath(file, '--new-file');
|
|
249
|
+
if (!declared.has(file)) throw new InputError(`--new-file path is not declared in scope records: ${file}`);
|
|
250
|
+
}
|
|
251
|
+
return new Set(newFiles);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Missing-file diagnostics are opt-in and never change scope grants or stdout.
|
|
256
|
+
* `*` and trailing `/` are the scope consumer's glob/prefix grammar; `?` and
|
|
257
|
+
* braces are literal names (scripts/lib/scope-gate.mjs isGlobScopeEntry).
|
|
258
|
+
*
|
|
259
|
+
* @param {Array<{id: string, files: string[]}>} records
|
|
260
|
+
* @param {Set<string>} newFiles
|
|
261
|
+
*/
|
|
262
|
+
function warnMissingPaths(records, newFiles) {
|
|
263
|
+
for (const { id, files } of records) {
|
|
264
|
+
for (const file of files) {
|
|
265
|
+
if (newFiles.has(file) || file.includes('*') || file.endsWith('/')) continue;
|
|
266
|
+
if (!existsSync(resolve(file))) {
|
|
267
|
+
process.stderr.write(`materialize-wave-scope: WARN ${id}: declared path not found: ${file}\n`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
223
273
|
|
|
224
274
|
/**
|
|
225
275
|
* Read the session ids the sibling manifest `<state-dir>/wave-scope.json`
|
|
@@ -462,14 +512,18 @@ export function main() {
|
|
|
462
512
|
return;
|
|
463
513
|
}
|
|
464
514
|
const records = validateScopeRecords(readStdinJson());
|
|
515
|
+
const newFiles = validateNewFiles(args.newFiles, records);
|
|
465
516
|
const { aggregatePath, perAgentPaths, removedOrphans, retainedOrphans } =
|
|
466
517
|
materializeWaveScope(records, args);
|
|
467
518
|
|
|
519
|
+
if (args.warnMissing) warnMissingPaths(records, newFiles);
|
|
520
|
+
|
|
468
521
|
// stderr carries ONLY the anomalous cases. Measured constraint, not taste:
|
|
469
522
|
// the corpus pins byte-empty stderr on this command's success path
|
|
470
523
|
// (tests/scripts/materialize-wave-scope.test.mjs and
|
|
471
524
|
// tests/integration/wave-scope-producer.test.mjs), and a wave with no
|
|
472
|
-
// orphans IS the success path
|
|
525
|
+
// orphans IS the success path unless missing-path diagnostics are explicitly
|
|
526
|
+
// requested above. Both orphan lists always reach --json.
|
|
473
527
|
for (const file of removedOrphans) {
|
|
474
528
|
process.stderr.write(`materialize-wave-scope: removed orphaned declaration ${file} (id absent from this wave's records)\n`);
|
|
475
529
|
}
|
package/scripts/mcp-server.sh
CHANGED
|
@@ -194,8 +194,20 @@ tool_session_metrics() {
|
|
|
194
194
|
local entries
|
|
195
195
|
entries=$(jq -R -c 'fromjson? | select(.status != "abandoned")' "$metrics_file" 2>/dev/null | tail -n 5) || true
|
|
196
196
|
|
|
197
|
+
# Report the stubs the filter above dropped (#1296) — counted over the WHOLE
|
|
198
|
+
# file, not the tail, with the same torn-line tolerance (`fromjson?`, plus
|
|
199
|
+
# `.status?` so a non-object line cannot abort the count either). Computed
|
|
200
|
+
# BEFORE the empty check: a stubs-only ledger is not an empty file.
|
|
201
|
+
local stub_count
|
|
202
|
+
stub_count=$(jq -R -n '[inputs | fromjson? | select(.status? == "abandoned")] | length' "$metrics_file" 2>/dev/null) || stub_count=0
|
|
203
|
+
[[ -z "$stub_count" ]] && stub_count=0
|
|
204
|
+
|
|
197
205
|
if [[ -z "$entries" ]]; then
|
|
198
|
-
|
|
206
|
+
if [[ "$stub_count" -gt 0 ]]; then
|
|
207
|
+
respond "$id" "$(text_content "No real sessions (abandoned stubs excluded: ${stub_count})")"
|
|
208
|
+
else
|
|
209
|
+
respond "$id" "$(text_content "No metrics found (file is empty)")"
|
|
210
|
+
fi
|
|
199
211
|
return
|
|
200
212
|
fi
|
|
201
213
|
|
|
@@ -229,6 +241,9 @@ tool_session_metrics() {
|
|
|
229
241
|
' 2>/dev/null) || token_summary=""
|
|
230
242
|
|
|
231
243
|
local output
|
|
244
|
+
entries="${entries}
|
|
245
|
+
|
|
246
|
+
abandoned stubs excluded: ${stub_count}"
|
|
232
247
|
if [[ -n "$token_summary" ]]; then
|
|
233
248
|
output="${entries}
|
|
234
249
|
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* it to each dispatched agent's prompt.
|
|
12
12
|
*
|
|
13
13
|
* Resolution:
|
|
14
|
-
* - scopePaths ← `allowedPaths` from
|
|
14
|
+
* - scopePaths ← `allowedPaths` from the active harness wave-scope.json
|
|
15
15
|
* (override: --wave-scope <path>)
|
|
16
|
-
* - mode ← `session-type:` frontmatter in
|
|
16
|
+
* - mode ← `session-type:` frontmatter in the active harness STATE.md
|
|
17
17
|
* (override: --mode <m>; unreadable → null = no mode gating)
|
|
18
18
|
* - hostClass ← `host_class` from `.orchestrator/host.json` via readHostClass
|
|
19
19
|
* (override: --host-class <c>; unreadable → null = no gating)
|
|
@@ -103,6 +103,7 @@ import { createHash } from 'node:crypto';
|
|
|
103
103
|
import { findProjectRoot } from './lib/common.mjs';
|
|
104
104
|
import { loadApplicableRules } from './lib/rule-loader.mjs';
|
|
105
105
|
import { readHostClass } from './lib/autopilot/telemetry.mjs';
|
|
106
|
+
import { resolveStateArtifactPath, resolveStateMdPath } from './lib/state-md.mjs';
|
|
106
107
|
|
|
107
108
|
// ---------------------------------------------------------------------------
|
|
108
109
|
// EPIPE hardening (regression follow-up on #876)
|
|
@@ -136,10 +137,10 @@ as an injectable Markdown block, for the wave-executor to prepend to each
|
|
|
136
137
|
dispatched agent's prompt (#336 / #694).
|
|
137
138
|
|
|
138
139
|
Options:
|
|
139
|
-
--wave-scope <path> Path to wave-scope.json (default:
|
|
140
|
+
--wave-scope <path> Path to wave-scope.json (default: active harness, then legacy).
|
|
140
141
|
Its "allowedPaths" array is used as scopePaths.
|
|
141
142
|
--mode <m> Override session mode (default: session-type: from
|
|
142
|
-
|
|
143
|
+
active harness STATE.md; unreadable -> no mode gating).
|
|
143
144
|
--host-class <c> Override host class (default: host_class from
|
|
144
145
|
.orchestrator/host.json; unreadable -> no gating).
|
|
145
146
|
--context <c> Caller context for tier gating: 'wave' | 'coordinator'.
|
|
@@ -322,8 +323,8 @@ const repoRoot = findProjectRoot(process.cwd());
|
|
|
322
323
|
const rulesDir = join(repoRoot, '.claude', 'rules');
|
|
323
324
|
const waveScopePath = opts['wave-scope']
|
|
324
325
|
? opts['wave-scope']
|
|
325
|
-
:
|
|
326
|
-
const stateMdPath =
|
|
326
|
+
: resolveStateArtifactPath(repoRoot, 'wave-scope.json');
|
|
327
|
+
const stateMdPath = resolveStateMdPath(repoRoot);
|
|
327
328
|
const hostJsonPath = join(repoRoot, '.orchestrator', 'host.json');
|
|
328
329
|
|
|
329
330
|
// ---------------------------------------------------------------------------
|
|
@@ -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 { resolveStateArtifactPath } from './lib/state-md.mjs';
|
|
104
105
|
import {
|
|
105
106
|
CANDIDATE_POOL_SIZE,
|
|
106
107
|
DEFAULT_MAX_GLOBAL,
|
|
@@ -141,7 +142,7 @@ Options:
|
|
|
141
142
|
agent's declared "Files:" scope. Preferred input.
|
|
142
143
|
Unreadable or malformed -> exit 1.
|
|
143
144
|
--wave-scope <path> Fallback scope source; reads "allowedPaths" (default:
|
|
144
|
-
|
|
145
|
+
active harness, then legacy). An EXPLICIT path that is
|
|
145
146
|
unreadable/malformed -> exit 1; the DEFAULT path being
|
|
146
147
|
absent -> stderr diagnostic + empty scope, exit 0.
|
|
147
148
|
--task-text <text> Optional agent task title/description. Feeds the token
|
|
@@ -314,7 +315,7 @@ if (scopePaths.length === 0) {
|
|
|
314
315
|
const waveScopeExplicit = Boolean(opts['wave-scope']);
|
|
315
316
|
const waveScopePath = waveScopeExplicit
|
|
316
317
|
? opts['wave-scope']
|
|
317
|
-
:
|
|
318
|
+
: resolveStateArtifactPath(repoRoot, 'wave-scope.json');
|
|
318
319
|
const doc = readJsonOrNull(waveScopePath, waveScopeExplicit, '--wave-scope');
|
|
319
320
|
if (doc === null) {
|
|
320
321
|
note(`wave-scope not found at ${waveScopePath} — using empty scope`);
|
package/scripts/release.mjs
CHANGED
|
@@ -378,8 +378,13 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
378
378
|
// (same 2026-09-07 sweep): site/llms-full.txt ("The v4.0.0 release REMOVES public surfaces" — its
|
|
379
379
|
// version SURFACE is checked separately by the SURFACES row, so the sweep on it is redundant),
|
|
380
380
|
// skills/architecture/references/domain-model.md ("Merged here in v4.0.0"), skills/autopilot/SKILL.md
|
|
381
|
-
// ("4.0.0 — see docs/migration-v4.md")
|
|
382
|
-
|
|
381
|
+
// ("4.0.0 — see docs/migration-v4.md"). A fifth, templates/_shared/journey-manifest.md ("Retired …
|
|
382
|
+
// in 4.0.0"), was DELETED on 2026-09-12 (replaced by ux-manifest.template.md, Epic #1322) and its
|
|
383
|
+
// allowlist entry went with it — re-add only if a file of that name returns.
|
|
384
|
+
// September 10 campaign snapshot: inputs and receipt reproduce the dated 4.3-planned
|
|
385
|
+
// render made while 4.2.0 was current. Exact files only; neighboring marketing
|
|
386
|
+
// remains checked. Do not bump receipt props without a newly reviewed render.
|
|
387
|
+
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|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|commands\/release\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
|
|
383
388
|
|
|
384
389
|
/** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
|
|
385
390
|
export function checkLeakage(lines) {
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* session-shape.mjs — the mechanical caller for the session-shape resolution.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a CLI
|
|
6
|
+
*
|
|
7
|
+
* `scripts/lib/session-shape.mjs` holds the shape AND its telemetry. A library
|
|
8
|
+
* with no entrypoint is the repo's standing disease (built, not wired): its only
|
|
9
|
+
* caller would be a fenced code block in a skill file, which no process runs.
|
|
10
|
+
* Both `sessionType` and `profile` exist only AFTER the session-start Q&A
|
|
11
|
+
* resolves, and no hook event fires at that moment — so the caller has to be
|
|
12
|
+
* something the coordinator RUNS with the answers it just received, i.e. this
|
|
13
|
+
* file. Same shape as its sibling `scripts/express-path.mjs` (#1146).
|
|
14
|
+
*
|
|
15
|
+
* ## Usage
|
|
16
|
+
*
|
|
17
|
+
* node scripts/session-shape.mjs --repo-root <path> --session-type <type> \
|
|
18
|
+
* [--profile ultradeep] [--known-scope true|false] [--task-count <n>] \
|
|
19
|
+
* [--config-file <path>] [--no-event]
|
|
20
|
+
*
|
|
21
|
+
* Output: ONE JSON line on stdout — the shape verbatim. Always JSON, so there is
|
|
22
|
+
* no `--json` flag, matching `express-path.mjs` and `parse-config.mjs`. Human
|
|
23
|
+
* diagnostics go to stderr.
|
|
24
|
+
*
|
|
25
|
+
* Exit codes (`.claude/rules/cli-design.md`):
|
|
26
|
+
* 0 — the shape RESOLVED. Read it from stdout.
|
|
27
|
+
* 1 — user/input error: missing/invalid --repo-root, --session-type, --profile,
|
|
28
|
+
* --known-scope, --task-count, or an unknown flag.
|
|
29
|
+
* 2 — config I/O error: an unreadable or unparseable config file.
|
|
30
|
+
*
|
|
31
|
+
* `--repo-root` is REQUIRED and is never filled from `SO_PROJECT_DIR`: the
|
|
32
|
+
* library refuses that fallback for a measured reason (#941 — an ambient
|
|
33
|
+
* destination put a synthetic record into the operator's real fleet ledger), and
|
|
34
|
+
* a CLI that quietly supplied one would reinstate exactly what it refuses.
|
|
35
|
+
*
|
|
36
|
+
* A config file that EXISTS but cannot be read or parsed exits 2 — it carries
|
|
37
|
+
* `agents-per-wave`, and guessing past an unreadable cap would publish a shape
|
|
38
|
+
* with the wrong agent budget. A config file that is simply ABSENT is not an
|
|
39
|
+
* error: the documented defaults apply and a WARN goes to stderr.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
43
|
+
import { join, resolve } from 'node:path';
|
|
44
|
+
import { parseArgs } from 'node:util';
|
|
45
|
+
import { fileURLToPath } from 'node:url';
|
|
46
|
+
import { resolveInstructionFile } from './lib/common.mjs';
|
|
47
|
+
import { parseSessionConfig } from './lib/config.mjs';
|
|
48
|
+
import { resolveAndRecordSessionShape } from './lib/session-shape.mjs';
|
|
49
|
+
|
|
50
|
+
const USAGE = [
|
|
51
|
+
'Usage: node scripts/session-shape.mjs --repo-root <path> --session-type <housekeeping|feature|deep>',
|
|
52
|
+
' [--profile ultradeep] [--known-scope true|false]',
|
|
53
|
+
' [--task-count <n>] [--config-file <path>] [--no-event]',
|
|
54
|
+
'',
|
|
55
|
+
' --repo-root REQUIRED repo receiving the .orchestrator/metrics/events.jsonl record',
|
|
56
|
+
' --session-type REQUIRED housekeeping | feature | deep',
|
|
57
|
+
' --profile optional ultradeep (a wave-shape variant on top of --session-type deep)',
|
|
58
|
+
' --known-scope optional true|false; deep only — true drops the Discovery wave',
|
|
59
|
+
' --task-count optional agreed issue/task scope (non-negative integer), recorded only',
|
|
60
|
+
' --config-file optional defaults to SO_CONFIG_FILE, else CLAUDE.md / AGENTS.md under --repo-root',
|
|
61
|
+
' --no-event optional resolve without writing the ledger record (planning dry-run)',
|
|
62
|
+
'',
|
|
63
|
+
'stdout: one JSON line — the resolved shape',
|
|
64
|
+
'Exit codes: 0 resolved, 1 input error, 2 config I/O error',
|
|
65
|
+
].join('\n');
|
|
66
|
+
|
|
67
|
+
/** User/input error — usage class. */
|
|
68
|
+
const EXIT_INPUT = 1;
|
|
69
|
+
/** System error — config could not be read or parsed. */
|
|
70
|
+
const EXIT_CONFIG_IO = 2;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Write a diagnostic line to stderr. stdout carries the shape and nothing else
|
|
74
|
+
* (`cli-design.md` § JSON-First).
|
|
75
|
+
*
|
|
76
|
+
* @param {string} message
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
function warn(message) {
|
|
80
|
+
process.stderr.write(`session-shape: ${message}\n`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Fail with a usage-class diagnostic and the given exit code.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} message
|
|
87
|
+
* @param {number} code
|
|
88
|
+
* @returns {never}
|
|
89
|
+
*/
|
|
90
|
+
function fail(message, code) {
|
|
91
|
+
warn(message);
|
|
92
|
+
process.exit(code);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the config file to read, anchored at `--repo-root`.
|
|
97
|
+
*
|
|
98
|
+
* The CLAUDE.md → AGENTS.md walk is `resolveInstructionFile()` in
|
|
99
|
+
* `scripts/lib/common.mjs` — the SSOT for the instruction-file alias rule — and
|
|
100
|
+
* is deliberately not re-implemented here. Only the `SO_CONFIG_FILE` override
|
|
101
|
+
* that `scripts/parse-config.mjs` honours is layered on top, plus an explicit
|
|
102
|
+
* `--config-file`, which wins outright. Anchoring at `repoRoot` rather than
|
|
103
|
+
* walking up from cwd keeps a DIFFERENT repo's config out of this shape.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} repoRoot
|
|
106
|
+
* @param {string|undefined} explicit
|
|
107
|
+
* @returns {string|null}
|
|
108
|
+
*/
|
|
109
|
+
function resolveRepoConfigPath(repoRoot, explicit) {
|
|
110
|
+
if (explicit) {
|
|
111
|
+
const abs = resolve(explicit);
|
|
112
|
+
if (!existsSync(abs)) fail(`config file not found: ${explicit}`, EXIT_CONFIG_IO);
|
|
113
|
+
return abs;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (process.env.SO_CONFIG_FILE) {
|
|
117
|
+
const override = join(repoRoot, process.env.SO_CONFIG_FILE);
|
|
118
|
+
if (existsSync(override)) return override;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return resolveInstructionFile(repoRoot)?.path ?? null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Read and parse the Session Config. Returns `undefined` when no config file
|
|
126
|
+
* exists, so the library applies its documented defaults.
|
|
127
|
+
*
|
|
128
|
+
* @param {string|null} configFile
|
|
129
|
+
* @returns {object|undefined}
|
|
130
|
+
*/
|
|
131
|
+
function loadConfig(configFile) {
|
|
132
|
+
if (configFile === null) {
|
|
133
|
+
warn('no CLAUDE.md / AGENTS.md under --repo-root; applying documented defaults');
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let content;
|
|
138
|
+
try {
|
|
139
|
+
content = readFileSync(configFile, 'utf8');
|
|
140
|
+
} catch (err) {
|
|
141
|
+
fail(`failed to read ${configFile}: ${err.message}`, EXIT_CONFIG_IO);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
return parseSessionConfig(content);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
fail(`failed to parse ${configFile}: ${err.message}`, EXIT_CONFIG_IO);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Parse a boolean flag value. Strict on purpose: a typo'd `--known-scope yes`
|
|
153
|
+
* silently meaning `false` would drop or keep a whole Discovery wave.
|
|
154
|
+
*
|
|
155
|
+
* @param {string|undefined} raw
|
|
156
|
+
* @param {string} flag
|
|
157
|
+
* @returns {boolean|undefined}
|
|
158
|
+
*/
|
|
159
|
+
function parseBoolFlag(raw, flag) {
|
|
160
|
+
if (raw === undefined) return undefined;
|
|
161
|
+
const value = String(raw).trim().toLowerCase();
|
|
162
|
+
if (value === 'true') return true;
|
|
163
|
+
if (value === 'false') return false;
|
|
164
|
+
return fail(`${flag} must be "true" or "false" (got: ${raw})`, EXIT_INPUT);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function main() {
|
|
168
|
+
/** @type {{values: Record<string, string|boolean>}} */
|
|
169
|
+
let parsed;
|
|
170
|
+
try {
|
|
171
|
+
parsed = parseArgs({
|
|
172
|
+
args: process.argv.slice(2),
|
|
173
|
+
options: {
|
|
174
|
+
'repo-root': { type: 'string' },
|
|
175
|
+
'session-type': { type: 'string' },
|
|
176
|
+
profile: { type: 'string' },
|
|
177
|
+
'known-scope': { type: 'string' },
|
|
178
|
+
'task-count': { type: 'string' },
|
|
179
|
+
'config-file': { type: 'string' },
|
|
180
|
+
'no-event': { type: 'boolean' },
|
|
181
|
+
help: { type: 'boolean', short: 'h' },
|
|
182
|
+
},
|
|
183
|
+
allowPositionals: false,
|
|
184
|
+
});
|
|
185
|
+
} catch (err) {
|
|
186
|
+
fail(`${err.message}\n\n${USAGE}`, EXIT_INPUT);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const { values } = parsed;
|
|
190
|
+
|
|
191
|
+
if (values.help) {
|
|
192
|
+
process.stdout.write(`${USAGE}\n`);
|
|
193
|
+
process.exit(0);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const repoRootArg = typeof values['repo-root'] === 'string' ? values['repo-root'].trim() : '';
|
|
197
|
+
if (repoRootArg === '') {
|
|
198
|
+
fail(`--repo-root is required (never defaulted from SO_PROJECT_DIR)\n\n${USAGE}`, EXIT_INPUT);
|
|
199
|
+
}
|
|
200
|
+
const repoRoot = resolve(repoRootArg);
|
|
201
|
+
if (!existsSync(repoRoot) || !statSync(repoRoot).isDirectory()) {
|
|
202
|
+
// A typo'd root would otherwise be CREATED by the emitter's mkdir, leaving
|
|
203
|
+
// an orphan `.orchestrator/metrics/` tree that answers no question.
|
|
204
|
+
fail(`--repo-root is not an existing directory: ${repoRootArg}`, EXIT_INPUT);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const sessionType =
|
|
208
|
+
typeof values['session-type'] === 'string' ? values['session-type'].trim() : '';
|
|
209
|
+
if (sessionType === '') {
|
|
210
|
+
fail(`--session-type is required\n\n${USAGE}`, EXIT_INPUT);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const profileArg = typeof values.profile === 'string' ? values.profile.trim() : '';
|
|
214
|
+
const profile = profileArg === '' ? null : profileArg;
|
|
215
|
+
|
|
216
|
+
const knownScope = parseBoolFlag(
|
|
217
|
+
typeof values['known-scope'] === 'string' ? values['known-scope'] : undefined,
|
|
218
|
+
'--known-scope',
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
let taskCount = null;
|
|
222
|
+
const taskCountRaw = typeof values['task-count'] === 'string' ? values['task-count'].trim() : '';
|
|
223
|
+
if (taskCountRaw !== '') {
|
|
224
|
+
taskCount = Number(taskCountRaw);
|
|
225
|
+
if (!Number.isInteger(taskCount) || taskCount < 0) {
|
|
226
|
+
fail(`--task-count must be a non-negative integer (got: ${taskCountRaw})`, EXIT_INPUT);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const configFile = resolveRepoConfigPath(
|
|
231
|
+
repoRoot,
|
|
232
|
+
typeof values['config-file'] === 'string' ? values['config-file'] : undefined,
|
|
233
|
+
);
|
|
234
|
+
const config = loadConfig(configFile);
|
|
235
|
+
|
|
236
|
+
let shape;
|
|
237
|
+
try {
|
|
238
|
+
shape = await resolveAndRecordSessionShape({
|
|
239
|
+
repoRoot,
|
|
240
|
+
config,
|
|
241
|
+
sessionType,
|
|
242
|
+
profile,
|
|
243
|
+
knownScope: knownScope === true,
|
|
244
|
+
taskCount,
|
|
245
|
+
emit: values['no-event'] !== true,
|
|
246
|
+
});
|
|
247
|
+
} catch (err) {
|
|
248
|
+
// The library throws TypeError on an unknown session type or profile —
|
|
249
|
+
// that is an INPUT error here, not a crash. Its message already carries the
|
|
250
|
+
// `session-shape: ` prefix that `warn()` adds, so strip the inner one:
|
|
251
|
+
// `session-shape: session-shape: unknown sessionType …` reads as a bug in
|
|
252
|
+
// the tool rather than a typo in the flag.
|
|
253
|
+
if (err instanceof TypeError) fail(err.message.replace(/^session-shape:\s*/, ''), EXIT_INPUT);
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
process.stdout.write(`${JSON.stringify(shape)}\n`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Entrypoint guard — importing this file must not run it (check-unwired-features S3).
|
|
261
|
+
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
262
|
+
main().catch((err) => {
|
|
263
|
+
process.stderr.write(`session-shape: unexpected error: ${err?.stack ?? err}\n`);
|
|
264
|
+
process.exit(EXIT_CONFIG_IO);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
@@ -54,18 +54,22 @@ Store the JSON output as `$CONFIG` for use throughout this skill — extract fie
|
|
|
54
54
|
|
|
55
55
|
### Handling `agents-per-wave` Overrides
|
|
56
56
|
|
|
57
|
-
`agents-per-wave` may be a plain integer (`6`) or a JSON object with session-type overrides (`{"default": 6, "deep": 18}`).
|
|
57
|
+
`agents-per-wave` may be a plain integer (`6`) or a JSON object with session-type overrides (`{"default": 6, "deep": 18}`). Do NOT resolve it with a hand-written `jq` expression — use `resolveAgentCap(cap, sessionType)` from `scripts/lib/session-shape.mjs`, the one exported resolver:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
import { resolveAgentCap } from './scripts/lib/session-shape.mjs';
|
|
61
|
+
const effective = resolveAgentCap(config['agents-per-wave'], sessionType); // number | null
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
From a shell, the same answer arrives inside the resolved shape (`waves[].agentCap`):
|
|
58
65
|
|
|
59
66
|
```bash
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if echo "$APW" | jq -e 'type == "object"' > /dev/null 2>&1; then
|
|
63
|
-
EFFECTIVE_APW=$(echo "$APW" | jq -r --arg st "$SESSION_TYPE" '.[$st] // .default')
|
|
64
|
-
else
|
|
65
|
-
EFFECTIVE_APW="$APW"
|
|
66
|
-
fi
|
|
67
|
+
node scripts/session-shape.mjs --repo-root "$PWD" --session-type "$SESSION_TYPE" --no-event \
|
|
68
|
+
| jq '[.waves[] | {n, role, agentCap}]'
|
|
67
69
|
```
|
|
68
70
|
|
|
71
|
+
The **session type picks the override** when the config carries one; `.default` is only the fallback, and it is the more restrictive of the two (an override such as `deep: 18` exists precisely to raise the ceiling above it). A resolver that returns `.default` unconditionally is therefore safe for a resource ceiling but wrong for wave shaping — see the note on the two mode-blind call sites (`wave-resource-gate.mjs`, `resource-probe/evaluate.mjs`) in `docs/session-config-reference.md` § `agents-per-wave`, both of which call the same `resolveAgentCap` but pass a local `MODE_BLIND_SESSION_TYPE` (`undefined`) instead of the real session type.
|
|
72
|
+
|
|
69
73
|
## Handling `agent-mapping` Config
|
|
70
74
|
|
|
71
75
|
`agent-mapping` is an optional JSON object that maps role keys to agent names. If present, session-plan uses these explicit mappings to assign agents to tasks (overriding auto-discovery matching).
|
|
@@ -166,7 +170,9 @@ Rule files at `.claude/rules/*.md` may carry an optional `globs:` YAML frontmatt
|
|
|
166
170
|
|
|
167
171
|
Parse-error rules carry no meta, so they pass every gate (fail-open: never silently dropped).
|
|
168
172
|
|
|
169
|
-
**Call shape.** `loadApplicableRules({ rulesDir, scopePaths = [], mode = null, hostClass = null, now = Date.now() })`. The `mode` / `hostClass` / `now` params are strictly optional and default to "no gating", so the original #336 two-key call shape stays 100% backward-compatible. In the wired path, `scripts/print-applicable-rules.mjs` resolves `scopePaths` from `wave-scope.json` `allowedPaths
|
|
173
|
+
**Call shape.** `loadApplicableRules({ rulesDir, scopePaths = [], mode = null, hostClass = null, now = Date.now() })`. The `mode` / `hostClass` / `now` params are strictly optional and default to "no gating", so the original #336 two-key call shape stays 100% backward-compatible. In the wired path, `scripts/print-applicable-rules.mjs` resolves `scopePaths` from `wave-scope.json` `allowedPaths`;
|
|
174
|
+
`mode` from `session-type:` in the active harness's `STATE.md` (state-directory override first, then active-harness and legacy fallback);
|
|
175
|
+
and `hostClass` from `.orchestrator/host.json` (`readHostClass`) — each overridable via a CLI flag and each degrading to `null`/`[]` when unreadable.
|
|
170
176
|
|
|
171
177
|
**Where in the config-reading flow this hook fires.** After `parse-config.mjs` completes and `$CONFIG` is populated (Phase 2 of session-start / wave-executor pre-wave setup), and after `wave-scope.json` is written, but before the agent prompt for the wave is assembled. The CLI is invoked at the wave boundary so that each wave gets a fresh rule set scoped to its `allowedPaths`. It does NOT run at session-start for the coordinator prompt; the coordinator always receives all always-on rules regardless of scope.
|
|
172
178
|
|