arkgate 4.8.23 → 4.8.25
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 +153 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +41 -91
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +707 -121
- package/bin/ark-mcp-runtime.mjs +448 -169
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +42 -22
- package/bin/ark.mjs +65 -97
- package/bin/lib/adapter-contract-types.mjs +5 -0
- package/bin/lib/adapter-contract.mjs +15 -0
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +9 -5
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +61 -15
- package/bin/lib/ark-order-doctor.mjs +4 -29
- package/bin/lib/ark-order-facts.mjs +319 -17
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +3 -2
- package/bin/lib/ark-run-doctor.mjs +17 -28
- package/bin/lib/ark-run-facts.mjs +461 -48
- package/bin/lib/ark-run-sensors.mjs +6 -4
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +55 -252
- package/bin/lib/check-args.mjs +85 -1
- package/bin/lib/ci-and-commands.mjs +24 -78
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +5 -3
- package/bin/lib/config-contract-slices.mjs +739 -0
- package/bin/lib/config-contract.mjs +42 -607
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +23 -9
- package/bin/lib/doctor-advisories.mjs +78 -8
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +19 -4
- package/bin/lib/doctor-plan.mjs +9 -13
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +282 -83
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +10 -5
- package/bin/lib/flat-parent-importers.mjs +139 -0
- package/bin/lib/flat-parent-pilot.mjs +40 -100
- package/bin/lib/gate-files.mjs +58 -90
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +166 -29
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +17 -15
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +85 -11
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +13 -6
- package/bin/lib/mcp-hook-payload.mjs +403 -63
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +60 -2
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +106 -0
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +219 -14
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +115 -7
- package/bin/lib/prepare-write.mjs +2 -2
- package/bin/lib/presets.mjs +59 -73
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +31 -62
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +405 -117
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +247 -9
- package/bin/lib/scan-files.mjs +36 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/start-install-recovery.mjs +72 -2
- package/bin/lib/start-preview.mjs +31 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +35 -22
- package/bin/lib/status-manifest.mjs +50 -30
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +76 -88
- package/bin/lib/team-parliament.mjs +21 -10
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +6 -4
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-AHACEKSV.js +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/chunk-UN5EH4QV.cjs +16 -0
- package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
- package/dist/configTypes-GPvvdleM.d.ts +257 -0
- package/dist/eslint/index.cjs +18 -5
- package/dist/eslint/index.d.cts +368 -0
- package/dist/eslint/index.d.ts +139 -156
- package/dist/eslint/index.js +11 -5
- package/dist/index.cjs +36 -35
- package/dist/index.d.cts +5547 -0
- package/dist/index.d.ts +3283 -198
- package/dist/index.js +36 -35
- package/dist/inspectorListen-6DQVJ4DC.js +1 -0
- package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
- package/dist/nestjs/index.cjs +1 -18
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +1 -3
- package/dist/nestjs/index.js +1 -18
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.d.cts +155 -0
- package/dist/order/index.d.ts +15 -3
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +13 -30
- package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
- package/dist/runtime/index.d.ts +2741 -133
- package/dist/runtime/index.js +13 -30
- package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
- package/dist/types-CnauMQQL.d.ts +1800 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +217 -16
- package/docs/ai-gates.md +174 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +230 -31
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +212 -19
- package/docs/package-surface.md +35 -9
- package/docs/product-voice.md +7 -1
- package/package.json +69 -19
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +77 -12
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +22 -0
- package/server.json +3 -9
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
- package/templates/arkrules/PersistenceAdapters.json +1 -1
- package/templates/arkrules/PresentationAdapters.json +1 -1
- package/templates/skills/ark-adopt.md +5 -1
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-place.md +8 -0
- package/templates/skills/ark-runtime.md +6 -1
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -346
- package/dist/types-Ik_OakcI.d.ts +0 -183
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/invariantProbe.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/invariant-probe.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Versioned, closed operator set. Changing an operator changes this id. */
|
|
12
|
+
export const INVARIANT_PROBE_OPERATOR_SET = 'ip-ops@1';
|
|
13
|
+
const INVARIANT_PROBE_SCHEMA_VERSION = '1.0';
|
|
14
|
+
const INVARIANT_PROBE_KIND = 'arkgate-invariant-probe';
|
|
15
|
+
/**
|
|
16
|
+
* Project-relative path of the persisted artifact (`--write`).
|
|
17
|
+
*/
|
|
18
|
+
export const INVARIANT_PROBE_ARTIFACT_PATH = '.ark/invariant-probe.json';
|
|
19
|
+
const INVARIANT_PROBE_MAX_MUTANTS = 3;
|
|
20
|
+
/**
|
|
21
|
+
* Targets per run; truncation is stated.
|
|
22
|
+
*/
|
|
23
|
+
export const INVARIANT_PROBE_MAX_TARGETS = 25;
|
|
24
|
+
/**
|
|
25
|
+
* Covering tests per target.
|
|
26
|
+
*/
|
|
27
|
+
export const INVARIANT_PROBE_MAX_TESTS = 8;
|
|
28
|
+
/** Reader bounds for the committed artifact. */
|
|
29
|
+
const INVARIANT_PROBE_MAX_ROWS = 500;
|
|
30
|
+
/**
|
|
31
|
+
* Largest artifact the reader opens.
|
|
32
|
+
*/
|
|
33
|
+
export const INVARIANT_PROBE_MAX_BYTES = 1024 * 1024;
|
|
34
|
+
const INVARIANT_PROBE_LOAD_MARKER = 'ARK_PROBE_LOAD';
|
|
35
|
+
const INVARIANT_PROBE_REACH_MARKER = 'ARK_PROBE_REACH';
|
|
36
|
+
/** Priority order: guard-family operators first, constants last. */
|
|
37
|
+
export const INVARIANT_PROBE_OPERATORS = [
|
|
38
|
+
'negate-guard',
|
|
39
|
+
'drop-throw',
|
|
40
|
+
'flip-comparison',
|
|
41
|
+
'boundary-shift',
|
|
42
|
+
'const-shift',
|
|
43
|
+
];
|
|
44
|
+
export const INVARIANT_PROBE_VERDICTS = [
|
|
45
|
+
'killed',
|
|
46
|
+
'survived',
|
|
47
|
+
'not-reached',
|
|
48
|
+
'inconclusive',
|
|
49
|
+
'unprobeable',
|
|
50
|
+
];
|
|
51
|
+
export const INVARIANT_PROBE_REASONS = [
|
|
52
|
+
'all-killed',
|
|
53
|
+
'mutant-survived',
|
|
54
|
+
'reach-canary-survived',
|
|
55
|
+
'baseline-red',
|
|
56
|
+
'baseline-timeout',
|
|
57
|
+
'runner-error',
|
|
58
|
+
'file-not-loaded',
|
|
59
|
+
'mutant-runtime-error',
|
|
60
|
+
'no-symbol',
|
|
61
|
+
'declaration-only',
|
|
62
|
+
'symbol-not-found',
|
|
63
|
+
'no-covering-test',
|
|
64
|
+
'no-site',
|
|
65
|
+
];
|
|
66
|
+
/** What a single run of the covering tests showed. */
|
|
67
|
+
export const INVARIANT_PROBE_RUN_STATUSES = [
|
|
68
|
+
'killed',
|
|
69
|
+
'survived',
|
|
70
|
+
'timeout',
|
|
71
|
+
'runtime-error',
|
|
72
|
+
'invalid',
|
|
73
|
+
];
|
|
74
|
+
export const INVARIANT_PROBE_BASELINE_STATUSES = ['green', 'red', 'timeout', 'runtime-error'];
|
|
75
|
+
const FLIP = {
|
|
76
|
+
'<': '>=',
|
|
77
|
+
'>=': '<',
|
|
78
|
+
'>': '<=',
|
|
79
|
+
'<=': '>',
|
|
80
|
+
'===': '!==',
|
|
81
|
+
'!==': '===',
|
|
82
|
+
'==': '!=',
|
|
83
|
+
'!=': '==',
|
|
84
|
+
};
|
|
85
|
+
const BOUNDARY = {
|
|
86
|
+
'<': '<=',
|
|
87
|
+
'<=': '<',
|
|
88
|
+
'>': '>=',
|
|
89
|
+
'>=': '>',
|
|
90
|
+
};
|
|
91
|
+
const PLAIN_NUMBER = /^[0-9][0-9_]*(?:\.[0-9_]+)?$/;
|
|
92
|
+
function shiftedNumber(text) {
|
|
93
|
+
if (!PLAIN_NUMBER.test(text))
|
|
94
|
+
return null;
|
|
95
|
+
const value = Number(text.replace(/_/g, ''));
|
|
96
|
+
if (!Number.isFinite(value))
|
|
97
|
+
return null;
|
|
98
|
+
return String(value + 1);
|
|
99
|
+
}
|
|
100
|
+
/** Every operator that applies to one site, in priority order. */
|
|
101
|
+
export function mutantsForSite(site) {
|
|
102
|
+
const make = (operator, replacement) => ({
|
|
103
|
+
id: `${operator}@${site.line}:${site.column}`,
|
|
104
|
+
operator,
|
|
105
|
+
start: site.start,
|
|
106
|
+
end: site.end,
|
|
107
|
+
line: site.line,
|
|
108
|
+
column: site.column,
|
|
109
|
+
original: site.text,
|
|
110
|
+
replacement,
|
|
111
|
+
});
|
|
112
|
+
switch (site.kind) {
|
|
113
|
+
case 'guard':
|
|
114
|
+
return [make('negate-guard', `!(${site.text})`)];
|
|
115
|
+
case 'throw':
|
|
116
|
+
return [make('drop-throw', ';')];
|
|
117
|
+
case 'comparison': {
|
|
118
|
+
const out = [];
|
|
119
|
+
const flip = FLIP[site.text];
|
|
120
|
+
if (flip)
|
|
121
|
+
out.push(make('flip-comparison', flip));
|
|
122
|
+
const boundary = BOUNDARY[site.text];
|
|
123
|
+
if (boundary)
|
|
124
|
+
out.push(make('boundary-shift', boundary));
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
case 'numeric': {
|
|
128
|
+
const shifted = shiftedNumber(site.text);
|
|
129
|
+
return shifted === null ? [] : [make('const-shift', shifted)];
|
|
130
|
+
}
|
|
131
|
+
case 'boolean':
|
|
132
|
+
if (site.text === 'true')
|
|
133
|
+
return [make('const-shift', 'false')];
|
|
134
|
+
if (site.text === 'false')
|
|
135
|
+
return [make('const-shift', 'true')];
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Deterministic selection: for each operator in priority order, the first site
|
|
141
|
+
* (by source position) that yields it, up to `max` mutants. Input order does
|
|
142
|
+
* not matter.
|
|
143
|
+
*/
|
|
144
|
+
export function planMutants(sites, max = INVARIANT_PROBE_MAX_MUTANTS) {
|
|
145
|
+
const ordered = [...sites].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
146
|
+
const candidates = ordered.flatMap((site) => mutantsForSite(site));
|
|
147
|
+
const planned = [];
|
|
148
|
+
for (const operator of INVARIANT_PROBE_OPERATORS) {
|
|
149
|
+
if (planned.length >= max)
|
|
150
|
+
break;
|
|
151
|
+
const first = candidates.find((mutant) => mutant.operator === operator);
|
|
152
|
+
if (first)
|
|
153
|
+
planned.push(first);
|
|
154
|
+
}
|
|
155
|
+
return planned;
|
|
156
|
+
}
|
|
157
|
+
/** Pure text splice. */
|
|
158
|
+
export function applyEdit(text, edit) {
|
|
159
|
+
return `${text.slice(0, edit.start)}${edit.replacement}${text.slice(edit.end)}`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The two wiring canaries (ADR 0039 D4). The load canary throws at the top of
|
|
163
|
+
* the file (after a shebang). The reach canary throws as the first statement of
|
|
164
|
+
* the symbol; for a `const`, the initializer becomes a throwing call, so for a
|
|
165
|
+
* constant "reached" and "loaded" are the same event.
|
|
166
|
+
*/
|
|
167
|
+
export function canaryEdits(text, body) {
|
|
168
|
+
const shebangEnd = text.startsWith('#!') ? text.indexOf('\n') + 1 || text.length : 0;
|
|
169
|
+
const load = {
|
|
170
|
+
start: shebangEnd,
|
|
171
|
+
end: shebangEnd,
|
|
172
|
+
replacement: `throw new Error('${INVARIANT_PROBE_LOAD_MARKER}');\n`,
|
|
173
|
+
};
|
|
174
|
+
const reachThrow = `throw new Error('${INVARIANT_PROBE_REACH_MARKER}');`;
|
|
175
|
+
let reach;
|
|
176
|
+
switch (body.kind) {
|
|
177
|
+
case 'block':
|
|
178
|
+
reach = { start: body.insertAt, end: body.insertAt, replacement: ` ${reachThrow}` };
|
|
179
|
+
break;
|
|
180
|
+
case 'expression':
|
|
181
|
+
reach = { start: body.start, end: body.end, replacement: `{ ${reachThrow} }` };
|
|
182
|
+
break;
|
|
183
|
+
case 'initializer':
|
|
184
|
+
reach = { start: body.start, end: body.end, replacement: `(() => { ${reachThrow} })()` };
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
return { load, reach };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* One verdict from the wiring checks and the mutant runs (ADR 0039 D4/D5).
|
|
191
|
+
* A timeout on a mutant counts as caught (the change was detected); a runner
|
|
192
|
+
* crash with no reported failure is never counted as caught.
|
|
193
|
+
*/
|
|
194
|
+
export function foldVerdict(wiring, mutants) {
|
|
195
|
+
if (wiring.baseline === 'timeout')
|
|
196
|
+
return { verdict: 'inconclusive', reason: 'baseline-timeout' };
|
|
197
|
+
if (wiring.baseline === 'runtime-error')
|
|
198
|
+
return { verdict: 'inconclusive', reason: 'runner-error' };
|
|
199
|
+
if (wiring.baseline !== 'green')
|
|
200
|
+
return { verdict: 'inconclusive', reason: 'baseline-red' };
|
|
201
|
+
if (wiring.load === 'survived')
|
|
202
|
+
return { verdict: 'inconclusive', reason: 'file-not-loaded' };
|
|
203
|
+
if (wiring.load !== 'killed' && wiring.load !== 'timeout') {
|
|
204
|
+
return { verdict: 'inconclusive', reason: 'runner-error' };
|
|
205
|
+
}
|
|
206
|
+
if (wiring.reach === 'survived')
|
|
207
|
+
return { verdict: 'not-reached', reason: 'reach-canary-survived' };
|
|
208
|
+
if (wiring.reach !== 'killed' && wiring.reach !== 'timeout') {
|
|
209
|
+
return { verdict: 'inconclusive', reason: 'runner-error' };
|
|
210
|
+
}
|
|
211
|
+
const valid = mutants.filter((mutant) => mutant.status !== 'invalid');
|
|
212
|
+
if (valid.length === 0)
|
|
213
|
+
return { verdict: 'unprobeable', reason: 'no-site' };
|
|
214
|
+
if (valid.some((mutant) => mutant.status === 'survived')) {
|
|
215
|
+
return { verdict: 'survived', reason: 'mutant-survived' };
|
|
216
|
+
}
|
|
217
|
+
if (valid.some((mutant) => mutant.status === 'runtime-error')) {
|
|
218
|
+
return { verdict: 'inconclusive', reason: 'mutant-runtime-error' };
|
|
219
|
+
}
|
|
220
|
+
return { verdict: 'killed', reason: 'all-killed' };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Canonical identity text for an invariant. `mode` is left out on purpose:
|
|
224
|
+
* promoting the rule must not make its own evidence stale (ADR 0039 D3).
|
|
225
|
+
* Tooling hashes this text.
|
|
226
|
+
*/
|
|
227
|
+
export function invariantProbeIdentity(invariant) {
|
|
228
|
+
return JSON.stringify([
|
|
229
|
+
invariant.id,
|
|
230
|
+
invariant.coverage?.symbol ?? null,
|
|
231
|
+
invariant.coverage?.test ?? null,
|
|
232
|
+
]);
|
|
233
|
+
}
|
|
234
|
+
function probeTotals(rows) {
|
|
235
|
+
const count = (verdict) => rows.filter((row) => row.verdict === verdict).length;
|
|
236
|
+
return {
|
|
237
|
+
probed: rows.filter((row) => row.verdict !== 'unprobeable').length,
|
|
238
|
+
killed: count('killed'),
|
|
239
|
+
survived: count('survived'),
|
|
240
|
+
notReached: count('not-reached'),
|
|
241
|
+
inconclusive: count('inconclusive'),
|
|
242
|
+
unprobeable: count('unprobeable'),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/** True when a fresh row of this verdict refuses promotion (ADR 0039 D2). */
|
|
246
|
+
export function verdictRefusesPromotion(verdict) {
|
|
247
|
+
return verdict === 'survived' || verdict === 'not-reached';
|
|
248
|
+
}
|
|
249
|
+
/** Command exit code for a finished run: 1 when any row would refuse promotion. */
|
|
250
|
+
export function probeExitCode(rows) {
|
|
251
|
+
return rows.some((row) => verdictRefusesPromotion(row.verdict)) ? 1 : 0;
|
|
252
|
+
}
|
|
253
|
+
export function buildInvariantProbeArtifact(input) {
|
|
254
|
+
const invariants = [...input.rows].sort((a, b) => a.invariantId < b.invariantId ? -1 : a.invariantId > b.invariantId ? 1 : 0);
|
|
255
|
+
return {
|
|
256
|
+
schemaVersion: INVARIANT_PROBE_SCHEMA_VERSION,
|
|
257
|
+
kind: INVARIANT_PROBE_KIND,
|
|
258
|
+
notAScore: true,
|
|
259
|
+
arkgateVersion: input.arkgateVersion,
|
|
260
|
+
operatorSet: INVARIANT_PROBE_OPERATOR_SET,
|
|
261
|
+
runner: input.runner,
|
|
262
|
+
probedOn: input.probedOn,
|
|
263
|
+
invariants,
|
|
264
|
+
totals: probeTotals(invariants),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/** Why a recorded row no longer describes the tree. Empty: fresh. */
|
|
268
|
+
export function probeRowStaleness(row, current, operatorSet = INVARIANT_PROBE_OPERATOR_SET) {
|
|
269
|
+
const reasons = [];
|
|
270
|
+
if (operatorSet !== INVARIANT_PROBE_OPERATOR_SET)
|
|
271
|
+
reasons.push('the operator set changed');
|
|
272
|
+
if (current.invariantHash === null)
|
|
273
|
+
reasons.push('the invariant is no longer declared');
|
|
274
|
+
else if (current.invariantHash !== row.invariantHash)
|
|
275
|
+
reasons.push('the invariant changed');
|
|
276
|
+
if (row.symbolFile !== null && row.symbolFileHash !== null && current.symbolFileHash !== row.symbolFileHash) {
|
|
277
|
+
reasons.push(`${row.symbolFile} changed`);
|
|
278
|
+
}
|
|
279
|
+
for (const test of row.tests) {
|
|
280
|
+
if (current.testHashes[test.path] !== test.contentHash)
|
|
281
|
+
reasons.push(`${test.path} changed`);
|
|
282
|
+
}
|
|
283
|
+
return reasons;
|
|
284
|
+
}
|
|
285
|
+
export function summarizeProbeForCoverage(row, staleBecause, probedOn) {
|
|
286
|
+
return {
|
|
287
|
+
verdict: row.verdict,
|
|
288
|
+
reason: row.reason,
|
|
289
|
+
fresh: staleBecause.length === 0,
|
|
290
|
+
survivors: row.mutants
|
|
291
|
+
.filter((mutant) => mutant.status === 'survived')
|
|
292
|
+
.map((mutant) => ({
|
|
293
|
+
operator: mutant.operator,
|
|
294
|
+
line: mutant.line,
|
|
295
|
+
original: mutant.original,
|
|
296
|
+
replacement: mutant.replacement,
|
|
297
|
+
})),
|
|
298
|
+
...(staleBecause.length > 0 ? { staleBecause: [...staleBecause] } : {}),
|
|
299
|
+
...(row.symbolFile ? { symbolFile: row.symbolFile } : {}),
|
|
300
|
+
...(probedOn ? { probedOn } : {}),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/** Short state for status and inventory lines. */
|
|
304
|
+
export function probeStatusLabel(summary) {
|
|
305
|
+
if (!summary)
|
|
306
|
+
return 'not run';
|
|
307
|
+
if (!summary.fresh)
|
|
308
|
+
return 'stale';
|
|
309
|
+
return summary.verdict;
|
|
310
|
+
}
|
|
311
|
+
function changeText(mutant) {
|
|
312
|
+
switch (mutant.operator) {
|
|
313
|
+
case 'negate-guard':
|
|
314
|
+
return 'the guard is negated';
|
|
315
|
+
case 'drop-throw':
|
|
316
|
+
return 'the throw is removed';
|
|
317
|
+
case 'flip-comparison':
|
|
318
|
+
return `\`${mutant.original}\` becomes \`${mutant.replacement}\``;
|
|
319
|
+
case 'boundary-shift':
|
|
320
|
+
return `the boundary moves (\`${mutant.original}\` becomes \`${mutant.replacement}\`)`;
|
|
321
|
+
case 'const-shift':
|
|
322
|
+
return `\`${mutant.original}\` becomes \`${mutant.replacement}\``;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/** One plain sentence per row (product voice; no score). */
|
|
326
|
+
export function probeRowLine(row) {
|
|
327
|
+
const id = row.invariantId;
|
|
328
|
+
const symbol = row.symbol ?? 'the symbol';
|
|
329
|
+
const file = row.symbolFile ?? row.sourceFile ?? 'its file';
|
|
330
|
+
switch (row.reason) {
|
|
331
|
+
case 'all-killed':
|
|
332
|
+
return `${id}: a covering test fails for every change made to ${symbol} (${file}).`;
|
|
333
|
+
case 'mutant-survived': {
|
|
334
|
+
const survivor = row.mutants.find((mutant) => mutant.status === 'survived');
|
|
335
|
+
const where = survivor ? `${file}:${survivor.line}` : file;
|
|
336
|
+
return `${id}: the tests still pass when ${survivor ? changeText(survivor) : `${symbol} is changed`} (${where}). The test names the rule but does not pin it. Next: add a case that fails when the rule is broken, then re-run with --write.`;
|
|
337
|
+
}
|
|
338
|
+
case 'reach-canary-survived':
|
|
339
|
+
return `${id}: the tests load ${file} but never call ${symbol}. Next: add a test that calls it, then re-run with --write.`;
|
|
340
|
+
case 'baseline-red':
|
|
341
|
+
return `${id}: the covering tests fail before any change, so nothing was probed. Next: make them pass first.`;
|
|
342
|
+
case 'baseline-timeout':
|
|
343
|
+
return `${id}: the covering tests did not finish in time, so nothing was probed.`;
|
|
344
|
+
case 'runner-error':
|
|
345
|
+
return `${id}: the test runner crashed instead of reporting results, so nothing is claimed.`;
|
|
346
|
+
case 'file-not-loaded':
|
|
347
|
+
return `${id}: the tests never load this copy of ${file} (it may come from node_modules), so nothing was probed.`;
|
|
348
|
+
case 'mutant-runtime-error':
|
|
349
|
+
return `${id}: the runner crashed on a change instead of reporting a failure, so nothing is claimed.`;
|
|
350
|
+
case 'no-symbol':
|
|
351
|
+
return `${id}: declares no coverage.symbol, so there is nothing to change.`;
|
|
352
|
+
case 'declaration-only':
|
|
353
|
+
return `${id}: ${symbol} is a declaration with no behavior to change.`;
|
|
354
|
+
case 'symbol-not-found':
|
|
355
|
+
return `${id}: ${symbol} was not found as a function, const or method in ${file}.`;
|
|
356
|
+
case 'no-covering-test':
|
|
357
|
+
return `${id}: no test names it or imports ${file}. A declaration is not a test.`;
|
|
358
|
+
case 'no-site':
|
|
359
|
+
return `${id}: ${symbol} has no guard, throw, comparison or constant to change.`;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** Promotion refusal sentence for a fresh survived / not-reached summary. */
|
|
363
|
+
export function probeRefusalReason(invariantId, summary) {
|
|
364
|
+
if (summary.verdict === 'not-reached') {
|
|
365
|
+
return `Invariant ${invariantId}: the covering tests load the file but never call the declared symbol (mutation probe: not reached); add a test that calls it and re-run --probe-invariants --write before promoting.`;
|
|
366
|
+
}
|
|
367
|
+
const survivor = summary.survivors[0];
|
|
368
|
+
const detail = survivor ? ` (${changeText(survivor)} at line ${survivor.line})` : '';
|
|
369
|
+
return `Invariant ${invariantId}: the mutation probe shows the covering tests do not pin it${detail}; strengthen the test and re-run --probe-invariants --write before promoting.`;
|
|
370
|
+
}
|
|
371
|
+
function isRecord(value) {
|
|
372
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
373
|
+
}
|
|
374
|
+
function isText(value) {
|
|
375
|
+
return typeof value === 'string' && value.length > 0;
|
|
376
|
+
}
|
|
377
|
+
function oneOf(values, value) {
|
|
378
|
+
return typeof value === 'string' && values.includes(value);
|
|
379
|
+
}
|
|
380
|
+
function isNullableText(value) {
|
|
381
|
+
return value === null || isText(value);
|
|
382
|
+
}
|
|
383
|
+
function isRunStatusOrNull(value) {
|
|
384
|
+
return value === null || oneOf(INVARIANT_PROBE_RUN_STATUSES, value);
|
|
385
|
+
}
|
|
386
|
+
function isMutantRecord(value) {
|
|
387
|
+
return (isRecord(value) &&
|
|
388
|
+
isText(value.id) &&
|
|
389
|
+
oneOf(INVARIANT_PROBE_OPERATORS, value.operator) &&
|
|
390
|
+
Number.isInteger(value.line) &&
|
|
391
|
+
Number.isInteger(value.column) &&
|
|
392
|
+
typeof value.original === 'string' &&
|
|
393
|
+
typeof value.replacement === 'string' &&
|
|
394
|
+
oneOf(INVARIANT_PROBE_RUN_STATUSES, value.status) &&
|
|
395
|
+
typeof value.durationMs === 'number');
|
|
396
|
+
}
|
|
397
|
+
function isProbeRow(value) {
|
|
398
|
+
if (!isRecord(value))
|
|
399
|
+
return false;
|
|
400
|
+
const baselineOk = value.baseline === null ||
|
|
401
|
+
(isRecord(value.baseline) &&
|
|
402
|
+
oneOf(INVARIANT_PROBE_BASELINE_STATUSES, value.baseline.status) &&
|
|
403
|
+
typeof value.baseline.durationMs === 'number');
|
|
404
|
+
const wiringOk = value.wiring === null ||
|
|
405
|
+
(isRecord(value.wiring) && isRunStatusOrNull(value.wiring.load) && isRunStatusOrNull(value.wiring.reach));
|
|
406
|
+
return (isText(value.invariantId) &&
|
|
407
|
+
isText(value.invariantHash) &&
|
|
408
|
+
isNullableText(value.layer) &&
|
|
409
|
+
isNullableText(value.sourceFile) &&
|
|
410
|
+
(value.mode === 'advisory' || value.mode === 'enforced') &&
|
|
411
|
+
isNullableText(value.symbol) &&
|
|
412
|
+
isNullableText(value.symbolFile) &&
|
|
413
|
+
isNullableText(value.symbolFileHash) &&
|
|
414
|
+
Array.isArray(value.tests) &&
|
|
415
|
+
value.tests.every((test) => isRecord(test) && isText(test.path) && isText(test.contentHash)) &&
|
|
416
|
+
baselineOk &&
|
|
417
|
+
wiringOk &&
|
|
418
|
+
Array.isArray(value.mutants) &&
|
|
419
|
+
value.mutants.every(isMutantRecord) &&
|
|
420
|
+
oneOf(INVARIANT_PROBE_VERDICTS, value.verdict) &&
|
|
421
|
+
oneOf(INVARIANT_PROBE_REASONS, value.reason));
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Read a parsed artifact. A malformed envelope is refused whole; a malformed
|
|
425
|
+
* row is dropped and counted. Either way a malformed artifact can only change
|
|
426
|
+
* nothing — it never grants anything (ADR 0039 D2).
|
|
427
|
+
*/
|
|
428
|
+
export function readInvariantProbeArtifact(value) {
|
|
429
|
+
if (!isRecord(value))
|
|
430
|
+
return { ok: false, reason: 'not a JSON object' };
|
|
431
|
+
if (value.kind !== INVARIANT_PROBE_KIND)
|
|
432
|
+
return { ok: false, reason: `kind is not ${INVARIANT_PROBE_KIND}` };
|
|
433
|
+
if (value.schemaVersion !== INVARIANT_PROBE_SCHEMA_VERSION) {
|
|
434
|
+
return { ok: false, reason: `schemaVersion is not ${INVARIANT_PROBE_SCHEMA_VERSION}` };
|
|
435
|
+
}
|
|
436
|
+
if (!Array.isArray(value.invariants))
|
|
437
|
+
return { ok: false, reason: 'invariants is not a list' };
|
|
438
|
+
if (!isText(value.operatorSet))
|
|
439
|
+
return { ok: false, reason: 'operatorSet is missing' };
|
|
440
|
+
const kept = value.invariants.slice(0, INVARIANT_PROBE_MAX_ROWS);
|
|
441
|
+
const rows = kept.filter(isProbeRow);
|
|
442
|
+
const runner = isRecord(value.runner) && isText(value.runner.id)
|
|
443
|
+
? { id: value.runner.id, version: isText(value.runner.version) ? value.runner.version : null }
|
|
444
|
+
: null;
|
|
445
|
+
return {
|
|
446
|
+
ok: true,
|
|
447
|
+
artifact: {
|
|
448
|
+
schemaVersion: INVARIANT_PROBE_SCHEMA_VERSION,
|
|
449
|
+
kind: INVARIANT_PROBE_KIND,
|
|
450
|
+
notAScore: true,
|
|
451
|
+
arkgateVersion: isText(value.arkgateVersion) ? value.arkgateVersion : 'unknown',
|
|
452
|
+
operatorSet: value.operatorSet,
|
|
453
|
+
runner,
|
|
454
|
+
probedOn: isText(value.probedOn) ? value.probedOn : 'unknown',
|
|
455
|
+
invariants: rows,
|
|
456
|
+
totals: probeTotals(rows),
|
|
457
|
+
},
|
|
458
|
+
droppedRows: kept.length - rows.length,
|
|
459
|
+
truncatedRows: value.invariants.length - kept.length,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
@@ -48,13 +48,14 @@ export function scanDemandsInvariantTestsPath(root, args = {}) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
51
|
+
* True when some declared glob's concrete prefix (text before the first `*`/`?`)
|
|
52
|
+
* exists under root, or when every declared glob is wildcard-only. Prefixes that
|
|
53
|
+
* escape root are skipped. No declarations → false; no root → true.
|
|
52
54
|
*
|
|
53
55
|
* @param {string} root
|
|
54
|
-
* @param {
|
|
56
|
+
* @param {string[]} declared
|
|
55
57
|
*/
|
|
56
|
-
|
|
57
|
-
const declared = configuredInvariantTestsPaths(coverage);
|
|
58
|
+
function firstConcreteGlobPrefixPresent(root, declared) {
|
|
58
59
|
if (declared.length === 0) return false;
|
|
59
60
|
if (typeof root !== 'string' || root.length === 0) return true;
|
|
60
61
|
let sawConcrete = false;
|
|
@@ -74,6 +75,16 @@ export function declaredInvariantTestsPathPresent(root, coverage) {
|
|
|
74
75
|
return !sawConcrete;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* First concrete path segment exists, or every declared glob is wildcard-only.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} root
|
|
82
|
+
* @param {{ testGlobs?: unknown, coverageRoots?: unknown } | null | undefined} coverage
|
|
83
|
+
*/
|
|
84
|
+
export function declaredInvariantTestsPathPresent(root, coverage) {
|
|
85
|
+
return firstConcreteGlobPrefixPresent(root, configuredInvariantTestsPaths(coverage));
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
/**
|
|
78
89
|
* Doctor residual when adopted + invariants + missing/empty tests path.
|
|
79
90
|
*
|
|
@@ -131,24 +142,7 @@ export function collectInvariantTestsPathResidual(input = {}) {
|
|
|
131
142
|
* @param {{ coverageRoots?: unknown } | null | undefined} coverage
|
|
132
143
|
*/
|
|
133
144
|
export function declaredCoverageRootsPresent(root, coverage) {
|
|
134
|
-
|
|
135
|
-
if (declared.length === 0) return false;
|
|
136
|
-
if (typeof root !== 'string' || root.length === 0) return true;
|
|
137
|
-
let sawConcrete = false;
|
|
138
|
-
for (const entry of declared) {
|
|
139
|
-
const prefix = entry.split(/[*?]/)[0].replace(/\/$/, '');
|
|
140
|
-
if (!prefix) continue;
|
|
141
|
-
sawConcrete = true;
|
|
142
|
-
const abs = path.resolve(root, prefix);
|
|
143
|
-
const rel = path.relative(path.resolve(root), abs).replace(/\\/g, '/');
|
|
144
|
-
if (!rel || rel === '..' || rel.startsWith('../') || path.isAbsolute(rel)) continue;
|
|
145
|
-
try {
|
|
146
|
-
if (fs.existsSync(abs)) return true;
|
|
147
|
-
} catch {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return !sawConcrete;
|
|
145
|
+
return firstConcreteGlobPrefixPresent(root, configuredCoverageRoots(coverage));
|
|
152
146
|
}
|
|
153
147
|
|
|
154
148
|
function residualHasEnforcedInvariant(input) {
|
|
@@ -85,8 +85,10 @@ const MAX_TOKEN_LENGTH = 200;
|
|
|
85
85
|
* Alias map used when the caller declares none: the `@/` convention over `src/`.
|
|
86
86
|
* The CLI side derives the real map from the project's tsconfig `paths`.
|
|
87
87
|
*/
|
|
88
|
-
|
|
89
|
-
/**
|
|
88
|
+
const DEFAULT_ALIASES = Object.freeze({ '@/': 'src/' });
|
|
89
|
+
/**
|
|
90
|
+
* Include roots assumed when the contract declares none.
|
|
91
|
+
*/
|
|
90
92
|
export const DEFAULT_INCLUDE_ROOTS = Object.freeze(['src']);
|
|
91
93
|
/**
|
|
92
94
|
* A replacement must look like a path before it may be written into a file.
|
|
@@ -31,17 +31,11 @@ import {
|
|
|
31
31
|
} from './skill-install.mjs';
|
|
32
32
|
|
|
33
33
|
export {
|
|
34
|
-
formatManagedUpgradeSelfServiceHonesty,
|
|
35
34
|
projectHostWritePathActivation,
|
|
36
35
|
projectManagedUpgradeSelfServiceHonesty,
|
|
37
36
|
} from './managed-upgrade-honesty.mjs';
|
|
38
|
-
export {
|
|
39
|
-
buildUpgradeWhatsNewSuggestions,
|
|
40
|
-
formatUpgradeWhatsNewSuggestions,
|
|
41
|
-
UPGRADE_WHATS_NEW_SCHEMA_VERSION,
|
|
42
|
-
} from './upgrade-whats-new.mjs';
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
const MANAGED_MANIFEST_PATH = 'ark.managed.json';
|
|
45
39
|
const MANIFEST_VERSION = '1.0';
|
|
46
40
|
const AFTER_CONTENT = Symbol('managed-after-content');
|
|
47
41
|
// Exact normalized identities shipped by the last pre-manifest release. They are
|
|
@@ -438,7 +432,7 @@ function summaryFor(assets, manifestChanged) {
|
|
|
438
432
|
};
|
|
439
433
|
}
|
|
440
434
|
|
|
441
|
-
|
|
435
|
+
function managedPlanDigest(plan) {
|
|
442
436
|
const payload = {
|
|
443
437
|
schemaVersion: plan.schemaVersion,
|
|
444
438
|
root: plan.root,
|
package/bin/lib/mcp-adoption.mjs
CHANGED
|
@@ -42,19 +42,24 @@ export const COMMAND_GATE_JSON_FILES = [
|
|
|
42
42
|
// Primary CLI names (product) + one-major aliases. migrate-commands must strip ALL of these
|
|
43
43
|
// before re-emitting a single preferred bin — otherwise a partial rename leaves
|
|
44
44
|
// args: ["ark-mcp", "arkgate-mcp", ...] which breaks stdio MCP hosts.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
const ARK_MCP_BINS = new Set(['arkgate-mcp', 'ark-mcp']);
|
|
46
|
+
const ARK_CHECK_BINS = new Set(['arkgate-check', 'ark-check']);
|
|
47
|
+
const ARK_CLI_BINS = new Set(['arkgate', 'ark']);
|
|
48
48
|
// PREFERRED_MCP_BIN lives in hook-templates.mjs (re-exported above).
|
|
49
49
|
export const PREFERRED_CHECK_BIN = 'arkgate-check';
|
|
50
|
-
export const PREFERRED_CLI_BIN = 'arkgate';
|
|
51
50
|
// Runner argv noise that is not a bin argument (pnpm exec form).
|
|
52
|
-
|
|
51
|
+
const MCP_RUNNER_ARGV = new Set(['exec', '--config.verify-deps-before-run=false']);
|
|
53
52
|
// The runner token immediately before an ark command in a text command string.
|
|
54
53
|
// Matches npm/yarn runners and both pnpm forms (legacy `pnpm exec` + verify-deps-safe form).
|
|
55
54
|
// Longer bin names first so `arkgate-check` is not partially matched as `ark`.
|
|
55
|
+
// The pinned npx form (`npx -y -p arkgate@x`) comes first so it is replaced whole.
|
|
56
56
|
export const RUNNER_BEFORE_ARK =
|
|
57
|
-
/\b(?:npx|pnpm --config\.verify-deps-before-run=false exec|pnpm exec|yarn)(?= (?:arkgate-check|arkgate-mcp|arkgate|ark-check|ark-mcp|ark)\b)/g;
|
|
57
|
+
/\b(?:npx -y -p arkgate@[^\s"'`]+|npx|pnpm --config\.verify-deps-before-run=false exec|pnpm exec|yarn)(?= (?:arkgate-check|arkgate-mcp|arkgate|ark-check|ark-mcp|ark)\b)/g;
|
|
58
|
+
|
|
59
|
+
/** npx pinned-package argv (`-y`, `-p`, `arkgate@x`) — runner noise, not a server flag. */
|
|
60
|
+
function isPinnedNpxArgv(entry) {
|
|
61
|
+
return /^(?:-y|--yes|-p|--package|--package=arkgate(?:@\S+)?|arkgate@\S+)$/.test(entry);
|
|
62
|
+
}
|
|
58
63
|
|
|
59
64
|
/** Keep only MCP server flags from existing args (drop runner tokens + any ark* bin names). */
|
|
60
65
|
|
|
@@ -66,6 +71,7 @@ export function stripMcpServerArgs(args) {
|
|
|
66
71
|
(entry) =>
|
|
67
72
|
typeof entry === 'string' &&
|
|
68
73
|
!MCP_RUNNER_ARGV.has(entry) &&
|
|
74
|
+
!isPinnedNpxArgv(entry) &&
|
|
69
75
|
!ARK_MCP_BINS.has(entry) &&
|
|
70
76
|
!ARK_CHECK_BINS.has(entry) &&
|
|
71
77
|
!ARK_CLI_BINS.has(entry)
|
|
@@ -89,6 +95,7 @@ export function stripOpencodeMcpCommand(command) {
|
|
|
89
95
|
if (
|
|
90
96
|
runners.has(base) ||
|
|
91
97
|
MCP_RUNNER_ARGV.has(entry) ||
|
|
98
|
+
isPinnedNpxArgv(entry) ||
|
|
92
99
|
ARK_MCP_BINS.has(base) ||
|
|
93
100
|
ARK_MCP_BINS.has(entry) ||
|
|
94
101
|
ARK_CHECK_BINS.has(base) ||
|