token-harness 0.1.0 → 0.1.2
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/README.md +577 -236
- package/package.json +1 -1
- package/sbom.json +5 -5
- package/token-harness.mjs +1756 -275
package/token-harness.mjs
CHANGED
|
@@ -111,6 +111,113 @@ function classifyVersion(observed, range) {
|
|
|
111
111
|
return compareVersions(version2, maximum) > 0 ? "unknown-newer" : "in-range";
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// packages/core/dist/src/domain/compatibility-rows.js
|
|
115
|
+
var COMPATIBILITY_ROWS = [
|
|
116
|
+
{
|
|
117
|
+
harness: "claude",
|
|
118
|
+
harnessVersion: { minimum: "2.1.220", maximum: "2.1.220" },
|
|
119
|
+
provider: "rtk",
|
|
120
|
+
providerVersion: "0.44.0",
|
|
121
|
+
platform: { os: "windows", wsl: false, supported: true, limitation: null },
|
|
122
|
+
configSchema: "claude-settings-json-hooks",
|
|
123
|
+
fixture: "tests/fixtures/rows/rtk-claude-windows",
|
|
124
|
+
verificationTier: "canary"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
harness: "claude",
|
|
128
|
+
harnessVersion: { minimum: "2.1.220", maximum: "2.1.220" },
|
|
129
|
+
provider: "harnesstrim",
|
|
130
|
+
providerVersion: "0.1.0",
|
|
131
|
+
platform: { os: "windows", wsl: false, supported: true, limitation: null },
|
|
132
|
+
// The install writes skill files, not a document this build parses, so the schema it depends on
|
|
133
|
+
// is the skills directory layout rather than `settings.json`.
|
|
134
|
+
configSchema: "claude-skills-directory",
|
|
135
|
+
fixture: "tests/fixtures/rows/harnesstrim-claude-windows",
|
|
136
|
+
verificationTier: "config-only"
|
|
137
|
+
}
|
|
138
|
+
];
|
|
139
|
+
function classifyRowHarnessVersion(rows, observed) {
|
|
140
|
+
if (rows.length === 0)
|
|
141
|
+
return null;
|
|
142
|
+
const parsed = parseSemanticVersion(observed);
|
|
143
|
+
if (parsed === null)
|
|
144
|
+
return null;
|
|
145
|
+
let lowestMinimum = null;
|
|
146
|
+
let highestMaximum = null;
|
|
147
|
+
for (const row2 of rows) {
|
|
148
|
+
const minimum = parseSemanticVersion(row2.harnessVersion.minimum);
|
|
149
|
+
const maximum = parseSemanticVersion(row2.harnessVersion.maximum);
|
|
150
|
+
if (minimum === null || maximum === null)
|
|
151
|
+
continue;
|
|
152
|
+
if (compareVersions(parsed, minimum) >= 0 && compareVersions(parsed, maximum) <= 0)
|
|
153
|
+
return "in-row";
|
|
154
|
+
if (lowestMinimum === null || compareVersions(minimum, lowestMinimum) < 0)
|
|
155
|
+
lowestMinimum = minimum;
|
|
156
|
+
if (highestMaximum === null || compareVersions(maximum, highestMaximum) > 0)
|
|
157
|
+
highestMaximum = maximum;
|
|
158
|
+
}
|
|
159
|
+
if (lowestMinimum === null || highestMaximum === null)
|
|
160
|
+
return null;
|
|
161
|
+
if (compareVersions(parsed, lowestMinimum) < 0)
|
|
162
|
+
return "unknown-older";
|
|
163
|
+
if (compareVersions(parsed, highestMaximum) > 0)
|
|
164
|
+
return "unknown-newer";
|
|
165
|
+
return "below-range";
|
|
166
|
+
}
|
|
167
|
+
function rowsFor(rows, combination) {
|
|
168
|
+
return rows.filter((row2) => row2.provider === combination.provider && row2.harness === combination.harness && row2.platform.os === combination.os && row2.platform.wsl === combination.wsl && row2.platform.supported);
|
|
169
|
+
}
|
|
170
|
+
function admitManagedMutation(rows, combination) {
|
|
171
|
+
const keyed = rowsFor(rows, combination);
|
|
172
|
+
if (combination.providerVersion === null)
|
|
173
|
+
return {
|
|
174
|
+
state: "refused",
|
|
175
|
+
verdict: "no-row",
|
|
176
|
+
missing: "the exact provider version a row would record could not be established"
|
|
177
|
+
};
|
|
178
|
+
if (combination.harnessVersion === null)
|
|
179
|
+
return {
|
|
180
|
+
state: "refused",
|
|
181
|
+
verdict: "no-row",
|
|
182
|
+
missing: "the exact harness version a row would cover could not be established"
|
|
183
|
+
};
|
|
184
|
+
if (keyed.length === 0)
|
|
185
|
+
return {
|
|
186
|
+
state: "refused",
|
|
187
|
+
verdict: "no-row",
|
|
188
|
+
missing: `a reviewed config schema and fixture for ${combination.provider} on ${combination.harness} at ${combination.providerVersion} on ${combination.os}`
|
|
189
|
+
};
|
|
190
|
+
const versioned = keyed.filter((row3) => row3.providerVersion === combination.providerVersion);
|
|
191
|
+
if (versioned.length === 0) {
|
|
192
|
+
const fixture = keyed[0]?.fixture ?? "a provider fixture";
|
|
193
|
+
return {
|
|
194
|
+
state: "refused",
|
|
195
|
+
verdict: "no-row",
|
|
196
|
+
missing: `a provider fixture covering ${combination.provider} at ${combination.providerVersion} (the nearest row's fixture is ${JSON.stringify(fixture)})`
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const parsedHarness = parseSemanticVersion(combination.harnessVersion);
|
|
200
|
+
const row2 = versioned.find((candidate) => {
|
|
201
|
+
const minimum = parseSemanticVersion(candidate.harnessVersion.minimum);
|
|
202
|
+
const maximum = parseSemanticVersion(candidate.harnessVersion.maximum);
|
|
203
|
+
return minimum !== null && maximum !== null && parsedHarness !== null && compareVersions(parsedHarness, minimum) >= 0 && compareVersions(parsedHarness, maximum) <= 0;
|
|
204
|
+
});
|
|
205
|
+
if (row2 !== void 0)
|
|
206
|
+
return { state: "admitted", row: row2 };
|
|
207
|
+
const verdict = parsedHarness === null ? null : classifyRowHarnessVersion(versioned, combination.harnessVersion);
|
|
208
|
+
if (verdict === null || verdict === "in-row")
|
|
209
|
+
return {
|
|
210
|
+
state: "refused",
|
|
211
|
+
verdict: "no-row",
|
|
212
|
+
missing: `a reviewed config schema and fixture for ${combination.provider} on ${combination.harness}`
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
state: "refused",
|
|
216
|
+
verdict,
|
|
217
|
+
missing: verdict === "unknown-newer" ? `a harness schema for ${combination.harness} newer than every row's maximum (the newest row names schema ${JSON.stringify(versioned[0]?.configSchema ?? "none")})` : verdict === "unknown-older" ? `a harness schema for ${combination.harness} older than every row's minimum (the oldest row names schema ${JSON.stringify(versioned[0]?.configSchema ?? "none")})` : `a harness schema covering ${combination.harness} at ${combination.harnessVersion}, between the rows that name schemas ${JSON.stringify(keyed.map((entry) => entry.configSchema))}`
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
114
221
|
// packages/core/dist/src/domain/compatibility.js
|
|
115
222
|
function coversHarness(rule, harness) {
|
|
116
223
|
return rule.harnesses === "*" || rule.harnesses.includes(harness);
|
|
@@ -155,6 +262,7 @@ function diagnostic(init) {
|
|
|
155
262
|
severity: init.severity,
|
|
156
263
|
code: init.code,
|
|
157
264
|
message: init.message,
|
|
265
|
+
subject: init.subject ?? null,
|
|
158
266
|
path: init.path ?? null,
|
|
159
267
|
remediation: init.remediation ?? null
|
|
160
268
|
};
|
|
@@ -206,6 +314,7 @@ function harnessId(value) {
|
|
|
206
314
|
}
|
|
207
315
|
return value;
|
|
208
316
|
}
|
|
317
|
+
var UNATTRIBUTED_PROJECT_ID = "p_unattributed";
|
|
209
318
|
|
|
210
319
|
// packages/core/dist/src/domain/manifest.js
|
|
211
320
|
var MANIFEST_SCHEMA_VERSION = 1;
|
|
@@ -263,6 +372,9 @@ function isStartableExecutable(resolved) {
|
|
|
263
372
|
// packages/core/dist/src/domain/process.js
|
|
264
373
|
var DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
265
374
|
var DEFAULT_PROCESS_TIMEOUT_MS = 3e4;
|
|
375
|
+
function processSucceeded(outcome2) {
|
|
376
|
+
return outcome2.failure === null && outcome2.exitCode === 0;
|
|
377
|
+
}
|
|
266
378
|
|
|
267
379
|
// packages/core/dist/src/domain/redaction.js
|
|
268
380
|
var REDACTED = "[redacted]";
|
|
@@ -584,12 +696,13 @@ function resolveOwnership(input) {
|
|
|
584
696
|
continue;
|
|
585
697
|
}
|
|
586
698
|
if (!provider.assignable) {
|
|
699
|
+
const observed = input.observedVersions[provider.id] ?? null;
|
|
587
700
|
exclusions.push({
|
|
588
701
|
scope,
|
|
589
702
|
excluded: provider.id,
|
|
590
703
|
retained: null,
|
|
591
704
|
reason: [
|
|
592
|
-
`${provider.id} implements ${capability} but no installer state produces it in isolation`,
|
|
705
|
+
observed === null ? `${provider.id} implements ${capability} but no installer state this build could observe produces it in isolation` : `${provider.id} ${observed} implements ${capability} but no installer state at that version produces it in isolation`,
|
|
593
706
|
"RFC 0003: a capability the provider has but cannot be asked for is not an assignable capability",
|
|
594
707
|
"It is still detected, adopted, reconciled against the owner, and measured"
|
|
595
708
|
]
|
|
@@ -630,6 +743,7 @@ function resolveOwnership(input) {
|
|
|
630
743
|
}
|
|
631
744
|
}
|
|
632
745
|
}
|
|
746
|
+
narrowChannelOverlaps({ ownership, exclusions, conflicts, input });
|
|
633
747
|
const ownerByScope = new Map(ownership.map((entry) => [formatCapabilityScope(entry.scope), entry.owner]));
|
|
634
748
|
for (const exclusion of exclusions) {
|
|
635
749
|
if (exclusion.retained !== null)
|
|
@@ -640,6 +754,66 @@ function resolveOwnership(input) {
|
|
|
640
754
|
}
|
|
641
755
|
return { ownership, exclusions, conflicts, pipelineId: derivePipelineId(ownership) };
|
|
642
756
|
}
|
|
757
|
+
function narrowChannelOverlaps(context) {
|
|
758
|
+
const { ownership, exclusions, conflicts, input } = context;
|
|
759
|
+
const channels = /* @__PURE__ */ new Map();
|
|
760
|
+
for (const entry of ownership) {
|
|
761
|
+
if (entry.mode !== "exclusive")
|
|
762
|
+
continue;
|
|
763
|
+
const key = `${entry.scope.harness}/${entry.scope.toolFamily}/${entry.scope.capability}`;
|
|
764
|
+
const group = channels.get(key) ?? [];
|
|
765
|
+
group.push(entry);
|
|
766
|
+
channels.set(key, group);
|
|
767
|
+
}
|
|
768
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
769
|
+
for (const group of channels.values()) {
|
|
770
|
+
const owners = [...new Set(group.map((entry) => entry.owner))];
|
|
771
|
+
if (owners.length < 2)
|
|
772
|
+
continue;
|
|
773
|
+
const first = group[0];
|
|
774
|
+
if (first === void 0)
|
|
775
|
+
continue;
|
|
776
|
+
const { harness, toolFamily, capability } = first.scope;
|
|
777
|
+
const rule = findCompatibilityRule(input.rules, { providers: owners, harness, capability });
|
|
778
|
+
if (rule === null || rule.outcome !== "narrowed" || rule.retains === void 0) {
|
|
779
|
+
conflicts.push({
|
|
780
|
+
code: "exclusive-scope-contested",
|
|
781
|
+
scope: `${harness}/${toolFamily}/${capability}`,
|
|
782
|
+
claimants: [...owners].sort(),
|
|
783
|
+
detail: [
|
|
784
|
+
`${owners.join(" and ")} each own ${capability} on ${harness}/${toolFamily}, at different interception points`,
|
|
785
|
+
"The interception points differ but the payload does not, so the reduction would run twice",
|
|
786
|
+
rule === null ? "No rule names this pair on this channel, and an unreviewed overlap is not permission" : `Rule ${rule.id} does not say which provider keeps the channel`
|
|
787
|
+
],
|
|
788
|
+
remediation: `Choose one owner for ${harness}/${toolFamily}/${capability} with \`profile: custom\``
|
|
789
|
+
});
|
|
790
|
+
for (const entry of group)
|
|
791
|
+
dropped.add(entry);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const retains = rule.retains;
|
|
795
|
+
for (const entry of group) {
|
|
796
|
+
if (entry.owner === retains)
|
|
797
|
+
continue;
|
|
798
|
+
dropped.add(entry);
|
|
799
|
+
exclusions.push({
|
|
800
|
+
scope: entry.scope,
|
|
801
|
+
excluded: entry.owner,
|
|
802
|
+
retained: retains,
|
|
803
|
+
reason: [
|
|
804
|
+
`rule ${rule.id} keeps ${capability} on ${harness}/${toolFamily} with ${retains}, at every interception point`,
|
|
805
|
+
`${entry.owner} is narrowed to the install its own flags produce rather than excluded whole`,
|
|
806
|
+
rule.rationale
|
|
807
|
+
]
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (dropped.size > 0) {
|
|
812
|
+
const kept = ownership.filter((entry) => !dropped.has(entry));
|
|
813
|
+
ownership.length = 0;
|
|
814
|
+
ownership.push(...kept);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
643
817
|
function resolveContested(context) {
|
|
644
818
|
const { scope, claims, capability, input, ownership, exclusions, conflicts } = context;
|
|
645
819
|
const claimants = claims.map((claim) => claim.provider);
|
|
@@ -715,6 +889,41 @@ function resolveContested(context) {
|
|
|
715
889
|
}
|
|
716
890
|
return;
|
|
717
891
|
}
|
|
892
|
+
if (rule.outcome === "narrowed") {
|
|
893
|
+
const retains = rule.retains;
|
|
894
|
+
if (retains === void 0) {
|
|
895
|
+
conflicts.push({
|
|
896
|
+
code: "compatibility-rule-malformed",
|
|
897
|
+
scope: formatCapabilityScope(scope),
|
|
898
|
+
claimants,
|
|
899
|
+
detail: [
|
|
900
|
+
`Rule ${rule.id} narrows a pair without naming which provider keeps the channel`,
|
|
901
|
+
"Choosing one here would make the resolver decide what the rule exists to record"
|
|
902
|
+
],
|
|
903
|
+
remediation: `Fix rule ${rule.id} so it names the provider that retains the capability`
|
|
904
|
+
});
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
for (const claimant of claimants) {
|
|
908
|
+
if (claimant === retains)
|
|
909
|
+
continue;
|
|
910
|
+
exclusions.push({
|
|
911
|
+
scope,
|
|
912
|
+
excluded: claimant,
|
|
913
|
+
retained: retains,
|
|
914
|
+
reason: [
|
|
915
|
+
`rule ${rule.id} keeps ${capability} on ${scope.harness} with ${retains}`,
|
|
916
|
+
`${claimant} is narrowed to the install its own flags produce rather than excluded whole`,
|
|
917
|
+
rule.rationale
|
|
918
|
+
]
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
const retainedClaim = claims.find((claim) => claim.provider === retains);
|
|
922
|
+
if (retainedClaim !== void 0) {
|
|
923
|
+
ownership.push({ scope, owner: retains, mode: retainedClaim.mode, order: 0 });
|
|
924
|
+
}
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
718
927
|
const winner = rule.providers.find((provider) => claimants.includes(provider)) ?? claimants[0];
|
|
719
928
|
for (const claimant of claimants) {
|
|
720
929
|
if (claimant === winner)
|
|
@@ -745,9 +954,10 @@ var RTK_HARNESSTRIM_SHELL_OVERLAP = {
|
|
|
745
954
|
// for different reasons. Listing them individually would imply a fourth harness is exempt.
|
|
746
955
|
harnesses: "*",
|
|
747
956
|
capabilities: ["shell.output.reduce"],
|
|
748
|
-
outcome: "
|
|
749
|
-
|
|
750
|
-
|
|
957
|
+
outcome: "narrowed",
|
|
958
|
+
retains: RTK,
|
|
959
|
+
testedVersions: { rtk: "0.44.0", harnesstrim: "0.1.0" },
|
|
960
|
+
rationale: "RTK keeps shell output reduction: it rewrites the command, so the output arriving at any later point is already reduced, and reducing it again loses signal and counts the saving twice. HarnessTrim 0.1.0 no longer has to be excluded whole for that \u2014 `--no-hook` and `--no-instructions` produce a Claude install that adds skills and reduces nothing, and `--tools` confines its OpenCode reduction to a chosen tool family. So it is narrowed to the form that leaves this channel alone, rather than refused. At 0.0.5 no flag produced any of those states, which is why this rule read `conflict` and said no configuration narrows it.",
|
|
751
961
|
fixtures: ["docs/rfcs/0003-capabilities-and-conflicts.md#the-table-is-an-intent"]
|
|
752
962
|
};
|
|
753
963
|
var COMPATIBILITY_RULES = [RTK_HARNESSTRIM_SHELL_OVERLAP];
|
|
@@ -882,12 +1092,12 @@ function isUsableSalt(value) {
|
|
|
882
1092
|
}
|
|
883
1093
|
|
|
884
1094
|
// packages/core/dist/src/metrics/local-database.js
|
|
885
|
-
function numberAt(
|
|
886
|
-
const value =
|
|
1095
|
+
function numberAt(row2, column2) {
|
|
1096
|
+
const value = row2[column2];
|
|
887
1097
|
return typeof value === "number" ? value : null;
|
|
888
1098
|
}
|
|
889
|
-
function stringAt(
|
|
890
|
-
const value =
|
|
1099
|
+
function stringAt(row2, column2) {
|
|
1100
|
+
const value = row2[column2];
|
|
891
1101
|
return typeof value === "string" ? value : null;
|
|
892
1102
|
}
|
|
893
1103
|
|
|
@@ -988,7 +1198,7 @@ function aggregateEvents(input) {
|
|
|
988
1198
|
const managed = new Set(input.managedProviders ?? []);
|
|
989
1199
|
const modes = input.adapterModes ?? {};
|
|
990
1200
|
const classes = MEASUREMENT_CLASSES.map((measurementClass) => {
|
|
991
|
-
const rows = [...classTotals.values()].filter((
|
|
1201
|
+
const rows = [...classTotals.values()].filter((row3) => row3.class === measurementClass);
|
|
992
1202
|
if (rows.length > 1) {
|
|
993
1203
|
return {
|
|
994
1204
|
class: measurementClass,
|
|
@@ -996,11 +1206,11 @@ function aggregateEvents(input) {
|
|
|
996
1206
|
before: null,
|
|
997
1207
|
after: null,
|
|
998
1208
|
saved: null,
|
|
999
|
-
note: `recorded in ${rows.map((
|
|
1209
|
+
note: `recorded in ${rows.map((row3) => row3.unit).join(" and ")}, which are not addable`
|
|
1000
1210
|
};
|
|
1001
1211
|
}
|
|
1002
|
-
const
|
|
1003
|
-
if (
|
|
1212
|
+
const row2 = rows[0];
|
|
1213
|
+
if (row2 === void 0) {
|
|
1004
1214
|
return {
|
|
1005
1215
|
class: measurementClass,
|
|
1006
1216
|
unit: null,
|
|
@@ -1012,22 +1222,22 @@ function aggregateEvents(input) {
|
|
|
1012
1222
|
}
|
|
1013
1223
|
return {
|
|
1014
1224
|
class: measurementClass,
|
|
1015
|
-
unit:
|
|
1016
|
-
before:
|
|
1017
|
-
after:
|
|
1018
|
-
saved:
|
|
1225
|
+
unit: row2.unit,
|
|
1226
|
+
before: row2.before,
|
|
1227
|
+
after: row2.after,
|
|
1228
|
+
saved: row2.before - row2.after,
|
|
1019
1229
|
note: null
|
|
1020
1230
|
};
|
|
1021
1231
|
});
|
|
1022
|
-
const providers = [...providerTotals.values()].sort((left, right) => right.saved - left.saved || left.providerId.localeCompare(right.providerId)).map((
|
|
1023
|
-
providerId:
|
|
1024
|
-
saved:
|
|
1025
|
-
unit:
|
|
1026
|
-
class:
|
|
1027
|
-
operations:
|
|
1028
|
-
harnesses: [...
|
|
1029
|
-
managedByTokenHarness: managed.has(
|
|
1030
|
-
adapterMode: modes[
|
|
1232
|
+
const providers = [...providerTotals.values()].sort((left, right) => right.saved - left.saved || left.providerId.localeCompare(right.providerId)).map((row2) => ({
|
|
1233
|
+
providerId: row2.providerId,
|
|
1234
|
+
saved: row2.saved,
|
|
1235
|
+
unit: row2.unit,
|
|
1236
|
+
class: row2.class,
|
|
1237
|
+
operations: row2.operations,
|
|
1238
|
+
harnesses: [...row2.harnesses].sort(),
|
|
1239
|
+
managedByTokenHarness: managed.has(row2.providerId),
|
|
1240
|
+
adapterMode: modes[row2.providerId] ?? null
|
|
1031
1241
|
}));
|
|
1032
1242
|
const operations = realized + bypassed;
|
|
1033
1243
|
return {
|
|
@@ -1266,6 +1476,201 @@ async function queryAvailableVersion(input) {
|
|
|
1266
1476
|
}
|
|
1267
1477
|
return { status: "found", version: version2, destination, diagnostics };
|
|
1268
1478
|
}
|
|
1479
|
+
var INVENTORY_COMMANDS = {
|
|
1480
|
+
/**
|
|
1481
|
+
* `winget list --id <id> --exact` prints the same dashes-anchored table as `show --versions`,
|
|
1482
|
+
* with the installed version as the last token of the row. The header is localized, which is
|
|
1483
|
+
* why the parse is anchored on the separator and the row tail rather than on a column title.
|
|
1484
|
+
*
|
|
1485
|
+
* A missing package makes winget exit non-zero with a localized "no package found" message,
|
|
1486
|
+
* which this build cannot tell from any other failure — so that case is `failed` at the caller,
|
|
1487
|
+
* never `absent`.
|
|
1488
|
+
*/
|
|
1489
|
+
winget: {
|
|
1490
|
+
executable: "winget",
|
|
1491
|
+
args: (packageName) => ["list", "--id", packageName, "--exact"],
|
|
1492
|
+
parse: (stdout, _packageName) => {
|
|
1493
|
+
const lines = stdout.split(/\r?\n/);
|
|
1494
|
+
const separator = lines.findIndex((line) => /^-{3,}\s*$/.test(line.trim()));
|
|
1495
|
+
if (separator < 0)
|
|
1496
|
+
return { status: "unknown", version: null };
|
|
1497
|
+
for (const line of lines.slice(separator + 1)) {
|
|
1498
|
+
const row2 = line.trim();
|
|
1499
|
+
if (row2 === "")
|
|
1500
|
+
continue;
|
|
1501
|
+
const candidate = row2.split(/\s+/).at(-1) ?? "";
|
|
1502
|
+
if (parseSemanticVersion(candidate) === null)
|
|
1503
|
+
return { status: "unknown", version: null };
|
|
1504
|
+
return { status: "captured", version: candidate };
|
|
1505
|
+
}
|
|
1506
|
+
return { status: "unknown", version: null };
|
|
1507
|
+
},
|
|
1508
|
+
verified: false
|
|
1509
|
+
},
|
|
1510
|
+
/**
|
|
1511
|
+
* `cargo install --list` prints one `crate v0.1.0:` line per installed crate, so absence is a
|
|
1512
|
+
* positive answer: the crate simply does not appear. This is the one inventory among the six
|
|
1513
|
+
* that can confirm absence.
|
|
1514
|
+
*/
|
|
1515
|
+
cargo: {
|
|
1516
|
+
executable: "cargo",
|
|
1517
|
+
args: () => ["install", "--list"],
|
|
1518
|
+
parse: (stdout, packageName) => {
|
|
1519
|
+
const pattern = new RegExp(`^\\s*${packageName}\\s+v(\\S+)\\s*:`, "m");
|
|
1520
|
+
const match = pattern.exec(stdout);
|
|
1521
|
+
if (match === null)
|
|
1522
|
+
return { status: "absent", version: null };
|
|
1523
|
+
const candidate = match[1] ?? "";
|
|
1524
|
+
if (parseSemanticVersion(candidate) === null)
|
|
1525
|
+
return { status: "unknown", version: null };
|
|
1526
|
+
return { status: "captured", version: candidate };
|
|
1527
|
+
},
|
|
1528
|
+
verified: false
|
|
1529
|
+
},
|
|
1530
|
+
npm: {
|
|
1531
|
+
executable: "npm",
|
|
1532
|
+
args: (packageName) => ["ls", "-g", packageName, "--depth=0"],
|
|
1533
|
+
parse: (stdout, packageName) => {
|
|
1534
|
+
const pattern = new RegExp(`(?:^|[^@\\w.-])${packageName}@(\\d[^\\s]*)`);
|
|
1535
|
+
const match = pattern.exec(stdout);
|
|
1536
|
+
const candidate = match?.[1] ?? null;
|
|
1537
|
+
if (candidate === null || parseSemanticVersion(candidate) === null) {
|
|
1538
|
+
return { status: "unknown", version: null };
|
|
1539
|
+
}
|
|
1540
|
+
return { status: "captured", version: candidate };
|
|
1541
|
+
},
|
|
1542
|
+
verified: false
|
|
1543
|
+
},
|
|
1544
|
+
homebrew: {
|
|
1545
|
+
executable: "brew",
|
|
1546
|
+
args: (packageName) => ["list", "--versions", packageName],
|
|
1547
|
+
parse: (stdout, packageName) => {
|
|
1548
|
+
const pattern = new RegExp(`^${packageName}\\s+(.+)$`);
|
|
1549
|
+
const match = pattern.exec(stdout.trim());
|
|
1550
|
+
const candidate = match?.[1]?.trim().split(/\s+/).at(-1) ?? null;
|
|
1551
|
+
if (candidate === null || parseSemanticVersion(candidate) === null) {
|
|
1552
|
+
return { status: "unknown", version: null };
|
|
1553
|
+
}
|
|
1554
|
+
return { status: "captured", version: candidate };
|
|
1555
|
+
},
|
|
1556
|
+
verified: false
|
|
1557
|
+
},
|
|
1558
|
+
uv: {
|
|
1559
|
+
executable: "uv",
|
|
1560
|
+
args: () => ["tool", "list"],
|
|
1561
|
+
parse: (stdout, packageName) => {
|
|
1562
|
+
const pattern = new RegExp(`^${packageName}\\s+v?(\\S+)`, "m");
|
|
1563
|
+
const match = pattern.exec(stdout);
|
|
1564
|
+
if (match === null)
|
|
1565
|
+
return { status: "absent", version: null };
|
|
1566
|
+
const candidate = match[1] ?? "";
|
|
1567
|
+
if (parseSemanticVersion(candidate) === null)
|
|
1568
|
+
return { status: "unknown", version: null };
|
|
1569
|
+
return { status: "captured", version: candidate };
|
|
1570
|
+
},
|
|
1571
|
+
verified: false
|
|
1572
|
+
},
|
|
1573
|
+
pipx: {
|
|
1574
|
+
executable: "pipx",
|
|
1575
|
+
args: () => ["list"],
|
|
1576
|
+
parse: (stdout, packageName) => {
|
|
1577
|
+
const pattern = new RegExp(`^\\s*${packageName}\\s+(\\S+)`, "m");
|
|
1578
|
+
const match = pattern.exec(stdout);
|
|
1579
|
+
if (match === null)
|
|
1580
|
+
return { status: "absent", version: null };
|
|
1581
|
+
const candidate = match[1] ?? "";
|
|
1582
|
+
if (parseSemanticVersion(candidate) === null)
|
|
1583
|
+
return { status: "unknown", version: null };
|
|
1584
|
+
return { status: "captured", version: candidate };
|
|
1585
|
+
},
|
|
1586
|
+
verified: false
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
function channelCanReportInventory(channelId) {
|
|
1590
|
+
return Object.hasOwn(INVENTORY_COMMANDS, channelId);
|
|
1591
|
+
}
|
|
1592
|
+
var DEFAULT_INVENTORY_TIMEOUT_MS = 6e4;
|
|
1593
|
+
async function queryPackageInventory(input) {
|
|
1594
|
+
const recipe = INVENTORY_COMMANDS[input.channel];
|
|
1595
|
+
if (recipe === void 0) {
|
|
1596
|
+
return {
|
|
1597
|
+
channel: input.channel,
|
|
1598
|
+
packageName: input.packageName,
|
|
1599
|
+
status: "unsupported",
|
|
1600
|
+
version: null,
|
|
1601
|
+
diagnostics: [
|
|
1602
|
+
diagnostic({
|
|
1603
|
+
severity: "warning",
|
|
1604
|
+
code: "inventory-query-unsupported",
|
|
1605
|
+
message: `This build cannot ask ${input.channel} what it has installed`,
|
|
1606
|
+
remediation: null
|
|
1607
|
+
})
|
|
1608
|
+
]
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
if (input.runner === null) {
|
|
1612
|
+
return {
|
|
1613
|
+
channel: input.channel,
|
|
1614
|
+
packageName: input.packageName,
|
|
1615
|
+
status: "failed",
|
|
1616
|
+
version: null,
|
|
1617
|
+
diagnostics: [
|
|
1618
|
+
diagnostic({
|
|
1619
|
+
severity: "error",
|
|
1620
|
+
code: "no-process-runner",
|
|
1621
|
+
message: "No process runner is available, so no inventory can be captured",
|
|
1622
|
+
remediation: null
|
|
1623
|
+
})
|
|
1624
|
+
]
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
const diagnostics = [];
|
|
1628
|
+
if (!recipe.verified) {
|
|
1629
|
+
diagnostics.push(diagnostic({
|
|
1630
|
+
severity: "warning",
|
|
1631
|
+
code: "inventory-query-unverified",
|
|
1632
|
+
message: `The ${input.channel} inventory query follows that tool's documented output but has not been observed working`,
|
|
1633
|
+
remediation: "Confirm the rollback receipt after any failure"
|
|
1634
|
+
}));
|
|
1635
|
+
}
|
|
1636
|
+
const outcome2 = await input.runner.run({
|
|
1637
|
+
executable: recipe.executable,
|
|
1638
|
+
args: recipe.args(input.packageName),
|
|
1639
|
+
cwd: input.cwd,
|
|
1640
|
+
timeoutMs: input.timeoutMs ?? DEFAULT_INVENTORY_TIMEOUT_MS
|
|
1641
|
+
});
|
|
1642
|
+
if (outcome2.failure !== null || outcome2.exitCode !== 0) {
|
|
1643
|
+
diagnostics.push(diagnostic({
|
|
1644
|
+
severity: "warning",
|
|
1645
|
+
code: "inventory-query-failed",
|
|
1646
|
+
message: outcome2.failure === null ? `${outcome2.displayCommand} exited with ${String(outcome2.exitCode)}` : `${recipe.executable} could not be run: ${outcome2.failure.reason}`,
|
|
1647
|
+
remediation: null
|
|
1648
|
+
}));
|
|
1649
|
+
return {
|
|
1650
|
+
channel: input.channel,
|
|
1651
|
+
packageName: input.packageName,
|
|
1652
|
+
status: "failed",
|
|
1653
|
+
version: null,
|
|
1654
|
+
diagnostics
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
const reading = recipe.parse(outcome2.stdout, input.packageName);
|
|
1658
|
+
if (reading.status === "unknown") {
|
|
1659
|
+
diagnostics.push(diagnostic({
|
|
1660
|
+
severity: "warning",
|
|
1661
|
+
code: "inventory-query-unreadable",
|
|
1662
|
+
message: `${recipe.executable} answered, but its output did not name an installed version in the form this build reads`,
|
|
1663
|
+
remediation: null
|
|
1664
|
+
}));
|
|
1665
|
+
}
|
|
1666
|
+
return {
|
|
1667
|
+
channel: input.channel,
|
|
1668
|
+
packageName: input.packageName,
|
|
1669
|
+
status: reading.status,
|
|
1670
|
+
version: reading.version,
|
|
1671
|
+
diagnostics
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1269
1674
|
var DEFAULT_INSTALL_TIMEOUT_MS = 6e5;
|
|
1270
1675
|
async function runPackageManagerInstall(input) {
|
|
1271
1676
|
const { action } = input;
|
|
@@ -1345,14 +1750,119 @@ async function runPackageManagerInstall(input) {
|
|
|
1345
1750
|
}));
|
|
1346
1751
|
return { status: "failed", diagnostics };
|
|
1347
1752
|
}
|
|
1348
|
-
diagnostics.push(diagnostic({
|
|
1349
|
-
severity: "info",
|
|
1350
|
-
code: "install-not-reversible",
|
|
1351
|
-
message: `${action.packageName} was installed through ${action.packageManager}; a rollback restores files and will not uninstall it`,
|
|
1352
|
-
remediation: null
|
|
1353
|
-
}));
|
|
1354
1753
|
return { status: "installed", diagnostics };
|
|
1355
1754
|
}
|
|
1755
|
+
async function restorePackageInventory(input) {
|
|
1756
|
+
const { capture } = input;
|
|
1757
|
+
if (capture.status === "absent") {
|
|
1758
|
+
return {
|
|
1759
|
+
restored: false,
|
|
1760
|
+
diagnostics: [
|
|
1761
|
+
diagnostic({
|
|
1762
|
+
severity: "info",
|
|
1763
|
+
code: "package-inventory-unrestored",
|
|
1764
|
+
message: `${capture.packageName} was not installed before the transaction; restoring that absence would require an uninstall command, so it stays installed`,
|
|
1765
|
+
remediation: null
|
|
1766
|
+
})
|
|
1767
|
+
]
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
if (capture.status !== "captured" || capture.version === null) {
|
|
1771
|
+
return {
|
|
1772
|
+
restored: false,
|
|
1773
|
+
diagnostics: [
|
|
1774
|
+
diagnostic({
|
|
1775
|
+
severity: "info",
|
|
1776
|
+
code: "package-inventory-unrestored",
|
|
1777
|
+
message: `${capture.packageName} was installed without a captured inventory, so it could not be restored`,
|
|
1778
|
+
remediation: null
|
|
1779
|
+
})
|
|
1780
|
+
]
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
if (input.runner === null) {
|
|
1784
|
+
return {
|
|
1785
|
+
restored: false,
|
|
1786
|
+
diagnostics: [
|
|
1787
|
+
diagnostic({
|
|
1788
|
+
severity: "info",
|
|
1789
|
+
code: "package-inventory-unrestored",
|
|
1790
|
+
message: `${capture.packageName} could not be restored: no process runner is available`,
|
|
1791
|
+
remediation: null
|
|
1792
|
+
})
|
|
1793
|
+
]
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
const restoreAction = {
|
|
1797
|
+
kind: "package-manager-install",
|
|
1798
|
+
id: `restore-${capture.packageName}`,
|
|
1799
|
+
riskClass: "delegated",
|
|
1800
|
+
requiresNetwork: true,
|
|
1801
|
+
requiresElevation: false,
|
|
1802
|
+
affectedPaths: [],
|
|
1803
|
+
affectedProcesses: [capture.channel],
|
|
1804
|
+
preconditions: [],
|
|
1805
|
+
postconditions: [],
|
|
1806
|
+
rollbackData: "none",
|
|
1807
|
+
explanation: `Restore ${capture.packageName} to ${capture.version} after a rollback`,
|
|
1808
|
+
packageManager: capture.channel,
|
|
1809
|
+
packageName: capture.packageName,
|
|
1810
|
+
version: capture.version
|
|
1811
|
+
};
|
|
1812
|
+
const result = await runPackageManagerInstall({
|
|
1813
|
+
action: restoreAction,
|
|
1814
|
+
runner: input.runner,
|
|
1815
|
+
cwd: input.cwd,
|
|
1816
|
+
...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs }
|
|
1817
|
+
});
|
|
1818
|
+
if (result.status !== "installed") {
|
|
1819
|
+
return {
|
|
1820
|
+
restored: false,
|
|
1821
|
+
diagnostics: [
|
|
1822
|
+
...result.diagnostics,
|
|
1823
|
+
diagnostic({
|
|
1824
|
+
severity: "error",
|
|
1825
|
+
code: "package-restore-failed",
|
|
1826
|
+
message: `${capture.packageName} could not be restored to ${capture.version} through ${capture.channel}`,
|
|
1827
|
+
path: null,
|
|
1828
|
+
remediation: `Install ${capture.packageName} at ${capture.version} yourself, then run the command again`
|
|
1829
|
+
})
|
|
1830
|
+
]
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const verified = await queryPackageInventory({
|
|
1834
|
+
channel: capture.channel,
|
|
1835
|
+
packageName: capture.packageName,
|
|
1836
|
+
runner: input.runner,
|
|
1837
|
+
cwd: input.cwd,
|
|
1838
|
+
...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs }
|
|
1839
|
+
});
|
|
1840
|
+
if (verified.status !== "captured" || verified.version !== capture.version) {
|
|
1841
|
+
return {
|
|
1842
|
+
restored: false,
|
|
1843
|
+
diagnostics: [
|
|
1844
|
+
...verified.diagnostics,
|
|
1845
|
+
diagnostic({
|
|
1846
|
+
severity: "error",
|
|
1847
|
+
code: "package-restore-failed",
|
|
1848
|
+
message: `${capture.packageName} was reinstalled but the inventory does not report ${capture.version}`,
|
|
1849
|
+
remediation: `Confirm what ${capture.channel} has installed and correct it by hand`
|
|
1850
|
+
})
|
|
1851
|
+
]
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
return {
|
|
1855
|
+
restored: true,
|
|
1856
|
+
diagnostics: [
|
|
1857
|
+
diagnostic({
|
|
1858
|
+
severity: "info",
|
|
1859
|
+
code: "package-inventory-restored",
|
|
1860
|
+
message: `${capture.packageName} was restored to ${capture.version} through ${capture.channel} and the inventory was re-read`,
|
|
1861
|
+
remediation: null
|
|
1862
|
+
})
|
|
1863
|
+
]
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1356
1866
|
|
|
1357
1867
|
// packages/core/dist/src/state/json-merge.js
|
|
1358
1868
|
function isJsonObject(value) {
|
|
@@ -1772,7 +2282,8 @@ function outcome(action, status, parts = {}) {
|
|
|
1772
2282
|
status,
|
|
1773
2283
|
snapshots: parts.snapshots ?? [],
|
|
1774
2284
|
ownership: parts.ownership ?? [],
|
|
1775
|
-
diagnostics: parts.diagnostics ?? []
|
|
2285
|
+
diagnostics: parts.diagnostics ?? [],
|
|
2286
|
+
packageInventory: parts.packageInventory ?? null
|
|
1776
2287
|
};
|
|
1777
2288
|
}
|
|
1778
2289
|
function drift(action, path, message) {
|
|
@@ -2037,6 +2548,141 @@ async function applyRemoveOwnedChange(action, context) {
|
|
|
2037
2548
|
await fs.writeFile(action.target.path, UTF8.encode(removal.text), null);
|
|
2038
2549
|
return outcome(action, "applied", { snapshots: [snapshot] });
|
|
2039
2550
|
}
|
|
2551
|
+
async function scanTree(fs, path, entries = []) {
|
|
2552
|
+
const stat3 = await fs.stat(path);
|
|
2553
|
+
if (stat3 === null)
|
|
2554
|
+
return entries;
|
|
2555
|
+
const digest = stat3.kind === "file" ? digestBytes(await fs.readFile(path)) : null;
|
|
2556
|
+
entries.push({ path, kind: stat3.kind, digest, byteLength: stat3.byteLength });
|
|
2557
|
+
if (stat3.kind === "directory") {
|
|
2558
|
+
for (const child of await fs.readDirectory(path)) {
|
|
2559
|
+
await scanTree(fs, fs.join(path, child), entries);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
return entries;
|
|
2563
|
+
}
|
|
2564
|
+
function equalTreeEntry(left, right) {
|
|
2565
|
+
if (left === void 0 && right === void 0)
|
|
2566
|
+
return true;
|
|
2567
|
+
return left !== void 0 && right !== void 0 && left.kind === right.kind && left.digest === right.digest;
|
|
2568
|
+
}
|
|
2569
|
+
async function applyDelegatedProviderInstall(action, context) {
|
|
2570
|
+
if (context.runner === null || context.runner === void 0 || context.cwd === void 0) {
|
|
2571
|
+
return refusal(action, "process-runner-unavailable", action.executable, "A delegated provider install requires a process runner and working directory", "Run this command through the Token Harness CLI");
|
|
2572
|
+
}
|
|
2573
|
+
const before = /* @__PURE__ */ new Map();
|
|
2574
|
+
for (const boundary of action.containmentBoundary) {
|
|
2575
|
+
for (const entry of await scanTree(context.fs, boundary))
|
|
2576
|
+
before.set(entry.path, entry);
|
|
2577
|
+
}
|
|
2578
|
+
if (action.expectedArtifacts.every((artifact) => before.get(artifact.path)?.kind === "file" && before.get(artifact.path)?.digest === artifact.digest)) {
|
|
2579
|
+
return outcome(action, "already-satisfied");
|
|
2580
|
+
}
|
|
2581
|
+
const bytes = [...before.values()].reduce((total, entry) => total + entry.byteLength, 0);
|
|
2582
|
+
if (bytes > action.snapshotSizeCapBytes) {
|
|
2583
|
+
return refusal(action, "delegated-install-snapshot-too-large", action.containmentBoundary[0] ?? action.executable, `The delegated install boundary is ${String(bytes)} bytes, above its ${String(action.snapshotSizeCapBytes)} byte snapshot cap`, "Narrow the provider containment boundary before applying this plan");
|
|
2584
|
+
}
|
|
2585
|
+
const snapshots = [];
|
|
2586
|
+
const capturedPaths = /* @__PURE__ */ new Set();
|
|
2587
|
+
for (const boundary of action.containmentBoundary) {
|
|
2588
|
+
const entries = await scanTree(context.fs, boundary);
|
|
2589
|
+
if (entries.length === 0) {
|
|
2590
|
+
snapshots.push(await context.snapshots.capture(boundary));
|
|
2591
|
+
capturedPaths.add(boundary);
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
for (const entry of entries) {
|
|
2595
|
+
snapshots.push(await context.snapshots.capture(entry.path));
|
|
2596
|
+
capturedPaths.add(entry.path);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
const result = await context.runner.run({
|
|
2600
|
+
executable: action.executable,
|
|
2601
|
+
args: action.args,
|
|
2602
|
+
cwd: context.cwd,
|
|
2603
|
+
timeoutMs: 3e4
|
|
2604
|
+
});
|
|
2605
|
+
if (!processSucceeded(result)) {
|
|
2606
|
+
return outcome(action, "failed", {
|
|
2607
|
+
snapshots,
|
|
2608
|
+
diagnostics: [
|
|
2609
|
+
diagnostic({
|
|
2610
|
+
severity: "error",
|
|
2611
|
+
code: "delegated-install-failed",
|
|
2612
|
+
message: `${action.executable} did not complete its delegated install successfully`,
|
|
2613
|
+
remediation: "Review the provider output and rerun the plan after correcting the problem"
|
|
2614
|
+
})
|
|
2615
|
+
]
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
const after = /* @__PURE__ */ new Map();
|
|
2619
|
+
for (const boundary of action.containmentBoundary) {
|
|
2620
|
+
for (const entry of await scanTree(context.fs, boundary))
|
|
2621
|
+
after.set(entry.path, entry);
|
|
2622
|
+
}
|
|
2623
|
+
for (const entry of after.values()) {
|
|
2624
|
+
if (!before.has(entry.path) && !capturedPaths.has(entry.path)) {
|
|
2625
|
+
snapshots.push(context.snapshots.captureAbsent(entry.path));
|
|
2626
|
+
capturedPaths.add(entry.path);
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
const protectedPath = action.protectedPaths.find((path) => !equalTreeEntry(before.get(path), after.get(path)));
|
|
2630
|
+
if (protectedPath !== void 0) {
|
|
2631
|
+
return outcome(action, "failed", {
|
|
2632
|
+
snapshots,
|
|
2633
|
+
diagnostics: [
|
|
2634
|
+
diagnostic({
|
|
2635
|
+
severity: "error",
|
|
2636
|
+
code: "delegated-install-protected-path-changed",
|
|
2637
|
+
message: `The delegated installer changed the protected path ${protectedPath}`,
|
|
2638
|
+
path: protectedPath,
|
|
2639
|
+
remediation: "Do not apply this provider until its reviewed write set is updated"
|
|
2640
|
+
})
|
|
2641
|
+
]
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
const changed = /* @__PURE__ */ new Set([...before.keys(), ...after.keys()]);
|
|
2645
|
+
const undeclared = [...changed].map((path) => after.get(path) ?? before.get(path)).find((entry) => entry !== void 0 && !equalTreeEntry(before.get(entry.path), after.get(entry.path)) && !action.expectedArtifacts.some((artifact) => artifact.path === entry.path || entry.kind === "directory" && context.fs.isInside(artifact.path, entry.path)));
|
|
2646
|
+
if (undeclared !== void 0) {
|
|
2647
|
+
return outcome(action, "failed", {
|
|
2648
|
+
snapshots,
|
|
2649
|
+
diagnostics: [
|
|
2650
|
+
diagnostic({
|
|
2651
|
+
severity: "error",
|
|
2652
|
+
code: "delegated-install-undeclared-write",
|
|
2653
|
+
message: `The delegated installer changed an undeclared path inside its containment boundary: ${undeclared.path}`,
|
|
2654
|
+
path: undeclared.path,
|
|
2655
|
+
remediation: "Do not apply this provider until its reviewed write set is updated"
|
|
2656
|
+
})
|
|
2657
|
+
]
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
const missing = action.expectedArtifacts.find((artifact) => after.get(artifact.path)?.kind !== "file" || after.get(artifact.path)?.digest !== artifact.digest);
|
|
2661
|
+
if (missing !== void 0) {
|
|
2662
|
+
return outcome(action, "failed", {
|
|
2663
|
+
snapshots,
|
|
2664
|
+
diagnostics: [
|
|
2665
|
+
diagnostic({
|
|
2666
|
+
severity: "error",
|
|
2667
|
+
code: "delegated-install-artifact-mismatch",
|
|
2668
|
+
message: `The delegated installer did not produce the reviewed artifact ${missing.path}`,
|
|
2669
|
+
path: missing.path,
|
|
2670
|
+
remediation: "Do not apply this provider until its reviewed artifact digests are updated"
|
|
2671
|
+
})
|
|
2672
|
+
]
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
const ownership = [];
|
|
2676
|
+
for (const artifact of action.expectedArtifacts) {
|
|
2677
|
+
ownership.push({
|
|
2678
|
+
kind: "owned-file",
|
|
2679
|
+
path: artifact.path,
|
|
2680
|
+
digest: artifact.digest,
|
|
2681
|
+
mode: (await context.fs.stat(artifact.path))?.mode ?? null
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
return outcome(action, "applied", { snapshots, ownership });
|
|
2685
|
+
}
|
|
2040
2686
|
function notImplemented(action) {
|
|
2041
2687
|
return outcome(action, "failed", {
|
|
2042
2688
|
diagnostics: [
|
|
@@ -2063,9 +2709,10 @@ async function applyAction(action, context) {
|
|
|
2063
2709
|
return applyMergeJson(action, context);
|
|
2064
2710
|
case "package-manager-install":
|
|
2065
2711
|
return applyPackageManagerInstall(action, context);
|
|
2712
|
+
case "delegated-provider-install":
|
|
2713
|
+
return applyDelegatedProviderInstall(action, context);
|
|
2066
2714
|
case "download-artifact":
|
|
2067
2715
|
case "run-installer-command":
|
|
2068
|
-
case "delegated-provider-install":
|
|
2069
2716
|
case "merge-toml":
|
|
2070
2717
|
case "merge-yaml":
|
|
2071
2718
|
case "register-mcp-server":
|
|
@@ -2074,13 +2721,38 @@ async function applyAction(action, context) {
|
|
|
2074
2721
|
}
|
|
2075
2722
|
}
|
|
2076
2723
|
async function applyPackageManagerInstall(action, context) {
|
|
2724
|
+
const wantsInventory = action.rollbackData === "package-inventory" && !action.requiresElevation;
|
|
2725
|
+
const packageInventory = wantsInventory && context.runner !== void 0 ? await queryPackageInventory({
|
|
2726
|
+
channel: action.packageManager,
|
|
2727
|
+
packageName: action.packageName,
|
|
2728
|
+
runner: context.runner,
|
|
2729
|
+
cwd: context.cwd ?? ""
|
|
2730
|
+
}) : null;
|
|
2077
2731
|
const result = await runPackageManagerInstall({
|
|
2078
2732
|
action,
|
|
2079
2733
|
runner: context.runner ?? null,
|
|
2080
2734
|
cwd: context.cwd ?? ""
|
|
2081
2735
|
});
|
|
2082
2736
|
const status = result.status === "installed" ? "applied" : result.status === "refused" ? "refused" : "failed";
|
|
2083
|
-
|
|
2737
|
+
const diagnostics = [...result.diagnostics];
|
|
2738
|
+
if (status === "applied") {
|
|
2739
|
+
if (packageInventory?.status === "captured" && packageInventory.version !== null) {
|
|
2740
|
+
diagnostics.push(diagnostic({
|
|
2741
|
+
severity: "info",
|
|
2742
|
+
code: "install-inventory-captured",
|
|
2743
|
+
message: `${action.packageName} was installed through ${action.packageManager}; the prior state (${packageInventory.version}) was captured, so a rollback can restore it`,
|
|
2744
|
+
remediation: null
|
|
2745
|
+
}));
|
|
2746
|
+
} else {
|
|
2747
|
+
diagnostics.push(diagnostic({
|
|
2748
|
+
severity: "info",
|
|
2749
|
+
code: "install-not-reversible",
|
|
2750
|
+
message: `${action.packageName} was installed through ${action.packageManager}; a rollback restores files and will not uninstall it`,
|
|
2751
|
+
remediation: null
|
|
2752
|
+
}));
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
return outcome(action, status, { diagnostics, packageInventory });
|
|
2084
2756
|
}
|
|
2085
2757
|
|
|
2086
2758
|
// packages/core/dist/src/state/journal.js
|
|
@@ -2559,23 +3231,25 @@ var TransactionSnapshotStore = class _TransactionSnapshotStore {
|
|
|
2559
3231
|
this.taken.push(snapshot);
|
|
2560
3232
|
return snapshot;
|
|
2561
3233
|
}
|
|
3234
|
+
captureAbsent(path) {
|
|
3235
|
+
return this.remember({
|
|
3236
|
+
schemaVersion: 1,
|
|
3237
|
+
path,
|
|
3238
|
+
existed: false,
|
|
3239
|
+
wasDirectory: false,
|
|
3240
|
+
digest: null,
|
|
3241
|
+
mode: null,
|
|
3242
|
+
byteLength: null,
|
|
3243
|
+
contentRef: null,
|
|
3244
|
+
capturedAt: this.input.now()
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
2562
3247
|
async capture(path) {
|
|
2563
3248
|
const { fs } = this.input;
|
|
2564
3249
|
const stat3 = await fs.stat(path);
|
|
2565
3250
|
const capturedAt = this.input.now();
|
|
2566
|
-
if (stat3 === null)
|
|
2567
|
-
return this.
|
|
2568
|
-
schemaVersion: 1,
|
|
2569
|
-
path,
|
|
2570
|
-
existed: false,
|
|
2571
|
-
wasDirectory: false,
|
|
2572
|
-
digest: null,
|
|
2573
|
-
mode: null,
|
|
2574
|
-
byteLength: null,
|
|
2575
|
-
contentRef: null,
|
|
2576
|
-
capturedAt
|
|
2577
|
-
});
|
|
2578
|
-
}
|
|
3251
|
+
if (stat3 === null)
|
|
3252
|
+
return this.captureAbsent(path);
|
|
2579
3253
|
if (stat3.kind === "directory") {
|
|
2580
3254
|
return this.remember({
|
|
2581
3255
|
schemaVersion: 1,
|
|
@@ -2674,6 +3348,24 @@ async function verifyRestoration(fs, snapshots) {
|
|
|
2674
3348
|
}
|
|
2675
3349
|
return unrestored;
|
|
2676
3350
|
}
|
|
3351
|
+
async function restorePackageInventories(records, runner, cwd) {
|
|
3352
|
+
const diagnostics = [];
|
|
3353
|
+
for (const record of records) {
|
|
3354
|
+
if (record.kind !== "package-manager-install")
|
|
3355
|
+
continue;
|
|
3356
|
+
if (record.status !== "applied")
|
|
3357
|
+
continue;
|
|
3358
|
+
if (record.packageInventory === null || record.packageInventory === void 0)
|
|
3359
|
+
continue;
|
|
3360
|
+
const outcome2 = await restorePackageInventory({
|
|
3361
|
+
capture: record.packageInventory,
|
|
3362
|
+
runner,
|
|
3363
|
+
cwd
|
|
3364
|
+
});
|
|
3365
|
+
diagnostics.push(...outcome2.diagnostics);
|
|
3366
|
+
}
|
|
3367
|
+
return diagnostics;
|
|
3368
|
+
}
|
|
2677
3369
|
async function executeTransaction(request) {
|
|
2678
3370
|
const startedAt = request.now();
|
|
2679
3371
|
const entries = [];
|
|
@@ -2717,7 +3409,10 @@ async function executeTransaction(request) {
|
|
|
2717
3409
|
restoreError = error instanceof Error ? error.message : String(error);
|
|
2718
3410
|
}
|
|
2719
3411
|
const unrestored = await verifyRestoration(request.fs, snapshots);
|
|
2720
|
-
|
|
3412
|
+
const packageDiagnostics = await restorePackageInventories(applied, request.runner ?? null, request.projectRoot);
|
|
3413
|
+
diagnostics.push(...packageDiagnostics);
|
|
3414
|
+
const packageRestoreFailed = packageDiagnostics.some((entry) => entry.severity === "error");
|
|
3415
|
+
if (restoreError === null && unrestored.length === 0 && !packageRestoreFailed) {
|
|
2721
3416
|
diagnostics.push(diagnostic({
|
|
2722
3417
|
severity: "info",
|
|
2723
3418
|
code: "transaction-rolled-back",
|
|
@@ -2745,6 +3440,7 @@ async function executeTransaction(request) {
|
|
|
2745
3440
|
status: "failed",
|
|
2746
3441
|
snapshots: [],
|
|
2747
3442
|
ownership: [],
|
|
3443
|
+
packageInventory: null,
|
|
2748
3444
|
diagnostics: [
|
|
2749
3445
|
diagnostic({
|
|
2750
3446
|
severity: "error",
|
|
@@ -2762,7 +3458,8 @@ async function executeTransaction(request) {
|
|
|
2762
3458
|
status: result.status,
|
|
2763
3459
|
snapshots: result.snapshots,
|
|
2764
3460
|
ownership: result.ownership,
|
|
2765
|
-
diagnostics: result.diagnostics
|
|
3461
|
+
diagnostics: result.diagnostics,
|
|
3462
|
+
packageInventory: result.packageInventory
|
|
2766
3463
|
};
|
|
2767
3464
|
entries.push(entry);
|
|
2768
3465
|
applied.push(result);
|
|
@@ -2847,7 +3544,10 @@ async function rollbackTransaction(request) {
|
|
|
2847
3544
|
restoreError = error instanceof Error ? error.message : String(error);
|
|
2848
3545
|
}
|
|
2849
3546
|
const unrestored = await verifyRestoration(request.fs, snapshots);
|
|
2850
|
-
|
|
3547
|
+
const packageDiagnostics = await restorePackageInventories(journal.entries, request.runner ?? null, request.cwd ?? journal.projectRoot);
|
|
3548
|
+
diagnostics.push(...packageDiagnostics);
|
|
3549
|
+
const packageRestoreFailed = packageDiagnostics.some((entry) => entry.severity === "error");
|
|
3550
|
+
if (restoreError === null && unrestored.length === 0 && !packageRestoreFailed) {
|
|
2851
3551
|
journal.outcome = "rolled-back";
|
|
2852
3552
|
journal.ownership = [];
|
|
2853
3553
|
journal.finishedAt = request.now();
|
|
@@ -3168,9 +3868,9 @@ async function readLocalDatabase(request) {
|
|
|
3168
3868
|
database = new construct(request.path, { readOnly: true });
|
|
3169
3869
|
const statement = database.prepare(request.sql);
|
|
3170
3870
|
const raw = statement.all(...request.parameters);
|
|
3171
|
-
const rows = raw.map((
|
|
3871
|
+
const rows = raw.map((row2) => {
|
|
3172
3872
|
const mapped = {};
|
|
3173
|
-
for (const [column2, value] of Object.entries(
|
|
3873
|
+
for (const [column2, value] of Object.entries(row2))
|
|
3174
3874
|
mapped[column2] = toValue(value);
|
|
3175
3875
|
return mapped;
|
|
3176
3876
|
});
|
|
@@ -4447,9 +5147,20 @@ async function inspect(context) {
|
|
|
4447
5147
|
diagnostics.push(diagnostic({
|
|
4448
5148
|
severity: "warning",
|
|
4449
5149
|
code: "tool-family-not-covered",
|
|
4450
|
-
|
|
5150
|
+
subject: CLAUDE,
|
|
5151
|
+
/**
|
|
5152
|
+
* Says whose gap it is, and that the tool is working.
|
|
5153
|
+
*
|
|
5154
|
+
* The first wording — "No configured matcher covers the PowerShell tool family" — read like a
|
|
5155
|
+
* defect in Token Harness on a first run. It is not: the hook in the file below matches some
|
|
5156
|
+
* tool families and not others, Claude Code routes shell commands through more than one on
|
|
5157
|
+
* Windows, and the commands going through the uncovered ones never reach the provider at all.
|
|
5158
|
+
*/
|
|
5159
|
+
// Short enough to survive the one-line human rendering without being cut.
|
|
5160
|
+
message: `${uncoveredToolFamilies.join(", ")} has no hook, so those commands are not optimized`,
|
|
4451
5161
|
path: configs.find((config) => config.configuredPoints.length > 0)?.path ?? null,
|
|
4452
|
-
|
|
5162
|
+
// Short on purpose: a remediation nobody finishes reading is not one.
|
|
5163
|
+
remediation: "Optional. Widen the matcher, or accept the gap"
|
|
4453
5164
|
}));
|
|
4454
5165
|
}
|
|
4455
5166
|
return {
|
|
@@ -4743,6 +5454,7 @@ async function inspect2(context) {
|
|
|
4743
5454
|
diagnostic({
|
|
4744
5455
|
severity: "info",
|
|
4745
5456
|
code: "hook-enablement-unobservable",
|
|
5457
|
+
subject: CODEX,
|
|
4746
5458
|
message: "Codex declares this hook, but its persisted enablement and trust state cannot be read outside the TUI",
|
|
4747
5459
|
path: hook.path,
|
|
4748
5460
|
remediation: "Open Codex and confirm the hook is enabled and trusted after any hook edit"
|
|
@@ -4852,20 +5564,47 @@ var MANIFEST3 = {
|
|
|
4852
5564
|
* configuration must be able to exit 0. A declared limitation is not a problem, and reporting
|
|
4853
5565
|
* it as one is the fastest way to teach users to ignore the exit code."
|
|
4854
5566
|
*
|
|
4855
|
-
* A
|
|
4856
|
-
*
|
|
5567
|
+
* A range now, because spike 9.1 observed two more: the CLI at `1.18.11` and OpenCode Desktop at
|
|
5568
|
+
* `1.18.14`, both loading plugins from the same directories. The span is what was watched, not a
|
|
5569
|
+
* guess that every 1.18.x behaves alike — and the two ends differ in a way that matters, which is
|
|
5570
|
+
* exactly why both are named. An older 1.x reports `unknown-older`, which is a warning and not a
|
|
4857
5571
|
* problem, and a newer one reports `unknown-newer` — which is then a true statement.
|
|
4858
5572
|
*/
|
|
4859
|
-
testedVersions: { minimum: "1.18.9", maximum: "1.18.
|
|
5573
|
+
testedVersions: { minimum: "1.18.9", maximum: "1.18.14" },
|
|
4860
5574
|
// RFC 0007: a managed provider can emit a receipt, but an adopted generated wrapper
|
|
4861
5575
|
// has no externally observable receipt. The harness adapter alone therefore declares
|
|
4862
5576
|
// the achievable baseline, not a promise about a provider it does not own.
|
|
4863
5577
|
verificationTier: "config-only",
|
|
4864
5578
|
versionCommand: { executable: "opencode", args: ["--version"] },
|
|
4865
|
-
interceptionPoints: [
|
|
5579
|
+
interceptionPoints: [
|
|
5580
|
+
{ scopeId: "tool-execute-before", eventName: "tool.execute.before" },
|
|
5581
|
+
{ scopeId: "tool-execute-after", eventName: "tool.execute.after" }
|
|
5582
|
+
],
|
|
5583
|
+
/**
|
|
5584
|
+
* Two kinds of entry, because OpenCode has two ways to register a plugin and only one of them is
|
|
5585
|
+
* a document.
|
|
5586
|
+
*
|
|
5587
|
+
* The `opencode.jsonc` files carry a `plugin` string array. The directories are auto-loaded: any
|
|
5588
|
+
* module dropped in one is registered without being named anywhere, which is how both real
|
|
5589
|
+
* integrations install — `rtk init -g --opencode` writes `.config/opencode/plugins/rtk.ts` and
|
|
5590
|
+
* touches no JSON at all, and HarnessTrim's generated wrapper says in its own header not to
|
|
5591
|
+
* reference it from `opencode.json`. An adapter that read only the array would report every such
|
|
5592
|
+
* installation as absent, which is the state this file was in before spike 9.1.
|
|
5593
|
+
*
|
|
5594
|
+
* Both spellings are listed because both load. Spike 9.1 put a probe in each and one run fired
|
|
5595
|
+
* both; the upstream documentation names the plural, the binary carries both strings, and the two
|
|
5596
|
+
* shipped integrations disagree with each other about which to use.
|
|
5597
|
+
*
|
|
5598
|
+
* `markers` is the parser because there is nothing to parse: the presence of a file is the
|
|
5599
|
+
* configuration, and its contents are a plugin module rather than a document this build reads.
|
|
5600
|
+
*/
|
|
4866
5601
|
configFiles: [
|
|
4867
5602
|
{ path: ".config/opencode/opencode.jsonc", scope: "user", parser: "jsonc", primary: true },
|
|
4868
|
-
{ path: "opencode.jsonc", scope: "project", parser: "jsonc", primary: false }
|
|
5603
|
+
{ path: "opencode.jsonc", scope: "project", parser: "jsonc", primary: false },
|
|
5604
|
+
{ path: ".config/opencode/plugins", scope: "user", parser: "markers", primary: false },
|
|
5605
|
+
{ path: ".config/opencode/plugin", scope: "user", parser: "markers", primary: false },
|
|
5606
|
+
{ path: ".opencode/plugins", scope: "project", parser: "markers", primary: false },
|
|
5607
|
+
{ path: ".opencode/plugin", scope: "project", parser: "markers", primary: false }
|
|
4869
5608
|
],
|
|
4870
5609
|
toolFamilies: [
|
|
4871
5610
|
{ id: "tool.execute", platforms: ["windows", "macos", "linux"], executesShellCommands: true }
|
|
@@ -4882,7 +5621,38 @@ function pluginEntries(document2) {
|
|
|
4882
5621
|
return [];
|
|
4883
5622
|
return document2["plugin"].filter((item) => typeof item === "string");
|
|
4884
5623
|
}
|
|
5624
|
+
var PLUGIN_MODULE_PATTERN = /\.(ts|js|mjs|cjs|mts|cts)$/i;
|
|
5625
|
+
var PLUGIN_POINTS = ["tool-execute-before", "tool-execute-after"];
|
|
5626
|
+
async function resolvePluginDirectory(declaration, context) {
|
|
5627
|
+
const path = resolveConfigPath(declaration, context);
|
|
5628
|
+
const stat3 = await context.fs.stat(path);
|
|
5629
|
+
if (stat3 === null || stat3.kind !== "directory") {
|
|
5630
|
+
return {
|
|
5631
|
+
declaration,
|
|
5632
|
+
path,
|
|
5633
|
+
exists: false,
|
|
5634
|
+
parsed: false,
|
|
5635
|
+
configuredPoints: [],
|
|
5636
|
+
matchers: [],
|
|
5637
|
+
commands: []
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
const modules = (await context.fs.readDirectory(path)).filter((entry) => PLUGIN_MODULE_PATTERN.test(entry));
|
|
5641
|
+
return {
|
|
5642
|
+
declaration,
|
|
5643
|
+
path,
|
|
5644
|
+
exists: true,
|
|
5645
|
+
// A directory listing cannot fail to parse. `markers` declares that there is nothing to read.
|
|
5646
|
+
parsed: true,
|
|
5647
|
+
configuredPoints: modules.length > 0 ? [...PLUGIN_POINTS] : [],
|
|
5648
|
+
// The plugin API sees every tool result; this is not a shell matcher.
|
|
5649
|
+
matchers: modules.length > 0 ? ["tool.execute"] : [],
|
|
5650
|
+
commands: modules.map((entry) => context.fs.join(path, entry))
|
|
5651
|
+
};
|
|
5652
|
+
}
|
|
4885
5653
|
async function resolveConfig3(declaration, context) {
|
|
5654
|
+
if (declaration.parser === "markers")
|
|
5655
|
+
return resolvePluginDirectory(declaration, context);
|
|
4886
5656
|
const path = resolveConfigPath(declaration, context);
|
|
4887
5657
|
const stat3 = await context.fs.stat(path);
|
|
4888
5658
|
if (stat3 === null || stat3.kind !== "file") {
|
|
@@ -4914,7 +5684,9 @@ async function resolveConfig3(declaration, context) {
|
|
|
4914
5684
|
path,
|
|
4915
5685
|
exists: true,
|
|
4916
5686
|
parsed: true,
|
|
4917
|
-
|
|
5687
|
+
// A `plugin` array entry is the same kind of thing as a module in a plugin directory: a
|
|
5688
|
+
// registration whose hooks are only known once it runs. Same claim, same reason.
|
|
5689
|
+
configuredPoints: plugins.length > 0 ? [...PLUGIN_POINTS] : [],
|
|
4918
5690
|
// The plugin API sees every tool result; this is not a shell matcher.
|
|
4919
5691
|
matchers: plugins.length > 0 ? ["tool.execute"] : [],
|
|
4920
5692
|
commands: plugins
|
|
@@ -5072,8 +5844,24 @@ function listHarnessAdapters() {
|
|
|
5072
5844
|
var HARNESSTRIM2 = providerId("harnesstrim");
|
|
5073
5845
|
var CLAUDE2 = harnessId("claude");
|
|
5074
5846
|
var CODEX2 = harnessId("codex");
|
|
5847
|
+
var CLAUDE_ARTIFACT_DIGESTS = {
|
|
5848
|
+
"compact-handoff/SKILL.md": "sha256:0efbf35581c559359e755b204778b64a289dab4d20dadc1cba3d5b5c995b5f01",
|
|
5849
|
+
"debug-log-slim/SKILL.md": "sha256:17f5ccc34d29d7aba083444e0e4d87fd3d49916ebcb4e6544caf8c89f13f0045",
|
|
5850
|
+
"delegate-bulk/SKILL.md": "sha256:3753de2ad18271c24832e4cda63115d353620515d5d80f40e93bc07d2a7257d7",
|
|
5851
|
+
"delta-response/SKILL.md": "sha256:b6a71f4bdcfcadde3b5242994baa017d0371fe11652dd763f55a2f6d3840cfa8",
|
|
5852
|
+
"delta-response/references/examples.md": "sha256:c67a1f57e63550b396043c3072b7e1a3a0c1522376471f53d6829253339e64e7",
|
|
5853
|
+
"review-delta/SKILL.md": "sha256:4dfbf9d6ec08dff27b4759726536b43928370e288706fa01f5b498648e741388",
|
|
5854
|
+
"scaffold-fast/SKILL.md": "sha256:1a18d52c4d335fbd3f74e2559bc20d6346193a40eef7b85149f51f44f697d182"
|
|
5855
|
+
};
|
|
5075
5856
|
var OPENCODE2 = harnessId("opencode");
|
|
5857
|
+
var MODE_CARRYING_HARNESSES = /* @__PURE__ */ new Set([
|
|
5858
|
+
OPENCODE2,
|
|
5859
|
+
harnessId("hermes"),
|
|
5860
|
+
harnessId("pi"),
|
|
5861
|
+
harnessId("omp")
|
|
5862
|
+
]);
|
|
5076
5863
|
var HOOK_COMMAND_PATTERN = /(^|[\\/\s"'])harnesstrim(\.cmd|\.exe)?([\s"']|$)/i;
|
|
5864
|
+
var PLUGIN_MODULE_PATTERN2 = /(^|[\\/])harnesstrim\.(ts|js|mjs|cjs|mts|cts)$/i;
|
|
5077
5865
|
var MANIFEST4 = {
|
|
5078
5866
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
5079
5867
|
id: HARNESSTRIM2,
|
|
@@ -5150,19 +5938,219 @@ var MANIFEST4 = {
|
|
|
5150
5938
|
}
|
|
5151
5939
|
],
|
|
5152
5940
|
metrics: {
|
|
5153
|
-
// RFC 0005 §Importer degradation policy
|
|
5154
|
-
//
|
|
5155
|
-
//
|
|
5941
|
+
// RFC 0005 §Importer degradation policy. `native` since PLAN §15 item 43d: the `0.1.0`
|
|
5942
|
+
// telemetry lines carry a producer `eventId` and nullable token counts, so dedup uses the
|
|
5943
|
+
// native ID and a token count is a token count where one exists. Legacy schema 0 lines
|
|
5944
|
+
// still parse, stay `estimated-local`, and never merge with the native figures.
|
|
5156
5945
|
source: "jsonl",
|
|
5157
|
-
mode: "
|
|
5158
|
-
|
|
5946
|
+
mode: "native",
|
|
5947
|
+
// PLAN §15 item 25: Hermes has no adapter, tested range or tier, so the home-relative path that
|
|
5948
|
+
// names it is not read. Item 30 readmits it together with the adapter and a matrix row; the
|
|
5949
|
+
// registry assertion in `registries.test.ts` refuses a home-relative location that names a
|
|
5950
|
+
// harness the registry does not know.
|
|
5951
|
+
locations: [".harnesstrim/metrics.jsonl"]
|
|
5159
5952
|
},
|
|
5160
|
-
delegatedInstallReview:
|
|
5953
|
+
delegatedInstallReview: {
|
|
5954
|
+
upstreamVersion: "0.0.7",
|
|
5955
|
+
reviewedWriteSet: [
|
|
5956
|
+
...Object.keys(CLAUDE_ARTIFACT_DIGESTS).map((path) => `.claude/skills/${path}`)
|
|
5957
|
+
],
|
|
5958
|
+
containmentBoundary: [".claude", "CLAUDE.md"],
|
|
5959
|
+
upstreamUninstallAvailable: true
|
|
5960
|
+
}
|
|
5161
5961
|
};
|
|
5162
|
-
var TESTED_UPSTREAM = "0.0.
|
|
5163
|
-
var TESTED_VERSIONS = { minimum:
|
|
5962
|
+
var TESTED_UPSTREAM = "0.0.7";
|
|
5963
|
+
var TESTED_VERSIONS = { minimum: "0.0.5", maximum: TESTED_UPSTREAM };
|
|
5164
5964
|
function identifiesCommand(command) {
|
|
5165
|
-
return HOOK_COMMAND_PATTERN.test(command);
|
|
5965
|
+
return HOOK_COMMAND_PATTERN.test(command) || PLUGIN_MODULE_PATTERN2.test(command);
|
|
5966
|
+
}
|
|
5967
|
+
function isStringArray(value) {
|
|
5968
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
5969
|
+
}
|
|
5970
|
+
function parseHarnessCapabilities(value) {
|
|
5971
|
+
if (typeof value !== "object" || value === null)
|
|
5972
|
+
return null;
|
|
5973
|
+
const record = value;
|
|
5974
|
+
const surfaces = record["surfaces"];
|
|
5975
|
+
const writeSet = record["writeSet"];
|
|
5976
|
+
const narrowing = record["narrowing"];
|
|
5977
|
+
if (!isStringArray(surfaces) || !isStringArray(writeSet) || !Array.isArray(narrowing)) {
|
|
5978
|
+
return null;
|
|
5979
|
+
}
|
|
5980
|
+
const flags = [];
|
|
5981
|
+
for (const item of narrowing) {
|
|
5982
|
+
if (typeof item !== "object" || item === null)
|
|
5983
|
+
return null;
|
|
5984
|
+
const flag = item["flag"];
|
|
5985
|
+
const produces = item["produces"];
|
|
5986
|
+
if (typeof flag !== "string" || typeof produces !== "string")
|
|
5987
|
+
return null;
|
|
5988
|
+
flags.push({ flag, produces });
|
|
5989
|
+
}
|
|
5990
|
+
return {
|
|
5991
|
+
adapter: typeof record["adapter"] === "string" ? record["adapter"] : "",
|
|
5992
|
+
surfaces,
|
|
5993
|
+
writeSet,
|
|
5994
|
+
narrowing: flags
|
|
5995
|
+
};
|
|
5996
|
+
}
|
|
5997
|
+
function parseCapabilities(stdout) {
|
|
5998
|
+
let parsed;
|
|
5999
|
+
try {
|
|
6000
|
+
parsed = JSON.parse(stdout);
|
|
6001
|
+
} catch {
|
|
6002
|
+
return null;
|
|
6003
|
+
}
|
|
6004
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
6005
|
+
return null;
|
|
6006
|
+
const record = parsed;
|
|
6007
|
+
const version2 = record["version"];
|
|
6008
|
+
const harnesses = record["harnesses"];
|
|
6009
|
+
if (typeof version2 !== "string" || typeof harnesses !== "object" || harnesses === null) {
|
|
6010
|
+
return null;
|
|
6011
|
+
}
|
|
6012
|
+
const entries = {};
|
|
6013
|
+
for (const [harness, value] of Object.entries(harnesses)) {
|
|
6014
|
+
const entry = parseHarnessCapabilities(value);
|
|
6015
|
+
if (entry === null)
|
|
6016
|
+
return null;
|
|
6017
|
+
entries[harness] = entry;
|
|
6018
|
+
}
|
|
6019
|
+
return { version: version2, harnesses: entries };
|
|
6020
|
+
}
|
|
6021
|
+
async function probeCapabilities(context) {
|
|
6022
|
+
const outcome2 = await context.runner.run({
|
|
6023
|
+
executable: "harnesstrim",
|
|
6024
|
+
args: ["capabilities"],
|
|
6025
|
+
cwd: context.projectRoot,
|
|
6026
|
+
timeoutMs: 2e4
|
|
6027
|
+
});
|
|
6028
|
+
if (outcome2.failure !== null || outcome2.exitCode !== 0) {
|
|
6029
|
+
return {
|
|
6030
|
+
capabilities: null,
|
|
6031
|
+
evidence: [
|
|
6032
|
+
evidence({
|
|
6033
|
+
kind: "absence",
|
|
6034
|
+
source: "harnesstrim capabilities",
|
|
6035
|
+
detail: "did not answer with a machine-readable declaration"
|
|
6036
|
+
})
|
|
6037
|
+
]
|
|
6038
|
+
};
|
|
6039
|
+
}
|
|
6040
|
+
const capabilities = parseCapabilities(outcome2.stdout);
|
|
6041
|
+
if (capabilities === null) {
|
|
6042
|
+
return {
|
|
6043
|
+
capabilities: null,
|
|
6044
|
+
evidence: [
|
|
6045
|
+
evidence({
|
|
6046
|
+
kind: "version-output",
|
|
6047
|
+
source: "harnesstrim capabilities",
|
|
6048
|
+
path: outcome2.executablePath,
|
|
6049
|
+
detail: "answered, but not with a declaration this build can read"
|
|
6050
|
+
})
|
|
6051
|
+
]
|
|
6052
|
+
};
|
|
6053
|
+
}
|
|
6054
|
+
return {
|
|
6055
|
+
capabilities,
|
|
6056
|
+
evidence: [
|
|
6057
|
+
evidence({
|
|
6058
|
+
kind: "version-output",
|
|
6059
|
+
source: "harnesstrim capabilities",
|
|
6060
|
+
path: outcome2.executablePath,
|
|
6061
|
+
detail: `declared ${capabilities.version} for ${Object.keys(capabilities.harnesses).join(", ")}`
|
|
6062
|
+
})
|
|
6063
|
+
]
|
|
6064
|
+
};
|
|
6065
|
+
}
|
|
6066
|
+
var SURFACE_ANCHORS = {
|
|
6067
|
+
"post-tool-use": "PostToolUse",
|
|
6068
|
+
"tool-execute-after": "tool.execute.after"
|
|
6069
|
+
};
|
|
6070
|
+
function surfaceMatchesDeclaration(surface, declaration) {
|
|
6071
|
+
const pointAnchor = SURFACE_ANCHORS[declaration.interceptionPoint];
|
|
6072
|
+
if (pointAnchor !== void 0 && !surface.includes(pointAnchor))
|
|
6073
|
+
return false;
|
|
6074
|
+
if (declaration.toolFamily !== EVERY_TOOL_FAMILY && !surface.includes(declaration.toolFamily)) {
|
|
6075
|
+
return false;
|
|
6076
|
+
}
|
|
6077
|
+
return true;
|
|
6078
|
+
}
|
|
6079
|
+
function writeSetPath(entry) {
|
|
6080
|
+
return entry.replace(/\s*\([^)]*\)\s*$/, "").replaceAll("\\", "/");
|
|
6081
|
+
}
|
|
6082
|
+
function coveredBy(path, declared) {
|
|
6083
|
+
const base = declared.endsWith("/") ? declared : `${declared}/`;
|
|
6084
|
+
return path === declared || path.startsWith(base);
|
|
6085
|
+
}
|
|
6086
|
+
function driftWarning(message, remediation) {
|
|
6087
|
+
return diagnostic({
|
|
6088
|
+
severity: "warning",
|
|
6089
|
+
code: "provider-capabilities-drift",
|
|
6090
|
+
subject: "harnesstrim",
|
|
6091
|
+
message,
|
|
6092
|
+
remediation
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
function formatSurface(surface) {
|
|
6096
|
+
return `${surface.toolFamily}/${surface.interceptionPoint}`;
|
|
6097
|
+
}
|
|
6098
|
+
function narrowable(capabilities) {
|
|
6099
|
+
if (capabilities === null || capabilities === void 0)
|
|
6100
|
+
return false;
|
|
6101
|
+
const managed = MANIFEST4.harnesses.map((entry) => entry.harness);
|
|
6102
|
+
return managed.every((harness) => {
|
|
6103
|
+
const observed = capabilities.harnesses[harness];
|
|
6104
|
+
return observed !== void 0 && observed.narrowing.length > 0;
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
function writeSetStillReviewed(capabilities, harness) {
|
|
6108
|
+
const review = MANIFEST4.delegatedInstallReview;
|
|
6109
|
+
if (review === null || capabilities === null)
|
|
6110
|
+
return false;
|
|
6111
|
+
const observed = capabilities.harnesses[harness];
|
|
6112
|
+
if (observed === void 0)
|
|
6113
|
+
return false;
|
|
6114
|
+
const declared = observed.writeSet.map(writeSetPath);
|
|
6115
|
+
const boundary = review.containmentBoundary.map(writeSetPath);
|
|
6116
|
+
const everyDeclaredPathContained = declared.every((entry) => boundary.some((prefix) => coveredBy(entry, prefix)));
|
|
6117
|
+
const everyReviewedPathCovered = review.reviewedWriteSet.every((reviewed) => declared.some((entry) => coveredBy(reviewed, entry)));
|
|
6118
|
+
return everyDeclaredPathContained && everyReviewedPathCovered;
|
|
6119
|
+
}
|
|
6120
|
+
function compareCapabilities(manifest, capabilities) {
|
|
6121
|
+
const warnings = [];
|
|
6122
|
+
for (const declaration of manifest.capabilities) {
|
|
6123
|
+
for (const harness of declaration.harnesses) {
|
|
6124
|
+
const observed = capabilities.harnesses[harness];
|
|
6125
|
+
if (observed === void 0) {
|
|
6126
|
+
warnings.push(driftWarning(`the manifest declares ${declaration.capability} on ${harness}, but \`harnesstrim capabilities\` (${capabilities.version}) lists no ${harness} entry`, `Re-test the ${harness} integration: either the manifest is stale or ${harness} lost its HarnessTrim surface`));
|
|
6127
|
+
continue;
|
|
6128
|
+
}
|
|
6129
|
+
const matched = declaration.surfaces.some((surface) => observed.surfaces.some((entry) => surfaceMatchesDeclaration(entry, surface)));
|
|
6130
|
+
if (!matched) {
|
|
6131
|
+
warnings.push(driftWarning(`the manifest records ${declaration.capability} on ${harness} over ${declaration.surfaces.map(formatSurface).join(", ")}, but \`harnesstrim capabilities\` (${capabilities.version}) reports ${observed.surfaces.length === 0 ? "no surfaces" : observed.surfaces.join(" | ")}`, `Re-test the ${harness} integration and update the manifest declaration`));
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
6135
|
+
const review = manifest.delegatedInstallReview;
|
|
6136
|
+
if (review === null)
|
|
6137
|
+
return warnings;
|
|
6138
|
+
const claude = capabilities.harnesses["claude"];
|
|
6139
|
+
if (claude === void 0)
|
|
6140
|
+
return warnings;
|
|
6141
|
+
const declared = claude.writeSet.map(writeSetPath);
|
|
6142
|
+
for (const reviewed of review.reviewedWriteSet) {
|
|
6143
|
+
if (declared.some((entry) => coveredBy(reviewed, entry)))
|
|
6144
|
+
continue;
|
|
6145
|
+
warnings.push(driftWarning(`the reviewed write set records ${reviewed} at ${review.upstreamVersion}, but \`harnesstrim capabilities\` (${capabilities.version}) declares nothing covering it (${declared.length === 0 ? "nothing" : declared.join(", ")})`, `Re-review the write set at the installed version before delegating another install`));
|
|
6146
|
+
}
|
|
6147
|
+
const boundary = review.containmentBoundary.map(writeSetPath);
|
|
6148
|
+
for (const entry of declared) {
|
|
6149
|
+
if (boundary.some((prefix) => coveredBy(entry, prefix)))
|
|
6150
|
+
continue;
|
|
6151
|
+
warnings.push(driftWarning(`\`harnesstrim capabilities\` (${capabilities.version}) declares ${entry} for claude, which sits outside the containment boundary recorded at ${review.upstreamVersion} (${boundary.join(", ")})`, "Re-review the write set: rollback restores the boundary, and a path it does not cover would survive a rollback"));
|
|
6152
|
+
}
|
|
6153
|
+
return warnings;
|
|
5166
6154
|
}
|
|
5167
6155
|
function harnessesWiredToHarnessTrim(configs) {
|
|
5168
6156
|
const wired = /* @__PURE__ */ new Set();
|
|
@@ -5245,16 +6233,17 @@ async function probeExecutable(context) {
|
|
|
5245
6233
|
};
|
|
5246
6234
|
}
|
|
5247
6235
|
function metricsLocations(context) {
|
|
5248
|
-
return [
|
|
5249
|
-
context.fs.join(context.projectRoot, ".harnesstrim", "metrics.jsonl"),
|
|
5250
|
-
context.fs.join(context.paths.home, ".hermes", "harnesstrim-metrics.jsonl")
|
|
5251
|
-
];
|
|
6236
|
+
return [context.fs.join(context.projectRoot, ".harnesstrim", "metrics.jsonl")];
|
|
5252
6237
|
}
|
|
5253
6238
|
async function detect4(context) {
|
|
5254
6239
|
const probe = await probeExecutable(context);
|
|
6240
|
+
const observed = probe.installed ? await probeCapabilities(context) : null;
|
|
5255
6241
|
const configured = harnessesWiredToHarnessTrim(context.harnessConfigs);
|
|
5256
6242
|
const warnings = [];
|
|
5257
|
-
const evidenceItems = [...probe.evidence];
|
|
6243
|
+
const evidenceItems = [...probe.evidence, ...observed?.evidence ?? []];
|
|
6244
|
+
if (observed !== null && observed.capabilities !== null) {
|
|
6245
|
+
warnings.push(...compareCapabilities(MANIFEST4, observed.capabilities));
|
|
6246
|
+
}
|
|
5258
6247
|
for (const harness of configured) {
|
|
5259
6248
|
evidenceItems.push(evidence({
|
|
5260
6249
|
kind: "config-entry",
|
|
@@ -5292,6 +6281,10 @@ async function detect4(context) {
|
|
|
5292
6281
|
// circumstantial: PLAN §11 says Token Harness never installs it, so every installation it ever
|
|
5293
6282
|
// sees is the user's.
|
|
5294
6283
|
managedByTokenHarness: false,
|
|
6284
|
+
// PLAN §15 item 46. Decided by the build in front of us, not by the manifest: a build that
|
|
6285
|
+
// declares narrowing flags for every managed harness can be asked for the narrowed states
|
|
6286
|
+
// §6.1 recorded as unproducible, and one that cannot answer keeps the `0.0.5` verdict.
|
|
6287
|
+
assignable: observed?.capabilities === null ? false : narrowable(observed?.capabilities),
|
|
5295
6288
|
evidence: evidenceItems,
|
|
5296
6289
|
warnings
|
|
5297
6290
|
};
|
|
@@ -5313,6 +6306,12 @@ function parseTrimEvent(line) {
|
|
|
5313
6306
|
if (typeof ts !== "string" || typeof harness !== "string" || typeof before !== "number" || typeof after !== "number") {
|
|
5314
6307
|
return null;
|
|
5315
6308
|
}
|
|
6309
|
+
const schemaVersion = typeof record["schemaVersion"] === "number" ? record["schemaVersion"] : 0;
|
|
6310
|
+
if (schemaVersion > 1)
|
|
6311
|
+
return null;
|
|
6312
|
+
const eventId = schemaVersion === 1 && typeof record["eventId"] === "string" && record["eventId"] !== "" ? record["eventId"] : null;
|
|
6313
|
+
if (schemaVersion === 1 && eventId === null)
|
|
6314
|
+
return null;
|
|
5316
6315
|
return {
|
|
5317
6316
|
ts,
|
|
5318
6317
|
harness,
|
|
@@ -5320,6 +6319,11 @@ function parseTrimEvent(line) {
|
|
|
5320
6319
|
reducer: typeof record["reducer"] === "string" ? record["reducer"] : null,
|
|
5321
6320
|
beforeChars: before,
|
|
5322
6321
|
afterChars: after,
|
|
6322
|
+
schemaVersion,
|
|
6323
|
+
eventId,
|
|
6324
|
+
changed: typeof record["changed"] === "boolean" ? record["changed"] : null,
|
|
6325
|
+
beforeTokens: typeof record["beforeTokens"] === "number" ? record["beforeTokens"] : null,
|
|
6326
|
+
afterTokens: typeof record["afterTokens"] === "number" ? record["afterTokens"] : null,
|
|
5323
6327
|
...typeof record["mode"] === "string" ? { mode: record["mode"] } : {}
|
|
5324
6328
|
};
|
|
5325
6329
|
}
|
|
@@ -5331,16 +6335,18 @@ function toEvent(event, sourceId, ordinal, line, context) {
|
|
|
5331
6335
|
const instant = new Date(event.ts);
|
|
5332
6336
|
if (Number.isNaN(instant.getTime()))
|
|
5333
6337
|
return null;
|
|
6338
|
+
const native = event.schemaVersion === 1;
|
|
5334
6339
|
const dryrun = event.mode === "dryrun";
|
|
5335
|
-
const eventId = synthesizeEventId(sourceId, ordinal, line);
|
|
6340
|
+
const eventId = native ? event.eventId : synthesizeEventId(sourceId, ordinal, line);
|
|
6341
|
+
const passThrough = native && event.changed === false;
|
|
6342
|
+
const hasTokens = native && event.beforeTokens !== null && event.afterTokens !== null;
|
|
6343
|
+
const hasUnprovenMode = native && MODE_CARRYING_HARNESSES.has(event.harness) && !hasTokens && !passThrough;
|
|
5336
6344
|
return {
|
|
5337
6345
|
schemaVersion: OPTIMIZATION_EVENT_SCHEMA_VERSION,
|
|
5338
6346
|
eventId,
|
|
5339
6347
|
timestamp: instant.toISOString(),
|
|
5340
6348
|
provider: { id: HARNESSTRIM2, version: null },
|
|
5341
6349
|
context: {
|
|
5342
|
-
// The project the metrics file belongs to, which for the default location is the project it
|
|
5343
|
-
// sits inside.
|
|
5344
6350
|
projectId: context.projectIdFor(context.projectRoot),
|
|
5345
6351
|
// Unlike RTK's database, a `TrimEvent` names its harness, so this is read rather than left
|
|
5346
6352
|
// unknown.
|
|
@@ -5353,25 +6359,34 @@ function toEvent(event, sourceId, ordinal, line, context) {
|
|
|
5353
6359
|
capability: event.harness === OPENCODE2 ? "tool.output.reduce" : "shell.output.reduce"
|
|
5354
6360
|
},
|
|
5355
6361
|
measurement: {
|
|
5356
|
-
class: dryrun ? "counterfactual" : "estimated-local",
|
|
6362
|
+
class: dryrun || hasUnprovenMode ? "counterfactual" : hasTokens ? "exact-local" : "estimated-local",
|
|
5357
6363
|
beforeChars: event.beforeChars,
|
|
5358
6364
|
afterChars: event.afterChars,
|
|
5359
|
-
// RFC 0005: "never derived silently". `0.0.5` counts characters and nothing else
|
|
5360
|
-
|
|
5361
|
-
|
|
6365
|
+
// RFC 0005: "never derived silently". `0.0.5` counts characters and nothing else; the
|
|
6366
|
+
// `0.1.0` producer's counts are used as they are because a token count is a token count
|
|
6367
|
+
// where one exists.
|
|
6368
|
+
beforeTokens: hasTokens ? event.beforeTokens : null,
|
|
6369
|
+
afterTokens: hasTokens ? event.afterTokens : null,
|
|
5362
6370
|
tokenizer: null,
|
|
5363
6371
|
confidenceLow: null,
|
|
5364
6372
|
confidenceHigh: null
|
|
5365
6373
|
},
|
|
5366
6374
|
outcome: {
|
|
5367
|
-
// A dryrun leaves `output.output` untouched
|
|
5368
|
-
changed
|
|
5369
|
-
|
|
6375
|
+
// A dryrun leaves `output.output` untouched; a native line states whether its attempt
|
|
6376
|
+
// actually changed the output. A native line whose mode the envelope cannot carry is
|
|
6377
|
+
// deliberately not asserted as a change: the figure may describe a dryrun, and reporting
|
|
6378
|
+
// `changed: true` would claim the model received fewer bytes than it did.
|
|
6379
|
+
changed: dryrun || hasUnprovenMode ? false : native ? event.changed ?? true : event.afterChars !== event.beforeChars,
|
|
6380
|
+
bypassReason: dryrun ? "dryrun" : (
|
|
6381
|
+
// Same reason value as the diagnostic code for the residual ambiguity: nothing refutes a
|
|
6382
|
+
// dryrun, and nothing proves one, so the bypass is the envelope's own silence.
|
|
6383
|
+
hasUnprovenMode ? "mode-unresolved" : native ? passThrough ? "pass-through" : null : event.afterChars === event.beforeChars ? "no-reduction-applied" : null
|
|
6384
|
+
),
|
|
5370
6385
|
originalReference: null,
|
|
5371
6386
|
latencyMs: null,
|
|
5372
6387
|
errorCode: null
|
|
5373
6388
|
},
|
|
5374
|
-
source: { nativeEventId: null, importedAt: context.now() }
|
|
6389
|
+
source: { nativeEventId: native ? eventId : null, importedAt: context.now() }
|
|
5375
6390
|
};
|
|
5376
6391
|
}
|
|
5377
6392
|
async function verify4(context) {
|
|
@@ -5412,7 +6427,7 @@ async function verify4(context) {
|
|
|
5412
6427
|
checks.push({
|
|
5413
6428
|
id: "canary-intercepted",
|
|
5414
6429
|
status: receipt === null ? "not-exercised" : "pass",
|
|
5415
|
-
summary: receipt === null ? "no telemetry file
|
|
6430
|
+
summary: receipt === null ? "no telemetry file yet, so nothing has been observed" : `${String(receipt.operations)} reductions recorded, most recently ${receipt.observedAt}`,
|
|
5416
6431
|
// A recorded reduction is the provider witnessing its own interception, which is what `canary`
|
|
5417
6432
|
// means in RFC 0007's tier table.
|
|
5418
6433
|
achievedTier: receipt === null ? null : "canary",
|
|
@@ -5422,7 +6437,7 @@ async function verify4(context) {
|
|
|
5422
6437
|
const agents = context.fs.join(context.projectRoot, "AGENTS.md");
|
|
5423
6438
|
if (await context.fs.stat(agents) !== null) {
|
|
5424
6439
|
const text = new TextDecoder().decode(await context.fs.readFile(agents));
|
|
5425
|
-
if (
|
|
6440
|
+
if (HOOK_COMMAND_PATTERN.test(text)) {
|
|
5426
6441
|
checks.push({
|
|
5427
6442
|
id: "instruction-path-present",
|
|
5428
6443
|
status: "info",
|
|
@@ -5458,6 +6473,8 @@ async function collectMetrics(context, store) {
|
|
|
5458
6473
|
let skipped = 0;
|
|
5459
6474
|
let lastCursor = null;
|
|
5460
6475
|
let readAny = false;
|
|
6476
|
+
let sawNative = false;
|
|
6477
|
+
let unprovableModeEvents = 0;
|
|
5461
6478
|
for (const path of metricsLocations(context)) {
|
|
5462
6479
|
const stat3 = await context.fs.stat(path);
|
|
5463
6480
|
if (stat3 === null)
|
|
@@ -5508,6 +6525,10 @@ async function collectMetrics(context, store) {
|
|
|
5508
6525
|
ordinal += 1;
|
|
5509
6526
|
continue;
|
|
5510
6527
|
}
|
|
6528
|
+
if (parsed.schemaVersion === 1)
|
|
6529
|
+
sawNative = true;
|
|
6530
|
+
if (event.outcome.bypassReason === "mode-unresolved")
|
|
6531
|
+
unprovableModeEvents += 1;
|
|
5511
6532
|
events.push(event);
|
|
5512
6533
|
lastLine = trimmed;
|
|
5513
6534
|
ordinal += 1;
|
|
@@ -5559,21 +6580,104 @@ async function collectMetrics(context, store) {
|
|
|
5559
6580
|
diagnostics
|
|
5560
6581
|
};
|
|
5561
6582
|
}
|
|
6583
|
+
if (unprovableModeEvents > 0) {
|
|
6584
|
+
diagnostics.push(diagnostic({
|
|
6585
|
+
severity: "info",
|
|
6586
|
+
code: "provider-metrics-mode-unresolved",
|
|
6587
|
+
message: `${String(unprovableModeEvents)} native event${unprovableModeEvents === 1 ? "" : "s"} from a mode-carrying harness could not be classed as realized: schema 1 carries no reduction mode`,
|
|
6588
|
+
remediation: "Treat these figures as counterfactual; only OpenCode can later prove its mode from the configured plugin option"
|
|
6589
|
+
}));
|
|
6590
|
+
}
|
|
5562
6591
|
return {
|
|
5563
6592
|
providerId: HARNESSTRIM2,
|
|
5564
|
-
// RFC 0005 §Importer degradation policy
|
|
5565
|
-
//
|
|
5566
|
-
|
|
5567
|
-
|
|
6593
|
+
// RFC 0005 §Importer degradation policy, decided by what this run read: `native` when the
|
|
6594
|
+
// file carried schema 1 events with producer IDs, `legacy` when it only had the character
|
|
6595
|
+
// stream. Legacy inside a native run is not a problem — those lines are older files.
|
|
6596
|
+
//
|
|
6597
|
+
// A native stream whose mode-carrying harness lines cannot prove a realized reduction (no
|
|
6598
|
+
// tokens, no pass-through) is `native-with-residue`: the import is native, but a part of it
|
|
6599
|
+
// could not be classed as realized, and the count above qualifies exactly how much. A bare
|
|
6600
|
+
// `native` must keep meaning "everything was classed".
|
|
6601
|
+
mode: sawNative && unprovableModeEvents > 0 ? "native-with-residue" : sawNative ? "native" : "legacy",
|
|
6602
|
+
source: `harnesstrim TrimEvent JSONL (schema 0 and schema 1)`,
|
|
5568
6603
|
imported,
|
|
5569
6604
|
skipped,
|
|
5570
6605
|
cursor: lastCursor,
|
|
5571
6606
|
diagnostics
|
|
5572
6607
|
};
|
|
5573
6608
|
}
|
|
5574
|
-
async function plan(
|
|
5575
|
-
|
|
5576
|
-
|
|
6609
|
+
async function plan(context, request) {
|
|
6610
|
+
const claudeDirectory = context.fs.join(context.projectRoot, ".claude");
|
|
6611
|
+
const skillsDirectory = context.fs.join(claudeDirectory, "skills");
|
|
6612
|
+
const expectedArtifacts = Object.entries(CLAUDE_ARTIFACT_DIGESTS).map(([path, digest]) => ({
|
|
6613
|
+
path: context.fs.join(skillsDirectory, ...path.split("/")),
|
|
6614
|
+
digest
|
|
6615
|
+
}));
|
|
6616
|
+
if (!request.harnesses.some((harness) => harness.id === CLAUDE2)) {
|
|
6617
|
+
return { providerId: HARNESSTRIM2, desiredState: request.desiredState, actions: [] };
|
|
6618
|
+
}
|
|
6619
|
+
if (request.desiredState === "absent") {
|
|
6620
|
+
const actions = expectedArtifacts.map((artifact) => ({
|
|
6621
|
+
kind: "remove-owned-change",
|
|
6622
|
+
id: `harnesstrim-claude-skill-remove-${digestText(artifact.path).slice(7, 15)}`,
|
|
6623
|
+
riskClass: "reversible",
|
|
6624
|
+
requiresNetwork: false,
|
|
6625
|
+
requiresElevation: false,
|
|
6626
|
+
affectedPaths: [artifact.path],
|
|
6627
|
+
affectedProcesses: [],
|
|
6628
|
+
preconditions: ["the HarnessTrim skill still matches the reviewed 0.0.7 artifact"],
|
|
6629
|
+
postconditions: ["the owned HarnessTrim skill is absent"],
|
|
6630
|
+
rollbackData: "file-snapshot",
|
|
6631
|
+
explanation: `Remove the owned HarnessTrim Claude skill ${context.fs.basename(context.fs.dirname(artifact.path))}`,
|
|
6632
|
+
path: artifact.path,
|
|
6633
|
+
reverses: `harnesstrim-claude-skills-${digestText(context.projectRoot).slice(7, 15)}`,
|
|
6634
|
+
target: { kind: "owned-file", path: artifact.path, digest: artifact.digest, mode: null }
|
|
6635
|
+
}));
|
|
6636
|
+
return { providerId: HARNESSTRIM2, desiredState: "absent", actions };
|
|
6637
|
+
}
|
|
6638
|
+
const installed = await probeExecutable(context);
|
|
6639
|
+
if (!installed.installed) {
|
|
6640
|
+
return { providerId: HARNESSTRIM2, desiredState: "configured", actions: [] };
|
|
6641
|
+
}
|
|
6642
|
+
const observed = await probeCapabilities(context);
|
|
6643
|
+
const reviewed = observed.capabilities === null ? installed.version === TESTED_UPSTREAM : writeSetStillReviewed(observed.capabilities, CLAUDE2);
|
|
6644
|
+
if (!reviewed) {
|
|
6645
|
+
return { providerId: HARNESSTRIM2, desiredState: "configured", actions: [] };
|
|
6646
|
+
}
|
|
6647
|
+
const review = MANIFEST4.delegatedInstallReview;
|
|
6648
|
+
if (review === null)
|
|
6649
|
+
throw new Error("HarnessTrim delegated-install review is missing");
|
|
6650
|
+
const action = {
|
|
6651
|
+
kind: "delegated-provider-install",
|
|
6652
|
+
id: `harnesstrim-claude-skills-${digestText(context.projectRoot).slice(7, 15)}`,
|
|
6653
|
+
riskClass: "delegated",
|
|
6654
|
+
requiresNetwork: false,
|
|
6655
|
+
requiresElevation: false,
|
|
6656
|
+
affectedPaths: expectedArtifacts.map((artifact) => artifact.path),
|
|
6657
|
+
affectedProcesses: ["harnesstrim"],
|
|
6658
|
+
preconditions: [
|
|
6659
|
+
"harnesstrim 0.0.7 is installed and on PATH",
|
|
6660
|
+
"the reviewed installer writes only the declared Claude skill artifacts"
|
|
6661
|
+
],
|
|
6662
|
+
postconditions: [
|
|
6663
|
+
"HarnessTrim Claude skills match the reviewed 0.0.7 artifacts",
|
|
6664
|
+
"no HarnessTrim hook or reduce-pipe instruction was added"
|
|
6665
|
+
],
|
|
6666
|
+
rollbackData: "directory-snapshot",
|
|
6667
|
+
explanation: "Install HarnessTrim Claude skills without a hook or reduce-pipe instruction",
|
|
6668
|
+
executable: "harnesstrim",
|
|
6669
|
+
args: ["install", "claude", context.projectRoot, "--apply", "--no-hook", "--no-instructions"],
|
|
6670
|
+
containmentBoundary: review.containmentBoundary.map((path) => context.fs.join(context.projectRoot, path)),
|
|
6671
|
+
expectedArtifacts,
|
|
6672
|
+
protectedPaths: [
|
|
6673
|
+
context.fs.join(claudeDirectory, "settings.json"),
|
|
6674
|
+
context.fs.join(context.projectRoot, "CLAUDE.md")
|
|
6675
|
+
],
|
|
6676
|
+
rollbackStrategy: "restore-snapshot",
|
|
6677
|
+
snapshotSizeCapBytes: 1048576,
|
|
6678
|
+
upstreamUninstallAvailable: review.upstreamUninstallAvailable
|
|
6679
|
+
};
|
|
6680
|
+
return { providerId: HARNESSTRIM2, desiredState: "configured", actions: [action] };
|
|
5577
6681
|
}
|
|
5578
6682
|
var harnesstrimAdapter = {
|
|
5579
6683
|
manifest: MANIFEST4,
|
|
@@ -5581,11 +6685,13 @@ var harnesstrimAdapter = {
|
|
|
5581
6685
|
identifiesCommand,
|
|
5582
6686
|
verify: verify4,
|
|
5583
6687
|
collectMetrics,
|
|
5584
|
-
plan
|
|
6688
|
+
plan,
|
|
6689
|
+
plansWithoutOwnership: true
|
|
5585
6690
|
};
|
|
5586
6691
|
|
|
5587
6692
|
// packages/adapters/dist/src/providers/rtk-plan.js
|
|
5588
6693
|
var RTK_HOOK_COMMAND_PREFIX = "rtk hook";
|
|
6694
|
+
var HOOK_LIST_HARNESSES = /* @__PURE__ */ new Set(["claude"]);
|
|
5589
6695
|
function hookListPointer(eventName) {
|
|
5590
6696
|
return `hooks.${eventName.replace(/\./g, "\\.")}`;
|
|
5591
6697
|
}
|
|
@@ -5601,6 +6707,8 @@ function planTargets(context, request) {
|
|
|
5601
6707
|
const harness = request.harnesses.find((entry) => entry.id === owned.scope.harness);
|
|
5602
6708
|
if (harness === void 0)
|
|
5603
6709
|
continue;
|
|
6710
|
+
if (!HOOK_LIST_HARNESSES.has(harness.id))
|
|
6711
|
+
continue;
|
|
5604
6712
|
const point = harness.interceptionPoints.find((entry) => entry.scopeId === owned.scope.interceptionPoint);
|
|
5605
6713
|
if (point === void 0)
|
|
5606
6714
|
continue;
|
|
@@ -5733,11 +6841,11 @@ function installAction(context, manifest) {
|
|
|
5733
6841
|
channel.digestAvailable ? "the release artifact carries a digest that will be verified" : "this channel publishes no digest, so the artifact cannot be verified by content"
|
|
5734
6842
|
],
|
|
5735
6843
|
postconditions: ["`rtk --version` reports a version inside the tested range"],
|
|
5736
|
-
//
|
|
5737
|
-
//
|
|
5738
|
-
//
|
|
5739
|
-
//
|
|
5740
|
-
rollbackData: "none",
|
|
6844
|
+
// The declared value the executor now implements (RFC 0009 §Initial delivery order item 1):
|
|
6845
|
+
// the executor captures whatever the channel has installed before the install runs, and a
|
|
6846
|
+
// rollback restores that captured version through the same channel. `none` is left only for
|
|
6847
|
+
// channels this build cannot ask about — there the executor says the package stayed instead.
|
|
6848
|
+
rollbackData: channelCanReportInventory(channel.id) ? "package-inventory" : "none",
|
|
5741
6849
|
explanation: `Install RTK through ${channel.id}`,
|
|
5742
6850
|
packageManager: channel.id,
|
|
5743
6851
|
// The channel's own name for the package, because they differ: `rtk-ai.rtk` on winget and
|
|
@@ -5776,6 +6884,7 @@ function buildRtkPlan(input) {
|
|
|
5776
6884
|
// packages/adapters/dist/src/providers/rtk.js
|
|
5777
6885
|
var RTK2 = providerId("rtk");
|
|
5778
6886
|
var CLAUDE3 = harnessId("claude");
|
|
6887
|
+
var OPENCODE3 = harnessId("opencode");
|
|
5779
6888
|
var MANIFEST5 = {
|
|
5780
6889
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
5781
6890
|
id: RTK2,
|
|
@@ -5814,10 +6923,53 @@ var MANIFEST5 = {
|
|
|
5814
6923
|
sourceReference: "docs/spikes/2.5-live-verification-log.md",
|
|
5815
6924
|
upstreamVersion: "0.42.0"
|
|
5816
6925
|
}
|
|
6926
|
+
},
|
|
6927
|
+
/**
|
|
6928
|
+
* OpenCode, from spike 9.1.
|
|
6929
|
+
*
|
|
6930
|
+
* Separate entries rather than adding `OPENCODE` to the two above, because the surface is not
|
|
6931
|
+
* the same one: RTK reaches Claude Code through a `PreToolUse` hook matching `Bash`, and
|
|
6932
|
+
* OpenCode through a plugin on `tool.execute.before`. A capability entry names one surface
|
|
6933
|
+
* list, and merging these would claim that RTK serves `Bash/pre-tool-use` on OpenCode — a
|
|
6934
|
+
* point OpenCode does not have.
|
|
6935
|
+
*
|
|
6936
|
+
* `shell.command.rewrite` and `shell.output.reduce` again arrive together, for the reason they
|
|
6937
|
+
* do on Claude Code: the plugin calls `rtk rewrite`, and the rewritten command is what filters
|
|
6938
|
+
* its own output. One interception point, two capabilities.
|
|
6939
|
+
*/
|
|
6940
|
+
{
|
|
6941
|
+
capability: "shell.command.rewrite",
|
|
6942
|
+
mode: "exclusive",
|
|
6943
|
+
harnesses: [OPENCODE3],
|
|
6944
|
+
// `tool.execute` is the family the harness manifest declares as shell-executing. The plugin
|
|
6945
|
+
// narrows to `tool === "bash" || tool === "shell"` inside that family, which is every shell
|
|
6946
|
+
// route OpenCode exposes — unlike Claude Code on Windows, there is no second one to miss.
|
|
6947
|
+
surfaces: [{ toolFamily: "tool.execute", interceptionPoint: "tool-execute-before" }],
|
|
6948
|
+
evidence: {
|
|
6949
|
+
sourceReference: "docs/spikes/9.1-rtk-opencode-observation-log.md",
|
|
6950
|
+
upstreamVersion: "0.44.0"
|
|
6951
|
+
}
|
|
6952
|
+
},
|
|
6953
|
+
{
|
|
6954
|
+
capability: "shell.output.reduce",
|
|
6955
|
+
mode: "exclusive",
|
|
6956
|
+
harnesses: [OPENCODE3],
|
|
6957
|
+
surfaces: [{ toolFamily: "tool.execute", interceptionPoint: "tool-execute-before" }],
|
|
6958
|
+
evidence: {
|
|
6959
|
+
sourceReference: "docs/spikes/9.1-rtk-opencode-observation-log.md",
|
|
6960
|
+
upstreamVersion: "0.44.0"
|
|
6961
|
+
}
|
|
5817
6962
|
}
|
|
5818
6963
|
],
|
|
5819
6964
|
platforms: [
|
|
5820
|
-
{
|
|
6965
|
+
{
|
|
6966
|
+
os: "windows",
|
|
6967
|
+
wsl: false,
|
|
6968
|
+
supported: true,
|
|
6969
|
+
// Spike 9.1. Named on Windows alone because Windows is where Desktop was run; the same
|
|
6970
|
+
// defect on another platform's Desktop build would be a guess until someone watches it.
|
|
6971
|
+
limitation: "RTK's OpenCode plugin is inert under OpenCode Desktop, which supplies no `$` shell helper to plugins, so the plugin's own guard disables it silently"
|
|
6972
|
+
},
|
|
5821
6973
|
{ os: "linux", wsl: true, supported: true, limitation: null },
|
|
5822
6974
|
{ os: "macos", wsl: false, supported: true, limitation: null },
|
|
5823
6975
|
{ os: "linux", wsl: false, supported: true, limitation: null }
|
|
@@ -5827,6 +6979,31 @@ var MANIFEST5 = {
|
|
|
5827
6979
|
harness: CLAUDE3,
|
|
5828
6980
|
testedVersions: { minimum: "2.0.0", maximum: "2.1.212" },
|
|
5829
6981
|
verificationTier: "canary"
|
|
6982
|
+
},
|
|
6983
|
+
{
|
|
6984
|
+
harness: OPENCODE3,
|
|
6985
|
+
// Spike 9.1 watched interception on the CLI at `1.18.11` and watched it fail on Desktop at
|
|
6986
|
+
// `1.18.14`. The range covers both because both were observed; the failure is a declared
|
|
6987
|
+
// platform limitation below rather than a narrower range, because it is a property of the
|
|
6988
|
+
// host application and not of the OpenCode version — the same 1.18.x plugin loads in both.
|
|
6989
|
+
testedVersions: { minimum: "1.18.11", maximum: "1.18.14" },
|
|
6990
|
+
/**
|
|
6991
|
+
* `config-only`, even though spike 9.1 watched a canary-grade interception here — the counter
|
|
6992
|
+
* moved by exactly the number of shell calls the OpenCode session made.
|
|
6993
|
+
*
|
|
6994
|
+
* A tier is what `verify` can prove on the user's machine, not what a spike proved once on a
|
|
6995
|
+
* clean one. RTK's receipt is its history database, and the `commands` table carries
|
|
6996
|
+
* `timestamp`, token counts, `exec_time_ms`, and `project_path` — no harness column, and no
|
|
6997
|
+
* column standing in for one. The spike could attribute those rows because it ran OpenCode
|
|
6998
|
+
* alone in a scratch directory and read the counter either side; `verify` runs where both
|
|
6999
|
+
* harnesses are wired to the same binary and writing into the same table, and cannot say
|
|
7000
|
+
* which of them a row came from.
|
|
7001
|
+
*
|
|
7002
|
+
* Declaring `canary` here would credit RTK's whole command history to whichever harness the
|
|
7003
|
+
* reader happened to be looking at. Claude Code keeps `canary` because its own hook receipt
|
|
7004
|
+
* is per-harness; this one is provider-wide, and RFC 0007 asks the question per harness.
|
|
7005
|
+
*/
|
|
7006
|
+
verificationTier: "config-only"
|
|
5830
7007
|
}
|
|
5831
7008
|
],
|
|
5832
7009
|
installationChannels: [
|
|
@@ -5870,9 +7047,10 @@ var MANIFEST5 = {
|
|
|
5870
7047
|
},
|
|
5871
7048
|
delegatedInstallReview: null
|
|
5872
7049
|
};
|
|
5873
|
-
var TESTED_VERSIONS2 = { minimum: "0.40.0", maximum: "0.
|
|
7050
|
+
var TESTED_VERSIONS2 = { minimum: "0.40.0", maximum: "0.44.0" };
|
|
5874
7051
|
var VERSION_PATTERN5 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
|
|
5875
7052
|
var HOOK_COMMAND_PATTERN2 = /(^|[\\/\s"'])rtk(\.exe)?([\s"']|$)/i;
|
|
7053
|
+
var PLUGIN_MODULE_PATTERN3 = /(^|[\\/])rtk\.(ts|js|mjs|cjs|mts|cts)$/i;
|
|
5876
7054
|
function isRecord4(value) {
|
|
5877
7055
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5878
7056
|
}
|
|
@@ -5915,7 +7093,7 @@ function parseRtkAnalytics(text) {
|
|
|
5915
7093
|
function harnessesWiredToRtk(configs) {
|
|
5916
7094
|
const wired = /* @__PURE__ */ new Set();
|
|
5917
7095
|
for (const config of configs) {
|
|
5918
|
-
if (config.commands.some((command) =>
|
|
7096
|
+
if (config.commands.some((command) => identifiesCommand2(command))) {
|
|
5919
7097
|
wired.add(config.harnessId);
|
|
5920
7098
|
}
|
|
5921
7099
|
}
|
|
@@ -6031,13 +7209,32 @@ async function detect5(context) {
|
|
|
6031
7209
|
versionVerdict: version2.verdict,
|
|
6032
7210
|
configuredHarnesses: configured,
|
|
6033
7211
|
unmanagedHarnessesConfigured: [],
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
7212
|
+
/**
|
|
7213
|
+
* RFC 0002 §Providers may exceed the managed surface.
|
|
7214
|
+
*
|
|
7215
|
+
* The field asks whether this manifest names a harness with no adapter behind it. It names two,
|
|
7216
|
+
* Claude Code and OpenCode, and both are managed — so the qualifier RFC 0006's doctor transcript
|
|
7217
|
+
* uses for HarnessTrim does not apply, and the report says "not configured for any harness"
|
|
7218
|
+
* without hedging.
|
|
7219
|
+
*
|
|
7220
|
+
* `rtk init --agent` separately targets Cursor, Windsurf, Cline, Kilo Code, Antigravity, Kimi,
|
|
7221
|
+
* Pi, Hermes, and Droid. That is RTK exceeding *Token Harness*, not this manifest exceeding the
|
|
7222
|
+
* managed set, and it stays out of the flag: claiming those here would make the resolver
|
|
7223
|
+
* responsible for harnesses this build has no adapter to inspect.
|
|
7224
|
+
*/
|
|
6037
7225
|
supportsUnmanagedHarnesses: false,
|
|
6038
7226
|
// RFC 0004 §Brownfield adoption: nothing has been applied by Token Harness yet, so
|
|
6039
7227
|
// every installation it finds is the user's.
|
|
6040
7228
|
managedByTokenHarness: false,
|
|
7229
|
+
/**
|
|
7230
|
+
* Always, and independent of the observed version.
|
|
7231
|
+
*
|
|
7232
|
+
* RTK's assignment is produced by this build writing the hook itself — `rtk-plan.ts` appends
|
|
7233
|
+
* the entry — so nothing about the installed `rtk` decides whether the state is reachable.
|
|
7234
|
+
* That is the difference from HarnessTrim, whose assignment is produced by *its* installer and
|
|
7235
|
+
* therefore depends on which flags that installer has.
|
|
7236
|
+
*/
|
|
7237
|
+
assignable: true,
|
|
6041
7238
|
evidence: evidenceItems,
|
|
6042
7239
|
warnings
|
|
6043
7240
|
};
|
|
@@ -6138,17 +7335,17 @@ function rtkDatabasePath(context) {
|
|
|
6138
7335
|
function cursorGeneration(low) {
|
|
6139
7336
|
return `rtk-history:low=${String(low ?? 0)}`;
|
|
6140
7337
|
}
|
|
6141
|
-
function toEvent2(
|
|
6142
|
-
const id = numberAt(
|
|
6143
|
-
const timestamp = stringAt(
|
|
6144
|
-
const before = numberAt(
|
|
6145
|
-
const after = numberAt(
|
|
7338
|
+
function toEvent2(row2, context) {
|
|
7339
|
+
const id = numberAt(row2, "id");
|
|
7340
|
+
const timestamp = stringAt(row2, "timestamp");
|
|
7341
|
+
const before = numberAt(row2, "input_tokens");
|
|
7342
|
+
const after = numberAt(row2, "output_tokens");
|
|
6146
7343
|
if (id === null || timestamp === null || before === null || after === null)
|
|
6147
7344
|
return null;
|
|
6148
7345
|
const instant = new Date(timestamp);
|
|
6149
7346
|
if (Number.isNaN(instant.getTime()))
|
|
6150
7347
|
return null;
|
|
6151
|
-
const projectPath = stringAt(
|
|
7348
|
+
const projectPath = stringAt(row2, "project_path");
|
|
6152
7349
|
return {
|
|
6153
7350
|
schemaVersion: OPTIMIZATION_EVENT_SCHEMA_VERSION,
|
|
6154
7351
|
// Native, not synthesized: RFC 0005 prefers the upstream identifier when there is one,
|
|
@@ -6159,7 +7356,7 @@ function toEvent2(row, context) {
|
|
|
6159
7356
|
context: {
|
|
6160
7357
|
// An empty `project_path` means RTK recorded no directory; attributing the event to
|
|
6161
7358
|
// the directory `metrics` happens to run in would invent an attribution.
|
|
6162
|
-
projectId: projectPath === null || projectPath === "" ?
|
|
7359
|
+
projectId: projectPath === null || projectPath === "" ? UNATTRIBUTED_PROJECT_ID : context.projectIdFor(projectPath),
|
|
6163
7360
|
// RTK proxies commands for whichever harness invoked it and does not record which.
|
|
6164
7361
|
// The hook that wires it is per harness, but a *row* carries no harness, and reading
|
|
6165
7362
|
// one off the current configuration would attribute months of history to today's
|
|
@@ -6292,11 +7489,11 @@ async function collectMetrics2(context, store) {
|
|
|
6292
7489
|
const events = [];
|
|
6293
7490
|
let skipped = 0;
|
|
6294
7491
|
let highest = from;
|
|
6295
|
-
for (const
|
|
6296
|
-
const id = numberAt(
|
|
7492
|
+
for (const row2 of batch.rows) {
|
|
7493
|
+
const id = numberAt(row2, "id");
|
|
6297
7494
|
if (id !== null && id > highest)
|
|
6298
7495
|
highest = id;
|
|
6299
|
-
const event = toEvent2(
|
|
7496
|
+
const event = toEvent2(row2, context);
|
|
6300
7497
|
if (event === null) {
|
|
6301
7498
|
skipped += 1;
|
|
6302
7499
|
continue;
|
|
@@ -6349,7 +7546,7 @@ async function collectMetrics2(context, store) {
|
|
|
6349
7546
|
}
|
|
6350
7547
|
var SOURCE_LABEL = "rtk history.db (commands)";
|
|
6351
7548
|
function identifiesCommand2(command) {
|
|
6352
|
-
return HOOK_COMMAND_PATTERN2.test(command);
|
|
7549
|
+
return HOOK_COMMAND_PATTERN2.test(command) || PLUGIN_MODULE_PATTERN3.test(command);
|
|
6353
7550
|
}
|
|
6354
7551
|
async function plan2(context, request) {
|
|
6355
7552
|
const version2 = await readVersion(context);
|
|
@@ -6378,7 +7575,6 @@ function listProviderAdapters() {
|
|
|
6378
7575
|
|
|
6379
7576
|
// apps/cli/dist/src/commands/plan.js
|
|
6380
7577
|
var DEFAULT_PROFILE = "safe";
|
|
6381
|
-
var ASSIGNABLE_PROVIDERS = ["rtk"];
|
|
6382
7578
|
async function computePlan(context) {
|
|
6383
7579
|
const diagnostics = [];
|
|
6384
7580
|
const providerAdapters = listProviderAdapters().filter((adapter) => context.provider === null || adapter.manifest.id === context.provider);
|
|
@@ -6405,11 +7601,6 @@ async function computePlan(context) {
|
|
|
6405
7601
|
harnessConfigs.push(...inspection.summaries);
|
|
6406
7602
|
}
|
|
6407
7603
|
}
|
|
6408
|
-
const providers = providerAdapters.map((adapter) => ({
|
|
6409
|
-
id: adapter.manifest.id,
|
|
6410
|
-
capabilities: adapter.manifest.capabilities,
|
|
6411
|
-
assignable: ASSIGNABLE_PROVIDERS.includes(adapter.manifest.id)
|
|
6412
|
-
}));
|
|
6413
7604
|
const providerContext = detectionContext === null || context.adapters === null ? null : {
|
|
6414
7605
|
...detectionContext,
|
|
6415
7606
|
harnessConfigs,
|
|
@@ -6417,12 +7608,19 @@ async function computePlan(context) {
|
|
|
6417
7608
|
localDatabase: context.adapters.localDatabase,
|
|
6418
7609
|
projectIdFor: context.adapters.projectIdFor
|
|
6419
7610
|
};
|
|
7611
|
+
const assignable = /* @__PURE__ */ new Map();
|
|
6420
7612
|
if (providerContext !== null) {
|
|
6421
7613
|
for (const adapter of providerAdapters) {
|
|
6422
7614
|
const detection = await adapter.detect(providerContext);
|
|
6423
7615
|
versions.providers[adapter.manifest.id] = detection.version;
|
|
7616
|
+
assignable.set(adapter.manifest.id, detection.assignable);
|
|
6424
7617
|
}
|
|
6425
7618
|
}
|
|
7619
|
+
const providers = providerAdapters.map((adapter) => ({
|
|
7620
|
+
id: adapter.manifest.id,
|
|
7621
|
+
capabilities: adapter.manifest.capabilities,
|
|
7622
|
+
assignable: assignable.get(adapter.manifest.id) ?? false
|
|
7623
|
+
}));
|
|
6426
7624
|
const resolution = resolveOwnership({
|
|
6427
7625
|
profile,
|
|
6428
7626
|
harnesses: present,
|
|
@@ -6432,17 +7630,47 @@ async function computePlan(context) {
|
|
|
6432
7630
|
harness: context.harness
|
|
6433
7631
|
});
|
|
6434
7632
|
const actions = [];
|
|
7633
|
+
const blocked = [];
|
|
6435
7634
|
if (providerContext !== null) {
|
|
7635
|
+
const rows = context.compatibilityRows ?? COMPATIBILITY_ROWS;
|
|
6436
7636
|
for (const adapter of providerAdapters) {
|
|
6437
7637
|
const owned = resolution.ownership.filter((entry) => entry.owner === adapter.manifest.id);
|
|
6438
|
-
if (owned.length === 0)
|
|
7638
|
+
if (owned.length === 0 && adapter.plansWithoutOwnership !== true)
|
|
6439
7639
|
continue;
|
|
6440
7640
|
const providerPlan = await adapter.plan(providerContext, {
|
|
6441
7641
|
ownership: owned,
|
|
6442
7642
|
harnesses: present,
|
|
6443
7643
|
desiredState: "configured"
|
|
6444
7644
|
});
|
|
6445
|
-
|
|
7645
|
+
if (providerPlan.actions.length === 0)
|
|
7646
|
+
continue;
|
|
7647
|
+
const touched = [...new Set(owned.map((entry) => entry.scope.harness))];
|
|
7648
|
+
if (touched.length === 0)
|
|
7649
|
+
touched.push(...present.map((harness) => harness.id));
|
|
7650
|
+
let admitted = true;
|
|
7651
|
+
for (const harness of touched) {
|
|
7652
|
+
const admission = admitManagedMutation(rows, {
|
|
7653
|
+
provider: adapter.manifest.id,
|
|
7654
|
+
providerVersion: versions.providers[adapter.manifest.id] ?? null,
|
|
7655
|
+
harness,
|
|
7656
|
+
harnessVersion: versions.harnesses[harness] ?? null,
|
|
7657
|
+
os: context.platform.os,
|
|
7658
|
+
wsl: context.platform.isWsl
|
|
7659
|
+
});
|
|
7660
|
+
if (admission.state !== "admitted") {
|
|
7661
|
+
admitted = false;
|
|
7662
|
+
blocked.push({
|
|
7663
|
+
provider: adapter.manifest.id,
|
|
7664
|
+
providerVersion: versions.providers[adapter.manifest.id] ?? null,
|
|
7665
|
+
harness,
|
|
7666
|
+
harnessVersion: versions.harnesses[harness] ?? null,
|
|
7667
|
+
verdict: admission.verdict,
|
|
7668
|
+
missing: admission.missing
|
|
7669
|
+
});
|
|
7670
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
if (admitted)
|
|
7673
|
+
actions.push(...providerPlan.actions);
|
|
6446
7674
|
}
|
|
6447
7675
|
}
|
|
6448
7676
|
const report = {
|
|
@@ -6500,7 +7728,7 @@ async function computePlan(context) {
|
|
|
6500
7728
|
remediation: null
|
|
6501
7729
|
}));
|
|
6502
7730
|
}
|
|
6503
|
-
if (resolution.ownership.length > 0 && actions.length === 0) {
|
|
7731
|
+
if (resolution.ownership.length > 0 && actions.length === 0 && blocked.length === 0) {
|
|
6504
7732
|
diagnostics.push(diagnostic({
|
|
6505
7733
|
severity: "info",
|
|
6506
7734
|
code: "already-in-desired-state",
|
|
@@ -6508,6 +7736,14 @@ async function computePlan(context) {
|
|
|
6508
7736
|
remediation: null
|
|
6509
7737
|
}));
|
|
6510
7738
|
}
|
|
7739
|
+
for (const entry of blocked) {
|
|
7740
|
+
diagnostics.push(diagnostic({
|
|
7741
|
+
severity: "error",
|
|
7742
|
+
code: "managed-mutation-blocked",
|
|
7743
|
+
message: `plan refuses managed mutation of ${entry.harness} by ${entry.provider}${entry.providerVersion !== null ? ` at ${entry.providerVersion}` : ""}: ${entry.missing}`,
|
|
7744
|
+
remediation: "Add a compatibility row whose fixture proves this combination, or configure this integration by hand"
|
|
7745
|
+
}));
|
|
7746
|
+
}
|
|
6511
7747
|
for (const conflict of resolution.conflicts) {
|
|
6512
7748
|
diagnostics.push(diagnostic({
|
|
6513
7749
|
severity: "error",
|
|
@@ -6516,7 +7752,7 @@ async function computePlan(context) {
|
|
|
6516
7752
|
remediation: conflict.remediation
|
|
6517
7753
|
}));
|
|
6518
7754
|
}
|
|
6519
|
-
return { report, versions, diagnostics, present, harnessConfigs };
|
|
7755
|
+
return { report, versions, diagnostics, present, harnessConfigs, blocked };
|
|
6520
7756
|
}
|
|
6521
7757
|
async function persist(context, computed) {
|
|
6522
7758
|
if (context.adapters === null || context.stateRoot === null)
|
|
@@ -6541,7 +7777,7 @@ async function runPlan(context) {
|
|
|
6541
7777
|
const computed = await computePlan(context);
|
|
6542
7778
|
const persisted = await persist(context, computed);
|
|
6543
7779
|
const report = { ...computed.report, persisted };
|
|
6544
|
-
const exitCode = report.conflicts.length > 0 ? EXIT_CODES["blocked-by-conflict"] : EXIT_CODES.ok;
|
|
7780
|
+
const exitCode = computed.blocked.length > 0 ? EXIT_CODES["unsupported-environment"] : report.conflicts.length > 0 ? EXIT_CODES["blocked-by-conflict"] : EXIT_CODES.ok;
|
|
6545
7781
|
return commandResult({
|
|
6546
7782
|
command: "plan",
|
|
6547
7783
|
exitCode,
|
|
@@ -6612,6 +7848,9 @@ async function runApply(context) {
|
|
|
6612
7848
|
const diagnostics = [];
|
|
6613
7849
|
const computed = await computePlan(context);
|
|
6614
7850
|
diagnostics.push(...computed.diagnostics);
|
|
7851
|
+
if (computed.blocked.length > 0) {
|
|
7852
|
+
return finish("rejected", EXIT_CODES["unsupported-environment"], emptyReport("rejected"), diagnostics);
|
|
7853
|
+
}
|
|
6615
7854
|
if (computed.report.conflicts.length > 0) {
|
|
6616
7855
|
return finish("rejected", EXIT_CODES["blocked-by-conflict"], emptyReport("rejected"), diagnostics);
|
|
6617
7856
|
}
|
|
@@ -6766,10 +8005,40 @@ async function runDoctor(context) {
|
|
|
6766
8005
|
projectIdFor: context.adapters?.projectIdFor ?? (() => "p_unattributed")
|
|
6767
8006
|
})));
|
|
6768
8007
|
const problemCount = harnesses.filter(isBroken).length + providers.filter(isBroken).length + [...harnesses, ...providers].filter((detection) => detection.versionVerdict === "unknown-newer").length;
|
|
8008
|
+
const rows = context.compatibilityRows ?? COMPATIBILITY_ROWS;
|
|
8009
|
+
const providersWithCoverage = providers.map((detection) => {
|
|
8010
|
+
if (detection.state !== "configured" || detection.configuredHarnesses.length === 0) {
|
|
8011
|
+
return detection;
|
|
8012
|
+
}
|
|
8013
|
+
const uncovered = detection.configuredHarnesses.flatMap((harness) => {
|
|
8014
|
+
const harnessVersion = harnesses.find((candidate) => candidate.harnessId === harness)?.version ?? null;
|
|
8015
|
+
const admission = admitManagedMutation(rows, {
|
|
8016
|
+
provider: detection.providerId,
|
|
8017
|
+
providerVersion: detection.version,
|
|
8018
|
+
harness,
|
|
8019
|
+
harnessVersion,
|
|
8020
|
+
os: context.platform.os,
|
|
8021
|
+
wsl: context.platform.isWsl
|
|
8022
|
+
});
|
|
8023
|
+
if (admission.state === "admitted")
|
|
8024
|
+
return [];
|
|
8025
|
+
return [
|
|
8026
|
+
diagnostic({
|
|
8027
|
+
severity: "warning",
|
|
8028
|
+
code: "no-compatibility-row",
|
|
8029
|
+
message: `no compatibility row covers ${detection.providerId} on ${harness}${detection.version !== null ? ` at ${detection.version}` : ""} \u2014 ${admission.missing}`,
|
|
8030
|
+
remediation: "Add a compatibility row whose fixture proves this combination, or configure this integration by hand"
|
|
8031
|
+
})
|
|
8032
|
+
];
|
|
8033
|
+
});
|
|
8034
|
+
if (uncovered.length === 0)
|
|
8035
|
+
return detection;
|
|
8036
|
+
return { ...detection, warnings: [...detection.warnings, ...uncovered] };
|
|
8037
|
+
});
|
|
6769
8038
|
const report = {
|
|
6770
8039
|
platform: context.platform,
|
|
6771
8040
|
harnesses,
|
|
6772
|
-
providers,
|
|
8041
|
+
providers: providersWithCoverage,
|
|
6773
8042
|
problemCount
|
|
6774
8043
|
};
|
|
6775
8044
|
return commandResult({
|
|
@@ -6861,14 +8130,42 @@ async function runMetrics(context) {
|
|
|
6861
8130
|
}
|
|
6862
8131
|
}
|
|
6863
8132
|
}
|
|
8133
|
+
const projectId = context.adapters === null ? null : context.adapters.projectIdFor(context.projectRoot);
|
|
6864
8134
|
const events = [];
|
|
6865
8135
|
for await (const event of store.query({
|
|
6866
8136
|
since: window.sinceInstant,
|
|
6867
8137
|
until: window.untilInstant,
|
|
8138
|
+
...projectId === null ? {} : { projectId },
|
|
6868
8139
|
...context.provider === null ? {} : { providerIds: [context.provider] }
|
|
6869
8140
|
})) {
|
|
6870
8141
|
events.push(event);
|
|
6871
8142
|
}
|
|
8143
|
+
if (projectId === null) {
|
|
8144
|
+
diagnostics.push(diagnostic({
|
|
8145
|
+
severity: "info",
|
|
8146
|
+
code: "metrics-not-project-scoped",
|
|
8147
|
+
message: "This report covers every project in the store: no project identity was available",
|
|
8148
|
+
remediation: null
|
|
8149
|
+
}));
|
|
8150
|
+
} else {
|
|
8151
|
+
let unattributed = 0;
|
|
8152
|
+
for await (const _event of store.query({
|
|
8153
|
+
since: window.sinceInstant,
|
|
8154
|
+
until: window.untilInstant,
|
|
8155
|
+
projectId: UNATTRIBUTED_PROJECT_ID,
|
|
8156
|
+
...context.provider === null ? {} : { providerIds: [context.provider] }
|
|
8157
|
+
})) {
|
|
8158
|
+
unattributed += 1;
|
|
8159
|
+
}
|
|
8160
|
+
if (unattributed > 0) {
|
|
8161
|
+
diagnostics.push(diagnostic({
|
|
8162
|
+
severity: "info",
|
|
8163
|
+
code: "metrics-unattributed-excluded",
|
|
8164
|
+
message: `${String(unattributed)} operation${unattributed === 1 ? "" : "s"} named no project and are excluded from this report`,
|
|
8165
|
+
remediation: null
|
|
8166
|
+
}));
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
6872
8169
|
const report = aggregateEvents({
|
|
6873
8170
|
events,
|
|
6874
8171
|
windowStart: window.windowStart,
|
|
@@ -6993,7 +8290,12 @@ async function runRollback(context) {
|
|
|
6993
8290
|
fs,
|
|
6994
8291
|
snapshots: built.store,
|
|
6995
8292
|
journal: built.journal,
|
|
6996
|
-
now: context.now
|
|
8293
|
+
now: context.now,
|
|
8294
|
+
// Package inventories, when the transaction captured any: restoring a package needs the same
|
|
8295
|
+
// process runner the install used. Absent, the rollback reports those packages as not
|
|
8296
|
+
// restored instead of attempting a silent reinstall.
|
|
8297
|
+
runner: context.adapters.runner,
|
|
8298
|
+
cwd: context.projectRoot
|
|
6997
8299
|
});
|
|
6998
8300
|
diagnostics.push(...result.diagnostics);
|
|
6999
8301
|
const report = empty(result.exitCode === 0 ? "rolled-back" : "dirty");
|
|
@@ -7115,7 +8417,6 @@ async function runUninstall(context) {
|
|
|
7115
8417
|
}
|
|
7116
8418
|
|
|
7117
8419
|
// apps/cli/dist/src/commands/status.js
|
|
7118
|
-
var ASSIGNABLE_PROVIDERS2 = ["rtk"];
|
|
7119
8420
|
async function runStatus(context) {
|
|
7120
8421
|
const report = {
|
|
7121
8422
|
platform: context.platform,
|
|
@@ -7153,12 +8454,8 @@ async function runStatus(context) {
|
|
|
7153
8454
|
configs.push(...inspection.summaries);
|
|
7154
8455
|
}
|
|
7155
8456
|
const providerAdapters = listProviderAdapters();
|
|
7156
|
-
const providers = providerAdapters.map((adapter) => ({
|
|
7157
|
-
id: adapter.manifest.id,
|
|
7158
|
-
capabilities: adapter.manifest.capabilities,
|
|
7159
|
-
assignable: ASSIGNABLE_PROVIDERS2.includes(adapter.manifest.id)
|
|
7160
|
-
}));
|
|
7161
8457
|
const observedVersions = {};
|
|
8458
|
+
const providers = [];
|
|
7162
8459
|
for (const adapter of providerAdapters) {
|
|
7163
8460
|
const detection = await adapter.detect({
|
|
7164
8461
|
...detectionContext,
|
|
@@ -7168,6 +8465,11 @@ async function runStatus(context) {
|
|
|
7168
8465
|
projectIdFor: context.adapters.projectIdFor
|
|
7169
8466
|
});
|
|
7170
8467
|
observedVersions[adapter.manifest.id] = detection.version;
|
|
8468
|
+
providers.push({
|
|
8469
|
+
id: adapter.manifest.id,
|
|
8470
|
+
capabilities: adapter.manifest.capabilities,
|
|
8471
|
+
assignable: detection.assignable
|
|
8472
|
+
});
|
|
7171
8473
|
}
|
|
7172
8474
|
const resolution = resolveOwnership({
|
|
7173
8475
|
profile: "safe",
|
|
@@ -7228,14 +8530,15 @@ function upgradeAction(input) {
|
|
|
7228
8530
|
],
|
|
7229
8531
|
postconditions: [`${input.packageName} reports ${input.target}`],
|
|
7230
8532
|
/**
|
|
7231
|
-
* `
|
|
8533
|
+
* `package-inventory` where the channel can report what is installed, `none` otherwise.
|
|
7232
8534
|
*
|
|
7233
8535
|
* An update replaces a binary. Rolling the transaction back restores files and leaves the new
|
|
7234
|
-
* version in place, so the executor's
|
|
7235
|
-
*
|
|
7236
|
-
*
|
|
8536
|
+
* version in place, so the executor's receipt is not boilerplate here — it is the difference
|
|
8537
|
+
* between the machine being restored and the *configuration* being restored. With an
|
|
8538
|
+
* inventory-capturing channel the rollback also reinstalls the previous version, and the
|
|
8539
|
+
* receipt says per package whether that reinstall took.
|
|
7237
8540
|
*/
|
|
7238
|
-
rollbackData: "none",
|
|
8541
|
+
rollbackData: channelCanReportInventory(input.channel) ? "package-inventory" : "none",
|
|
7239
8542
|
explanation: `Update ${input.packageName} from ${input.installed} to ${input.target} through ${input.channel}`,
|
|
7240
8543
|
packageManager: input.channel,
|
|
7241
8544
|
packageName: input.packageName,
|
|
@@ -7288,7 +8591,7 @@ async function runUpdate(context) {
|
|
|
7288
8591
|
for (const adapter of providerAdapters) {
|
|
7289
8592
|
const detection = await adapter.detect(providerContext);
|
|
7290
8593
|
const channel = preferredInstallationChannel(adapter.manifest.installationChannels, context.platform.os);
|
|
7291
|
-
const
|
|
8594
|
+
const row2 = {
|
|
7292
8595
|
providerId: adapter.manifest.id,
|
|
7293
8596
|
installed: detection.version,
|
|
7294
8597
|
available: null,
|
|
@@ -7297,24 +8600,24 @@ async function runUpdate(context) {
|
|
|
7297
8600
|
pin: pins.pins.get(adapter.manifest.id) ?? null
|
|
7298
8601
|
};
|
|
7299
8602
|
if (detection.state === "absent") {
|
|
7300
|
-
|
|
7301
|
-
report.providers.push(
|
|
8603
|
+
row2.verdict = "not-installed";
|
|
8604
|
+
report.providers.push(row2);
|
|
7302
8605
|
continue;
|
|
7303
8606
|
}
|
|
7304
|
-
if (
|
|
7305
|
-
|
|
7306
|
-
report.providers.push(
|
|
8607
|
+
if (row2.pin !== null) {
|
|
8608
|
+
row2.verdict = "pinned";
|
|
8609
|
+
report.providers.push(row2);
|
|
7307
8610
|
diagnostics.push(diagnostic({
|
|
7308
8611
|
severity: "info",
|
|
7309
8612
|
code: "provider-pinned",
|
|
7310
|
-
message: `${adapter.manifest.id} is pinned at ${
|
|
8613
|
+
message: `${adapter.manifest.id} is pinned at ${row2.pin}, so no update was planned for it`,
|
|
7311
8614
|
remediation: `Remove it from the pin file to allow updates`
|
|
7312
8615
|
}));
|
|
7313
8616
|
continue;
|
|
7314
8617
|
}
|
|
7315
8618
|
if (channel === null) {
|
|
7316
|
-
|
|
7317
|
-
report.providers.push(
|
|
8619
|
+
row2.verdict = "no-channel";
|
|
8620
|
+
report.providers.push(row2);
|
|
7318
8621
|
continue;
|
|
7319
8622
|
}
|
|
7320
8623
|
const packageName = channel.packageId ?? adapter.manifest.id;
|
|
@@ -7327,21 +8630,21 @@ async function runUpdate(context) {
|
|
|
7327
8630
|
diagnostics.push(...query.diagnostics);
|
|
7328
8631
|
if (query.destination !== null)
|
|
7329
8632
|
destinations.add(query.destination);
|
|
7330
|
-
|
|
8633
|
+
row2.available = query.version;
|
|
7331
8634
|
if (query.status !== "found" || query.version === null) {
|
|
7332
|
-
|
|
7333
|
-
report.providers.push(
|
|
8635
|
+
row2.verdict = query.status === "unknown" ? "unknown" : "unavailable";
|
|
8636
|
+
report.providers.push(row2);
|
|
7334
8637
|
continue;
|
|
7335
8638
|
}
|
|
7336
8639
|
const installed = detection.version === null ? null : parseSemanticVersion(detection.version);
|
|
7337
8640
|
const offered = parseSemanticVersion(query.version);
|
|
7338
8641
|
if (installed === null || offered === null || compareVersions(offered, installed) <= 0) {
|
|
7339
|
-
|
|
7340
|
-
report.providers.push(
|
|
8642
|
+
row2.verdict = "current";
|
|
8643
|
+
report.providers.push(row2);
|
|
7341
8644
|
continue;
|
|
7342
8645
|
}
|
|
7343
|
-
|
|
7344
|
-
report.providers.push(
|
|
8646
|
+
row2.verdict = "upgradable";
|
|
8647
|
+
report.providers.push(row2);
|
|
7345
8648
|
actions.push(upgradeAction({
|
|
7346
8649
|
providerId: adapter.manifest.id,
|
|
7347
8650
|
channel: channel.id,
|
|
@@ -7508,11 +8811,12 @@ async function runVerify(context) {
|
|
|
7508
8811
|
presentHarnesses
|
|
7509
8812
|
);
|
|
7510
8813
|
for (const harnessId2 of harnesses) {
|
|
8814
|
+
const declaredTier = adapter.manifest.harnesses.find((entry) => entry.harness === harnessId2)?.verificationTier ?? verification.declaredTier;
|
|
7511
8815
|
results.push({
|
|
7512
8816
|
providerId: adapter.manifest.id,
|
|
7513
8817
|
harnessId: harnessId2,
|
|
7514
|
-
status: statusFor(verification.achievedTier,
|
|
7515
|
-
declaredTier
|
|
8818
|
+
status: statusFor(verification.achievedTier, declaredTier),
|
|
8819
|
+
declaredTier,
|
|
7516
8820
|
managedByTokenHarness: MANAGED_PROVIDERS.includes(adapter.manifest.id),
|
|
7517
8821
|
checks: verification.checks
|
|
7518
8822
|
});
|
|
@@ -7556,11 +8860,52 @@ function statusFor(achieved, declared) {
|
|
|
7556
8860
|
}
|
|
7557
8861
|
|
|
7558
8862
|
// apps/cli/dist/src/render/layout.js
|
|
8863
|
+
var MAX_WIDTH = 78;
|
|
8864
|
+
function wrap(text, indent, width = MAX_WIDTH) {
|
|
8865
|
+
const limit = Math.max(20, width - indent);
|
|
8866
|
+
const words = text.split(/\s+/).filter((word) => word !== "");
|
|
8867
|
+
if (words.length === 0)
|
|
8868
|
+
return [];
|
|
8869
|
+
const lines = [];
|
|
8870
|
+
let current = "";
|
|
8871
|
+
for (const word of words) {
|
|
8872
|
+
if (current === "") {
|
|
8873
|
+
current = word;
|
|
8874
|
+
continue;
|
|
8875
|
+
}
|
|
8876
|
+
if (`${current} ${word}`.length <= limit) {
|
|
8877
|
+
current = `${current} ${word}`;
|
|
8878
|
+
continue;
|
|
8879
|
+
}
|
|
8880
|
+
lines.push(current);
|
|
8881
|
+
current = word;
|
|
8882
|
+
}
|
|
8883
|
+
lines.push(current);
|
|
8884
|
+
const pad = " ".repeat(indent);
|
|
8885
|
+
return lines.map((line, index) => index === 0 ? pad + line : pad + line);
|
|
8886
|
+
}
|
|
7559
8887
|
function column(value, width, minimumGutter = 2) {
|
|
7560
8888
|
if (value.length >= width)
|
|
7561
8889
|
return value + " ".repeat(minimumGutter);
|
|
7562
8890
|
return value.padEnd(width, " ");
|
|
7563
8891
|
}
|
|
8892
|
+
function truncate(value, width) {
|
|
8893
|
+
const characters = [...value];
|
|
8894
|
+
if (characters.length <= width)
|
|
8895
|
+
return value;
|
|
8896
|
+
return `${characters.slice(0, Math.max(1, width - 1)).join("").trimEnd()}\u2026`;
|
|
8897
|
+
}
|
|
8898
|
+
var COLUMN_SEPARATOR = " - ";
|
|
8899
|
+
function row(cells) {
|
|
8900
|
+
const rendered = cells.map(([value, width], index) => index === cells.length - 1 ? value : column(value, width, 0));
|
|
8901
|
+
return rendered.join(COLUMN_SEPARATOR);
|
|
8902
|
+
}
|
|
8903
|
+
function truncatePath(value, width) {
|
|
8904
|
+
const characters = [...value];
|
|
8905
|
+
if (characters.length <= width)
|
|
8906
|
+
return value;
|
|
8907
|
+
return `\u2026${characters.slice(characters.length - (width - 1)).join("")}`;
|
|
8908
|
+
}
|
|
7564
8909
|
function rightAlign(value, width) {
|
|
7565
8910
|
return value.padStart(width, " ");
|
|
7566
8911
|
}
|
|
@@ -7639,7 +8984,7 @@ var HEADERS = {
|
|
|
7639
8984
|
rollback: "Rollback",
|
|
7640
8985
|
uninstall: "Uninstall"
|
|
7641
8986
|
};
|
|
7642
|
-
function renderApplyReport(report,
|
|
8987
|
+
function renderApplyReport(report, context, command = "apply") {
|
|
7643
8988
|
const lines = [];
|
|
7644
8989
|
const header = [HEADERS[command] ?? "Apply"];
|
|
7645
8990
|
header.push(report.planId === null ? "no plan" : `plan ${report.planId}`);
|
|
@@ -7652,72 +8997,155 @@ function renderApplyReport(report, _context, command = "apply") {
|
|
|
7652
8997
|
if (report.results.length > 0) {
|
|
7653
8998
|
lines.push("Actions");
|
|
7654
8999
|
for (const [index, result] of report.results.entries()) {
|
|
7655
|
-
lines.push(` ${String(index + 1)}. ${
|
|
9000
|
+
lines.push(` ${String(index + 1)}. ${row([
|
|
9001
|
+
[result.kind, KIND_WIDTH],
|
|
9002
|
+
[result.status, STATUS_WIDTH],
|
|
9003
|
+
// Abbreviated: an absolute Windows path under a long account name ran past 140
|
|
9004
|
+
// characters, and `~/` is both shorter and what every other command shows.
|
|
9005
|
+
[result.path === null ? "" : displayPath(result.path, context.home), 0]
|
|
9006
|
+
])}`);
|
|
7656
9007
|
}
|
|
7657
9008
|
lines.push("");
|
|
7658
9009
|
}
|
|
7659
9010
|
if (report.unrestored.length > 0) {
|
|
7660
9011
|
lines.push("Not restored");
|
|
7661
9012
|
for (const path of report.unrestored)
|
|
7662
|
-
lines.push(` ${path}`);
|
|
9013
|
+
lines.push(` ${displayPath(path, context.home)}`);
|
|
7663
9014
|
lines.push("");
|
|
7664
9015
|
}
|
|
7665
|
-
|
|
9016
|
+
const removedNothing = command === "uninstall" && report.outcome === "committed" && report.results.length > 0 && report.results.every((result) => result.status !== "applied");
|
|
9017
|
+
lines.push(removedNothing ? "Removed nothing: no owned entry was recognised. Everything is untouched." : (OUTCOME_LINES[command] ?? OUTCOME_LINES["apply"])?.[report.outcome] ?? "");
|
|
7666
9018
|
return document(lines);
|
|
7667
9019
|
}
|
|
7668
9020
|
|
|
7669
9021
|
// apps/cli/dist/src/render/doctor.js
|
|
7670
|
-
var
|
|
7671
|
-
var
|
|
7672
|
-
var
|
|
7673
|
-
var
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
if (detection.
|
|
7688
|
-
|
|
7689
|
-
|
|
9022
|
+
var NAME_WIDTH = 15;
|
|
9023
|
+
var STATE_WIDTH = 12;
|
|
9024
|
+
var VERSION_WIDTH = 10;
|
|
9025
|
+
var WIRED_WIDTH = 14;
|
|
9026
|
+
function harnessState(detection) {
|
|
9027
|
+
switch (detection.state) {
|
|
9028
|
+
case "absent":
|
|
9029
|
+
return "not found";
|
|
9030
|
+
case "detected":
|
|
9031
|
+
return "no hooks";
|
|
9032
|
+
case "configured":
|
|
9033
|
+
return "hooks";
|
|
9034
|
+
case "broken":
|
|
9035
|
+
return "unreadable";
|
|
9036
|
+
}
|
|
9037
|
+
}
|
|
9038
|
+
function wiredTo(detection) {
|
|
9039
|
+
if (detection.state === "broken")
|
|
9040
|
+
return "cannot run";
|
|
9041
|
+
if (detection.state === "absent")
|
|
9042
|
+
return "not installed";
|
|
9043
|
+
if (detection.configuredHarnesses.length === 0)
|
|
9044
|
+
return "nothing yet";
|
|
9045
|
+
return detection.configuredHarnesses.join(", ");
|
|
9046
|
+
}
|
|
9047
|
+
function setUpBy(detection) {
|
|
9048
|
+
if (detection.state === "absent" || detection.configuredHarnesses.length === 0)
|
|
9049
|
+
return "\u2014";
|
|
9050
|
+
return detection.managedByTokenHarness ? "this tool" : "you";
|
|
9051
|
+
}
|
|
9052
|
+
function notes(report) {
|
|
9053
|
+
const lines = [];
|
|
9054
|
+
for (const harness of report.harnesses) {
|
|
9055
|
+
if (harness.state === "broken") {
|
|
9056
|
+
lines.push(` ${row([
|
|
9057
|
+
[harness.harnessId, NAME_WIDTH - 2],
|
|
9058
|
+
["configuration could not be read", 0]
|
|
9059
|
+
])}`);
|
|
9060
|
+
}
|
|
9061
|
+
if (harness.versionVerdict === "unknown-newer") {
|
|
9062
|
+
lines.push(` ${row([
|
|
9063
|
+
[harness.harnessId, NAME_WIDTH - 2],
|
|
9064
|
+
[`${harness.version ?? ""} is newer than any tested version`, 0]
|
|
9065
|
+
])}`);
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
for (const provider of report.providers) {
|
|
9069
|
+
if (provider.state === "broken") {
|
|
9070
|
+
lines.push(` ${row([
|
|
9071
|
+
[provider.providerId, NAME_WIDTH - 2],
|
|
9072
|
+
["installed but could not be run", 0]
|
|
9073
|
+
])}`);
|
|
9074
|
+
}
|
|
9075
|
+
if (provider.versionVerdict === "unknown-newer") {
|
|
9076
|
+
lines.push(` ${row([
|
|
9077
|
+
[provider.providerId, NAME_WIDTH - 2],
|
|
9078
|
+
[`${provider.version ?? ""} is newer than any tested version`, 0]
|
|
9079
|
+
])}`);
|
|
9080
|
+
}
|
|
7690
9081
|
}
|
|
7691
|
-
|
|
7692
|
-
const adopted = detection.managedByTokenHarness ? "" : " (adopted, not managed)";
|
|
7693
|
-
return `configured for ${list}${adopted}`;
|
|
9082
|
+
return lines;
|
|
7694
9083
|
}
|
|
7695
9084
|
function renderDoctorReport(report, context) {
|
|
7696
9085
|
const lines = [];
|
|
7697
9086
|
lines.push(`Token Harness ${context.toolVersion} \u2014 ${renderPlatformSummary(report.platform)}, Node ${report.platform.nodeVersion}`);
|
|
7698
9087
|
lines.push("");
|
|
7699
|
-
lines.push(
|
|
9088
|
+
lines.push(` ${row([
|
|
9089
|
+
["HARNESSES", NAME_WIDTH - 2],
|
|
9090
|
+
["STATE", STATE_WIDTH],
|
|
9091
|
+
["CONFIG FILE", 0]
|
|
9092
|
+
])}`);
|
|
7700
9093
|
if (report.harnesses.length === 0) {
|
|
7701
|
-
lines.push("
|
|
9094
|
+
lines.push(" none registered");
|
|
7702
9095
|
} else {
|
|
7703
9096
|
for (const harness of report.harnesses) {
|
|
7704
|
-
lines.push(` ${
|
|
9097
|
+
lines.push(` ${row([
|
|
9098
|
+
[harness.harnessId, NAME_WIDTH - 2],
|
|
9099
|
+
[harnessState(harness), STATE_WIDTH],
|
|
9100
|
+
[
|
|
9101
|
+
harness.configPath === null ? "" : (
|
|
9102
|
+
// The columns before this one, plus their separators, consume a fixed prefix; the
|
|
9103
|
+
// path gets what is left and is cut from the left so the file name survives.
|
|
9104
|
+
truncatePath(displayPath(harness.configPath, context.home), MAX_WIDTH - (2 + NAME_WIDTH - 2 + 3 + STATE_WIDTH + 3))
|
|
9105
|
+
),
|
|
9106
|
+
0
|
|
9107
|
+
]
|
|
9108
|
+
])}`);
|
|
7705
9109
|
}
|
|
7706
9110
|
}
|
|
7707
9111
|
lines.push("");
|
|
7708
|
-
lines.push(
|
|
9112
|
+
lines.push(` ${row([
|
|
9113
|
+
["PROVIDERS", NAME_WIDTH - 2],
|
|
9114
|
+
["VERSION", VERSION_WIDTH],
|
|
9115
|
+
["WIRED TO", WIRED_WIDTH],
|
|
9116
|
+
["SET UP BY", 0]
|
|
9117
|
+
])}`);
|
|
7709
9118
|
if (report.providers.length === 0) {
|
|
7710
|
-
lines.push("
|
|
9119
|
+
lines.push(" none registered");
|
|
7711
9120
|
} else {
|
|
7712
9121
|
for (const provider of report.providers) {
|
|
7713
|
-
lines.push(` ${
|
|
9122
|
+
lines.push(` ${row([
|
|
9123
|
+
[provider.providerId, NAME_WIDTH - 2],
|
|
9124
|
+
[provider.version ?? "-", VERSION_WIDTH],
|
|
9125
|
+
[wiredTo(provider), WIRED_WIDTH],
|
|
9126
|
+
[setUpBy(provider), 0]
|
|
9127
|
+
])}`);
|
|
7714
9128
|
}
|
|
7715
9129
|
}
|
|
9130
|
+
const notable = notes(report);
|
|
9131
|
+
if (notable.length > 0) {
|
|
9132
|
+
lines.push("");
|
|
9133
|
+
lines.push("NOTES");
|
|
9134
|
+
lines.push(...notable);
|
|
9135
|
+
}
|
|
9136
|
+
const unwired = report.providers.filter((provider) => provider.state !== "absent" && provider.configuredHarnesses.length === 0);
|
|
7716
9137
|
lines.push("");
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
9138
|
+
lines.push("NEXT");
|
|
9139
|
+
const suggestions = report.providers.every((provider) => provider.state === "absent") || unwired.length > 0 ? [["token-harness plan", "see what would change \u2014 writes nothing"]] : [
|
|
9140
|
+
["token-harness verify", "check the pipeline actually intercepts"],
|
|
9141
|
+
["token-harness metrics --since 7d", "see what it saved"]
|
|
9142
|
+
];
|
|
9143
|
+
const commandWidth = Math.max(...suggestions.map(([command]) => command.length));
|
|
9144
|
+
for (const [command, description] of suggestions) {
|
|
9145
|
+
lines.push(` ${row([
|
|
9146
|
+
[command, commandWidth],
|
|
9147
|
+
[description, 0]
|
|
9148
|
+
])}`);
|
|
7721
9149
|
}
|
|
7722
9150
|
return document(lines);
|
|
7723
9151
|
}
|
|
@@ -7725,11 +9153,10 @@ function renderDoctorReport(report, context) {
|
|
|
7725
9153
|
// apps/cli/dist/src/render/metrics.js
|
|
7726
9154
|
var CLASS_LABEL_WIDTH = 23;
|
|
7727
9155
|
var FIGURE_BLOCK_GUTTER = 4;
|
|
7728
|
-
var
|
|
7729
|
-
var PROVIDER_SAVED_WIDTH =
|
|
7730
|
-
var PROVIDER_CLASS_WIDTH =
|
|
7731
|
-
var
|
|
7732
|
-
var PROVIDER_CONTINUATION_INDENT = 2 + PROVIDER_ID_WIDTH2;
|
|
9156
|
+
var PROVIDER_ID_WIDTH = 13;
|
|
9157
|
+
var PROVIDER_SAVED_WIDTH = 21;
|
|
9158
|
+
var PROVIDER_CLASS_WIDTH = 15;
|
|
9159
|
+
var PROVIDER_CONTINUATION_INDENT = 2 + PROVIDER_ID_WIDTH;
|
|
7733
9160
|
var CLASS_LABELS = {
|
|
7734
9161
|
"exact-local": "Exact local",
|
|
7735
9162
|
"estimated-local": "Estimated local",
|
|
@@ -7737,21 +9164,21 @@ var CLASS_LABELS = {
|
|
|
7737
9164
|
"end-to-end-billed": "End-to-end billed"
|
|
7738
9165
|
};
|
|
7739
9166
|
function renderClassRows(report) {
|
|
7740
|
-
const byClass = new Map(report.classes.map((
|
|
7741
|
-
const ordered = MEASUREMENT_CLASSES.map((id) => byClass.get(id)).filter((
|
|
7742
|
-
const withFigures = ordered.filter((
|
|
7743
|
-
const beforeWidth = Math.max(0, ...withFigures.map((
|
|
7744
|
-
const afterWidth = Math.max(0, ...withFigures.map((
|
|
7745
|
-
const blocks = ordered.map((
|
|
7746
|
-
if (
|
|
9167
|
+
const byClass = new Map(report.classes.map((row2) => [row2.class, row2]));
|
|
9168
|
+
const ordered = MEASUREMENT_CLASSES.map((id) => byClass.get(id)).filter((row2) => row2 !== void 0);
|
|
9169
|
+
const withFigures = ordered.filter((row2) => row2.before !== null && row2.after !== null);
|
|
9170
|
+
const beforeWidth = Math.max(0, ...withFigures.map((row2) => formatCount(row2.before).length));
|
|
9171
|
+
const afterWidth = Math.max(0, ...withFigures.map((row2) => formatCount(row2.after).length));
|
|
9172
|
+
const blocks = ordered.map((row2) => {
|
|
9173
|
+
if (row2.before === null || row2.after === null)
|
|
7747
9174
|
return "";
|
|
7748
|
-
return `${rightAlign(formatCount(
|
|
9175
|
+
return `${rightAlign(formatCount(row2.before), beforeWidth)} -> ${rightAlign(formatCount(row2.after), afterWidth)} ${row2.unit ?? ""}`;
|
|
7749
9176
|
});
|
|
7750
9177
|
const blockWidth = Math.max(0, ...blocks.map((block) => block.length)) + FIGURE_BLOCK_GUTTER;
|
|
7751
|
-
return ordered.map((
|
|
7752
|
-
const label = column(CLASS_LABELS[
|
|
9178
|
+
return ordered.map((row2, index) => {
|
|
9179
|
+
const label = column(CLASS_LABELS[row2.class], CLASS_LABEL_WIDTH);
|
|
7753
9180
|
const block = (blocks[index] ?? "").padEnd(blockWidth, " ");
|
|
7754
|
-
const tail =
|
|
9181
|
+
const tail = row2.saved === null ? row2.note ?? "" : `saved ${formatCount(row2.saved)}`;
|
|
7755
9182
|
return `${label}${block}${tail}`;
|
|
7756
9183
|
});
|
|
7757
9184
|
}
|
|
@@ -7769,14 +9196,21 @@ function renderMetricsReport(report, _context) {
|
|
|
7769
9196
|
lines.push(" no provider reported a measurable saving in this window");
|
|
7770
9197
|
} else {
|
|
7771
9198
|
for (const provider of report.providers) {
|
|
7772
|
-
lines.push(` ${
|
|
7773
|
-
|
|
9199
|
+
lines.push(` ${row([
|
|
9200
|
+
[provider.providerId, PROVIDER_ID_WIDTH],
|
|
9201
|
+
[`saved ${formatCount(provider.saved)} ${provider.unit}`, PROVIDER_SAVED_WIDTH],
|
|
9202
|
+
[provider.class, PROVIDER_CLASS_WIDTH],
|
|
9203
|
+
[`${formatCount(provider.operations)} operations`, 0]
|
|
9204
|
+
])}`);
|
|
9205
|
+
const notes2 = [];
|
|
9206
|
+
if (provider.harnesses.length > 0)
|
|
9207
|
+
notes2.push(`on ${provider.harnesses.join(", ")}`);
|
|
7774
9208
|
if (!provider.managedByTokenHarness)
|
|
7775
|
-
|
|
9209
|
+
notes2.push("set up by you");
|
|
7776
9210
|
if (provider.adapterMode !== null)
|
|
7777
|
-
|
|
7778
|
-
if (
|
|
7779
|
-
lines.push(`${" ".repeat(PROVIDER_CONTINUATION_INDENT)}${
|
|
9211
|
+
notes2.push(`mode ${provider.adapterMode}`);
|
|
9212
|
+
if (notes2.length > 0) {
|
|
9213
|
+
lines.push(`${" ".repeat(PROVIDER_CONTINUATION_INDENT)}${notes2.join(" \u2014 ")}`);
|
|
7780
9214
|
}
|
|
7781
9215
|
}
|
|
7782
9216
|
}
|
|
@@ -7808,8 +9242,8 @@ function renderConflicts(report) {
|
|
|
7808
9242
|
lines.push("");
|
|
7809
9243
|
lines.push(` ${column("conflict", CONFLICT_LABEL_WIDTH)}${conflict.code}`);
|
|
7810
9244
|
for (const detail2 of conflict.detail)
|
|
7811
|
-
lines.push(
|
|
7812
|
-
lines.push(`
|
|
9245
|
+
lines.push(...wrap(detail2, 4));
|
|
9246
|
+
lines.push(...wrap(`Fix: ${conflict.remediation}`, 4));
|
|
7813
9247
|
}
|
|
7814
9248
|
lines.push("");
|
|
7815
9249
|
lines.push("No plan was produced.");
|
|
@@ -7852,13 +9286,24 @@ function renderPlanReport(report, context) {
|
|
|
7852
9286
|
if (report.exclusions.length > 0) {
|
|
7853
9287
|
lines.push("");
|
|
7854
9288
|
lines.push("Excluded");
|
|
9289
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7855
9290
|
for (const exclusion of report.exclusions) {
|
|
7856
|
-
const
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
9291
|
+
const key = `${exclusion.excluded}\0${exclusion.scope.capability}`;
|
|
9292
|
+
const existing = grouped.get(key);
|
|
9293
|
+
if (existing === void 0) {
|
|
9294
|
+
grouped.set(key, {
|
|
9295
|
+
capability: exclusion.scope.capability,
|
|
9296
|
+
harnesses: [exclusion.scope.harness]
|
|
9297
|
+
});
|
|
9298
|
+
} else if (!existing.harnesses.includes(exclusion.scope.harness)) {
|
|
9299
|
+
existing.harnesses.push(exclusion.scope.harness);
|
|
7860
9300
|
}
|
|
7861
9301
|
}
|
|
9302
|
+
for (const [key, entry] of grouped) {
|
|
9303
|
+
const provider = key.slice(0, key.indexOf("\0"));
|
|
9304
|
+
lines.push(` ${column(provider, EXCLUSION_PROVIDER_WIDTH)}${column(entry.capability, EXCLUSION_CAPABILITY_WIDTH)}${entry.harnesses.join(", ")}`);
|
|
9305
|
+
lines.push(...wrap("its installer cannot produce this in isolation, so it is measured but not given the scope", EXCLUSION_CONTINUATION_INDENT));
|
|
9306
|
+
}
|
|
7862
9307
|
}
|
|
7863
9308
|
lines.push("");
|
|
7864
9309
|
lines.push("Actions");
|
|
@@ -7934,22 +9379,22 @@ var VERDICT_WIDTH = 15;
|
|
|
7934
9379
|
function widthOf(values, floor) {
|
|
7935
9380
|
return Math.max(floor, ...values.map((value) => value.length + 2));
|
|
7936
9381
|
}
|
|
7937
|
-
function detail(
|
|
7938
|
-
switch (
|
|
9382
|
+
function detail(row2) {
|
|
9383
|
+
switch (row2.verdict) {
|
|
7939
9384
|
case "upgradable":
|
|
7940
|
-
return `${String(
|
|
9385
|
+
return `${String(row2.installed)} \u2192 ${String(row2.available)} via ${String(row2.channel)}`;
|
|
7941
9386
|
case "current":
|
|
7942
|
-
return `${String(
|
|
9387
|
+
return `${String(row2.installed)} is the newest ${String(row2.channel)} offers`;
|
|
7943
9388
|
case "pinned":
|
|
7944
|
-
return `held at ${String(
|
|
9389
|
+
return `held at ${String(row2.pin)} by a pin`;
|
|
7945
9390
|
case "not-installed":
|
|
7946
9391
|
return "not installed, so there is nothing to update";
|
|
7947
9392
|
case "no-channel":
|
|
7948
9393
|
return "no installation channel on this platform";
|
|
7949
9394
|
case "unknown":
|
|
7950
|
-
return `${String(
|
|
9395
|
+
return `${String(row2.channel)} answered without a version this build can read`;
|
|
7951
9396
|
case "unavailable":
|
|
7952
|
-
return `${String(
|
|
9397
|
+
return `${String(row2.channel)} could not be asked`;
|
|
7953
9398
|
}
|
|
7954
9399
|
}
|
|
7955
9400
|
function renderUpdateReport(report, _context) {
|
|
@@ -7958,16 +9403,20 @@ function renderUpdateReport(report, _context) {
|
|
|
7958
9403
|
lines.push("No provider was inspected.");
|
|
7959
9404
|
return document(lines);
|
|
7960
9405
|
}
|
|
7961
|
-
const providerWidth = widthOf(report.providers.map((
|
|
7962
|
-
const
|
|
7963
|
-
for (const
|
|
7964
|
-
lines.push(
|
|
9406
|
+
const providerWidth = Math.min(20, widthOf(report.providers.map((entry) => entry.providerId), PROVIDER_WIDTH));
|
|
9407
|
+
const detailStart = providerWidth + 3 + VERDICT_WIDTH + 3;
|
|
9408
|
+
for (const entry of report.providers) {
|
|
9409
|
+
lines.push(truncate(row([
|
|
9410
|
+
[entry.providerId, providerWidth],
|
|
9411
|
+
[entry.verdict, VERDICT_WIDTH],
|
|
9412
|
+
[truncate(detail(entry), MAX_WIDTH - detailStart), 0]
|
|
9413
|
+
]), MAX_WIDTH));
|
|
7965
9414
|
}
|
|
7966
9415
|
if (report.network.length > 0) {
|
|
7967
9416
|
lines.push("");
|
|
7968
9417
|
lines.push(`Network: ${report.network.join(", ")}`);
|
|
7969
9418
|
}
|
|
7970
|
-
const upgradable = report.providers.filter((
|
|
9419
|
+
const upgradable = report.providers.filter((row2) => row2.verdict === "upgradable").length;
|
|
7971
9420
|
const execution = report.execution;
|
|
7972
9421
|
lines.push("");
|
|
7973
9422
|
if (execution === null || execution.outcome === "nothing-to-do") {
|
|
@@ -7983,33 +9432,34 @@ function renderUpdateReport(report, _context) {
|
|
|
7983
9432
|
}
|
|
7984
9433
|
|
|
7985
9434
|
// apps/cli/dist/src/render/verify.js
|
|
7986
|
-
var
|
|
7987
|
-
var
|
|
7988
|
-
function
|
|
7989
|
-
|
|
7990
|
-
return Math.max(CHECK_STATUS_WIDTH, longest + 2);
|
|
9435
|
+
var STATUS_WIDTH2 = 16;
|
|
9436
|
+
var CHECK_WIDTH = 12;
|
|
9437
|
+
function shortCheck(id) {
|
|
9438
|
+
return truncate(id.replace(/-resolves$|-registered$|-intercepted$/, ""), CHECK_WIDTH);
|
|
7991
9439
|
}
|
|
7992
9440
|
function renderVerifyReport(report, _context) {
|
|
7993
9441
|
const lines = [];
|
|
7994
|
-
|
|
7995
|
-
lines.push(report.receiptId === null || report.appliedAt === null ? "No receipt \u2014 verifying the live configuration" : `Receipt ${report.receiptId} \u2014 applied ${report.appliedAt}`);
|
|
7996
|
-
lines.push("");
|
|
9442
|
+
lines.push(report.receiptId === null || report.appliedAt === null ? "No receipt: verifying what is on the machine now" : `Receipt ${report.receiptId} \u2014 applied ${report.appliedAt}`);
|
|
7997
9443
|
for (const result of report.results) {
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
header.push("adopted, not managed");
|
|
8001
|
-
header.push(`declared tier: ${result.declaredTier}`);
|
|
8002
|
-
lines.push(header.join(" \u2014 "));
|
|
9444
|
+
lines.push("");
|
|
9445
|
+
lines.push(`${result.providerId} on ${result.harnessId} \u2014 set up by ${result.managedByTokenHarness ? "this tool" : "you"}, tier ${result.declaredTier}`);
|
|
8003
9446
|
for (const check of result.checks) {
|
|
8004
|
-
|
|
9447
|
+
const indent = 2 + CHECK_WIDTH + 3 + STATUS_WIDTH2 + 3;
|
|
9448
|
+
const wrapped = wrap(check.summary, indent);
|
|
9449
|
+
lines.push(` ${row([
|
|
9450
|
+
[shortCheck(check.id), CHECK_WIDTH],
|
|
9451
|
+
[check.status, STATUS_WIDTH2],
|
|
9452
|
+
[(wrapped[0] ?? "").trimStart(), 0]
|
|
9453
|
+
])}`);
|
|
9454
|
+
lines.push(...wrapped.slice(1));
|
|
8005
9455
|
}
|
|
8006
9456
|
}
|
|
8007
9457
|
lines.push("");
|
|
8008
9458
|
if (report.healthyAtDeclaredTier) {
|
|
8009
|
-
lines.push("
|
|
9459
|
+
lines.push("Healthy at the declared tier for every provider.");
|
|
8010
9460
|
} else {
|
|
8011
9461
|
const failures = report.results.flatMap((result) => result.checks).filter((check) => check.status === "fail").length;
|
|
8012
|
-
lines.push(`${failures} ${pluralize(failures, "check")} below
|
|
9462
|
+
lines.push(`${String(failures)} ${pluralize(failures, "check")} below tier. Not proven.`);
|
|
8013
9463
|
}
|
|
8014
9464
|
return document(lines);
|
|
8015
9465
|
}
|
|
@@ -8053,27 +9503,37 @@ function renderHuman(result, context) {
|
|
|
8053
9503
|
}
|
|
8054
9504
|
|
|
8055
9505
|
// apps/cli/dist/src/usage.js
|
|
8056
|
-
var ROOT_USAGE = `token-harness \u2014
|
|
9506
|
+
var ROOT_USAGE = `token-harness \u2014 set up and measure token-saving tools for coding agents
|
|
9507
|
+
|
|
9508
|
+
First time here
|
|
9509
|
+
1 token-harness doctor what is on this machine writes nothing
|
|
9510
|
+
2 token-harness plan what would change writes nothing
|
|
9511
|
+
3 token-harness apply --yes make those changes WRITES
|
|
9512
|
+
4 token-harness verify is it actually intercepting writes nothing
|
|
9513
|
+
5 token-harness metrics what it saved writes nothing
|
|
9514
|
+
|
|
9515
|
+
Steps 1 and 2 are safe to run right now. Nothing changes until step 3.
|
|
8057
9516
|
|
|
8058
9517
|
Usage
|
|
8059
9518
|
token-harness <command> [flags]
|
|
9519
|
+
token-harness help <command>
|
|
8060
9520
|
|
|
8061
|
-
Commands
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
9521
|
+
Commands, in the order you would use them
|
|
9522
|
+
doctor What is installed, what is wired up, what is worth knowing
|
|
9523
|
+
plan Compute what would change, file by file. Changes nothing
|
|
9524
|
+
apply Run that plan inside a transaction that can be rolled back
|
|
9525
|
+
verify Check the pipeline actually intercepts, at its declared tier
|
|
9526
|
+
metrics Report what was saved, labelled with how it was measured
|
|
8066
9527
|
status Report applied pipelines, drift, and importer modes
|
|
8067
|
-
|
|
8068
|
-
rollback Restore the files a transaction changed, as they were before
|
|
8069
|
-
uninstall Remove what Token Harness
|
|
8070
|
-
update Update the installed providers to what their channel offers
|
|
9528
|
+
update Update installed providers to what their channel offers
|
|
9529
|
+
rollback Restore the files a transaction changed, as they were before
|
|
9530
|
+
uninstall Remove what Token Harness set up, leaving everything else
|
|
8071
9531
|
|
|
8072
9532
|
Flags
|
|
8073
9533
|
--json Emit one machine-readable envelope on stdout
|
|
8074
9534
|
--harness <id> Restrict the operation to one harness
|
|
8075
9535
|
--provider <id> Restrict the operation to one provider
|
|
8076
|
-
--project <dir>
|
|
9536
|
+
--project <dir> Use that project instead of the current directory
|
|
8077
9537
|
--since <window> Report from this point: a duration like 7d, or a date
|
|
8078
9538
|
--until <window> Report up to this point; defaults to now
|
|
8079
9539
|
--plan <id> Apply a previously computed plan by id
|
|
@@ -8081,8 +9541,8 @@ Flags
|
|
|
8081
9541
|
--version Print the version and exit 0
|
|
8082
9542
|
--help Print usage and exit 0
|
|
8083
9543
|
|
|
8084
|
-
|
|
8085
|
-
|
|
9544
|
+
Only apply, update, rollback and uninstall change anything, and none of
|
|
9545
|
+
them do without --yes. Exit codes and JSON are specified in RFC 0006.`;
|
|
8086
9546
|
var COMMAND_USAGE = {
|
|
8087
9547
|
apply: `token-harness apply \u2014 execute a plan inside a reversible transaction
|
|
8088
9548
|
|
|
@@ -8221,7 +9681,7 @@ function usageText(topic) {
|
|
|
8221
9681
|
}
|
|
8222
9682
|
|
|
8223
9683
|
// apps/cli/dist/src/version.js
|
|
8224
|
-
var TOOL_VERSION = "0.1.
|
|
9684
|
+
var TOOL_VERSION = "0.1.2";
|
|
8225
9685
|
|
|
8226
9686
|
// apps/cli/dist/src/run.js
|
|
8227
9687
|
var DEFAULT_COMMANDS = {
|
|
@@ -8235,12 +9695,31 @@ var DEFAULT_COMMANDS = {
|
|
|
8235
9695
|
update: runUpdate,
|
|
8236
9696
|
verify: runVerify
|
|
8237
9697
|
};
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
9698
|
+
var SUBJECT_WIDTH = 12;
|
|
9699
|
+
function diagnosticLine(entry) {
|
|
9700
|
+
const label = entry.subject ?? entry.code;
|
|
9701
|
+
return truncate(` ${row([
|
|
9702
|
+
[label, SUBJECT_WIDTH],
|
|
9703
|
+
[entry.message, 0]
|
|
9704
|
+
])}`, MAX_WIDTH);
|
|
9705
|
+
}
|
|
9706
|
+
function formatDiagnostics(diagnostics) {
|
|
9707
|
+
const errors = diagnostics.filter((entry) => entry.severity === "error");
|
|
9708
|
+
const warnings = diagnostics.filter((entry) => entry.severity === "warning");
|
|
9709
|
+
if (errors.length === 0 && warnings.length === 0)
|
|
9710
|
+
return "";
|
|
9711
|
+
const lines = [];
|
|
9712
|
+
for (const [title, group] of [
|
|
9713
|
+
["ERRORS", errors],
|
|
9714
|
+
["WARNINGS", warnings]
|
|
9715
|
+
]) {
|
|
9716
|
+
if (group.length === 0)
|
|
9717
|
+
continue;
|
|
9718
|
+
lines.push("");
|
|
9719
|
+
lines.push(title);
|
|
9720
|
+
for (const entry of group)
|
|
9721
|
+
lines.push(diagnosticLine(entry));
|
|
9722
|
+
}
|
|
8244
9723
|
return `${lines.join("\n")}
|
|
8245
9724
|
`;
|
|
8246
9725
|
}
|
|
@@ -8282,8 +9761,9 @@ function emit(result, options, renderContext, json) {
|
|
|
8282
9761
|
const rendering = renderHuman(result, renderContext);
|
|
8283
9762
|
if (rendering.report !== "")
|
|
8284
9763
|
options.streams.out(rendering.report);
|
|
8285
|
-
|
|
8286
|
-
|
|
9764
|
+
const diagnostics = formatDiagnostics(rendering.stderrDiagnostics);
|
|
9765
|
+
if (diagnostics !== "")
|
|
9766
|
+
options.streams.err(diagnostics);
|
|
8287
9767
|
return result.exitCode;
|
|
8288
9768
|
}
|
|
8289
9769
|
function emitHelpOrVersion(invocation, options) {
|
|
@@ -8378,7 +9858,8 @@ async function run(options) {
|
|
|
8378
9858
|
until: invocation.options.until,
|
|
8379
9859
|
planId: invocation.options.plan,
|
|
8380
9860
|
confirmed: invocation.options.yes,
|
|
8381
|
-
metrics: options.metrics ?? null
|
|
9861
|
+
metrics: options.metrics ?? null,
|
|
9862
|
+
compatibilityRows: options.compatibilityRows ?? null
|
|
8382
9863
|
};
|
|
8383
9864
|
const table = options.commands ?? DEFAULT_COMMANDS;
|
|
8384
9865
|
let result;
|