session-orchestrator 4.0.1 → 4.2.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/session-plan/SKILL.md +1 -1
- package/.cursor/skills/session-plan/SKILL.md +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +57 -0
- package/README.md +55 -51
- 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/release.md +4 -4
- package/commands/session.md +3 -2
- package/docs/README.md +4 -4
- package/docs/USER-GUIDE.md +115 -48
- 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 +1 -0
- package/docs/components.md +2 -2
- package/docs/cursor-setup.md +1 -0
- package/docs/events-schema.md +4 -1
- package/docs/instruction-delivery.md +1 -1
- package/docs/memory-proposal-flow.md +3 -3
- package/docs/migration-v4.md +2 -2
- 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 +2 -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 +28 -3
- package/hooks/_lib/vcs-create-matcher.mjs +214 -16
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/pre-bash-issue-budget.mjs +123 -26
- package/hooks/subagent-telemetry.mjs +106 -20
- package/package.json +4 -4
- package/scripts/baseline-archetypes.mjs +28 -0
- package/scripts/ci/assert-coverage-green.mjs +100 -0
- 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/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/events.mjs +3 -3
- package/scripts/lib/file-lock.mjs +47 -5
- package/scripts/lib/issue-budget-reconcile.mjs +392 -0
- package/scripts/lib/issue-budget.mjs +76 -3
- package/scripts/lib/learnings/evolve-telemetry.mjs +1 -2
- package/scripts/lib/maintenance-due-banner.mjs +440 -0
- package/scripts/lib/owner-config.example.yaml +29 -46
- package/scripts/lib/owner-yaml.mjs +14 -13
- package/scripts/lib/project-hygiene.mjs +182 -6
- package/scripts/lib/quality-gate.mjs +13 -6
- package/scripts/lib/resource-probe/evaluate.mjs +19 -21
- package/scripts/lib/rules-sync.mjs +34 -4
- 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-identity/own-session.mjs +24 -13
- package/scripts/lib/session-schema/constants.mjs +6 -0
- package/scripts/lib/session-schema/validator.mjs +20 -0
- package/scripts/lib/session-shape.mjs +558 -0
- package/scripts/lib/session-start-probes.mjs +10 -3
- 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/validate/check-owner-leakage.mjs +17 -8
- package/scripts/lib/validate/check-skill-script-paths.mjs +33 -10
- package/scripts/lib/validate/check-unwired-features.mjs +8 -7
- package/scripts/lib/vault-mirror/process.mjs +2 -1
- package/scripts/lib/vault-mirror/render-sessions.mjs +8 -1
- 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/print-applicable-rules.mjs +7 -6
- package/scripts/print-learnings-index.mjs +3 -2
- package/scripts/release.mjs +32 -11
- 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 +61 -13
- 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/standard-template.md +39 -24
- package/skills/discovery/probes-ui.md +1 -1
- package/skills/docs-orchestrator/audience-mapping.md +1 -1
- package/skills/evolve/SKILL.md +2 -2
- 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/reconcile/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +3 -2
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +23 -65
- package/skills/session-end/phase-3-7a-recommendations.md +2 -2
- package/skills/session-end/references/phase-3-documentation-updates.md +8 -6
- package/skills/session-end/references/phase-5-issue-cleanup.md +26 -0
- package/skills/session-end/session-metrics-write.md +31 -12
- package/skills/session-plan/SKILL.md +56 -48
- package/skills/session-plan/wave-template.md +8 -15
- package/skills/session-start/SKILL.md +18 -2
- 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/phase-1-5-session-continuity.md +2 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +21 -5
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +3 -1
- package/skills/test-runner/rubric-v1.md +2 -2
- package/skills/wave-executor/SKILL.md +42 -12
- package/skills/wave-executor/circuit-breaker.md +3 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +4 -2
- package/skills/wave-executor/references/wave-loop-review.md +1 -1
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +6 -2
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* check-owner-leakage.mjs — Scan tracked files for owner-privacy leakage patterns.
|
|
4
|
+
* Add --include-untracked to also scan new files that Git does not ignore.
|
|
4
5
|
*
|
|
5
6
|
* Implements the #462 audit trail durable CI guard (#471).
|
|
6
7
|
* Canonicalization-before-matching refactor (issue #661): the historical
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
* toward over-matching: this is a security guard, a false-positive is cheap, a
|
|
20
21
|
* false-negative ships a leak to the public mirror.
|
|
21
22
|
*
|
|
22
|
-
* Usage: check-owner-leakage.mjs <plugin-root>
|
|
23
|
+
* Usage: check-owner-leakage.mjs <plugin-root> [--include-untracked]
|
|
23
24
|
*
|
|
24
25
|
* Forbidden patterns (canonical rules CP1–CP10):
|
|
25
26
|
* CP1 personal home path `/Users/bernhardg…` — matched on the CANONICAL form,
|
|
@@ -128,8 +129,9 @@ const isMain =
|
|
|
128
129
|
|
|
129
130
|
// CLI: single positional arg required (only enforced when run directly).
|
|
130
131
|
const pluginRoot = argv[2];
|
|
132
|
+
const includeUntracked = argv.slice(3).includes('--include-untracked');
|
|
131
133
|
if (isMain && !pluginRoot) {
|
|
132
|
-
console.error('Usage: check-owner-leakage.mjs <plugin-root>');
|
|
134
|
+
console.error('Usage: check-owner-leakage.mjs <plugin-root> [--include-untracked]');
|
|
133
135
|
process.exit(1);
|
|
134
136
|
}
|
|
135
137
|
|
|
@@ -789,9 +791,10 @@ function rawConfidentialNamesKeyState(ownerYamlPath) {
|
|
|
789
791
|
* exists to block. The operator knows their own path; the CLASS of failure is what
|
|
790
792
|
* this line has to convey.
|
|
791
793
|
*
|
|
794
|
+
* @param {{ loadHostPaths?: typeof import('../config/host-paths.mjs').loadHostPaths }} [opts]
|
|
792
795
|
* @returns {Promise<{ patterns: RegExp[], disabledReason?: string, inertWarn?: string }>}
|
|
793
796
|
*/
|
|
794
|
-
async function getConfidentialNamePatterns() {
|
|
797
|
+
export async function getConfidentialNamePatterns({ loadHostPaths } = {}) {
|
|
795
798
|
let helpers;
|
|
796
799
|
try {
|
|
797
800
|
helpers = {
|
|
@@ -833,7 +836,7 @@ async function getConfidentialNamePatterns() {
|
|
|
833
836
|
}
|
|
834
837
|
|
|
835
838
|
try {
|
|
836
|
-
const {
|
|
839
|
+
const { resolveHostPath } = helpers.hostPaths;
|
|
837
840
|
const { inspectConfidentialNames } = helpers.confidentialNames;
|
|
838
841
|
const { resolveOwnerYamlPath } = helpers.ownerYaml;
|
|
839
842
|
|
|
@@ -841,7 +844,7 @@ async function getConfidentialNamePatterns() {
|
|
|
841
844
|
// health (source / reason / droppedSections) straight through, so the
|
|
842
845
|
// env>owner.yaml>default precedence and the load's diagnosis come from the
|
|
843
846
|
// same read — no second loadOwnerConfig() call to recover what was discarded.
|
|
844
|
-
const hostCtx = loadHostPaths();
|
|
847
|
+
const hostCtx = (loadHostPaths ?? helpers.hostPaths.loadHostPaths)();
|
|
845
848
|
|
|
846
849
|
// FAIL-CLOSED on an unknowable load (LOW-2). `loadHostPaths` has its own
|
|
847
850
|
// defensive catch that swallows a THROWING owner loader and returns
|
|
@@ -987,6 +990,7 @@ async function getConfidentialNamePatterns() {
|
|
|
987
990
|
// adding: all 7 tracked `.jsonl` files pass (1525 -> 1532 scanned, 0 findings).
|
|
988
991
|
const TEXT_EXTS = new Set([
|
|
989
992
|
'.md',
|
|
993
|
+
'.mdx',
|
|
990
994
|
'.mjs',
|
|
991
995
|
'.js',
|
|
992
996
|
'.ts',
|
|
@@ -1021,9 +1025,14 @@ function isTextFile(filePath) {
|
|
|
1021
1025
|
|
|
1022
1026
|
function getTrackedFiles() {
|
|
1023
1027
|
try {
|
|
1024
|
-
|
|
1028
|
+
// NUL delimiters preserve Unicode, spaces and newlines without Git's quoted
|
|
1029
|
+
// filename encoding. --exclude-standard applies only to untracked entries;
|
|
1030
|
+
// tracked files remain visible even if an ignore rule now matches them.
|
|
1031
|
+
const args = ['ls-files', '-z', '--cached'];
|
|
1032
|
+
if (includeUntracked) args.push('--others', '--exclude-standard');
|
|
1033
|
+
const output = execFileSync('git', args, { cwd: pluginRoot, encoding: 'utf8' });
|
|
1025
1034
|
return output
|
|
1026
|
-
.split('\
|
|
1035
|
+
.split('\0')
|
|
1027
1036
|
.filter(Boolean)
|
|
1028
1037
|
.map((f) => join(pluginRoot, f));
|
|
1029
1038
|
} catch {
|
|
@@ -1402,7 +1411,7 @@ if (violations.length === 0) {
|
|
|
1402
1411
|
}
|
|
1403
1412
|
|
|
1404
1413
|
console.log('');
|
|
1405
|
-
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
1414
|
+
console.log(`Results: ${passed} passed, ${failed} failed (${scanFiles.length} scanned files)`);
|
|
1406
1415
|
process.exit(failed === 0 ? 0 : 1);
|
|
1407
1416
|
}
|
|
1408
1417
|
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* Check: every `scripts/**.mjs` path cited in `skills/`, `commands/`,
|
|
4
4
|
* `agents/` and `docs/` either EXISTS or is annotated as deliberately absent
|
|
5
5
|
* (#1176). Extended (#1187) to also cite `scripts/**.sh` and `hooks/**.sh` —
|
|
6
|
-
* see "## Mode
|
|
6
|
+
* see "## Mode" below for why that
|
|
7
7
|
* half is advisory, not blocking. `docs/` joined `SCAN_DIRS` in #1208, after
|
|
8
8
|
* the 22 dead paths it carried at the time (9 `.mjs`, all ADR/reference
|
|
9
9
|
* prose) were annotated — see that section below for the census and why
|
|
10
10
|
* widening the scan root had to wait for the annotation pass, not precede it.
|
|
11
|
+
* #1241 adds blocking repo-rooted `.md` paths quoted as complete inline-code
|
|
12
|
+
* spans, with the same annotations and fence handling. Scan roots stay fixed.
|
|
11
13
|
*
|
|
12
14
|
* ## Why
|
|
13
15
|
*
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
* looking misplaced. A malformed marker (unknown class, or `planned` without a
|
|
51
53
|
* `#<iid>`) is itself a finding for the same reason — it must never fail silent.
|
|
52
54
|
*
|
|
53
|
-
* ## Mode: BLOCKING for `.mjs`, ADVISORY for `.sh` (#1187)
|
|
55
|
+
* ## Mode: BLOCKING for `.mjs` / `.md`, ADVISORY for `.sh` (#1187)
|
|
54
56
|
*
|
|
55
57
|
* Unlike `check-doc-cli-commands.mjs`, the oracle here is the repository's own
|
|
56
58
|
* filesystem, not a locally installed third-party binary — there is no version
|
|
@@ -115,6 +117,22 @@ export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', 'docs'])
|
|
|
115
117
|
*/
|
|
116
118
|
const CITATION_RE = /scripts\/[a-zA-Z0-9_/-]*\.(?:mjs|sh)|hooks\/[a-zA-Z0-9_/-]*\.sh/g;
|
|
117
119
|
|
|
120
|
+
/**
|
|
121
|
+
* A complete inline-code span, including spans delimited by multiple backticks.
|
|
122
|
+
* Consume the outer span before judging its content, so a quoted command that
|
|
123
|
+
* contains backticks cannot donate a nested path-looking substring.
|
|
124
|
+
*/
|
|
125
|
+
const INLINE_CODE_RE = /(?<!`)(`+)(.+?)(?<!`)\1(?!`)/g;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* #1241's bounded grammar: a literal repo-rooted .md path, optionally followed
|
|
129
|
+
* by :line, :line:end / :line-end, and #anchor. Only the file is checked.
|
|
130
|
+
* URLs, commands, absolute paths, skill-relative references/, variables and
|
|
131
|
+
* globs are outside this grammar. Matching is line-local; revisit if the
|
|
132
|
+
* instruction corpus adopts multiline path spans or another target root.
|
|
133
|
+
*/
|
|
134
|
+
const MARKDOWN_CITATION_RE = /^((?:docs|skills|commands|agents|templates|rules|scripts|hooks|tests|\.(?:claude|codex|cursor|pi|orchestrator|gitlab|github))\/[a-zA-Z0-9_./-]+\.md)(?::\d+(?:[:-]\d+)?)?(?:#[^\s`]+)?$/;
|
|
135
|
+
|
|
118
136
|
/**
|
|
119
137
|
* Filename fragments that mark a citation as an ILLUSTRATIVE placeholder —
|
|
120
138
|
* `scripts/example.sh`, `hooks/my-hook.sh`, `scripts/<name>.sh` — rather than
|
|
@@ -211,6 +229,10 @@ export function extractCitations(lines) {
|
|
|
211
229
|
for (const hit of raw.matchAll(CITATION_RE)) {
|
|
212
230
|
citations.push({ line: lineNumber, path: hit[0] });
|
|
213
231
|
}
|
|
232
|
+
for (const span of raw.matchAll(INLINE_CODE_RE)) {
|
|
233
|
+
const hit = span[2].match(MARKDOWN_CITATION_RE);
|
|
234
|
+
if (hit) citations.push({ line: lineNumber, path: hit[1] });
|
|
235
|
+
}
|
|
214
236
|
};
|
|
215
237
|
|
|
216
238
|
// A blockquoted fence is still a fence — the shared tracker strips the `>`
|
|
@@ -234,12 +256,12 @@ export function extractCitations(lines) {
|
|
|
234
256
|
|
|
235
257
|
/**
|
|
236
258
|
* Census the documentation corpus for dead `scripts/**.mjs`/`.sh` and
|
|
237
|
-
* `hooks/**.sh` citations.
|
|
259
|
+
* `hooks/**.sh` citations, plus repo-rooted `.md` inline-code paths.
|
|
238
260
|
*
|
|
239
261
|
* @param {{pluginRoot: string, dirs?: string[], strictSh?: boolean}} options
|
|
240
262
|
* `strictSh` (default `false`) promotes a dead `.sh` citation from
|
|
241
263
|
* `severity: 'warn'` to `severity: 'fail'` — see the module docblock
|
|
242
|
-
* "Mode
|
|
264
|
+
* "Mode" for why the default stays
|
|
243
265
|
* advisory in this release.
|
|
244
266
|
* @returns {{ok: boolean, summary: object, findings: {kind: string, file: string, line: number, path: string, annotation: string | null, message: string, severity: 'fail' | 'warn'}[], toolError: boolean}}
|
|
245
267
|
*/
|
|
@@ -344,10 +366,11 @@ export function scanSkillScriptPaths({ pluginRoot, dirs = SCAN_DIRS, strictSh =
|
|
|
344
366
|
summary.existing += 1;
|
|
345
367
|
continue;
|
|
346
368
|
}
|
|
347
|
-
//
|
|
369
|
+
// A script placeholder name needs no marker — see
|
|
348
370
|
// `isPlaceholderCitation`'s docblock for the closed fragment list and
|
|
349
|
-
// its named ceiling.
|
|
350
|
-
|
|
371
|
+
// its named ceiling. Markdown examples require explicit annotations:
|
|
372
|
+
// a real missing docs/foobar.md must not disappear because of its name.
|
|
373
|
+
if (path.extname(citation.path) !== '.md' && isPlaceholderCitation(citation.path)) {
|
|
351
374
|
summary.placeholders += 1;
|
|
352
375
|
continue;
|
|
353
376
|
}
|
|
@@ -366,7 +389,7 @@ export function scanSkillScriptPaths({ pluginRoot, dirs = SCAN_DIRS, strictSh =
|
|
|
366
389
|
}
|
|
367
390
|
if (marker && !marker.ok) continue; // already reported as bad-annotation
|
|
368
391
|
|
|
369
|
-
// `.mjs`
|
|
392
|
+
// `.mjs` and `.md` are blocking.
|
|
370
393
|
// `.sh` is advisory (`warn`) unless the caller opted into `strictSh`.
|
|
371
394
|
const isSh = path.extname(citation.path) === '.sh';
|
|
372
395
|
const severity = isSh && !strictSh ? 'warn' : 'fail';
|
|
@@ -405,7 +428,7 @@ export function scanSkillScriptPaths({ pluginRoot, dirs = SCAN_DIRS, strictSh =
|
|
|
405
428
|
* @returns {number} 0 = clean, 1 = findings, 2 = tool error
|
|
406
429
|
*/
|
|
407
430
|
export function runCheckSkillScriptPaths(pluginRoot, { strictSh = false } = {}) {
|
|
408
|
-
console.log('--- Check:
|
|
431
|
+
console.log('--- Check: script and Markdown paths cited in skills/commands/agents/docs exist ---');
|
|
409
432
|
const inspection = scanSkillScriptPaths({ pluginRoot, strictSh });
|
|
410
433
|
|
|
411
434
|
for (const item of inspection.findings) {
|
|
@@ -427,7 +450,7 @@ export function runCheckSkillScriptPaths(pluginRoot, { strictSh = false } = {})
|
|
|
427
450
|
const blockingCount = inspection.findings.filter((f) => f.severity !== 'warn').length;
|
|
428
451
|
if (inspection.ok) {
|
|
429
452
|
console.log(
|
|
430
|
-
` PASS: ${s.citations}
|
|
453
|
+
` PASS: ${s.citations} file citation(s) in ${s.filesScanned} doc file(s) — ` +
|
|
431
454
|
`${s.existing} exist, ${s.annotated} annotated as deliberately absent, ` +
|
|
432
455
|
`${s.placeholders} placeholder(s)` +
|
|
433
456
|
(s.warnings > 0 ? `, ${s.warnings} advisory .sh warning(s) (see --strict-sh)` : ''),
|
|
@@ -114,8 +114,9 @@
|
|
|
114
114
|
* `CHANGELOG.md` is excluded from the prose corpus for the same reason: it is an
|
|
115
115
|
* append-only record of what a PAST release shipped, so it names the symbols of
|
|
116
116
|
* code that may since have died. Counting it silenced a true positive
|
|
117
|
-
* (`soul-resolve.mjs
|
|
118
|
-
*
|
|
117
|
+
* (`soul-resolve.mjs` — since DELETED as dead: its only live claim was in
|
|
118
|
+
* `.claude/rules/owner-persona.md`, while its symbols appeared in a 2026-06
|
|
119
|
+
* changelog entry).
|
|
119
120
|
*
|
|
120
121
|
* ## S4 `unreachable-library-module` — the question the machine can answer
|
|
121
122
|
*
|
|
@@ -777,11 +778,11 @@ function mentionedModuleTokens(lines) {
|
|
|
777
778
|
* ## Cluster roots — one defect, one line
|
|
778
779
|
*
|
|
779
780
|
* Only the ROOT of each unreachable cluster is reported: a module no OTHER
|
|
780
|
-
* unreachable module references.
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
* disappear together. Measured on the live tree: 71 unreachable modules
|
|
784
|
-
* to 50 roots. This is category separation in the sense of
|
|
781
|
+
* unreachable module references. A drag-cluster looks like this: an unimported
|
|
782
|
+
* top-level module whose own subtree of interior files goes down with it —
|
|
783
|
+
* reporting the interior files too would multiply one deletion into N findings
|
|
784
|
+
* that all disappear together. Measured on the live tree: 71 unreachable modules
|
|
785
|
+
* collapse to 50 roots. This is category separation in the sense of
|
|
785
786
|
* `.claude/rules/development.md` § Guard & Threshold Design — a structural split,
|
|
786
787
|
* never a raised threshold.
|
|
787
788
|
*
|
|
@@ -879,7 +879,8 @@ export async function processSession(rawEntry, _lineNum, ctx) {
|
|
|
879
879
|
// This is deliberately REDUNDANT with the renderer's status mapping (#909,
|
|
880
880
|
// render-sessions.mjs) — see that module's header. The filter removes one
|
|
881
881
|
// status from the vault; the mapping keeps every OTHER status honest, and
|
|
882
|
-
// guards the generators' other entry point (
|
|
882
|
+
// guards the generators' other entry point (this module imports
|
|
883
|
+
// render-learnings.mjs and render-sessions.mjs directly — no barrel).
|
|
883
884
|
if (!isRealSession(entry)) {
|
|
884
885
|
return emitEntryAction(_lineNum, ctx, {
|
|
885
886
|
action: 'skipped-abandoned',
|
|
@@ -473,12 +473,19 @@ export function generateSessionNote(entry, options = {}) {
|
|
|
473
473
|
// that "repaired" that one too would be inventing a measurement.
|
|
474
474
|
const waveRows = waves
|
|
475
475
|
.map((w) => {
|
|
476
|
+
// #1276: lifecycle-only records omit every older count alias. Started
|
|
477
|
+
// measures participation; completed/planned-only counts retain their
|
|
478
|
+
// labels so a plan or a completion count never claims dispatch coverage.
|
|
479
|
+
const completedAgents = waveCount(w.agent_count_completed);
|
|
480
|
+
const plannedAgents = waveCount(w.agent_count_planned);
|
|
476
481
|
const agentsCell =
|
|
477
482
|
waveCount(w.agent_count) ??
|
|
478
483
|
waveCount(w.agents) ??
|
|
479
484
|
waveCount(w.agents_dispatched) ??
|
|
480
485
|
waveCount(w.dispatched) ??
|
|
481
|
-
|
|
486
|
+
waveCount(w.agent_count_started) ??
|
|
487
|
+
(completedAgents === undefined ? undefined : `${completedAgents} completed`) ??
|
|
488
|
+
(plannedAgents === undefined ? MISSING_CELL : `${plannedAgents} planned`);
|
|
482
489
|
const filesCell = waveCount(w.files_changed) ?? waveCount(w.files) ?? MISSING_CELL;
|
|
483
490
|
const qualityCell =
|
|
484
491
|
w.quality ?? w.quality_check ?? w.status ?? w.result ?? w.outcome ?? MISSING_CELL;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* narrative-mirror.mjs — Durable per-repo narrative mirror (Epic #673 Phase 1, #675).
|
|
3
3
|
*
|
|
4
|
-
* At session-end, extract from a repo's
|
|
4
|
+
* At session-end, extract from a repo's active `STATE.md` the DURABLE narrative —
|
|
5
5
|
* `## Wave History`, `## Deviations`, `## What Not To Retry`, plus the mission-status
|
|
6
6
|
* rollup — and idempotently mirror it into a generator-owned per-repo vault file, so a
|
|
7
7
|
* reviewer or stand-in can read PER REPO what was done, what failed, and what not to
|
|
@@ -31,7 +31,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
31
31
|
import path from 'node:path';
|
|
32
32
|
|
|
33
33
|
import { emitEvent, sessionAttribution } from '../events.mjs';
|
|
34
|
-
import { parseStateMd, parseMissionStatus } from '../state-md.mjs';
|
|
34
|
+
import { parseStateMd, parseMissionStatus, resolveStateMdPath } from '../state-md.mjs';
|
|
35
35
|
import {
|
|
36
36
|
parseFrontmatter,
|
|
37
37
|
toDate,
|
|
@@ -328,7 +328,7 @@ export function renderNarrative(opts) {
|
|
|
328
328
|
lines.push(`# ${repo ?? 'unknown'} — Session Narrative`);
|
|
329
329
|
lines.push('');
|
|
330
330
|
lines.push(
|
|
331
|
-
'> Durable per-repo narrative mirrored from
|
|
331
|
+
'> Durable per-repo narrative mirrored from the repo\'s active `STATE.md` (Epic #673). ' +
|
|
332
332
|
'What was done, what failed, and what not to retry — readable without opening the repo.',
|
|
333
333
|
);
|
|
334
334
|
lines.push('');
|
|
@@ -909,7 +909,7 @@ async function runNarrativeMirror(opts) {
|
|
|
909
909
|
}
|
|
910
910
|
|
|
911
911
|
// Read STATE.md (best-effort; absent STATE.md → nothing to mirror).
|
|
912
|
-
const stateMdPath =
|
|
912
|
+
const stateMdPath = resolveStateMdPath(repoRoot);
|
|
913
913
|
let stateContents;
|
|
914
914
|
try {
|
|
915
915
|
stateContents = await readFile(stateMdPath, 'utf8');
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { probe, evaluate } from './resource-probe.mjs';
|
|
11
|
+
import { resolveAgentCap } from './session-shape.mjs';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Wave roles that may run on a declared remote host, mapped to the
|
|
@@ -194,7 +195,10 @@ async function applyOffloadDecision(result, opts) {
|
|
|
194
195
|
// ceiling, which is a property of the REPO and holds wherever the wave runs.
|
|
195
196
|
// Restoring plannedAgents unconditionally here would let a heavy repo exceed
|
|
196
197
|
// its own cap by way of a remote host.
|
|
197
|
-
const cap =
|
|
198
|
+
const cap =
|
|
199
|
+
config?.['heavy-repo'] === true
|
|
200
|
+
? resolveAgentCap(config['agents-per-wave'], MODE_BLIND_SESSION_TYPE)
|
|
201
|
+
: null;
|
|
198
202
|
const agents = cap === null ? plannedAgents : Math.min(plannedAgents, cap);
|
|
199
203
|
|
|
200
204
|
return {
|
|
@@ -210,42 +214,34 @@ async function applyOffloadDecision(result, opts) {
|
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
/**
|
|
213
|
-
*
|
|
214
|
-
* `null` when no cap should apply.
|
|
217
|
+
* The session type this module resolves `agents-per-wave` FOR: none.
|
|
215
218
|
*
|
|
216
219
|
* `_coerceInteger()` (scripts/lib/config/coercers.mjs) parses the documented
|
|
217
220
|
* HR-003 parenthetical-override syntax — `agents-per-wave: 4 (deep: 18)` —
|
|
218
|
-
* into an OBJECT `{ default: 4, deep: 18 }`, not a plain number
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* HR-004 exactly where it matters most (a heavy repo that also runs deep
|
|
222
|
-
* sessions).
|
|
221
|
+
* into an OBJECT `{ default: 4, deep: 18 }`, not a plain number, so the cap
|
|
222
|
+
* must be resolved rather than type-guarded (a bare `typeof cap !== 'number'`
|
|
223
|
+
* check no-ops HR-004 for every repo using the override syntax).
|
|
223
224
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* `<default> (mode: <higher-ceiling>)`, i.e. `default` is the MORE
|
|
229
|
-
*
|
|
230
|
-
*
|
|
225
|
+
* This gate has NO session mode in scope — `waveRole` is a wave role (e.g.
|
|
226
|
+
* "Impl-Core"), never a session mode (e.g. "deep") — so it deliberately asks
|
|
227
|
+
* `resolveAgentCap` for no type and takes the `.default` fallback. That is the
|
|
228
|
+
* conservative reading: the HR-003 convention writes the override as
|
|
229
|
+
* `<default> (mode: <higher-ceiling>)`, i.e. `default` is the MORE restrictive
|
|
230
|
+
* of the pair. Under-applying a looser mode-specific ceiling is safe; passing a
|
|
231
|
+
* guessed `'deep'` here would silently RAISE a heavy repo's ceiling to the deep
|
|
232
|
+
* override, which is the bug this named constant exists to make un-writable.
|
|
231
233
|
*
|
|
232
|
-
* @
|
|
233
|
-
* @returns {number|null}
|
|
234
|
+
* @type {undefined}
|
|
234
235
|
*/
|
|
235
|
-
|
|
236
|
-
if (typeof cap === 'number') return Number.isFinite(cap) ? cap : null;
|
|
237
|
-
if (cap !== null && typeof cap === 'object' && !Array.isArray(cap)) {
|
|
238
|
-
const def = cap.default;
|
|
239
|
-
return typeof def === 'number' && Number.isFinite(def) ? def : null;
|
|
240
|
-
}
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
236
|
+
const MODE_BLIND_SESSION_TYPE = undefined;
|
|
243
237
|
|
|
244
238
|
/**
|
|
245
239
|
* HR-003/HR-004 heavy-repo preflight ceiling (baseline #60). A STATIC cap
|
|
246
240
|
* independent of the live resource-probe verdict: when `config['heavy-repo']`
|
|
247
241
|
* is `true`, `agents` is clamped to at most `config['agents-per-wave']`
|
|
248
|
-
* (resolved via
|
|
242
|
+
* (resolved via `resolveAgentCap` from `./session-shape.mjs` — the ONE exported
|
|
243
|
+
* copy of that resolver — to handle the object-override shape; see
|
|
244
|
+
* {@link MODE_BLIND_SESSION_TYPE} for why no session type is passed).
|
|
249
245
|
* More-restrictive-wins — this only ever LOWERS `agents`, never raises it
|
|
250
246
|
* above what the resource-driven rules already decided (e.g. a
|
|
251
247
|
* coordinator-direct 0 stays 0).
|
|
@@ -257,7 +253,7 @@ function resolveApwCap(cap) {
|
|
|
257
253
|
function applyHeavyRepoCap(result, opts) {
|
|
258
254
|
const { config } = opts;
|
|
259
255
|
if (!config || config['heavy-repo'] !== true) return result;
|
|
260
|
-
const cap =
|
|
256
|
+
const cap = resolveAgentCap(config['agents-per-wave'], MODE_BLIND_SESSION_TYPE);
|
|
261
257
|
if (cap === null) return result;
|
|
262
258
|
if (result.agents <= cap) return result; // already within the ceiling — never raise
|
|
263
259
|
return {
|
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
* Learning: coordinator-over-worktree-on-shared-files caused back-to-back regressions.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// SSOT for the session-type enum. It used to be RE-DECLARED here while this very comment named
|
|
7
|
+
// constants.mjs as the source of truth — two literals that agreed only by luck (and already
|
|
8
|
+
// differed in member order). The direction of the dependency is forced: constants.mjs declares
|
|
9
|
+
// itself a leaf ("no imports from siblings or parent") and mentions wave-sizing.mjs only in prose,
|
|
10
|
+
// so this module imports, never the reverse; no cycle exists in either direction.
|
|
11
|
+
//
|
|
12
|
+
// 'unknown' is a member because scripts/lib/session-close-backfill.mjs writes it for records whose
|
|
7
13
|
// type it could not measure (4.0.0). It is NOT a mode anyone selects — isolation resolves as for
|
|
8
|
-
// 'deep' (the conservative end).
|
|
9
|
-
|
|
14
|
+
// 'deep' (the conservative end).
|
|
15
|
+
import { VALID_SESSION_TYPES } from './session-schema/constants.mjs';
|
|
16
|
+
|
|
10
17
|
const VALID_COLLISION_RISKS = ['low', 'medium', 'high'];
|
|
11
18
|
const VALID_CONFIG_ISOLATIONS = ['auto', 'worktree', 'none'];
|
|
12
19
|
const VALID_ENFORCEMENTS = ['strict', 'warn', 'off'];
|
|
@@ -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
|
}
|
|
@@ -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
|
// ---------------------------------------------------------------------------
|