arkgate 4.8.19 → 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 +220 -0
- package/README.md +107 -117
- package/bin/ark-check-runtime.mjs +26 -4
- package/bin/ark-layer-match.mjs +236 -12
- package/bin/ark.mjs +6 -2
- package/bin/lib/analysis-engine.mjs +13 -6
- package/bin/lib/config-contract.mjs +6 -0
- package/bin/lib/diagnostic-catalog.mjs +1 -0
- package/bin/lib/doctor-advisories.mjs +22 -1
- package/bin/lib/doctor-human.mjs +11 -9
- package/bin/lib/doctor-plan.mjs +17 -14
- package/bin/lib/enforcement-honesty.mjs +30 -2
- 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 +12 -7
- package/bin/lib/invariant-coverage.mjs +406 -54
- package/bin/lib/managed-upgrade.mjs +59 -14
- package/bin/lib/package-manager.mjs +5 -2
- package/bin/lib/physical-cohesion.mjs +68 -51
- package/bin/lib/pilot-loop.mjs +183 -37
- package/bin/lib/reshape-decisions.mjs +2 -2
- package/bin/lib/rules-under-contract.mjs +40 -4
- package/bin/lib/start-install-recovery.mjs +156 -0
- package/bin/lib/start-preview.mjs +0 -54
- 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-VD0qcubY.d.ts → configTypes-CTELo87-.d.ts} +9 -0
- package/dist/{diagnosticCatalog-D2v0ASN2.d.ts → diagnosticCatalog-B7BXlet6.d.ts} +79 -5
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.d.ts +35 -4
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +25 -18
- package/dist/index.d.ts +13 -4
- package/dist/index.js +27 -20
- 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-D5GT5ZT8.d.ts → types-fQ9-KrBj.d.ts} +1 -1
- package/dist/{types-BSzRy2X1.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 +46 -8
- package/docs/develop.md +1 -1
- package/docs/diagnostics.md +12 -2
- package/docs/package-surface.md +4 -3
- package/docs/product-voice.md +11 -3
- package/docs/use.md +1 -1
- package/package.json +3 -3
- package/schemas/ark.config.schema.json +14 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +18 -1
- package/templates/agent-skills/ark-architect/SKILL.md +2 -0
- package/templates/agent-skills/ark-autopilot/SKILL.md +6 -2
- package/templates/agent-skills/ark-contract/SKILL.md +2 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +3 -1
- package/templates/agent-skills/ark-explain/SKILL.md +3 -1
- package/templates/agent-skills/ark-explore/SKILL.md +15 -1
- 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 +25 -10
- package/templates/skills/ark-adopt.md +18 -1
- package/templates/skills/ark-architect.md +2 -0
- package/templates/skills/ark-autopilot.md +6 -2
- package/templates/skills/ark-contract.md +2 -0
- package/templates/skills/ark-coverage.md +3 -1
- package/templates/skills/ark-explain.md +3 -1
- package/templates/skills/ark-explore.md +15 -1
- 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 +25 -10
|
@@ -214,6 +214,11 @@ export const ARK_CONFIG_SCHEMA = {
|
|
|
214
214
|
message: { type: 'string', minLength: 1 },
|
|
215
215
|
peerIsolation: { type: 'boolean' },
|
|
216
216
|
sliceFolders: { ...stringArraySchema, minItems: 1 },
|
|
217
|
+
sliceIdentity: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
enum: ['path', 'stars'],
|
|
220
|
+
description: 'How a starred sliceFolders prefix is named. Absent and path keep today\'s ids (every literal and star binding). stars keeps the last literal plus the star bindings, so parallel trees share one feature id. Bare names are unchanged.',
|
|
221
|
+
},
|
|
217
222
|
sharedRoots: { ...stringArraySchema, minItems: 1 },
|
|
218
223
|
allowedCrossSlice: {
|
|
219
224
|
type: 'array',
|
|
@@ -228,6 +233,7 @@ export const ARK_CONFIG_SCHEMA = {
|
|
|
228
233
|
},
|
|
229
234
|
},
|
|
230
235
|
},
|
|
236
|
+
sharedImportsSlice: { type: 'string', enum: ['deny'] },
|
|
231
237
|
},
|
|
232
238
|
},
|
|
233
239
|
safety: {
|
|
@@ -120,6 +120,7 @@ export const DIAGNOSTIC_CATALOG = Object.freeze([
|
|
|
120
120
|
entry('CONFIG_AMBIGUOUS_LAYERS', 'config', 'Ambiguous layer classification', 'Some files match multiple layers at equal specificity; classification falls back to declaration order.', 'Disambiguate overlapping patterns so each file has one clear layer owner.', { oftenAdvisory: true }),
|
|
121
121
|
entry('CONFIG_UNCLASSIFIED_FILES', 'config', 'Unclassified included files', 'Included source files match no layer pattern; import rules will not enforce on them.', 'Extend layer patterns or narrow include so every governed file is classified.', { oftenAdvisory: true }),
|
|
122
122
|
entry('CONFIG_LAYER_MISSING_OWNER', 'config', 'Layer missing owner', 'requireLayerOwners is on and this layer has no owners. Writes to that house fail closed, like a folder with no name on the door.', 'Add a GitHub handle or email to that layer’s owners in ark.config.json (/ark-adopt), then re-run. Reserved/allowEmpty houses may stay unnamed. Turn the flag off only if you no longer want owners required.'),
|
|
123
|
+
entry('CONFIG_SLICE_IDENTITY_COLLISION', 'config', 'Starred slice prefixes share one id', 'sliceIdentity is stars and two different sliceFolders prefixes bind as the same slice id. Parallel trees of one feature do that on purpose; unrelated trees that share the last folder name merge by accident.', 'Keep stars when those prefixes are one feature. Otherwise use path, or rename the last literal so the prefixes do not bind as the same id. Doctor names both paths.', { oftenAdvisory: true }),
|
|
123
124
|
// ── literal path drift ───────────────────────────────────────────────────
|
|
124
125
|
entry('LITERAL_PATH_DRIFT', 'drift', 'Literal path moved by a rename', 'A repo path written inside a string, a comment or a docstring no longer resolves, and the rename set says where it went. Nothing in the gate sees this class: `tsc` resolves imports, not strings, and ESLint does not either, so the rename compiles green and the reference lies afterwards. It appears in four forms — the tsconfig alias, a relative literal, a path written without the include-root prefix, and prose — and a hand sweep reliably covers one of them.', 'Apply the suggested replacement, or re-run `npx arkgate-check --path-drift --base-ref <ref> --write` to apply every writable anchored replacement at once. The rewrite is mechanical and one-directional: the destination comes from the rename, it must itself resolve and be path-shaped, and the token is rewritten in the form the author wrote it in. A destination that leaves the alias root of the literal is reported with the target only and must be rewritten by hand.'),
|
|
125
126
|
entry('LITERAL_PATH_UNRESOLVED', 'drift', 'Literal path does not resolve', 'A literal that looks like a repo path does not resolve under this root, and no rename explains where it went. Unlike LITERAL_PATH_DRIFT this is a candidate, not a verdict: with nothing to anchor it, ArkGate cannot tell a dead reference from an illustrative path in a comment, an example in documentation, or a path belonging to another tree.', 'Read the candidate and decide: fix the path, or leave it. Advisory only — it never fails a run and is never rewritten by --write, because there is no destination to propose. Run `--path-drift --all` to list the sweep.', { oftenAdvisory: true }),
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from './ark-order-doctor.mjs';
|
|
38
38
|
import { composeMergePlanesHonesty } from './extra-merge-teeth.mjs';
|
|
39
39
|
import { collectPrototypeShortcutsResidual } from './prototype-shortcuts.mjs';
|
|
40
|
+
import { sliceIdentityCollisions } from '../ark-layer-match.mjs';
|
|
40
41
|
|
|
41
42
|
export function attachExtraDoctorSections(rulesUnderContract, config, classification, findings) {
|
|
42
43
|
const arkRulesMerge = {
|
|
@@ -122,6 +123,13 @@ export function printCompactExtraDoctorLines(advisories, io) {
|
|
|
122
123
|
if (states.nextAction) io.line(' ', `Next: ${states.nextAction}`);
|
|
123
124
|
}
|
|
124
125
|
}
|
|
126
|
+
const sliceIdentity = advisories?.sliceIdentity;
|
|
127
|
+
if (Array.isArray(sliceIdentity?.collisions) && sliceIdentity.collisions.length > 0) {
|
|
128
|
+
console.log('');
|
|
129
|
+
for (const collision of sliceIdentity.collisions) {
|
|
130
|
+
io.line(io.warn, collision.message);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
125
133
|
const noDomain = advisories?.noDomainFrontend;
|
|
126
134
|
if (noDomain?.ask) {
|
|
127
135
|
console.log('');
|
|
@@ -200,7 +208,8 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
|
|
|
200
208
|
physicalCohesion,
|
|
201
209
|
files,
|
|
202
210
|
root,
|
|
203
|
-
decisionMemory
|
|
211
|
+
decisionMemory,
|
|
212
|
+
{ layers: config?.layers, rules: rules ?? config?.rules }
|
|
204
213
|
);
|
|
205
214
|
// Prefer architecture facts paths when available; still union the doctor walk
|
|
206
215
|
// so an empty-catalog residual can see Domain files the facts subset missed.
|
|
@@ -246,8 +255,12 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
|
|
|
246
255
|
coverage: cov,
|
|
247
256
|
files,
|
|
248
257
|
});
|
|
258
|
+
const sliceIdentityHits = sliceIdentityCollisions(rules ?? config?.rules);
|
|
249
259
|
return {
|
|
250
260
|
...(prototypeShortcuts ? { prototypeShortcuts } : {}),
|
|
261
|
+
...(sliceIdentityHits.length > 0
|
|
262
|
+
? { sliceIdentity: { notAScore: true, collisions: sliceIdentityHits } }
|
|
263
|
+
: {}),
|
|
251
264
|
contractHealth: computeContractHealth(root, config, cov, rules),
|
|
252
265
|
ambientState: computeAmbientState(ts, root, config, files),
|
|
253
266
|
physicalCohesion,
|
|
@@ -264,6 +277,14 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
|
|
|
264
277
|
}
|
|
265
278
|
|
|
266
279
|
export function printDoctorAdvisories(advisories, io) {
|
|
280
|
+
const sliceIdentity = advisories?.sliceIdentity;
|
|
281
|
+
if (Array.isArray(sliceIdentity?.collisions) && sliceIdentity.collisions.length > 0) {
|
|
282
|
+
console.log('');
|
|
283
|
+
console.log(io.color.bold('Slice identity'));
|
|
284
|
+
for (const collision of sliceIdentity.collisions) {
|
|
285
|
+
io.line(io.warn, collision.message);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
267
288
|
printContractHealthSection(advisories.contractHealth, io);
|
|
268
289
|
printAmbientStateSection(advisories.ambientState, io);
|
|
269
290
|
printPhysicalCohesionSection(
|
package/bin/lib/doctor-human.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { analysisIncompleteStatement } from './analysis-completeness.mjs';
|
|
|
14
14
|
import { skillGapsForActiveHost, detectCodexHomeGap, codexConcernIsActive, skillGapToolLabel } from './agent-gates.mjs';
|
|
15
15
|
import { agentHomeConcernIsActive } from './agent-homes.mjs';
|
|
16
16
|
import { layerGuidanceLine } from './layer-description.mjs';
|
|
17
|
+
import { printSharedImportsSliceBridgeList } from './violations.mjs';
|
|
17
18
|
import {
|
|
18
19
|
citedGreen,
|
|
19
20
|
ciMergeGreenCites,
|
|
@@ -187,6 +188,7 @@ export function printDoctorCompactHuman(view) {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
printSharedImportsSliceBridgeList(view.warnings);
|
|
190
192
|
console.log('');
|
|
191
193
|
console.log(color.dim('More: --doctor --all'));
|
|
192
194
|
}
|
|
@@ -298,15 +300,15 @@ export function printDoctorDetailsHuman(view) {
|
|
|
298
300
|
line(' ', color.dim(`evidence: ${smell.evidence.slice(0, 4).join(', ')}`));
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
303
|
+
}
|
|
304
|
+
if (pilotLoop?.active && pilotLoop.nextPilot) {
|
|
305
|
+
const np = pilotLoop.nextPilot;
|
|
306
|
+
line(
|
|
307
|
+
warn,
|
|
308
|
+
`Next pilot (one at a time): ${np.pilotTarget || np.pilot} [${np.smellId}] → re-doctor after change`
|
|
309
|
+
);
|
|
310
|
+
line(' ', color.dim(`success: ${np.successSignal}`));
|
|
311
|
+
line(' ', color.dim('never multi-pilot batch; pattern bets are never auto-applied'));
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
if (options.designDelta) {
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -59,13 +59,14 @@ import {
|
|
|
59
59
|
import {
|
|
60
60
|
buildCoverageHonesty,
|
|
61
61
|
computeDoctorEnforcementHonesty,
|
|
62
|
+
physicalCohesionResidualRemains,
|
|
62
63
|
} from './enforcement-honesty.mjs';
|
|
63
64
|
import {
|
|
64
65
|
computePureLayerOptInNudge,
|
|
65
66
|
loadGoldenPattern,
|
|
66
67
|
summarizeGoldenPattern,
|
|
67
68
|
} from './golden-pattern.mjs';
|
|
68
|
-
import { summarizePilotLoop } from './pilot-loop.mjs';
|
|
69
|
+
import { collectPilotCandidates, summarizePilotLoop } from './pilot-loop.mjs';
|
|
69
70
|
import { computeDoctorAdvisories } from './doctor-advisories.mjs';
|
|
70
71
|
import { ANALYSIS_COMPLETENESS, analysisIncompleteStatement, normalizeAnalysisCompleteness } from './analysis-completeness.mjs';
|
|
71
72
|
import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
|
|
@@ -324,12 +325,8 @@ export function buildRemediationPlan(
|
|
|
324
325
|
governedPercent,
|
|
325
326
|
totalFiles,
|
|
326
327
|
});
|
|
327
|
-
// Q04 —
|
|
328
|
-
const pilotLoop = summarizePilotLoop({
|
|
329
|
-
designWeak,
|
|
330
|
-
patternBets,
|
|
331
|
-
designSmells,
|
|
332
|
-
});
|
|
328
|
+
// Q04 — one candidate at a time. Plan has no cohesion advisory.
|
|
329
|
+
const pilotLoop = summarizePilotLoop(collectPilotCandidates({ designWeak, patternBets, designSmells }));
|
|
333
330
|
const coverageHonesty = buildCoverageHonesty({
|
|
334
331
|
percent: governedPercent,
|
|
335
332
|
totalFiles,
|
|
@@ -603,13 +600,6 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
603
600
|
const goldenPattern = summarizeGoldenPattern(goldenLoad);
|
|
604
601
|
// Y06 — pure-layer opt-in when golden names pure modules but no pure:true layer.
|
|
605
602
|
const pureLayerOptIn = computePureLayerOptInNudge(config, goldenLoad);
|
|
606
|
-
// Q04 — one next pilot (extraction card) when design-weak.
|
|
607
|
-
const patternBetsForLoop = buildPatternBetsFromSmells(designSmells);
|
|
608
|
-
const pilotLoop = summarizePilotLoop({
|
|
609
|
-
designWeak: designFitness.designWeak,
|
|
610
|
-
patternBets: patternBetsForLoop,
|
|
611
|
-
designSmells,
|
|
612
|
-
});
|
|
613
603
|
const activeViolations = baseline.exists
|
|
614
604
|
? violations.filter((_, index) => !baseline.keys.has(occurrenceKeys[index]))
|
|
615
605
|
: violations;
|
|
@@ -624,6 +614,17 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
624
614
|
options.facts ?? options.architectureFacts,
|
|
625
615
|
activeViolations
|
|
626
616
|
);
|
|
617
|
+
// After physical cohesion so a reshape pilot is a candidate (#309).
|
|
618
|
+
const pilotLoop = summarizePilotLoop(
|
|
619
|
+
collectPilotCandidates(
|
|
620
|
+
{
|
|
621
|
+
...designFitness,
|
|
622
|
+
patternBets: buildPatternBetsFromSmells(designSmells),
|
|
623
|
+
designSmells,
|
|
624
|
+
},
|
|
625
|
+
doctorAdvisories
|
|
626
|
+
)
|
|
627
|
+
);
|
|
627
628
|
const layerOwners = collectLayerOwnerResidual(config);
|
|
628
629
|
const rulesUnderContract = doctorAdvisories.rulesUnderContract;
|
|
629
630
|
const arkRun = doctorAdvisories.arkRun;
|
|
@@ -693,6 +694,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
693
694
|
github: githubForBoundary,
|
|
694
695
|
adoptionStance: stanceFile,
|
|
695
696
|
stewardNudge: doctorAdvisories.stewardNudge,
|
|
697
|
+
physicalCohesionResidual: physicalCohesionResidualRemains(doctorAdvisories.physicalCohesion),
|
|
696
698
|
});
|
|
697
699
|
|
|
698
700
|
// Improvement compass: projection only — never feeds ok/valid/goal.met.
|
|
@@ -963,6 +965,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
963
965
|
activeCount,
|
|
964
966
|
skillGaps,
|
|
965
967
|
agentHomeGaps,
|
|
968
|
+
warnings: options.warnings,
|
|
966
969
|
baseline,
|
|
967
970
|
baselineHonesty,
|
|
968
971
|
staleBaseline,
|
|
@@ -288,14 +288,26 @@ export function buildWritePathHonesty(activeHost, hardWriteActive = false, extra
|
|
|
288
288
|
* primaryNextAction?: string | null,
|
|
289
289
|
* operatingMode?: string | null,
|
|
290
290
|
* activeBlockingViolations?: number | null,
|
|
291
|
+
* physicalCohesionResidual?: boolean,
|
|
291
292
|
* }} input
|
|
292
293
|
*/
|
|
294
|
+
export function physicalCohesionResidualRemains(cohesion) {
|
|
295
|
+
const findings = Array.isArray(cohesion?.findings) ? cohesion.findings : [];
|
|
296
|
+
if (findings.length === 0) return false;
|
|
297
|
+
const covered = new Set();
|
|
298
|
+
for (const row of cohesion?.reshapeDecisions?.current ?? []) {
|
|
299
|
+
if (row?.suppressesPilot === true && typeof row.concept === 'string') covered.add(row.concept);
|
|
300
|
+
}
|
|
301
|
+
return findings.some((finding) => !covered.has(finding?.concept));
|
|
302
|
+
}
|
|
303
|
+
|
|
293
304
|
export function buildProductHonesty(input = {}) {
|
|
294
305
|
const reasons = [];
|
|
295
306
|
const cov = input.coverageHonesty;
|
|
296
307
|
const base = input.baselineHonesty;
|
|
297
308
|
const write = input.writePathHonesty;
|
|
298
309
|
const designWeak = input.designWeak === true;
|
|
310
|
+
const cohesionResidual = input.physicalCohesionResidual === true;
|
|
299
311
|
const dualTruth = input.packageVersionTruth?.dualTruth === true;
|
|
300
312
|
const pinCode = input.packageVersionTruth?.code || write?.packagePinCode || null;
|
|
301
313
|
const pinAbsent =
|
|
@@ -365,6 +377,14 @@ export function buildProductHonesty(input = {}) {
|
|
|
365
377
|
});
|
|
366
378
|
}
|
|
367
379
|
|
|
380
|
+
if (cohesionResidual) {
|
|
381
|
+
reasons.push({
|
|
382
|
+
id: 'physical-cohesion-residual',
|
|
383
|
+
message:
|
|
384
|
+
'Physical cohesion residual remains — the gate verdict and exit code are unchanged, and the reshape card is one judgment, not a queue to empty.',
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
368
388
|
if (dualTruth) {
|
|
369
389
|
reasons.push({
|
|
370
390
|
id: 'package-version-dual-truth',
|
|
@@ -551,6 +571,12 @@ export function buildProductHonesty(input = {}) {
|
|
|
551
571
|
headline = 'Advisory-only adoption — merge status is not required';
|
|
552
572
|
} else if (!unfinished) {
|
|
553
573
|
headline = 'Honesty clear on residual signals';
|
|
574
|
+
} else if (
|
|
575
|
+
wholeTreeGoverned &&
|
|
576
|
+
architectureReasons.length === 1 &&
|
|
577
|
+
architectureReasons[0].id === 'physical-cohesion-residual'
|
|
578
|
+
) {
|
|
579
|
+
headline = 'Residual remaining';
|
|
554
580
|
} else if (coverageIncomplete) {
|
|
555
581
|
headline = 'Not finished / not whole-tree guarantee';
|
|
556
582
|
} else {
|
|
@@ -592,8 +618,8 @@ export function buildProductHonesty(input = {}) {
|
|
|
592
618
|
: 'unknown';
|
|
593
619
|
|
|
594
620
|
return {
|
|
595
|
-
finished: !unfinished && wholeTreeGoverned && !designWeak && activeBlocking === 0,
|
|
596
|
-
elegant: !designWeak && !base?.dirtyBaselineRisk && !designSmellsOpenEdges && activeBlocking === 0,
|
|
621
|
+
finished: !unfinished && wholeTreeGoverned && !designWeak && !cohesionResidual && activeBlocking === 0,
|
|
622
|
+
elegant: !designWeak && !cohesionResidual && !base?.dirtyBaselineRisk && !designSmellsOpenEdges && activeBlocking === 0,
|
|
597
623
|
wholeTreeGuarantee:
|
|
598
624
|
wholeTreeGoverned &&
|
|
599
625
|
!designWeak &&
|
|
@@ -656,6 +682,7 @@ export function computeDoctorEnforcementHonesty({
|
|
|
656
682
|
stewardNudge,
|
|
657
683
|
nativeFailClosed,
|
|
658
684
|
nativeFailClosedPolicy,
|
|
685
|
+
physicalCohesionResidual,
|
|
659
686
|
} = {}) {
|
|
660
687
|
const coverageHonesty = buildCoverageHonesty({
|
|
661
688
|
percent: governedPercent,
|
|
@@ -713,6 +740,7 @@ export function computeDoctorEnforcementHonesty({
|
|
|
713
740
|
considerMergeBoundary: true,
|
|
714
741
|
emptyStewards,
|
|
715
742
|
stewardNudge,
|
|
743
|
+
physicalCohesionResidual,
|
|
716
744
|
});
|
|
717
745
|
return {
|
|
718
746
|
coverageHonesty,
|
|
@@ -27,6 +27,9 @@ export function upgradeUsage() {
|
|
|
27
27
|
arkgate upgrade --apply apply the previewed bytes (needs --plan-digest when applying managed files)
|
|
28
28
|
|
|
29
29
|
Customized files stay unless you pass --accept-conflicts or --refresh-skills.
|
|
30
|
+
A gitignored missing file (absent-local) is recreated without that flag.
|
|
31
|
+
Exit: 0 applied and green; 3 applied but red; 2 blocked (consent); 1 error.
|
|
32
|
+
Scripts comparing against 1 must switch to non-zero.
|
|
30
33
|
Then: arkgate-check --doctor
|
|
31
34
|
|
|
32
35
|
Every flag: arkgate --help --all
|
|
@@ -77,8 +80,11 @@ Options:
|
|
|
77
80
|
--no-install Skip adding/installing arkgate as a project devDependency (start/upgrade).
|
|
78
81
|
--apply Apply a start plan; for upgrade, update/repreview or apply managed bytes.
|
|
79
82
|
--accept-conflicts
|
|
80
|
-
Allow upgrade to recreate deleted managed
|
|
83
|
+
Allow upgrade to recreate a deleted managed asset that is not gitignored, or replace a recorded conflict.
|
|
84
|
+
A missing gitignored file (absent-local, for example per-machine .mcp.json) is recreated without this flag.
|
|
81
85
|
--plan-digest Digest emitted by an upgrade preview; required to apply managed bytes.
|
|
86
|
+
upgrade --apply exits 0 when applied and the architecture check is green, 3 when applied but red,
|
|
87
|
+
2 when blocked for consent, and 1 on error. Scripts comparing against 1 must switch to non-zero.
|
|
82
88
|
--json Emit the start/upgrade/status/agents-md preview as deterministic machine-readable JSON.
|
|
83
89
|
--write For agents-md: merge the version-matched projection into AGENTS.md.
|
|
84
90
|
--check For agents-md: exit 1 when projection stamp drifts from package version.
|
|
@@ -88,6 +88,13 @@ function ackLifecycleHtml(lc) {
|
|
|
88
88
|
return rows.join('\n');
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function sliceIdentityHtml(section) {
|
|
92
|
+
const collisions = Array.isArray(section?.collisions) ? section.collisions : [];
|
|
93
|
+
if (collisions.length === 0) return '';
|
|
94
|
+
const items = collisions.map((collision) => `<li>${esc(collision.message ?? '')}</li>`).join('');
|
|
95
|
+
return `<section class="section card" data-advisory="sliceIdentity"><h2>Slice identity <span class="muted">(advisory — not a verdict, not a score)</span></h2><ul>${items}</ul><p class="muted">Two prefixes bind as one slice id under <code>sliceIdentity: "stars"</code>. That is the point when the trees are one feature, and a mistake when they are not.</p></section>`;
|
|
96
|
+
}
|
|
97
|
+
|
|
91
98
|
function contractHealthHtml(health) {
|
|
92
99
|
if (!health) return '';
|
|
93
100
|
const smells = Array.isArray(health.smells) ? health.smells : [];
|
|
@@ -347,6 +354,7 @@ export function renderAdvisorySections(advisories, escape) {
|
|
|
347
354
|
improvementCompassHtml(advisories.improvementCompass),
|
|
348
355
|
deepModuleCoachHtml(advisories.deepModuleCoach),
|
|
349
356
|
stewardNudgeHtml(advisories.stewardNudge),
|
|
357
|
+
sliceIdentityHtml(advisories.sliceIdentity),
|
|
350
358
|
contractHealthHtml(advisories.contractHealth),
|
|
351
359
|
ambientStateHtml(advisories.ambientState),
|
|
352
360
|
physicalCohesionHtml(advisories.physicalCohesion),
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
summarizeDesignFitness,
|
|
9
9
|
buildPatternBetsFromSmells,
|
|
10
10
|
} from './design-smells.mjs';
|
|
11
|
-
import { summarizePilotLoop } from './pilot-loop.mjs';
|
|
11
|
+
import { collectPilotCandidates, summarizePilotLoop } from './pilot-loop.mjs';
|
|
12
12
|
import { buildPostGreenNextAction } from './post-green-path.mjs';
|
|
13
13
|
import { loadGoldenPattern, summarizeGoldenPattern } from './golden-pattern.mjs';
|
|
14
14
|
import { collectAdoptionGaps } from './mcp-adoption.mjs';
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
buildBaselineHonesty,
|
|
18
18
|
buildWritePathHonesty,
|
|
19
19
|
buildProductHonesty,
|
|
20
|
+
physicalCohesionResidualRemains,
|
|
20
21
|
} from './enforcement-honesty.mjs';
|
|
21
22
|
import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
|
|
22
23
|
import { attachExtraDoctorSections } from './doctor-advisories.mjs';
|
|
@@ -25,6 +26,7 @@ import { describePackageVersionDualTruth } from './field-install.mjs';
|
|
|
25
26
|
import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
|
|
26
27
|
import { buildDeepModuleCoachAdvisory } from './deep-module-coach.mjs';
|
|
27
28
|
import { computePhysicalCohesion } from './physical-cohesion.mjs';
|
|
29
|
+
import { computeReshapeDecisionMemory } from './reshape-decisions.mjs';
|
|
28
30
|
import { POST_GREEN_LEDE, operatingModeTitle } from './product-copy.mjs';
|
|
29
31
|
|
|
30
32
|
function esc(value) {
|
|
@@ -74,11 +76,14 @@ export function buildReportDepthPayload(
|
|
|
74
76
|
});
|
|
75
77
|
const postGreenPath = buildPostGreenNextAction(designFitness);
|
|
76
78
|
const patternBets = buildPatternBetsFromSmells(designSmells);
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const physicalCohesion = computePhysicalCohesion(root, files);
|
|
80
|
+
physicalCohesion.reshapeDecisions = computeReshapeDecisionMemory(root, files).summary;
|
|
81
|
+
const pilotLoop = summarizePilotLoop(
|
|
82
|
+
collectPilotCandidates(
|
|
83
|
+
{ ...designFitness, patternBets, designSmells },
|
|
84
|
+
{ physicalCohesion }
|
|
85
|
+
)
|
|
86
|
+
);
|
|
82
87
|
const goldenPattern = summarizeGoldenPattern(loadGoldenPattern(root));
|
|
83
88
|
const adoption = collectAdoptionGaps(root, config, coverage);
|
|
84
89
|
const baseline = readBaseline(root, '.ark-baseline.json');
|
|
@@ -169,9 +174,9 @@ export function buildReportDepthPayload(
|
|
|
169
174
|
: null,
|
|
170
175
|
primaryNextAction: postGreenPath?.action ?? dualTruthNext,
|
|
171
176
|
activeBlockingViolations: activeBlockingCount,
|
|
177
|
+
physicalCohesionResidual: physicalCohesionResidualRemains(physicalCohesion),
|
|
172
178
|
});
|
|
173
179
|
// Doctor parity: same physical-cohesion + baseline stale facts as runDoctor.
|
|
174
|
-
const physicalCohesion = computePhysicalCohesion(root, files);
|
|
175
180
|
const baselineStale =
|
|
176
181
|
typeof baselineSplit.baselineStale === 'number' ? baselineSplit.baselineStale : null;
|
|
177
182
|
// Improvement compass — same projection as doctor; notAScore; never a gate input.
|