arkgate 4.8.20 → 4.8.22
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/CHANGELOG.md +172 -5
- package/README.md +107 -117
- package/bin/ark-check-runtime.mjs +3 -4
- package/bin/ark-layer-match.mjs +110 -18
- package/bin/lib/analysis-engine.mjs +13 -6
- package/bin/lib/config-contract.mjs +5 -0
- package/bin/lib/diagnostic-catalog.mjs +1 -0
- package/bin/lib/doctor-advisories.mjs +20 -0
- package/bin/lib/doctor-human.mjs +11 -9
- package/bin/lib/doctor-plan.mjs +15 -14
- package/bin/lib/first-run-help.mjs +7 -1
- package/bin/lib/html-report-advisories.mjs +8 -0
- package/bin/lib/html-report-depth.mjs +9 -8
- package/bin/lib/invariant-coverage.mjs +417 -72
- package/bin/lib/managed-upgrade.mjs +59 -14
- package/bin/lib/pilot-loop.mjs +183 -37
- package/bin/lib/upgrade-behavior-changes.mjs +83 -0
- package/bin/lib/upgrade-command.mjs +69 -31
- package/bin/lib/upgrade-outcome.mjs +359 -0
- package/bin/lib/violations.mjs +109 -0
- package/dist/{configTypes-VVknqcTJ.d.ts → configTypes-CTELo87-.d.ts} +7 -0
- package/dist/{diagnosticCatalog-BPYWNyJR.d.ts → diagnosticCatalog-WD2TPSwZ.d.ts} +78 -5
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.d.ts +26 -3
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +32 -25
- package/dist/index.d.ts +9 -4
- package/dist/index.js +33 -26
- package/dist/nestjs/index.cjs +9 -9
- package/dist/nestjs/index.d.ts +3 -3
- package/dist/nestjs/index.js +9 -9
- package/dist/runtime/index.cjs +22 -15
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +22 -15
- package/dist/{types-p_fCp6P-.d.ts → types-fQ9-KrBj.d.ts} +1 -1
- package/dist/{types-Cs-46DRu.d.ts → types-yDNEQ603.d.ts} +1 -1
- package/docs/README.md +5 -3
- package/docs/agent-guide.md +15 -7
- package/docs/brownfield-adoption.md +4 -3
- package/docs/configuration.md +34 -11
- package/docs/diagnostics.md +12 -2
- package/docs/package-surface.md +3 -2
- package/docs/product-voice.md +11 -3
- package/package.json +2 -1
- package/schemas/ark.config.schema.json +8 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +5 -2
- package/templates/agent-skills/ark-architect/SKILL.md +2 -0
- package/templates/agent-skills/ark-autopilot/SKILL.md +6 -4
- package/templates/agent-skills/ark-contract/SKILL.md +2 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explain/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +4 -2
- package/templates/agent-skills/ark-fix/SKILL.md +2 -0
- package/templates/agent-skills/ark-loop/SKILL.md +2 -0
- package/templates/agent-skills/ark-order/SKILL.md +2 -0
- package/templates/agent-skills/ark-place/SKILL.md +2 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +2 -0
- package/templates/agent-skills/ark-think/SKILL.md +2 -0
- package/templates/agent-skills/ark-upgrade/SKILL.md +20 -7
- package/templates/skills/ark-adopt.md +5 -2
- package/templates/skills/ark-architect.md +2 -0
- package/templates/skills/ark-autopilot.md +6 -4
- package/templates/skills/ark-contract.md +2 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explain.md +2 -0
- package/templates/skills/ark-explore.md +4 -2
- package/templates/skills/ark-fix.md +2 -0
- package/templates/skills/ark-loop.md +2 -0
- package/templates/skills/ark-order.md +2 -0
- package/templates/skills/ark-place.md +2 -0
- package/templates/skills/ark-runtime.md +2 -0
- package/templates/skills/ark-think.md +2 -0
- package/templates/skills/ark-upgrade.md +20 -7
|
@@ -8,10 +8,19 @@ import {
|
|
|
8
8
|
formatManagedUpgradeSelfServiceHonesty,
|
|
9
9
|
projectManagedUpgradeSelfServiceHonesty,
|
|
10
10
|
} from './managed-upgrade-honesty.mjs';
|
|
11
|
+
import {
|
|
12
|
+
ABSENT_LOCAL_REASON,
|
|
13
|
+
architectureVerificationDetail,
|
|
14
|
+
architectureVerificationOk,
|
|
15
|
+
blockedAssetReason,
|
|
16
|
+
formatBlockedAssets,
|
|
17
|
+
gitignoredPathSet,
|
|
18
|
+
} from './upgrade-outcome.mjs';
|
|
11
19
|
import {
|
|
12
20
|
buildUpgradeWhatsNewSuggestions,
|
|
13
21
|
formatUpgradeWhatsNewSuggestions,
|
|
14
22
|
} from './upgrade-whats-new.mjs';
|
|
23
|
+
import { arkCommand } from './package-manager.mjs';
|
|
15
24
|
import {
|
|
16
25
|
KNOWN_TOOLS,
|
|
17
26
|
arkPackageVersion,
|
|
@@ -354,9 +363,20 @@ function afterFileContent(asset, currentFile, desiredScoped) {
|
|
|
354
363
|
return Buffer.from(upsertCodexMcpTable(currentFile?.toString('utf8') ?? '', 'ark', desiredScoped));
|
|
355
364
|
}
|
|
356
365
|
|
|
357
|
-
export function classifyManagedAsset({
|
|
366
|
+
export function classifyManagedAsset({
|
|
367
|
+
recorded,
|
|
368
|
+
currentContent,
|
|
369
|
+
targetContent: desired,
|
|
370
|
+
kind,
|
|
371
|
+
gitignored = false,
|
|
372
|
+
}) {
|
|
358
373
|
const targetIdentity = managedContentIdentity(desired, kind);
|
|
359
374
|
if (currentContent == null) {
|
|
375
|
+
// Fresh worktree: a recorded per-machine file (gitignored .mcp.json) is
|
|
376
|
+
// absent by nature. Recreate it. A deleted tracked file still needs consent.
|
|
377
|
+
if (recorded && gitignored === true) {
|
|
378
|
+
return { state: 'absent-local', managed: true, requiresConsent: false };
|
|
379
|
+
}
|
|
360
380
|
return { state: 'missing', managed: true, requiresConsent: Boolean(recorded) };
|
|
361
381
|
}
|
|
362
382
|
const currentIdentity = managedContentIdentity(currentContent, kind);
|
|
@@ -456,6 +476,10 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
456
476
|
const assets = [];
|
|
457
477
|
const nextEntries = [];
|
|
458
478
|
const selectedPaths = new Set();
|
|
479
|
+
const ignoredPaths = gitignoredPathSet(
|
|
480
|
+
resolvedRoot,
|
|
481
|
+
catalog.assets.map((asset) => asset.relativePath)
|
|
482
|
+
);
|
|
459
483
|
|
|
460
484
|
for (const catalogAsset of catalog.assets) {
|
|
461
485
|
selectedPaths.add(catalogAsset.relativePath);
|
|
@@ -472,6 +496,7 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
472
496
|
currentContent: currentScoped,
|
|
473
497
|
targetContent: desiredScoped,
|
|
474
498
|
kind: catalogAsset.kind,
|
|
499
|
+
gitignored: ignoredPaths.has(catalogAsset.relativePath),
|
|
475
500
|
});
|
|
476
501
|
const accepted = options.acceptConflicts === true;
|
|
477
502
|
// FX04: --refresh-skills opt-in rewrites customized *skill* assets to package
|
|
@@ -496,11 +521,17 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
496
521
|
}).reason === 'stamp-refresh';
|
|
497
522
|
const canApply =
|
|
498
523
|
classified.state === 'stale' ||
|
|
524
|
+
classified.state === 'absent-local' ||
|
|
499
525
|
skillRefresh ||
|
|
500
526
|
stampRefresh ||
|
|
501
527
|
(classified.state === 'missing' && (!recorded || accepted)) ||
|
|
502
528
|
(classified.state === 'conflicted' && accepted);
|
|
503
529
|
const blocked = classified.requiresConsent && !accepted && !skillRefresh;
|
|
530
|
+
const explanation = blocked
|
|
531
|
+
? blockedAssetReason(classified.state)
|
|
532
|
+
: classified.state === 'absent-local'
|
|
533
|
+
? ABSENT_LOCAL_REASON
|
|
534
|
+
: null;
|
|
504
535
|
const desiredFile = afterFileContent(catalogAsset, currentFile, desiredScoped);
|
|
505
536
|
const asset = {
|
|
506
537
|
path: catalogAsset.relativePath,
|
|
@@ -512,7 +543,9 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
512
543
|
? { reason: 'unparsed managed TOML scope preserved' }
|
|
513
544
|
: stampRefresh
|
|
514
545
|
? { reason: 'stamp-refresh' }
|
|
515
|
-
:
|
|
546
|
+
: explanation
|
|
547
|
+
? { reason: explanation }
|
|
548
|
+
: {}),
|
|
516
549
|
action: canApply ? (currentScoped == null ? 'create' : 'update') : 'none',
|
|
517
550
|
willApply: canApply,
|
|
518
551
|
blocked,
|
|
@@ -591,6 +624,16 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
591
624
|
manifestContent,
|
|
592
625
|
};
|
|
593
626
|
plan.planDigest = managedPlanDigest(plan);
|
|
627
|
+
if (summary.blocked > 0) {
|
|
628
|
+
const nextCommand = arkCommand(
|
|
629
|
+
resolvedRoot,
|
|
630
|
+
'arkgate',
|
|
631
|
+
['upgrade', '--apply', '--no-install', '--accept-conflicts', '--plan-digest', plan.planDigest].join(' ')
|
|
632
|
+
);
|
|
633
|
+
for (const asset of assets) {
|
|
634
|
+
if (asset.blocked) asset.nextCommand = nextCommand;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
594
637
|
return plan;
|
|
595
638
|
}
|
|
596
639
|
|
|
@@ -710,16 +753,8 @@ export function buildPostUpgradeChecks(root, options = {}) {
|
|
|
710
753
|
});
|
|
711
754
|
checks.push({
|
|
712
755
|
id: 'architecture-verification',
|
|
713
|
-
ok:
|
|
714
|
-
|
|
715
|
-
? null
|
|
716
|
-
: options.verification?.exitCode === 0,
|
|
717
|
-
detail:
|
|
718
|
-
options.verification?.mode === 'skipped'
|
|
719
|
-
? 'Strict architecture verification was skipped (--no-strict).'
|
|
720
|
-
: options.verification?.exitCode === 0
|
|
721
|
-
? 'Strict-merge architecture verification passed.'
|
|
722
|
-
: `Architecture verification exit ${options.verification?.exitCode ?? 'unknown'}.`,
|
|
756
|
+
ok: architectureVerificationOk(options.verification),
|
|
757
|
+
detail: architectureVerificationDetail(options.verification),
|
|
723
758
|
});
|
|
724
759
|
checks.push({
|
|
725
760
|
id: 'package-version-truth',
|
|
@@ -983,8 +1018,12 @@ export function renderManagedUpgrade(plan, options = {}) {
|
|
|
983
1018
|
);
|
|
984
1019
|
console.log(`Profile: ${plan.profile}; hosts: ${plan.hosts.join(', ') || 'shared only'}.`);
|
|
985
1020
|
for (const asset of plan.assets) {
|
|
986
|
-
const consent = asset.requiresConsent
|
|
987
|
-
|
|
1021
|
+
const consent = asset.requiresConsent
|
|
1022
|
+
? ' (consent required)'
|
|
1023
|
+
: asset.state === 'absent-local'
|
|
1024
|
+
? ' (gitignored; recreated without consent)'
|
|
1025
|
+
: '';
|
|
1026
|
+
console.log(` ${String(asset.state).padEnd(12)} ${asset.path}${consent}`);
|
|
988
1027
|
}
|
|
989
1028
|
const summary = plan.summary;
|
|
990
1029
|
const managedAssets = summary.managedAssets ?? summary.total ?? plan.assets.length;
|
|
@@ -998,6 +1037,11 @@ export function renderManagedUpgrade(plan, options = {}) {
|
|
|
998
1037
|
: '') +
|
|
999
1038
|
`; customized preserved: ${customizedPreserved}; blocked conflicts/deletions: ${blocked}.`
|
|
1000
1039
|
);
|
|
1040
|
+
for (const line of formatBlockedAssets(plan, {
|
|
1041
|
+
refused: options.refused === true || plan.blocked === true,
|
|
1042
|
+
})) {
|
|
1043
|
+
console.log(line);
|
|
1044
|
+
}
|
|
1001
1045
|
const honesty =
|
|
1002
1046
|
plan.selfService ??
|
|
1003
1047
|
projectManagedUpgradeSelfServiceHonesty({
|
|
@@ -1040,6 +1084,7 @@ export function renderManagedUpgrade(plan, options = {}) {
|
|
|
1040
1084
|
);
|
|
1041
1085
|
return;
|
|
1042
1086
|
}
|
|
1087
|
+
if (blocked > 0 && !options.next) return;
|
|
1043
1088
|
console.log(`Planned writes: ${wouldWrite}; blocked conflicts/deletions: ${blocked}.`);
|
|
1044
1089
|
if (options.next) console.log(options.next);
|
|
1045
1090
|
else console.log('Apply the exact preview with: npx arkgate upgrade --apply --no-install');
|
package/bin/lib/pilot-loop.mjs
CHANGED
|
@@ -120,35 +120,39 @@ export function extractionCardFromBet(bet, preferredFiles) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
123
|
+
* Viable pattern bets, best pilot first (same rank as the historical selector).
|
|
124
|
+
* Mechanical-safe claims and non-production god-module paths are dropped.
|
|
124
125
|
* @param {object[] | null | undefined} patternBets
|
|
125
|
-
* @param {
|
|
126
|
-
* @returns {null | ReturnType<typeof extractionCardFromBet>}
|
|
126
|
+
* @param {object[] | null | undefined} [designSmells]
|
|
127
127
|
*/
|
|
128
|
-
|
|
128
|
+
function rankedPatternBets(patternBets, designSmells) {
|
|
129
129
|
let bets = Array.isArray(patternBets) ? [...patternBets] : [];
|
|
130
|
-
if (bets.length === 0 && Array.isArray(
|
|
131
|
-
bets = buildPatternBetsFromSmells(
|
|
130
|
+
if (bets.length === 0 && Array.isArray(designSmells) && designSmells.length) {
|
|
131
|
+
bets = buildPatternBetsFromSmells(designSmells);
|
|
132
132
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let best = null;
|
|
136
|
-
let bestScore = -Infinity;
|
|
133
|
+
const ranked = [];
|
|
137
134
|
for (let i = 0; i < bets.length; i++) {
|
|
138
135
|
const bet = bets[i];
|
|
139
136
|
if (!bet || bet.neverMechanicalSafe === false) continue;
|
|
140
|
-
// Skip anything that claims mechanical-safe (honesty).
|
|
141
137
|
if (bet.class === 'mechanical-safe') continue;
|
|
142
138
|
const files = pilotFilesForBet(bet);
|
|
143
139
|
if (files === null) continue;
|
|
144
|
-
|
|
145
|
-
if (sc > bestScore) {
|
|
146
|
-
bestScore = sc;
|
|
147
|
-
best = { bet, files };
|
|
148
|
-
}
|
|
140
|
+
ranked.push({ bet, files, score: scoreBet(bet, i, files) });
|
|
149
141
|
}
|
|
150
|
-
|
|
151
|
-
return
|
|
142
|
+
ranked.sort((a, b) => b.score - a.score);
|
|
143
|
+
return ranked;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Select **one** next pilot from pattern bets (or build bets from smells).
|
|
148
|
+
* @param {object[] | null | undefined} patternBets
|
|
149
|
+
* @param {{ designSmells?: object[] }} [options]
|
|
150
|
+
* @returns {null | ReturnType<typeof extractionCardFromBet>}
|
|
151
|
+
*/
|
|
152
|
+
export function selectNextPilot(patternBets, options = {}) {
|
|
153
|
+
const ranked = rankedPatternBets(patternBets, options.designSmells);
|
|
154
|
+
if (ranked.length === 0) return null;
|
|
155
|
+
return extractionCardFromBet(ranked[0].bet, ranked[0].files);
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
/**
|
|
@@ -174,15 +178,159 @@ export function formatExtractionCard(card) {
|
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
/**
|
|
177
|
-
*
|
|
181
|
+
* One proposed pilot. The loop does not know designWeak or which sensor
|
|
182
|
+
* produced the card — only this list.
|
|
183
|
+
*
|
|
184
|
+
* @typedef {'pattern-bet' | 'reshape'} PilotSource
|
|
185
|
+
* @typedef {{
|
|
186
|
+
* source: PilotSource,
|
|
187
|
+
* target: string,
|
|
188
|
+
* move: string,
|
|
189
|
+
* moveSample: Array<string | { from: string, to: string }>,
|
|
190
|
+
* successSignal: string,
|
|
191
|
+
* killSwitch: string,
|
|
192
|
+
* bet?: object,
|
|
193
|
+
* files?: string[],
|
|
194
|
+
* doNot?: string[],
|
|
195
|
+
* }} PilotCandidate
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @param {object} bet
|
|
200
|
+
* @param {string[]} files
|
|
201
|
+
* @returns {PilotCandidate}
|
|
202
|
+
*/
|
|
203
|
+
function candidateFromBet(bet, files) {
|
|
204
|
+
const target =
|
|
205
|
+
(files && files[0]) ||
|
|
206
|
+
(typeof bet.pilot === 'string' ? bet.pilot : null) ||
|
|
207
|
+
'src/**';
|
|
208
|
+
return {
|
|
209
|
+
source: 'pattern-bet',
|
|
210
|
+
target,
|
|
211
|
+
move:
|
|
212
|
+
typeof bet.fix === 'string' && bet.fix.trim()
|
|
213
|
+
? bet.fix.trim()
|
|
214
|
+
: 'Apply one bounded extraction for this smell on pilot paths only',
|
|
215
|
+
moveSample: files || [],
|
|
216
|
+
successSignal:
|
|
217
|
+
typeof bet.successSignal === 'string'
|
|
218
|
+
? bet.successSignal
|
|
219
|
+
: 'Smell evidence paths cleared on pilot without weakening the contract',
|
|
220
|
+
killSwitch:
|
|
221
|
+
typeof bet.killSwitch === 'string'
|
|
222
|
+
? bet.killSwitch
|
|
223
|
+
: 'If pilot increases edge violations without design clarity, stop and re-map with /ark-explore',
|
|
224
|
+
bet,
|
|
225
|
+
files,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {PilotCandidate} candidate
|
|
231
|
+
* @returns {null | ReturnType<typeof extractionCardFromBet>}
|
|
232
|
+
*/
|
|
233
|
+
function extractionCardFromCandidate(candidate) {
|
|
234
|
+
if (!candidate || typeof candidate.target !== 'string' || candidate.target.length === 0) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
if (candidate.source === 'pattern-bet' && candidate.bet) {
|
|
238
|
+
return extractionCardFromBet(candidate.bet, candidate.files);
|
|
239
|
+
}
|
|
240
|
+
const sample = Array.isArray(candidate.moveSample) ? candidate.moveSample : [];
|
|
241
|
+
const evidence = sample
|
|
242
|
+
.map((entry) => (typeof entry === 'string' ? entry : entry?.from))
|
|
243
|
+
.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
244
|
+
.slice(0, 8);
|
|
245
|
+
const reshape = candidate.source === 'reshape';
|
|
246
|
+
return {
|
|
247
|
+
id: PILOT_LOOP_ID,
|
|
248
|
+
patternBetId: reshape ? `reshape:${candidate.target}` : `pattern-b:${candidate.target}`,
|
|
249
|
+
smellId: reshape ? 'physical-cohesion' : 'unknown',
|
|
250
|
+
pilot: candidate.target,
|
|
251
|
+
pilotTarget: candidate.target,
|
|
252
|
+
evidence,
|
|
253
|
+
move: candidate.move,
|
|
254
|
+
doNot:
|
|
255
|
+
Array.isArray(candidate.doNot) && candidate.doNot.length
|
|
256
|
+
? [...candidate.doNot]
|
|
257
|
+
: [...DEFAULT_DO_NOT],
|
|
258
|
+
successSignal: candidate.successSignal,
|
|
259
|
+
killSwitch: candidate.killSwitch,
|
|
260
|
+
neverMechanicalSafe: true,
|
|
261
|
+
class: 'judgment',
|
|
262
|
+
loopStep: 'one-pilot',
|
|
263
|
+
reDoctor: 'ark-check --doctor --json',
|
|
264
|
+
rePlan: 'ark-check --plan --json',
|
|
265
|
+
next: reshape
|
|
266
|
+
? '/ark-loop (one reshape pilot) | re-doctor after pilot'
|
|
267
|
+
: '/ark-autopilot (one cluster / one B pilot) | re-doctor after pilot',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Normalize design-weak pattern bets and a proposed reshape pilot into one list.
|
|
273
|
+
* Pattern bets stay in selector rank (best first) and are omitted unless
|
|
274
|
+
* design fitness is design-weak, so that path keeps today's card. A reshape
|
|
275
|
+
* card is appended when physical cohesion proposes one. Doctor builds this
|
|
276
|
+
* list after advisories, so the loop never depends on call order.
|
|
277
|
+
*
|
|
178
278
|
* @param {{
|
|
179
279
|
* designWeak?: boolean,
|
|
180
280
|
* patternBets?: object[],
|
|
181
281
|
* designSmells?: object[],
|
|
182
|
-
* }}
|
|
282
|
+
* }} [designFitness]
|
|
283
|
+
* @param {{
|
|
284
|
+
* physicalCohesion?: {
|
|
285
|
+
* reshapePilot?: { proposed?: boolean, nextPilot?: object | null },
|
|
286
|
+
* },
|
|
287
|
+
* }} [advisories]
|
|
288
|
+
* @returns {PilotCandidate[]}
|
|
183
289
|
*/
|
|
184
|
-
export function
|
|
185
|
-
|
|
290
|
+
export function collectPilotCandidates(designFitness = {}, advisories = {}) {
|
|
291
|
+
/** @type {PilotCandidate[]} */
|
|
292
|
+
const candidates = [];
|
|
293
|
+
if (designFitness?.designWeak === true) {
|
|
294
|
+
for (const ranked of rankedPatternBets(designFitness.patternBets, designFitness.designSmells)) {
|
|
295
|
+
candidates.push(candidateFromBet(ranked.bet, ranked.files));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const reshape = advisories?.physicalCohesion?.reshapePilot;
|
|
299
|
+
const card = reshape?.proposed === true ? reshape.nextPilot : null;
|
|
300
|
+
if (card && typeof card.pilotTarget === 'string' && card.pilotTarget.length > 0) {
|
|
301
|
+
candidates.push({
|
|
302
|
+
source: 'reshape',
|
|
303
|
+
target: card.pilotTarget,
|
|
304
|
+
move:
|
|
305
|
+
typeof card.move === 'string' && card.move.trim()
|
|
306
|
+
? card.move.trim()
|
|
307
|
+
: 'Consolidate one anchor only, then re-doctor before the next card',
|
|
308
|
+
moveSample: Array.isArray(card.moveSample) ? card.moveSample : [],
|
|
309
|
+
successSignal:
|
|
310
|
+
typeof card.successSignal === 'string'
|
|
311
|
+
? card.successSignal
|
|
312
|
+
: 're-run doctor: the cluster count drops, layer and slice id unchanged, card still proposed',
|
|
313
|
+
killSwitch:
|
|
314
|
+
typeof card.killSwitch === 'string'
|
|
315
|
+
? card.killSwitch
|
|
316
|
+
: 'revert this move set; nothing else was touched',
|
|
317
|
+
...(Array.isArray(card.doNot) ? { doNot: card.doNot } : {}),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return candidates;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Doctor/plan JSON summary. Active iff `candidates.length > 0`.
|
|
325
|
+
* Activates exactly one candidate (ADR 0010 one-pilot rule). The rest stay
|
|
326
|
+
* queued. No designWeak parameter.
|
|
327
|
+
*
|
|
328
|
+
* @param {PilotCandidate[]} [candidates]
|
|
329
|
+
*/
|
|
330
|
+
export function summarizePilotLoop(candidates = []) {
|
|
331
|
+
const list = (Array.isArray(candidates) ? candidates : []).filter(
|
|
332
|
+
(candidate) => candidate && typeof candidate.target === 'string' && candidate.target.length > 0
|
|
333
|
+
);
|
|
186
334
|
// Same forbid bits as DESIGN_WEAK_HONESTY_FLAGS (both auto-apply aliases).
|
|
187
335
|
const forbid = {
|
|
188
336
|
multiPilotBatchForbidden: true,
|
|
@@ -190,55 +338,53 @@ export function summarizePilotLoop(opts = {}) {
|
|
|
190
338
|
autoApplyPlanBForbidden: true,
|
|
191
339
|
};
|
|
192
340
|
|
|
193
|
-
if (
|
|
341
|
+
if (list.length === 0) {
|
|
194
342
|
return {
|
|
195
343
|
active: false,
|
|
196
344
|
id: PILOT_LOOP_ID,
|
|
197
|
-
reason: '
|
|
345
|
+
reason: 'no-pilot-candidates',
|
|
198
346
|
oneAtATime: true,
|
|
199
347
|
neverMechanicalSafe: true,
|
|
200
348
|
...forbid,
|
|
201
349
|
};
|
|
202
350
|
}
|
|
203
351
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (!nextPilot) {
|
|
352
|
+
// One pilot, not the list. Later candidates wait for a re-doctor.
|
|
353
|
+
const extractionCard = extractionCardFromCandidate(list[0]);
|
|
354
|
+
if (!extractionCard) {
|
|
208
355
|
return {
|
|
209
356
|
active: false,
|
|
210
357
|
id: PILOT_LOOP_ID,
|
|
211
|
-
reason: 'no-
|
|
358
|
+
reason: 'no-pilot-candidates',
|
|
212
359
|
oneAtATime: true,
|
|
213
360
|
neverMechanicalSafe: true,
|
|
214
361
|
...forbid,
|
|
215
362
|
};
|
|
216
363
|
}
|
|
217
364
|
|
|
218
|
-
const
|
|
219
|
-
const queued = Math.max(0, remaining - 1);
|
|
220
|
-
|
|
365
|
+
const queued = list.length - 1;
|
|
221
366
|
return {
|
|
222
367
|
active: true,
|
|
223
368
|
id: PILOT_LOOP_ID,
|
|
224
369
|
oneAtATime: true,
|
|
225
370
|
neverMechanicalSafe: true,
|
|
226
371
|
...forbid,
|
|
227
|
-
|
|
228
|
-
|
|
372
|
+
source: list[0].source,
|
|
373
|
+
remainingBets: list.length,
|
|
229
374
|
queuedBets: queued,
|
|
230
375
|
...(queued > 0
|
|
231
376
|
? {
|
|
232
|
-
queueNote: `${queued} additional
|
|
377
|
+
queueNote: `${queued} additional pilot candidate(s) stay queued — run the single nextPilot only, then re-doctor before selecting another.`,
|
|
233
378
|
}
|
|
234
379
|
: {}),
|
|
235
|
-
nextPilot,
|
|
380
|
+
nextPilot: extractionCard,
|
|
381
|
+
extractionCard,
|
|
236
382
|
instruction:
|
|
237
383
|
'Apply ONE pilot from nextPilot (extraction card), then re-doctor. ' +
|
|
238
384
|
'Do not multi-pilot batch. patternBets never mechanical-safe. ' +
|
|
239
385
|
'Never silent auto-apply of plan B. ' +
|
|
240
386
|
'Success = reduced smell evidence on pilot paths; residual outside pilot may remain.',
|
|
241
|
-
cardText: formatExtractionCard(
|
|
387
|
+
cardText: formatExtractionCard(extractionCard),
|
|
242
388
|
};
|
|
243
389
|
}
|
|
244
390
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-version upgrade behavior changes.
|
|
3
|
+
*
|
|
4
|
+
* This table is the package source for migration notes. CHANGELOG.md must
|
|
5
|
+
* carry the same sentences (Unreleased / 4.8.21, including the 4.8.20
|
|
6
|
+
* coverage.symbol note). Post-upgrade verification prints these lines so an
|
|
7
|
+
* agent reads them before editing code.
|
|
8
|
+
*
|
|
9
|
+
* Shipped with the package (`bin/lib` is in the published `files` list).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** @typedef {{ version: string, note: string }} UpgradeBehaviorChange */
|
|
13
|
+
|
|
14
|
+
/** @type {readonly UpgradeBehaviorChange[]} */
|
|
15
|
+
export const UPGRADE_BEHAVIOR_CHANGES = Object.freeze([
|
|
16
|
+
Object.freeze({
|
|
17
|
+
version: '4.8.20',
|
|
18
|
+
note: 'coverage.symbol now requires a real declaration (some green repos turn red)',
|
|
19
|
+
}),
|
|
20
|
+
Object.freeze({
|
|
21
|
+
version: '4.8.21',
|
|
22
|
+
note: 'a bare mention of an invariant id no longer counts as coverage (only describe/it titles or declarations, incl. type/interface/enum)',
|
|
23
|
+
}),
|
|
24
|
+
Object.freeze({
|
|
25
|
+
version: '4.8.21',
|
|
26
|
+
note: "upgrade exit code 3 = applied but red. Scripts comparing against 1 must switch to 'non-zero'",
|
|
27
|
+
}),
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} version
|
|
32
|
+
* @returns {number[]}
|
|
33
|
+
*/
|
|
34
|
+
function versionParts(version) {
|
|
35
|
+
return String(version)
|
|
36
|
+
.replace(/^v/, '')
|
|
37
|
+
.split('.')
|
|
38
|
+
.map((part) => {
|
|
39
|
+
const match = /^(\d+)/.exec(part);
|
|
40
|
+
return match ? Number(match[1]) : 0;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} left
|
|
46
|
+
* @param {string} right
|
|
47
|
+
* @returns {-1|0|1}
|
|
48
|
+
*/
|
|
49
|
+
export function compareUpgradeVersions(left, right) {
|
|
50
|
+
const a = versionParts(left);
|
|
51
|
+
const b = versionParts(right);
|
|
52
|
+
const length = Math.max(a.length, b.length);
|
|
53
|
+
for (let index = 0; index < length; index += 1) {
|
|
54
|
+
const delta = (a[index] ?? 0) - (b[index] ?? 0);
|
|
55
|
+
if (delta !== 0) return delta < 0 ? -1 : 1;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {UpgradeBehaviorChange} row
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
export function formatBehaviorChange(row) {
|
|
65
|
+
return `${row.version}: ${row.note}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Lines from the shipped table.
|
|
70
|
+
* Omit `upTo` to return every note in this package (the post-upgrade default:
|
|
71
|
+
* the build that implements a note must show it, even before the version bump).
|
|
72
|
+
* Pass `upTo` to keep notes whose version is less than or equal to that release.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} [upTo]
|
|
75
|
+
* @returns {string[]}
|
|
76
|
+
*/
|
|
77
|
+
export function behaviorChangeLines(upTo) {
|
|
78
|
+
const rows =
|
|
79
|
+
typeof upTo === 'string' && upTo.trim()
|
|
80
|
+
? UPGRADE_BEHAVIOR_CHANGES.filter((row) => compareUpgradeVersions(row.version, upTo) <= 0)
|
|
81
|
+
: UPGRADE_BEHAVIOR_CHANGES;
|
|
82
|
+
return rows.map((row) => formatBehaviorChange(row));
|
|
83
|
+
}
|