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.
Files changed (75) hide show
  1. package/CHANGELOG.md +124 -5
  2. package/README.md +107 -117
  3. package/bin/ark-check-runtime.mjs +3 -4
  4. package/bin/ark-layer-match.mjs +110 -18
  5. package/bin/lib/analysis-engine.mjs +13 -6
  6. package/bin/lib/config-contract.mjs +5 -0
  7. package/bin/lib/diagnostic-catalog.mjs +1 -0
  8. package/bin/lib/doctor-advisories.mjs +20 -0
  9. package/bin/lib/doctor-human.mjs +11 -9
  10. package/bin/lib/doctor-plan.mjs +15 -14
  11. package/bin/lib/first-run-help.mjs +7 -1
  12. package/bin/lib/html-report-advisories.mjs +8 -0
  13. package/bin/lib/html-report-depth.mjs +9 -8
  14. package/bin/lib/invariant-coverage.mjs +396 -75
  15. package/bin/lib/managed-upgrade.mjs +59 -14
  16. package/bin/lib/pilot-loop.mjs +183 -37
  17. package/bin/lib/upgrade-behavior-changes.mjs +83 -0
  18. package/bin/lib/upgrade-command.mjs +69 -31
  19. package/bin/lib/upgrade-outcome.mjs +359 -0
  20. package/bin/lib/violations.mjs +109 -0
  21. package/dist/{configTypes-VVknqcTJ.d.ts → configTypes-CTELo87-.d.ts} +7 -0
  22. package/dist/{diagnosticCatalog-BPYWNyJR.d.ts → diagnosticCatalog-B7BXlet6.d.ts} +73 -5
  23. package/dist/eslint/index.cjs +4 -4
  24. package/dist/eslint/index.d.ts +26 -3
  25. package/dist/eslint/index.js +4 -4
  26. package/dist/index.cjs +24 -17
  27. package/dist/index.d.ts +9 -4
  28. package/dist/index.js +25 -18
  29. package/dist/nestjs/index.cjs +9 -9
  30. package/dist/nestjs/index.d.ts +3 -3
  31. package/dist/nestjs/index.js +9 -9
  32. package/dist/runtime/index.cjs +18 -11
  33. package/dist/runtime/index.d.ts +6 -6
  34. package/dist/runtime/index.js +18 -11
  35. package/dist/{types-p_fCp6P-.d.ts → types-fQ9-KrBj.d.ts} +1 -1
  36. package/dist/{types-Cs-46DRu.d.ts → types-yDNEQ603.d.ts} +1 -1
  37. package/docs/README.md +4 -3
  38. package/docs/agent-guide.md +15 -7
  39. package/docs/brownfield-adoption.md +4 -3
  40. package/docs/configuration.md +34 -11
  41. package/docs/diagnostics.md +12 -2
  42. package/docs/package-surface.md +3 -2
  43. package/docs/product-voice.md +11 -3
  44. package/package.json +1 -1
  45. package/schemas/ark.config.schema.json +8 -0
  46. package/server.json +2 -2
  47. package/templates/agent-skills/README.md +1 -1
  48. package/templates/agent-skills/ark-adopt/SKILL.md +5 -2
  49. package/templates/agent-skills/ark-architect/SKILL.md +2 -0
  50. package/templates/agent-skills/ark-autopilot/SKILL.md +6 -4
  51. package/templates/agent-skills/ark-contract/SKILL.md +2 -0
  52. package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
  53. package/templates/agent-skills/ark-explain/SKILL.md +2 -0
  54. package/templates/agent-skills/ark-explore/SKILL.md +4 -2
  55. package/templates/agent-skills/ark-fix/SKILL.md +2 -0
  56. package/templates/agent-skills/ark-loop/SKILL.md +2 -0
  57. package/templates/agent-skills/ark-order/SKILL.md +2 -0
  58. package/templates/agent-skills/ark-place/SKILL.md +2 -0
  59. package/templates/agent-skills/ark-runtime/SKILL.md +2 -0
  60. package/templates/agent-skills/ark-think/SKILL.md +2 -0
  61. package/templates/agent-skills/ark-upgrade/SKILL.md +20 -7
  62. package/templates/skills/ark-adopt.md +5 -2
  63. package/templates/skills/ark-architect.md +2 -0
  64. package/templates/skills/ark-autopilot.md +6 -4
  65. package/templates/skills/ark-contract.md +2 -0
  66. package/templates/skills/ark-coverage.md +2 -0
  67. package/templates/skills/ark-explain.md +2 -0
  68. package/templates/skills/ark-explore.md +4 -2
  69. package/templates/skills/ark-fix.md +2 -0
  70. package/templates/skills/ark-loop.md +2 -0
  71. package/templates/skills/ark-order.md +2 -0
  72. package/templates/skills/ark-place.md +2 -0
  73. package/templates/skills/ark-runtime.md +2 -0
  74. package/templates/skills/ark-think.md +2 -0
  75. package/templates/skills/ark-upgrade.md +20 -7
@@ -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',
@@ -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('');
@@ -247,8 +255,12 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
247
255
  coverage: cov,
248
256
  files,
249
257
  });
258
+ const sliceIdentityHits = sliceIdentityCollisions(rules ?? config?.rules);
250
259
  return {
251
260
  ...(prototypeShortcuts ? { prototypeShortcuts } : {}),
261
+ ...(sliceIdentityHits.length > 0
262
+ ? { sliceIdentity: { notAScore: true, collisions: sliceIdentityHits } }
263
+ : {}),
252
264
  contractHealth: computeContractHealth(root, config, cov, rules),
253
265
  ambientState: computeAmbientState(ts, root, config, files),
254
266
  physicalCohesion,
@@ -265,6 +277,14 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
265
277
  }
266
278
 
267
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
+ }
268
288
  printContractHealthSection(advisories.contractHealth, io);
269
289
  printAmbientStateSection(advisories.ambientState, io);
270
290
  printPhysicalCohesionSection(
@@ -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
- if (pilotLoop?.active && pilotLoop.nextPilot) {
302
- const np = pilotLoop.nextPilot;
303
- line(
304
- warn,
305
- `Next pilot (one at a time): ${np.pilotTarget || np.pilot} [${np.smellId}] → re-doctor after change`
306
- );
307
- line(' ', color.dim(`success: ${np.successSignal}`));
308
- line(' ', color.dim('never multi-pilot batch; pattern bets are never auto-applied'));
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) {
@@ -66,7 +66,7 @@ import {
66
66
  loadGoldenPattern,
67
67
  summarizeGoldenPattern,
68
68
  } from './golden-pattern.mjs';
69
- import { summarizePilotLoop } from './pilot-loop.mjs';
69
+ import { collectPilotCandidates, summarizePilotLoop } from './pilot-loop.mjs';
70
70
  import { computeDoctorAdvisories } from './doctor-advisories.mjs';
71
71
  import { ANALYSIS_COMPLETENESS, analysisIncompleteStatement, normalizeAnalysisCompleteness } from './analysis-completeness.mjs';
72
72
  import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
@@ -325,12 +325,8 @@ export function buildRemediationPlan(
325
325
  governedPercent,
326
326
  totalFiles,
327
327
  });
328
- // Q04 — single next pilot extraction card (one at a time → re-doctor).
329
- const pilotLoop = summarizePilotLoop({
330
- designWeak,
331
- patternBets,
332
- designSmells,
333
- });
328
+ // Q04 — one candidate at a time. Plan has no cohesion advisory.
329
+ const pilotLoop = summarizePilotLoop(collectPilotCandidates({ designWeak, patternBets, designSmells }));
334
330
  const coverageHonesty = buildCoverageHonesty({
335
331
  percent: governedPercent,
336
332
  totalFiles,
@@ -604,13 +600,6 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
604
600
  const goldenPattern = summarizeGoldenPattern(goldenLoad);
605
601
  // Y06 — pure-layer opt-in when golden names pure modules but no pure:true layer.
606
602
  const pureLayerOptIn = computePureLayerOptInNudge(config, goldenLoad);
607
- // Q04 — one next pilot (extraction card) when design-weak.
608
- const patternBetsForLoop = buildPatternBetsFromSmells(designSmells);
609
- const pilotLoop = summarizePilotLoop({
610
- designWeak: designFitness.designWeak,
611
- patternBets: patternBetsForLoop,
612
- designSmells,
613
- });
614
603
  const activeViolations = baseline.exists
615
604
  ? violations.filter((_, index) => !baseline.keys.has(occurrenceKeys[index]))
616
605
  : violations;
@@ -625,6 +614,17 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
625
614
  options.facts ?? options.architectureFacts,
626
615
  activeViolations
627
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
+ );
628
628
  const layerOwners = collectLayerOwnerResidual(config);
629
629
  const rulesUnderContract = doctorAdvisories.rulesUnderContract;
630
630
  const arkRun = doctorAdvisories.arkRun;
@@ -965,6 +965,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
965
965
  activeCount,
966
966
  skillGaps,
967
967
  agentHomeGaps,
968
+ warnings: options.warnings,
968
969
  baseline,
969
970
  baselineHonesty,
970
971
  staleBaseline,
@@ -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 assets or replace recorded conflicts.
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';
@@ -76,11 +76,14 @@ export function buildReportDepthPayload(
76
76
  });
77
77
  const postGreenPath = buildPostGreenNextAction(designFitness);
78
78
  const patternBets = buildPatternBetsFromSmells(designSmells);
79
- const pilotLoop = summarizePilotLoop({
80
- designWeak: designFitness.designWeak,
81
- patternBets,
82
- designSmells,
83
- });
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
+ );
84
87
  const goldenPattern = summarizeGoldenPattern(loadGoldenPattern(root));
85
88
  const adoption = collectAdoptionGaps(root, config, coverage);
86
89
  const baseline = readBaseline(root, '.ark-baseline.json');
@@ -155,8 +158,6 @@ export function buildReportDepthPayload(
155
158
  : packageVersionTruth?.code === 'PACKAGE_PIN_ABSENT'
156
159
  ? 'Add arkgate to package.json and install so CI/npx resolve this CLI (PACKAGE_PIN_ABSENT)'
157
160
  : null;
158
- const physicalCohesion = computePhysicalCohesion(root, files);
159
- physicalCohesion.reshapeDecisions = computeReshapeDecisionMemory(root, files).summary;
160
161
  const productHonesty = buildProductHonesty({
161
162
  coverageHonesty,
162
163
  baselineHonesty,