ruvnet-brain 4.3.1 → 4.3.8
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/README.md +12 -13
- package/bin/install.mjs +18 -8
- package/console/app.js +4 -4
- package/data/model-catalog.json +3 -3
- package/kb/verify-citation.mjs +32 -5
- package/package.json +13 -9
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/mcp/managed-cli-interface.mjs +4 -2
- package/plugin/scripts/codex-hook-adapter.mjs +12 -7
- package/plugin/scripts/codex-hook-events.mjs +39 -0
- package/plugin/scripts/coverage-integrity.mjs +4 -4
- package/plugin/scripts/hook-shim.mjs +9 -7
- package/plugin/scripts/learn-capture.sh +20 -1
- package/plugin/scripts/learn-flush.mjs +7 -1
- package/plugin/scripts/lesson-gate.mjs +17 -2
- package/plugin/scripts/lesson-hooks.sh +17 -2
- package/plugin/scripts/session-start-core.mjs +47 -11
- package/plugin/skills/release-proof/SKILL.md +19 -10
- package/plugin/skills/release-proof/references/receipt-contract.md +18 -8
- package/scripts/brain-stamp-resolve.mjs +22 -0
- package/scripts/brain-stamp.mjs +5 -3
- package/scripts/build-bundle.mjs +41 -4
- package/scripts/candidate-host-evidence.mjs +9 -2
- package/scripts/ci/step-watchdog.mjs +86 -0
- package/scripts/convergence-manifest.mjs +66 -0
- package/scripts/distill-project.mjs +22 -4
- package/scripts/eval-brain.mjs +13 -3
- package/scripts/execution-policy.mjs +111 -0
- package/scripts/execution-preflight.mjs +18 -0
- package/scripts/git-hooks/pre-push +16 -0
- package/scripts/health-repair.mjs +5 -1
- package/scripts/host-install-matrix.mjs +247 -40
- package/scripts/integration-evidence.mjs +73 -0
- package/scripts/nightly-wrapper.sh +4 -3
- package/scripts/onboarding-console.mjs +3 -3
- package/scripts/prepublication-evidence.mjs +174 -0
- package/scripts/protected-release-invocation.mjs +32 -4
- package/scripts/public-verification-aggregate.mjs +27 -54
- package/scripts/public-verification-inputs.mjs +5 -3
- package/scripts/qa-runner.mjs +72 -0
- package/scripts/qe/agentic-qe-4.3.mjs +195 -0
- package/scripts/qe/aggregate-4.3.mjs +42 -0
- package/scripts/record-lesson.mjs +28 -6
- package/scripts/refresh-model-catalog.mjs +32 -3
- package/scripts/release-authority.mjs +19 -7
- package/scripts/release-convergence-watchdog.mjs +10 -22
- package/scripts/release-projection.mjs +76 -32
- package/scripts/release-transaction.mjs +1 -1
- package/scripts/release.mjs +9 -15
- package/scripts/retrieval-canary.mjs +14 -6
- package/scripts/set-version.mjs +31 -0
- package/scripts/source-coverage.mjs +11 -2
- package/scripts/staged-host-verifier.mjs +2 -2
- package/scripts/verify-channels.mjs +1 -1
- package/scripts/wired-check.mjs +1 -0
|
@@ -565,7 +565,7 @@ export async function finalizeReleaseTransaction({
|
|
|
565
565
|
lanes: aggregate.metrics.leaves,
|
|
566
566
|
recallAt10: aggregate.metrics.recallAt10,
|
|
567
567
|
deltaCitationRate: aggregate.metrics.deltaCitationRate,
|
|
568
|
-
reviewReceipts: aggregate.reviews,
|
|
568
|
+
reviewReceipts: aggregate.reviews || [],
|
|
569
569
|
},
|
|
570
570
|
npm: snapshot.npm,
|
|
571
571
|
github: snapshot.github,
|
package/scripts/release.mjs
CHANGED
|
@@ -29,13 +29,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
29
29
|
import { spawnSync, execFileSync } from 'node:child_process';
|
|
30
30
|
import fs from 'node:fs';
|
|
31
31
|
import crypto from 'node:crypto';
|
|
32
|
-
import { validateProtectedPublishInvocation } from './protected-release-invocation.mjs';
|
|
32
|
+
import { validateProtectedPublishEnvironment, validateProtectedPublishInvocation } from './protected-release-invocation.mjs';
|
|
33
33
|
import { runReleaseTransaction } from './release-transaction.mjs';
|
|
34
34
|
import { liveReleaseProvider } from './release-transaction-provider.mjs';
|
|
35
|
+
import { stagedHostVerifier } from './staged-host-verifier.mjs';
|
|
35
36
|
import { verifyPayload } from './release-payload.mjs';
|
|
36
37
|
|
|
37
38
|
const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
38
|
-
const IS_MAIN = path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url);
|
|
39
39
|
const PUBLISH = process.argv.includes('--publish');
|
|
40
40
|
const CORPUS_SEED = process.argv.includes('--corpus-seed');
|
|
41
41
|
let protectedCandidate = null;
|
|
@@ -87,9 +87,9 @@ export function runProtectedCorpusSeed({
|
|
|
87
87
|
root = ROOT,
|
|
88
88
|
run = (command, args, options) => spawnSync(command, args, { encoding: 'utf8', ...options }),
|
|
89
89
|
} = {}) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
corpusFailure('
|
|
90
|
+
const environmentFailures = validateProtectedPublishEnvironment(env);
|
|
91
|
+
if (environmentFailures.length) {
|
|
92
|
+
corpusFailure(environmentFailures.join('; '));
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const tag = cliArg(argv, '--corpus-tag');
|
|
@@ -190,7 +190,7 @@ export function runProtectedCorpusSeed({
|
|
|
190
190
|
return { tag, target, repository: repo, archiveSha256, receiptSha256: sha256File(receiptFile) };
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
if (
|
|
193
|
+
if (CORPUS_SEED) {
|
|
194
194
|
try {
|
|
195
195
|
const result = runProtectedCorpusSeed();
|
|
196
196
|
console.log(JSON.stringify({ ok: true, mode: 'corpus-seed', ...result }, null, 2));
|
|
@@ -198,7 +198,7 @@ if (IS_MAIN && CORPUS_SEED) {
|
|
|
198
198
|
console.error(error.message);
|
|
199
199
|
process.exitCode = 1;
|
|
200
200
|
}
|
|
201
|
-
} else
|
|
201
|
+
} else {
|
|
202
202
|
|
|
203
203
|
console.log(`\n${c.b('RuvNet Brain — release / definition-of-done')} ${c.dim('· ' + (PUBLISH ? 'PUBLISH' : 'check-only') + ' · shipping ' + V())}\n`);
|
|
204
204
|
|
|
@@ -373,9 +373,6 @@ if (PUBLISH) {
|
|
|
373
373
|
corpusSeedSha256: crypto.createHash('sha256').update(fs.readFileSync(assets.corpusSeedPath)).digest('hex'),
|
|
374
374
|
generationLedgerSha256: crypto.createHash('sha256').update(fs.readFileSync(assets.generationLedgerPath)).digest('hex'),
|
|
375
375
|
};
|
|
376
|
-
const identityPath = path.resolve(ROOT, process.env.RUVNET_RELEASE_IDENTITY || 'release-evidence/release-identity.json');
|
|
377
|
-
if (fs.existsSync(identityPath)) throw new Error(`refusing to overwrite release identity: ${identityPath}`);
|
|
378
|
-
fs.writeFileSync(identityPath, `${JSON.stringify(identity, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
379
376
|
const privatePem = process.env.RUVNET_SIGNING_KEY;
|
|
380
377
|
if (!privatePem) throw new Error('RUVNET_SIGNING_KEY is required for signed transaction receipts');
|
|
381
378
|
const finalReceipt = await runReleaseTransaction({
|
|
@@ -386,12 +383,9 @@ if (PUBLISH) {
|
|
|
386
383
|
}),
|
|
387
384
|
privateKey: crypto.createPrivateKey(privatePem),
|
|
388
385
|
publicKey: crypto.createPublicKey(fs.readFileSync(path.join(ROOT, 'keys/ruvnet-brain-signing.pub.pem'), 'utf8')),
|
|
386
|
+
hostVerifier: stagedHostVerifier({ assets, identity }),
|
|
389
387
|
});
|
|
390
388
|
if (finalReceipt.state !== 'channels-converged') throw new Error(`release transaction stopped at ${finalReceipt.state}`);
|
|
391
|
-
const channelReceiptPath = path.resolve(ROOT,
|
|
392
|
-
process.env.RUVNET_CHANNEL_RECEIPT || 'release-evidence/channels-converged-receipt.json');
|
|
393
|
-
if (fs.existsSync(channelReceiptPath)) throw new Error(`refusing to overwrite channel receipt: ${channelReceiptPath}`);
|
|
394
|
-
fs.writeFileSync(channelReceiptPath, `${JSON.stringify(finalReceipt, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
395
389
|
} else {
|
|
396
390
|
step('D', 'remote staged release transaction — SKIPPED (check-only; pass --publish to publish)');
|
|
397
391
|
}
|
|
@@ -404,7 +398,7 @@ if (!PUBLISH) {
|
|
|
404
398
|
}
|
|
405
399
|
|
|
406
400
|
if (PUBLISH) {
|
|
407
|
-
console.log(`\n${c.y(c.b('PUBLISHED, NOT VERIFIED'))}
|
|
401
|
+
console.log(`\n${c.y(c.b('PUBLISHED, NOT VERIFIED'))}`);
|
|
408
402
|
} else {
|
|
409
403
|
console.log(`\n${c.g(c.b('✓✓✓ PREFLIGHT PASS — NOT PUBLISHED'))} — the committed candidate passed every check-only gate.\n`);
|
|
410
404
|
}
|
|
@@ -108,14 +108,17 @@ export function validateRetrievalQueryEvidence(evidence) {
|
|
|
108
108
|
return evidence;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
export function verifyQueryOracleSource(queryEvidence, candidateSourceSha, {
|
|
111
|
+
export function verifyQueryOracleSource(queryEvidence, candidateSourceSha, {
|
|
112
|
+
cwd = process.cwd(), run = spawnSync, allowSquashedSource = false,
|
|
113
|
+
} = {}) {
|
|
112
114
|
validateRetrievalQueryEvidence(queryEvidence);
|
|
113
115
|
if (!HEX40.test(String(candidateSourceSha || '')) || queryEvidence.sourceCommit === candidateSourceSha) {
|
|
114
116
|
throw new Error('query oracle source identity is invalid');
|
|
115
117
|
}
|
|
116
118
|
const ancestor = run('git', ['merge-base', '--is-ancestor', queryEvidence.sourceCommit, candidateSourceSha],
|
|
117
119
|
{ cwd, encoding: 'utf8' });
|
|
118
|
-
|
|
120
|
+
const strictAncestor = !ancestor.error && ancestor.status === 0;
|
|
121
|
+
if (!strictAncestor && !allowSquashedSource) throw new Error('query oracle source is not a strict candidate ancestor');
|
|
119
122
|
const shown = run('git', ['show', `${queryEvidence.sourceCommit}:${queryEvidence.sourcePath}`],
|
|
120
123
|
{ cwd, encoding: null, maxBuffer: 64 * 1024 * 1024 });
|
|
121
124
|
let sourcePayload;
|
|
@@ -184,7 +187,8 @@ export function validateRetrievalCanaryPlan(plan) {
|
|
|
184
187
|
throw new Error('oracle denominator differs from eligible coverage');
|
|
185
188
|
}
|
|
186
189
|
if (new Set(ids).size !== ids.length) throw new Error('retrieval canary plan has duplicate case ids');
|
|
187
|
-
if (!plan.cases.some(({ cohort }) => cohort === 'delta')
|
|
190
|
+
if ((!plan.cases.some(({ cohort }) => cohort === 'delta') && plan.noDelta !== true)
|
|
191
|
+
|| !plan.cases.some(({ cohort }) => cohort === 'legacy')) {
|
|
188
192
|
throw new Error('retrieval canary plan requires delta and legacy cohorts');
|
|
189
193
|
}
|
|
190
194
|
for (const row of plan.cases) {
|
|
@@ -236,7 +240,7 @@ export function validatePlanAgainstCoverage(plan, coverage, { allowObservedBasel
|
|
|
236
240
|
return plan;
|
|
237
241
|
}
|
|
238
242
|
export function buildRetrievalCanaryPlan({ coverage, baseline, candidate, coverageIdentity = null, queryEvidence, assetsDir = '.',
|
|
239
|
-
readPassages = defaultReadPassages, legacySampleSize } = {}) {
|
|
243
|
+
readPassages = defaultReadPassages, legacySampleSize, allowNoDelta = false } = {}) {
|
|
240
244
|
const checked = validateCoverageLedger(coverage);
|
|
241
245
|
if (!checked.valid) throw new Error(`coverage ledger is invalid: ${checked.failures.join('; ')}`);
|
|
242
246
|
const coverageGeneration = coverage.kind === 'ruvnet-brain-release-coverage'
|
|
@@ -294,12 +298,15 @@ export function buildRetrievalCanaryPlan({ coverage, baseline, candidate, covera
|
|
|
294
298
|
const eligibleStores = ordered(eligible.map(storeOf));
|
|
295
299
|
if (queryEvidence.queryStoreSetSha256 !== setDigest(eligibleStores)
|
|
296
300
|
|| canonicalJson(ordered(Object.keys(queryEvidence.queries))) !== canonicalJson(eligibleStores)) {
|
|
297
|
-
|
|
301
|
+
const oracleStores = new Set(Object.keys(queryEvidence.queries));
|
|
302
|
+
const missing = eligibleStores.filter((store) => !oracleStores.has(store));
|
|
303
|
+
const extra = [...oracleStores].filter((store) => !eligibleStores.includes(store)).sort();
|
|
304
|
+
throw new Error(`independent query oracle does not cover the exact eligible store set (eligible=${eligibleStores.length}, oracle=${oracleStores.size}, missing=${missing.join(',') || 'none'}, extra=${extra.join(',') || 'none'})`);
|
|
298
305
|
}
|
|
299
306
|
const baselineStores = new Set(baseline.stores.map((name) => String(name).toLowerCase()));
|
|
300
307
|
const delta = eligible.filter((row) => !baselineStores.has(storeOf(row)));
|
|
301
308
|
const legacyPool = eligible.filter((row) => baselineStores.has(storeOf(row)));
|
|
302
|
-
if (!delta.length || !legacyPool.length) throw new Error('coverage does not establish both failed-seed delta and legacy cohorts');
|
|
309
|
+
if ((!delta.length && !allowNoDelta) || !legacyPool.length) throw new Error('coverage does not establish both failed-seed delta and legacy cohorts');
|
|
303
310
|
const passages = new Map(eligible.map((row) => [storeOf(row), readPassages(assetsDir, storeOf(row))]));
|
|
304
311
|
const rankedLegacy = legacyPool.map((row) => ({ row, count: passages.get(storeOf(row)).length }))
|
|
305
312
|
.sort((a, b) => a.count - b.count || storeOf(a.row).localeCompare(storeOf(b.row)));
|
|
@@ -376,6 +383,7 @@ export function buildRetrievalCanaryPlan({ coverage, baseline, candidate, covera
|
|
|
376
383
|
queryStoreSetSha256: queryEvidence.queryStoreSetSha256,
|
|
377
384
|
sourceCommit: queryEvidence.sourceCommit, sourceBlobSha256: queryEvidence.sourceBlobSha256,
|
|
378
385
|
evidence: structuredClone(queryEvidence) },
|
|
386
|
+
noDelta: delta.length === 0,
|
|
379
387
|
cohorts: { delta: delta.length, legacy: legacy.length,
|
|
380
388
|
legacyStrata: [...strata.keys()].sort().map((stratum) => ({ stratum,
|
|
381
389
|
population: rankedLegacy.filter((_entry, index) => Math.min(3, Math.floor(index * 4 / rankedLegacy.length)) === stratum).length,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// One release version command. The plugin manifest is the only editable source of truth;
|
|
3
|
+
// propagation and verification happen in the same process so a package-only bump cannot drift.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const version = process.argv[2];
|
|
11
|
+
if (!version || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
12
|
+
console.error('usage: npm run version:set -- X.Y.Z[-channel]');
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
const manifest = path.join(root, 'plugin/.claude-plugin/plugin.json');
|
|
16
|
+
const doc = JSON.parse(fs.readFileSync(manifest, 'utf8'));
|
|
17
|
+
const previous = doc.version;
|
|
18
|
+
doc.version = version;
|
|
19
|
+
fs.writeFileSync(manifest, `${JSON.stringify(doc, null, 2)}\n`);
|
|
20
|
+
const sync = spawnSync(process.execPath, [path.join(root, 'scripts/sync-version.mjs')], {
|
|
21
|
+
cwd: root, encoding: 'utf8', stdio: 'inherit',
|
|
22
|
+
});
|
|
23
|
+
if (sync.status !== 0) {
|
|
24
|
+
console.error(`[version:set] propagation failed; source changed ${previous} -> ${version}, run version:sync after fixing the reported error`);
|
|
25
|
+
process.exit(sync.status || 1);
|
|
26
|
+
}
|
|
27
|
+
const check = spawnSync(process.execPath, [path.join(root, 'scripts/sync-version.mjs'), '--check'], {
|
|
28
|
+
cwd: root, encoding: 'utf8', stdio: 'inherit',
|
|
29
|
+
});
|
|
30
|
+
if (check.status !== 0) process.exit(check.status || 1);
|
|
31
|
+
console.log(`[version:set] ${previous} -> ${version}; all release surfaces agree`);
|
|
@@ -6,6 +6,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { readRvfGenerations, sha256File } from './rvf-generation.mjs';
|
|
8
8
|
import { canonicalJson, coverageGenerationFor, digest, validateGistAggregateReceipt } from './coverage-integrity.mjs';
|
|
9
|
+
import { repositoryNames } from '../kb/card-lane.mjs';
|
|
9
10
|
|
|
10
11
|
export { canonicalJson, digest } from './coverage-integrity.mjs';
|
|
11
12
|
|
|
@@ -272,7 +273,15 @@ export function classifyGist(gist, evidence) {
|
|
|
272
273
|
};
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
|
|
276
|
+
// ALIAS-AWARE, BECAUSE THE ROUTER IS. `cardStores` holds card HEADINGS; a store reachable only
|
|
277
|
+
// under an alias (e.g. `metaharness` via its `## agent-harness-generator` card) has no heading of
|
|
278
|
+
// its own, so a direct `cardStores.has(store)` reports it card-absent even though the router finds
|
|
279
|
+
// it. `kb/store-root.mjs`'s `darkStores()` and `scripts/brain-score.mjs`'s `readCoverage()` were
|
|
280
|
+
// already fixed for this exact conflation (ADR-058; metaharness is the standing example in both);
|
|
281
|
+
// this sibling computation, which feeds the committed `data/source-coverage.json` and
|
|
282
|
+
// `docs/RUVNET-COVERAGE.md` (ADR-069), never received it. `repositoryNames` is the router's own
|
|
283
|
+
// resolver (kb/card-lane.mjs), imported rather than reimplemented.
|
|
284
|
+
export function artifactEvidence(kbDir, ledger, cardStores, name) {
|
|
276
285
|
const store = storeName(name);
|
|
277
286
|
const receipt = Object.entries(ledger.stores).find(([key]) => key.toLowerCase() === store)?.[1] || null;
|
|
278
287
|
const rvfFile = receipt?.file || `${store}.big.rvf`;
|
|
@@ -284,7 +293,7 @@ function artifactEvidence(kbDir, ledger, cardStores, name) {
|
|
|
284
293
|
bytesVerified: Boolean(rvfPresent && receipt?.sha256 && sha256File(rvfPath) === receipt.sha256),
|
|
285
294
|
passagesPresent: fs.existsSync(path.join(kbDir, `${store}.passages.jsonl`))
|
|
286
295
|
&& fs.statSync(path.join(kbDir, `${store}.passages.jsonl`)).size > 0,
|
|
287
|
-
cardPresent: cardStores.has(
|
|
296
|
+
cardPresent: repositoryNames(store, kbDir).some((alias) => cardStores.has(storeName(alias))),
|
|
288
297
|
};
|
|
289
298
|
}
|
|
290
299
|
|
|
@@ -3,7 +3,7 @@ import crypto from 'node:crypto';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { runHostMatrixAsync } from './host-install-matrix.mjs';
|
|
7
7
|
|
|
8
8
|
const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
9
9
|
const locate = (name) => {
|
|
@@ -50,7 +50,7 @@ export function stagedHostVerifier({ assets, identity }) {
|
|
|
50
50
|
// install shape than the one that runs after publication — so the two halves of a release
|
|
51
51
|
// were judging different things and could not be compared. Only the STAGED-vs-PUBLISHED
|
|
52
52
|
// difference is real, and it is now a named variant rather than a second implementation.
|
|
53
|
-
const matrix =
|
|
53
|
+
const matrix = await runHostMatrixAsync({
|
|
54
54
|
packageRoot: prepared.packageRoot,
|
|
55
55
|
version: identity.version,
|
|
56
56
|
variant: 'staged',
|
|
@@ -97,7 +97,7 @@ async function run() {
|
|
|
97
97
|
}
|
|
98
98
|
check('npm latest matches shipping version', ok,
|
|
99
99
|
ok ? `npm latest=${latest} · shipping=${V}`
|
|
100
|
-
: `npm latest=${latest || '(view failed: ' + lastErr + ')'} · shipping=${V}${latest && latest !== V ? ' ← STILL STALE after ~' + (ATTEMPTS * 5) + 's:
|
|
100
|
+
: `npm latest=${latest || '(view failed: ' + lastErr + ')'} · shipping=${V}${latest && latest !== V ? ' ← STILL STALE after ~' + (ATTEMPTS * 5) + 's: dispatch the protected-release workflow for the exact candidate SHA' : ''}`);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// 2) the self-update manifest URL actually resolves (the 404 that stranded users)
|
package/scripts/wired-check.mjs
CHANGED
|
@@ -95,6 +95,7 @@ const STANDALONE = [
|
|
|
95
95
|
+ 'RVFs to RVF-GENERATIONS.json and optionally prunes legacy sidecars; build-bundle.mjs consumes '
|
|
96
96
|
+ 'and validates the resulting manifest, so scheduling this destructive migration would be wrong'],
|
|
97
97
|
['release', 'the ship path, run by a human'],
|
|
98
|
+
['execution-preflight', 'external orchestration boundary — invoked by the host before consequential Ruflo/Codex execution; no in-repo caller exists because the host supplies the live Brain and AgentDB receipts'],
|
|
98
99
|
['fix-workstream', 'session-supervised coordination CLI run explicitly by the integration owner or an '
|
|
99
100
|
+ 'isolated writing agent to start and hand off a fix lane. Scheduling it would violate its safety '
|
|
100
101
|
+ 'boundary: it prepares evidence but never merges, pushes, publishes, deletes, or cleans worktrees'],
|