arkgate 4.8.20 → 4.8.21
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 +124 -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 +396 -75
- 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-B7BXlet6.d.ts} +73 -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 +24 -17
- package/dist/index.d.ts +9 -4
- package/dist/index.js +25 -18
- 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 +18 -11
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +18 -11
- 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 +4 -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 +1 -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
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
|
+
}
|
|
@@ -10,6 +10,13 @@ import {
|
|
|
10
10
|
formatPackageInstallDecisionHuman,
|
|
11
11
|
} from '../ark-shared.mjs';
|
|
12
12
|
import { describePackageVersionDualTruth } from './field-install.mjs';
|
|
13
|
+
import {
|
|
14
|
+
exitCodeFor,
|
|
15
|
+
formatBlockedAssets,
|
|
16
|
+
formatPostUpgradeHuman,
|
|
17
|
+
runPostUpgradeVerification,
|
|
18
|
+
upgradeOutcome,
|
|
19
|
+
} from './upgrade-outcome.mjs';
|
|
13
20
|
import { __packageRoot } from './gate-files.mjs';
|
|
14
21
|
import {
|
|
15
22
|
applyManagedUpgrade,
|
|
@@ -316,15 +323,13 @@ export function buildUpgradeNextCommand(args, planDigest) {
|
|
|
316
323
|
return arkCommand(args.root, 'arkgate', argsStr);
|
|
317
324
|
}
|
|
318
325
|
|
|
319
|
-
function
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
});
|
|
327
|
-
return { exitCode: result.status ?? 1, stderr: result.stderr?.trim() || undefined };
|
|
326
|
+
function stampBlockedNextCommands(plan, args) {
|
|
327
|
+
if ((plan?.summary?.blocked ?? 0) === 0) return plan;
|
|
328
|
+
const command = buildUpgradeNextCommand({ ...args, acceptConflicts: true }, plan.planDigest);
|
|
329
|
+
for (const asset of plan.assets ?? []) {
|
|
330
|
+
if (asset.blocked) asset.nextCommand = command;
|
|
331
|
+
}
|
|
332
|
+
return plan;
|
|
328
333
|
}
|
|
329
334
|
|
|
330
335
|
function runProjectLocalCli({ root, script, argv }) {
|
|
@@ -544,11 +549,14 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
544
549
|
}).status ?? 1;
|
|
545
550
|
}
|
|
546
551
|
|
|
547
|
-
const plan =
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
+
const plan = stampBlockedNextCommands(
|
|
553
|
+
planManagedUpgrade(root, {
|
|
554
|
+
tools: args.tools,
|
|
555
|
+
acceptConflicts: args.acceptConflicts,
|
|
556
|
+
refreshSkills: args.refreshSkills === true,
|
|
557
|
+
}),
|
|
558
|
+
args
|
|
559
|
+
);
|
|
552
560
|
if (!args.apply) {
|
|
553
561
|
const wouldWrite = plan.summary?.wouldWrite ?? 0;
|
|
554
562
|
const blocked = plan.summary?.blocked ?? 0;
|
|
@@ -577,11 +585,14 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
577
585
|
);
|
|
578
586
|
} else {
|
|
579
587
|
renderManagedUpgrade(plan, {
|
|
580
|
-
next:
|
|
581
|
-
|
|
582
|
-
?
|
|
583
|
-
:
|
|
584
|
-
|
|
588
|
+
next:
|
|
589
|
+
blocked > 0
|
|
590
|
+
? undefined
|
|
591
|
+
: needsApply
|
|
592
|
+
? args.install
|
|
593
|
+
? `Update the package and recompute this preview with: ${command}`
|
|
594
|
+
: `Apply the exact preview with: ${command}`
|
|
595
|
+
: undefined,
|
|
585
596
|
skillDrift,
|
|
586
597
|
hostSelection: hostHonesty,
|
|
587
598
|
});
|
|
@@ -600,54 +611,80 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
600
611
|
} catch (error) {
|
|
601
612
|
const message = error instanceof Error ? error.message : String(error);
|
|
602
613
|
console.error(message);
|
|
603
|
-
return
|
|
614
|
+
return exitCodeFor(upgradeOutcome({ error }));
|
|
604
615
|
}
|
|
605
616
|
if (applied.blocked) {
|
|
617
|
+
stampBlockedNextCommands(applied, args);
|
|
606
618
|
const command = buildUpgradeNextCommand(
|
|
607
619
|
{ ...args, acceptConflicts: true },
|
|
608
620
|
applied.planDigest
|
|
609
621
|
);
|
|
622
|
+
const refusal = formatBlockedAssets(applied, { refused: true }).join('\n');
|
|
623
|
+
console.error(refusal);
|
|
610
624
|
if (args.json) {
|
|
611
625
|
console.log(
|
|
612
626
|
managedUpgradeJson(applied, {
|
|
613
627
|
blocked: true,
|
|
628
|
+
outcome: 'blocked',
|
|
614
629
|
reasonCode: applied.reasonCode ?? 'managed-consent-required',
|
|
615
630
|
nextCommand: command,
|
|
616
631
|
})
|
|
617
632
|
);
|
|
618
633
|
} else {
|
|
619
|
-
renderManagedUpgrade(applied, {
|
|
620
|
-
next: 'Preview again with --accept-conflicts, then use that preview\'s exact next command.',
|
|
621
|
-
});
|
|
634
|
+
renderManagedUpgrade(applied, { refused: true });
|
|
622
635
|
}
|
|
623
|
-
return
|
|
636
|
+
return exitCodeFor(upgradeOutcome({ blocked: true }));
|
|
624
637
|
}
|
|
625
638
|
if (applied.nothingToApply && !applied.applied) {
|
|
626
|
-
|
|
639
|
+
const idle = upgradeOutcome({ nothingToApply: true, applied: false });
|
|
640
|
+
if (args.json) console.log(JSON.stringify({ ...applied, outcome: idle.kind }, null, 2));
|
|
627
641
|
else {
|
|
628
642
|
renderManagedUpgrade(applied);
|
|
629
643
|
console.log('No managed content writes pending.');
|
|
630
644
|
}
|
|
631
|
-
return
|
|
645
|
+
return exitCodeFor(idle);
|
|
632
646
|
}
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
:
|
|
647
|
+
const postUpgrade = runPostUpgradeVerification(root, {
|
|
648
|
+
strict: args.strict,
|
|
649
|
+
arkCheck: dependencies.arkCheck,
|
|
650
|
+
packageVersion: dependencies.cliVersion,
|
|
651
|
+
...(dependencies.checkResult ? { checkResult: dependencies.checkResult } : {}),
|
|
652
|
+
...(typeof dependencies.spawnArchitectureCheck === 'function'
|
|
653
|
+
? { checkResult: dependencies.spawnArchitectureCheck(root) }
|
|
654
|
+
: {}),
|
|
655
|
+
});
|
|
656
|
+
const outcome = upgradeOutcome({ applied: true, postUpgrade });
|
|
657
|
+
const verification = {
|
|
658
|
+
mode: postUpgrade.mode,
|
|
659
|
+
exitCode: postUpgrade.exitCode,
|
|
660
|
+
...(postUpgrade.stderr ? { stderr: postUpgrade.stderr } : {}),
|
|
661
|
+
};
|
|
636
662
|
const dualTruth = describePackageVersionDualTruth(root);
|
|
637
663
|
const skillDrift = buildSkillDriftSummary(applied);
|
|
638
664
|
const hostHonesty = buildHostSelectionHonesty(applied);
|
|
639
665
|
// FX05: post-upgrade verification block (advisory, notAScore).
|
|
640
666
|
const postUpgradeChecks = buildPostUpgradeChecks(root, {
|
|
641
667
|
cliVersion: dependencies.cliVersion,
|
|
642
|
-
verification
|
|
668
|
+
verification: {
|
|
669
|
+
...verification,
|
|
670
|
+
verdict: postUpgrade.verdict,
|
|
671
|
+
failing: postUpgrade.failing,
|
|
672
|
+
},
|
|
643
673
|
dualTruth,
|
|
644
674
|
});
|
|
675
|
+
const postUpgradePayload = {
|
|
676
|
+
verdict: postUpgrade.verdict,
|
|
677
|
+
failing: postUpgrade.failing,
|
|
678
|
+
behaviorChanges: postUpgrade.behaviorChanges,
|
|
679
|
+
};
|
|
645
680
|
if (args.json) {
|
|
646
681
|
console.log(
|
|
647
682
|
JSON.stringify(
|
|
648
683
|
{
|
|
649
684
|
...applied,
|
|
685
|
+
outcome: outcome.kind,
|
|
650
686
|
verification,
|
|
687
|
+
postUpgrade: postUpgradePayload,
|
|
651
688
|
skillDrift,
|
|
652
689
|
hostSelection: hostHonesty,
|
|
653
690
|
postUpgradeChecks,
|
|
@@ -673,6 +710,7 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
673
710
|
);
|
|
674
711
|
} else {
|
|
675
712
|
renderManagedUpgrade(applied, { skillDrift, hostSelection: hostHonesty });
|
|
713
|
+
for (const line of formatPostUpgradeHuman(postUpgrade)) console.log(line);
|
|
676
714
|
if (!args.strict) console.log('Architecture verification skipped (--no-strict).');
|
|
677
715
|
if (args.install === false || dualTruth.dualTruth) {
|
|
678
716
|
console.log(
|
|
@@ -690,5 +728,5 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
690
728
|
console.log(` [note] mcp: ${postUpgradeChecks.mcpNote}`);
|
|
691
729
|
}
|
|
692
730
|
}
|
|
693
|
-
return
|
|
731
|
+
return exitCodeFor(outcome);
|
|
694
732
|
}
|