token-harness 0.1.1 → 0.1.3

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/token-harness.mjs CHANGED
@@ -97,18 +97,138 @@ function compareVersions(a, b) {
97
97
  return comparePrerelease(a.prerelease, b.prerelease);
98
98
  }
99
99
  function classifyVersion(observed, range) {
100
- const version2 = parseSemanticVersion(observed);
100
+ const version3 = parseSemanticVersion(observed);
101
101
  const minimum = parseSemanticVersion(range.minimum);
102
- if (version2 === null || minimum === null)
102
+ if (version3 === null || minimum === null)
103
103
  return "unparseable";
104
- if (compareVersions(version2, minimum) < 0)
104
+ if (compareVersions(version3, minimum) < 0)
105
105
  return "below-range";
106
106
  if (range.maximum === null)
107
107
  return "in-range";
108
108
  const maximum = parseSemanticVersion(range.maximum);
109
109
  if (maximum === null)
110
110
  return "unparseable";
111
- return compareVersions(version2, maximum) > 0 ? "unknown-newer" : "in-range";
111
+ return compareVersions(version3, maximum) > 0 ? "unknown-newer" : "in-range";
112
+ }
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
+ harness: "codex",
140
+ harnessVersion: { minimum: "0.146.0", maximum: "0.146.0" },
141
+ provider: "harnesstrim",
142
+ providerVersion: "0.1.0",
143
+ platform: { os: "windows", wsl: false, supported: true, limitation: null },
144
+ // The same shape as the Claude skills row, and for the same reason: the install writes skill
145
+ // files rather than a document this build parses, so the schema it depends on is the directory
146
+ // layout. A separate id because the directory is `.codex/skills` and the protected paths differ.
147
+ configSchema: "codex-skills-directory",
148
+ fixture: "tests/fixtures/rows/harnesstrim-codex-windows",
149
+ verificationTier: "config-only"
150
+ }
151
+ ];
152
+ function classifyRowHarnessVersion(rows, observed) {
153
+ if (rows.length === 0)
154
+ return null;
155
+ const parsed = parseSemanticVersion(observed);
156
+ if (parsed === null)
157
+ return null;
158
+ let lowestMinimum = null;
159
+ let highestMaximum = null;
160
+ for (const row2 of rows) {
161
+ const minimum = parseSemanticVersion(row2.harnessVersion.minimum);
162
+ const maximum = parseSemanticVersion(row2.harnessVersion.maximum);
163
+ if (minimum === null || maximum === null)
164
+ continue;
165
+ if (compareVersions(parsed, minimum) >= 0 && compareVersions(parsed, maximum) <= 0)
166
+ return "in-row";
167
+ if (lowestMinimum === null || compareVersions(minimum, lowestMinimum) < 0)
168
+ lowestMinimum = minimum;
169
+ if (highestMaximum === null || compareVersions(maximum, highestMaximum) > 0)
170
+ highestMaximum = maximum;
171
+ }
172
+ if (lowestMinimum === null || highestMaximum === null)
173
+ return null;
174
+ if (compareVersions(parsed, lowestMinimum) < 0)
175
+ return "unknown-older";
176
+ if (compareVersions(parsed, highestMaximum) > 0)
177
+ return "unknown-newer";
178
+ return "below-range";
179
+ }
180
+ function rowsFor(rows, combination) {
181
+ 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);
182
+ }
183
+ function admitManagedMutation(rows, combination) {
184
+ const keyed = rowsFor(rows, combination);
185
+ if (combination.providerVersion === null)
186
+ return {
187
+ state: "refused",
188
+ verdict: "no-row",
189
+ missing: "the exact provider version a row would record could not be established"
190
+ };
191
+ if (combination.harnessVersion === null)
192
+ return {
193
+ state: "refused",
194
+ verdict: "no-row",
195
+ missing: "the exact harness version a row would cover could not be established"
196
+ };
197
+ if (keyed.length === 0)
198
+ return {
199
+ state: "refused",
200
+ verdict: "no-row",
201
+ missing: `a reviewed config schema and fixture for ${combination.provider} on ${combination.harness} at ${combination.providerVersion} on ${combination.os}`
202
+ };
203
+ const versioned = keyed.filter((row3) => row3.providerVersion === combination.providerVersion);
204
+ if (versioned.length === 0) {
205
+ const fixture = keyed[0]?.fixture ?? "a provider fixture";
206
+ return {
207
+ state: "refused",
208
+ verdict: "no-row",
209
+ missing: `a provider fixture covering ${combination.provider} at ${combination.providerVersion} (the nearest row's fixture is ${JSON.stringify(fixture)})`
210
+ };
211
+ }
212
+ const parsedHarness = parseSemanticVersion(combination.harnessVersion);
213
+ const row2 = versioned.find((candidate) => {
214
+ const minimum = parseSemanticVersion(candidate.harnessVersion.minimum);
215
+ const maximum = parseSemanticVersion(candidate.harnessVersion.maximum);
216
+ return minimum !== null && maximum !== null && parsedHarness !== null && compareVersions(parsedHarness, minimum) >= 0 && compareVersions(parsedHarness, maximum) <= 0;
217
+ });
218
+ if (row2 !== void 0)
219
+ return { state: "admitted", row: row2 };
220
+ const verdict = parsedHarness === null ? null : classifyRowHarnessVersion(versioned, combination.harnessVersion);
221
+ if (verdict === null || verdict === "in-row")
222
+ return {
223
+ state: "refused",
224
+ verdict: "no-row",
225
+ missing: `a reviewed config schema and fixture for ${combination.provider} on ${combination.harness}`
226
+ };
227
+ return {
228
+ state: "refused",
229
+ verdict,
230
+ 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))}`
231
+ };
112
232
  }
113
233
 
114
234
  // packages/core/dist/src/domain/compatibility.js
@@ -207,6 +327,7 @@ function harnessId(value) {
207
327
  }
208
328
  return value;
209
329
  }
330
+ var UNATTRIBUTED_PROJECT_ID = "p_unattributed";
210
331
 
211
332
  // packages/core/dist/src/domain/manifest.js
212
333
  var MANIFEST_SCHEMA_VERSION = 1;
@@ -587,13 +708,14 @@ function resolveOwnership(input) {
587
708
  });
588
709
  continue;
589
710
  }
590
- if (!provider.assignable) {
711
+ if (!provider.assignableHarnesses.has(harness.id)) {
712
+ const observed = input.observedVersions[provider.id] ?? null;
591
713
  exclusions.push({
592
714
  scope,
593
715
  excluded: provider.id,
594
716
  retained: null,
595
717
  reason: [
596
- `${provider.id} implements ${capability} but no installer state produces it in isolation`,
718
+ observed === null ? `${provider.id} implements ${capability} on ${harness.id} but no installer state this build could observe produces it there` : `${provider.id} ${observed} implements ${capability} on ${harness.id} but no installer state at that version produces it there`,
597
719
  "RFC 0003: a capability the provider has but cannot be asked for is not an assignable capability",
598
720
  "It is still detected, adopted, reconciled against the owner, and measured"
599
721
  ]
@@ -634,6 +756,7 @@ function resolveOwnership(input) {
634
756
  }
635
757
  }
636
758
  }
759
+ narrowChannelOverlaps({ ownership, exclusions, conflicts, input });
637
760
  const ownerByScope = new Map(ownership.map((entry) => [formatCapabilityScope(entry.scope), entry.owner]));
638
761
  for (const exclusion of exclusions) {
639
762
  if (exclusion.retained !== null)
@@ -644,6 +767,66 @@ function resolveOwnership(input) {
644
767
  }
645
768
  return { ownership, exclusions, conflicts, pipelineId: derivePipelineId(ownership) };
646
769
  }
770
+ function narrowChannelOverlaps(context) {
771
+ const { ownership, exclusions, conflicts, input } = context;
772
+ const channels = /* @__PURE__ */ new Map();
773
+ for (const entry of ownership) {
774
+ if (entry.mode !== "exclusive")
775
+ continue;
776
+ const key = `${entry.scope.harness}/${entry.scope.toolFamily}/${entry.scope.capability}`;
777
+ const group = channels.get(key) ?? [];
778
+ group.push(entry);
779
+ channels.set(key, group);
780
+ }
781
+ const dropped = /* @__PURE__ */ new Set();
782
+ for (const group of channels.values()) {
783
+ const owners = [...new Set(group.map((entry) => entry.owner))];
784
+ if (owners.length < 2)
785
+ continue;
786
+ const first = group[0];
787
+ if (first === void 0)
788
+ continue;
789
+ const { harness, toolFamily, capability } = first.scope;
790
+ const rule = findCompatibilityRule(input.rules, { providers: owners, harness, capability });
791
+ if (rule === null || rule.outcome !== "narrowed" || rule.retains === void 0) {
792
+ conflicts.push({
793
+ code: "exclusive-scope-contested",
794
+ scope: `${harness}/${toolFamily}/${capability}`,
795
+ claimants: [...owners].sort(),
796
+ detail: [
797
+ `${owners.join(" and ")} each own ${capability} on ${harness}/${toolFamily}, at different interception points`,
798
+ "The interception points differ but the payload does not, so the reduction would run twice",
799
+ 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`
800
+ ],
801
+ remediation: `Choose one owner for ${harness}/${toolFamily}/${capability} with \`profile: custom\``
802
+ });
803
+ for (const entry of group)
804
+ dropped.add(entry);
805
+ continue;
806
+ }
807
+ const retains = rule.retains;
808
+ for (const entry of group) {
809
+ if (entry.owner === retains)
810
+ continue;
811
+ dropped.add(entry);
812
+ exclusions.push({
813
+ scope: entry.scope,
814
+ excluded: entry.owner,
815
+ retained: retains,
816
+ reason: [
817
+ `rule ${rule.id} keeps ${capability} on ${harness}/${toolFamily} with ${retains}, at every interception point`,
818
+ `${entry.owner} is narrowed to the install its own flags produce rather than excluded whole`,
819
+ rule.rationale
820
+ ]
821
+ });
822
+ }
823
+ }
824
+ if (dropped.size > 0) {
825
+ const kept = ownership.filter((entry) => !dropped.has(entry));
826
+ ownership.length = 0;
827
+ ownership.push(...kept);
828
+ }
829
+ }
647
830
  function resolveContested(context) {
648
831
  const { scope, claims, capability, input, ownership, exclusions, conflicts } = context;
649
832
  const claimants = claims.map((claim) => claim.provider);
@@ -719,6 +902,41 @@ function resolveContested(context) {
719
902
  }
720
903
  return;
721
904
  }
905
+ if (rule.outcome === "narrowed") {
906
+ const retains = rule.retains;
907
+ if (retains === void 0) {
908
+ conflicts.push({
909
+ code: "compatibility-rule-malformed",
910
+ scope: formatCapabilityScope(scope),
911
+ claimants,
912
+ detail: [
913
+ `Rule ${rule.id} narrows a pair without naming which provider keeps the channel`,
914
+ "Choosing one here would make the resolver decide what the rule exists to record"
915
+ ],
916
+ remediation: `Fix rule ${rule.id} so it names the provider that retains the capability`
917
+ });
918
+ return;
919
+ }
920
+ for (const claimant of claimants) {
921
+ if (claimant === retains)
922
+ continue;
923
+ exclusions.push({
924
+ scope,
925
+ excluded: claimant,
926
+ retained: retains,
927
+ reason: [
928
+ `rule ${rule.id} keeps ${capability} on ${scope.harness} with ${retains}`,
929
+ `${claimant} is narrowed to the install its own flags produce rather than excluded whole`,
930
+ rule.rationale
931
+ ]
932
+ });
933
+ }
934
+ const retainedClaim = claims.find((claim) => claim.provider === retains);
935
+ if (retainedClaim !== void 0) {
936
+ ownership.push({ scope, owner: retains, mode: retainedClaim.mode, order: 0 });
937
+ }
938
+ return;
939
+ }
722
940
  const winner = rule.providers.find((provider) => claimants.includes(provider)) ?? claimants[0];
723
941
  for (const claimant of claimants) {
724
942
  if (claimant === winner)
@@ -749,9 +967,10 @@ var RTK_HARNESSTRIM_SHELL_OVERLAP = {
749
967
  // for different reasons. Listing them individually would imply a fourth harness is exempt.
750
968
  harnesses: "*",
751
969
  capabilities: ["shell.output.reduce"],
752
- outcome: "conflict",
753
- testedVersions: { rtk: "0.42.0", harnesstrim: "0.0.5" },
754
- rationale: "HarnessTrim 0.0.5 reduces the same command output RTK already reduced, and no configuration narrows it: its Claude and Codex adapters match Bash only, and its OpenCode plugin reduces every tool result. Reducing an already reduced payload loses signal and counts the saving twice.",
970
+ outcome: "narrowed",
971
+ retains: RTK,
972
+ testedVersions: { rtk: "0.44.0", harnesstrim: "0.1.0" },
973
+ 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.",
755
974
  fixtures: ["docs/rfcs/0003-capabilities-and-conflicts.md#the-table-is-an-intent"]
756
975
  };
757
976
  var COMPATIBILITY_RULES = [RTK_HARNESSTRIM_SHELL_OVERLAP];
@@ -820,21 +1039,21 @@ function validateStoredPlan(input) {
820
1039
  detail: `the plan was computed for ${stored.projectRoot} and this invocation is in ${input.projectRoot}`
821
1040
  });
822
1041
  }
823
- for (const [id, version2] of Object.entries(stored.versions.providers)) {
1042
+ for (const [id, version3] of Object.entries(stored.versions.providers)) {
824
1043
  const current = input.versions.providers[id];
825
- if (current !== version2) {
1044
+ if (current !== version3) {
826
1045
  rejections.push({
827
1046
  reason: "plan-version-drift",
828
- detail: `${id} was ${version2 ?? "absent"} when the plan was made and is now ${current ?? "absent"}`
1047
+ detail: `${id} was ${version3 ?? "absent"} when the plan was made and is now ${current ?? "absent"}`
829
1048
  });
830
1049
  }
831
1050
  }
832
- for (const [id, version2] of Object.entries(stored.versions.harnesses)) {
1051
+ for (const [id, version3] of Object.entries(stored.versions.harnesses)) {
833
1052
  const current = input.versions.harnesses[id];
834
- if (current !== version2) {
1053
+ if (current !== version3) {
835
1054
  rejections.push({
836
1055
  reason: "plan-version-drift",
837
- detail: `${id} was ${version2 ?? "absent"} when the plan was made and is now ${current ?? "absent"}`
1056
+ detail: `${id} was ${version3 ?? "absent"} when the plan was made and is now ${current ?? "absent"}`
838
1057
  });
839
1058
  }
840
1059
  }
@@ -1121,7 +1340,7 @@ function resolveMetricsWindow(input) {
1121
1340
  var INSTALL_COMMANDS = {
1122
1341
  winget: {
1123
1342
  executable: "winget",
1124
- args: (packageName, version2) => [
1343
+ args: (packageName, version3) => [
1125
1344
  "install",
1126
1345
  "--id",
1127
1346
  packageName,
@@ -1131,16 +1350,16 @@ var INSTALL_COMMANDS = {
1131
1350
  "--silent",
1132
1351
  "--accept-package-agreements",
1133
1352
  "--accept-source-agreements",
1134
- ...version2 === null ? [] : ["--version", version2]
1353
+ ...version3 === null ? [] : ["--version", version3]
1135
1354
  ],
1136
1355
  verified: true
1137
1356
  },
1138
1357
  cargo: {
1139
1358
  executable: "cargo",
1140
- args: (packageName, version2) => [
1359
+ args: (packageName, version3) => [
1141
1360
  "install",
1142
1361
  packageName,
1143
- ...version2 === null ? [] : ["--version", version2]
1362
+ ...version3 === null ? [] : ["--version", version3]
1144
1363
  ],
1145
1364
  verified: false
1146
1365
  }
@@ -1258,8 +1477,8 @@ async function queryAvailableVersion(input) {
1258
1477
  }));
1259
1478
  return { status: "failed", version: null, destination, diagnostics };
1260
1479
  }
1261
- const version2 = recipe.parse(outcome2.stdout);
1262
- if (version2 === null) {
1480
+ const version3 = recipe.parse(outcome2.stdout);
1481
+ if (version3 === null) {
1263
1482
  diagnostics.push(diagnostic({
1264
1483
  severity: "warning",
1265
1484
  code: "version-query-unreadable",
@@ -1268,7 +1487,202 @@ async function queryAvailableVersion(input) {
1268
1487
  }));
1269
1488
  return { status: "unknown", version: null, destination, diagnostics };
1270
1489
  }
1271
- return { status: "found", version: version2, destination, diagnostics };
1490
+ return { status: "found", version: version3, destination, diagnostics };
1491
+ }
1492
+ var INVENTORY_COMMANDS = {
1493
+ /**
1494
+ * `winget list --id <id> --exact` prints the same dashes-anchored table as `show --versions`,
1495
+ * with the installed version as the last token of the row. The header is localized, which is
1496
+ * why the parse is anchored on the separator and the row tail rather than on a column title.
1497
+ *
1498
+ * A missing package makes winget exit non-zero with a localized "no package found" message,
1499
+ * which this build cannot tell from any other failure — so that case is `failed` at the caller,
1500
+ * never `absent`.
1501
+ */
1502
+ winget: {
1503
+ executable: "winget",
1504
+ args: (packageName) => ["list", "--id", packageName, "--exact"],
1505
+ parse: (stdout, _packageName) => {
1506
+ const lines = stdout.split(/\r?\n/);
1507
+ const separator = lines.findIndex((line) => /^-{3,}\s*$/.test(line.trim()));
1508
+ if (separator < 0)
1509
+ return { status: "unknown", version: null };
1510
+ for (const line of lines.slice(separator + 1)) {
1511
+ const row2 = line.trim();
1512
+ if (row2 === "")
1513
+ continue;
1514
+ const candidate = row2.split(/\s+/).at(-1) ?? "";
1515
+ if (parseSemanticVersion(candidate) === null)
1516
+ return { status: "unknown", version: null };
1517
+ return { status: "captured", version: candidate };
1518
+ }
1519
+ return { status: "unknown", version: null };
1520
+ },
1521
+ verified: false
1522
+ },
1523
+ /**
1524
+ * `cargo install --list` prints one `crate v0.1.0:` line per installed crate, so absence is a
1525
+ * positive answer: the crate simply does not appear. This is the one inventory among the six
1526
+ * that can confirm absence.
1527
+ */
1528
+ cargo: {
1529
+ executable: "cargo",
1530
+ args: () => ["install", "--list"],
1531
+ parse: (stdout, packageName) => {
1532
+ const pattern = new RegExp(`^\\s*${packageName}\\s+v(\\S+)\\s*:`, "m");
1533
+ const match = pattern.exec(stdout);
1534
+ if (match === null)
1535
+ return { status: "absent", version: null };
1536
+ const candidate = match[1] ?? "";
1537
+ if (parseSemanticVersion(candidate) === null)
1538
+ return { status: "unknown", version: null };
1539
+ return { status: "captured", version: candidate };
1540
+ },
1541
+ verified: false
1542
+ },
1543
+ npm: {
1544
+ executable: "npm",
1545
+ args: (packageName) => ["ls", "-g", packageName, "--depth=0"],
1546
+ parse: (stdout, packageName) => {
1547
+ const pattern = new RegExp(`(?:^|[^@\\w.-])${packageName}@(\\d[^\\s]*)`);
1548
+ const match = pattern.exec(stdout);
1549
+ const candidate = match?.[1] ?? null;
1550
+ if (candidate === null || parseSemanticVersion(candidate) === null) {
1551
+ return { status: "unknown", version: null };
1552
+ }
1553
+ return { status: "captured", version: candidate };
1554
+ },
1555
+ verified: false
1556
+ },
1557
+ homebrew: {
1558
+ executable: "brew",
1559
+ args: (packageName) => ["list", "--versions", packageName],
1560
+ parse: (stdout, packageName) => {
1561
+ const pattern = new RegExp(`^${packageName}\\s+(.+)$`);
1562
+ const match = pattern.exec(stdout.trim());
1563
+ const candidate = match?.[1]?.trim().split(/\s+/).at(-1) ?? null;
1564
+ if (candidate === null || parseSemanticVersion(candidate) === null) {
1565
+ return { status: "unknown", version: null };
1566
+ }
1567
+ return { status: "captured", version: candidate };
1568
+ },
1569
+ verified: false
1570
+ },
1571
+ uv: {
1572
+ executable: "uv",
1573
+ args: () => ["tool", "list"],
1574
+ parse: (stdout, packageName) => {
1575
+ const pattern = new RegExp(`^${packageName}\\s+v?(\\S+)`, "m");
1576
+ const match = pattern.exec(stdout);
1577
+ if (match === null)
1578
+ return { status: "absent", version: null };
1579
+ const candidate = match[1] ?? "";
1580
+ if (parseSemanticVersion(candidate) === null)
1581
+ return { status: "unknown", version: null };
1582
+ return { status: "captured", version: candidate };
1583
+ },
1584
+ verified: false
1585
+ },
1586
+ pipx: {
1587
+ executable: "pipx",
1588
+ args: () => ["list"],
1589
+ parse: (stdout, packageName) => {
1590
+ const pattern = new RegExp(`^\\s*${packageName}\\s+(\\S+)`, "m");
1591
+ const match = pattern.exec(stdout);
1592
+ if (match === null)
1593
+ return { status: "absent", version: null };
1594
+ const candidate = match[1] ?? "";
1595
+ if (parseSemanticVersion(candidate) === null)
1596
+ return { status: "unknown", version: null };
1597
+ return { status: "captured", version: candidate };
1598
+ },
1599
+ verified: false
1600
+ }
1601
+ };
1602
+ function channelCanReportInventory(channelId) {
1603
+ return Object.hasOwn(INVENTORY_COMMANDS, channelId);
1604
+ }
1605
+ var DEFAULT_INVENTORY_TIMEOUT_MS = 6e4;
1606
+ async function queryPackageInventory(input) {
1607
+ const recipe = INVENTORY_COMMANDS[input.channel];
1608
+ if (recipe === void 0) {
1609
+ return {
1610
+ channel: input.channel,
1611
+ packageName: input.packageName,
1612
+ status: "unsupported",
1613
+ version: null,
1614
+ diagnostics: [
1615
+ diagnostic({
1616
+ severity: "warning",
1617
+ code: "inventory-query-unsupported",
1618
+ message: `This build cannot ask ${input.channel} what it has installed`,
1619
+ remediation: null
1620
+ })
1621
+ ]
1622
+ };
1623
+ }
1624
+ if (input.runner === null) {
1625
+ return {
1626
+ channel: input.channel,
1627
+ packageName: input.packageName,
1628
+ status: "failed",
1629
+ version: null,
1630
+ diagnostics: [
1631
+ diagnostic({
1632
+ severity: "error",
1633
+ code: "no-process-runner",
1634
+ message: "No process runner is available, so no inventory can be captured",
1635
+ remediation: null
1636
+ })
1637
+ ]
1638
+ };
1639
+ }
1640
+ const diagnostics = [];
1641
+ if (!recipe.verified) {
1642
+ diagnostics.push(diagnostic({
1643
+ severity: "warning",
1644
+ code: "inventory-query-unverified",
1645
+ message: `The ${input.channel} inventory query follows that tool's documented output but has not been observed working`,
1646
+ remediation: "Confirm the rollback receipt after any failure"
1647
+ }));
1648
+ }
1649
+ const outcome2 = await input.runner.run({
1650
+ executable: recipe.executable,
1651
+ args: recipe.args(input.packageName),
1652
+ cwd: input.cwd,
1653
+ timeoutMs: input.timeoutMs ?? DEFAULT_INVENTORY_TIMEOUT_MS
1654
+ });
1655
+ if (outcome2.failure !== null || outcome2.exitCode !== 0) {
1656
+ diagnostics.push(diagnostic({
1657
+ severity: "warning",
1658
+ code: "inventory-query-failed",
1659
+ message: outcome2.failure === null ? `${outcome2.displayCommand} exited with ${String(outcome2.exitCode)}` : `${recipe.executable} could not be run: ${outcome2.failure.reason}`,
1660
+ remediation: null
1661
+ }));
1662
+ return {
1663
+ channel: input.channel,
1664
+ packageName: input.packageName,
1665
+ status: "failed",
1666
+ version: null,
1667
+ diagnostics
1668
+ };
1669
+ }
1670
+ const reading = recipe.parse(outcome2.stdout, input.packageName);
1671
+ if (reading.status === "unknown") {
1672
+ diagnostics.push(diagnostic({
1673
+ severity: "warning",
1674
+ code: "inventory-query-unreadable",
1675
+ message: `${recipe.executable} answered, but its output did not name an installed version in the form this build reads`,
1676
+ remediation: null
1677
+ }));
1678
+ }
1679
+ return {
1680
+ channel: input.channel,
1681
+ packageName: input.packageName,
1682
+ status: reading.status,
1683
+ version: reading.version,
1684
+ diagnostics
1685
+ };
1272
1686
  }
1273
1687
  var DEFAULT_INSTALL_TIMEOUT_MS = 6e5;
1274
1688
  async function runPackageManagerInstall(input) {
@@ -1349,14 +1763,119 @@ async function runPackageManagerInstall(input) {
1349
1763
  }));
1350
1764
  return { status: "failed", diagnostics };
1351
1765
  }
1352
- diagnostics.push(diagnostic({
1353
- severity: "info",
1354
- code: "install-not-reversible",
1355
- message: `${action.packageName} was installed through ${action.packageManager}; a rollback restores files and will not uninstall it`,
1356
- remediation: null
1357
- }));
1358
1766
  return { status: "installed", diagnostics };
1359
1767
  }
1768
+ async function restorePackageInventory(input) {
1769
+ const { capture } = input;
1770
+ if (capture.status === "absent") {
1771
+ return {
1772
+ restored: false,
1773
+ diagnostics: [
1774
+ diagnostic({
1775
+ severity: "info",
1776
+ code: "package-inventory-unrestored",
1777
+ message: `${capture.packageName} was not installed before the transaction; restoring that absence would require an uninstall command, so it stays installed`,
1778
+ remediation: null
1779
+ })
1780
+ ]
1781
+ };
1782
+ }
1783
+ if (capture.status !== "captured" || capture.version === null) {
1784
+ return {
1785
+ restored: false,
1786
+ diagnostics: [
1787
+ diagnostic({
1788
+ severity: "info",
1789
+ code: "package-inventory-unrestored",
1790
+ message: `${capture.packageName} was installed without a captured inventory, so it could not be restored`,
1791
+ remediation: null
1792
+ })
1793
+ ]
1794
+ };
1795
+ }
1796
+ if (input.runner === null) {
1797
+ return {
1798
+ restored: false,
1799
+ diagnostics: [
1800
+ diagnostic({
1801
+ severity: "info",
1802
+ code: "package-inventory-unrestored",
1803
+ message: `${capture.packageName} could not be restored: no process runner is available`,
1804
+ remediation: null
1805
+ })
1806
+ ]
1807
+ };
1808
+ }
1809
+ const restoreAction = {
1810
+ kind: "package-manager-install",
1811
+ id: `restore-${capture.packageName}`,
1812
+ riskClass: "delegated",
1813
+ requiresNetwork: true,
1814
+ requiresElevation: false,
1815
+ affectedPaths: [],
1816
+ affectedProcesses: [capture.channel],
1817
+ preconditions: [],
1818
+ postconditions: [],
1819
+ rollbackData: "none",
1820
+ explanation: `Restore ${capture.packageName} to ${capture.version} after a rollback`,
1821
+ packageManager: capture.channel,
1822
+ packageName: capture.packageName,
1823
+ version: capture.version
1824
+ };
1825
+ const result = await runPackageManagerInstall({
1826
+ action: restoreAction,
1827
+ runner: input.runner,
1828
+ cwd: input.cwd,
1829
+ ...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs }
1830
+ });
1831
+ if (result.status !== "installed") {
1832
+ return {
1833
+ restored: false,
1834
+ diagnostics: [
1835
+ ...result.diagnostics,
1836
+ diagnostic({
1837
+ severity: "error",
1838
+ code: "package-restore-failed",
1839
+ message: `${capture.packageName} could not be restored to ${capture.version} through ${capture.channel}`,
1840
+ path: null,
1841
+ remediation: `Install ${capture.packageName} at ${capture.version} yourself, then run the command again`
1842
+ })
1843
+ ]
1844
+ };
1845
+ }
1846
+ const verified = await queryPackageInventory({
1847
+ channel: capture.channel,
1848
+ packageName: capture.packageName,
1849
+ runner: input.runner,
1850
+ cwd: input.cwd,
1851
+ ...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs }
1852
+ });
1853
+ if (verified.status !== "captured" || verified.version !== capture.version) {
1854
+ return {
1855
+ restored: false,
1856
+ diagnostics: [
1857
+ ...verified.diagnostics,
1858
+ diagnostic({
1859
+ severity: "error",
1860
+ code: "package-restore-failed",
1861
+ message: `${capture.packageName} was reinstalled but the inventory does not report ${capture.version}`,
1862
+ remediation: `Confirm what ${capture.channel} has installed and correct it by hand`
1863
+ })
1864
+ ]
1865
+ };
1866
+ }
1867
+ return {
1868
+ restored: true,
1869
+ diagnostics: [
1870
+ diagnostic({
1871
+ severity: "info",
1872
+ code: "package-inventory-restored",
1873
+ message: `${capture.packageName} was restored to ${capture.version} through ${capture.channel} and the inventory was re-read`,
1874
+ remediation: null
1875
+ })
1876
+ ]
1877
+ };
1878
+ }
1360
1879
 
1361
1880
  // packages/core/dist/src/state/json-merge.js
1362
1881
  function isJsonObject(value) {
@@ -1776,7 +2295,8 @@ function outcome(action, status, parts = {}) {
1776
2295
  status,
1777
2296
  snapshots: parts.snapshots ?? [],
1778
2297
  ownership: parts.ownership ?? [],
1779
- diagnostics: parts.diagnostics ?? []
2298
+ diagnostics: parts.diagnostics ?? [],
2299
+ packageInventory: parts.packageInventory ?? null
1780
2300
  };
1781
2301
  }
1782
2302
  function drift(action, path, message) {
@@ -2055,6 +2575,8 @@ async function scanTree(fs, path, entries = []) {
2055
2575
  return entries;
2056
2576
  }
2057
2577
  function equalTreeEntry(left, right) {
2578
+ if (left === void 0 && right === void 0)
2579
+ return true;
2058
2580
  return left !== void 0 && right !== void 0 && left.kind === right.kind && left.digest === right.digest;
2059
2581
  }
2060
2582
  async function applyDelegatedProviderInstall(action, context) {
@@ -2212,13 +2734,38 @@ async function applyAction(action, context) {
2212
2734
  }
2213
2735
  }
2214
2736
  async function applyPackageManagerInstall(action, context) {
2737
+ const wantsInventory = action.rollbackData === "package-inventory" && !action.requiresElevation;
2738
+ const packageInventory = wantsInventory && context.runner !== void 0 ? await queryPackageInventory({
2739
+ channel: action.packageManager,
2740
+ packageName: action.packageName,
2741
+ runner: context.runner,
2742
+ cwd: context.cwd ?? ""
2743
+ }) : null;
2215
2744
  const result = await runPackageManagerInstall({
2216
2745
  action,
2217
2746
  runner: context.runner ?? null,
2218
2747
  cwd: context.cwd ?? ""
2219
2748
  });
2220
2749
  const status = result.status === "installed" ? "applied" : result.status === "refused" ? "refused" : "failed";
2221
- return outcome(action, status, { diagnostics: result.diagnostics });
2750
+ const diagnostics = [...result.diagnostics];
2751
+ if (status === "applied") {
2752
+ if (packageInventory?.status === "captured" && packageInventory.version !== null) {
2753
+ diagnostics.push(diagnostic({
2754
+ severity: "info",
2755
+ code: "install-inventory-captured",
2756
+ message: `${action.packageName} was installed through ${action.packageManager}; the prior state (${packageInventory.version}) was captured, so a rollback can restore it`,
2757
+ remediation: null
2758
+ }));
2759
+ } else {
2760
+ diagnostics.push(diagnostic({
2761
+ severity: "info",
2762
+ code: "install-not-reversible",
2763
+ message: `${action.packageName} was installed through ${action.packageManager}; a rollback restores files and will not uninstall it`,
2764
+ remediation: null
2765
+ }));
2766
+ }
2767
+ }
2768
+ return outcome(action, status, { diagnostics, packageInventory });
2222
2769
  }
2223
2770
 
2224
2771
  // packages/core/dist/src/state/journal.js
@@ -2599,10 +3146,10 @@ function parsePins(raw, path, diagnostics) {
2599
3146
  if (typeof entry !== "object" || entry === null)
2600
3147
  continue;
2601
3148
  const provider = entry.provider;
2602
- const version2 = entry.version;
3149
+ const version3 = entry.version;
2603
3150
  if (typeof provider !== "string" || provider === "")
2604
3151
  continue;
2605
- if (typeof version2 !== "string" || parseSemanticVersion(version2) === null) {
3152
+ if (typeof version3 !== "string" || parseSemanticVersion(version3) === null) {
2606
3153
  diagnostics.push(diagnostic({
2607
3154
  severity: "warning",
2608
3155
  code: "pin-version-unparseable",
@@ -2611,7 +3158,7 @@ function parsePins(raw, path, diagnostics) {
2611
3158
  }));
2612
3159
  continue;
2613
3160
  }
2614
- pins.push({ provider, version: version2 });
3161
+ pins.push({ provider, version: version3 });
2615
3162
  }
2616
3163
  return pins;
2617
3164
  }
@@ -2814,6 +3361,24 @@ async function verifyRestoration(fs, snapshots) {
2814
3361
  }
2815
3362
  return unrestored;
2816
3363
  }
3364
+ async function restorePackageInventories(records, runner, cwd) {
3365
+ const diagnostics = [];
3366
+ for (const record of records) {
3367
+ if (record.kind !== "package-manager-install")
3368
+ continue;
3369
+ if (record.status !== "applied")
3370
+ continue;
3371
+ if (record.packageInventory === null || record.packageInventory === void 0)
3372
+ continue;
3373
+ const outcome2 = await restorePackageInventory({
3374
+ capture: record.packageInventory,
3375
+ runner,
3376
+ cwd
3377
+ });
3378
+ diagnostics.push(...outcome2.diagnostics);
3379
+ }
3380
+ return diagnostics;
3381
+ }
2817
3382
  async function executeTransaction(request) {
2818
3383
  const startedAt = request.now();
2819
3384
  const entries = [];
@@ -2857,7 +3422,10 @@ async function executeTransaction(request) {
2857
3422
  restoreError = error instanceof Error ? error.message : String(error);
2858
3423
  }
2859
3424
  const unrestored = await verifyRestoration(request.fs, snapshots);
2860
- if (restoreError === null && unrestored.length === 0) {
3425
+ const packageDiagnostics = await restorePackageInventories(applied, request.runner ?? null, request.projectRoot);
3426
+ diagnostics.push(...packageDiagnostics);
3427
+ const packageRestoreFailed = packageDiagnostics.some((entry) => entry.severity === "error");
3428
+ if (restoreError === null && unrestored.length === 0 && !packageRestoreFailed) {
2861
3429
  diagnostics.push(diagnostic({
2862
3430
  severity: "info",
2863
3431
  code: "transaction-rolled-back",
@@ -2885,6 +3453,7 @@ async function executeTransaction(request) {
2885
3453
  status: "failed",
2886
3454
  snapshots: [],
2887
3455
  ownership: [],
3456
+ packageInventory: null,
2888
3457
  diagnostics: [
2889
3458
  diagnostic({
2890
3459
  severity: "error",
@@ -2902,7 +3471,8 @@ async function executeTransaction(request) {
2902
3471
  status: result.status,
2903
3472
  snapshots: result.snapshots,
2904
3473
  ownership: result.ownership,
2905
- diagnostics: result.diagnostics
3474
+ diagnostics: result.diagnostics,
3475
+ packageInventory: result.packageInventory
2906
3476
  };
2907
3477
  entries.push(entry);
2908
3478
  applied.push(result);
@@ -2987,7 +3557,10 @@ async function rollbackTransaction(request) {
2987
3557
  restoreError = error instanceof Error ? error.message : String(error);
2988
3558
  }
2989
3559
  const unrestored = await verifyRestoration(request.fs, snapshots);
2990
- if (restoreError === null && unrestored.length === 0) {
3560
+ const packageDiagnostics = await restorePackageInventories(journal.entries, request.runner ?? null, request.cwd ?? journal.projectRoot);
3561
+ diagnostics.push(...packageDiagnostics);
3562
+ const packageRestoreFailed = packageDiagnostics.some((entry) => entry.severity === "error");
3563
+ if (restoreError === null && unrestored.length === 0 && !packageRestoreFailed) {
2991
3564
  journal.outcome = "rolled-back";
2992
3565
  journal.ownership = [];
2993
3566
  journal.finishedAt = request.now();
@@ -4493,8 +5066,8 @@ async function detectVersion2(context) {
4493
5066
  };
4494
5067
  }
4495
5068
  const matched = VERSION_PATTERN.exec(outcome2.stdout);
4496
- const version2 = matched?.[1] ?? null;
4497
- if (version2 === null) {
5069
+ const version3 = matched?.[1] ?? null;
5070
+ if (version3 === null) {
4498
5071
  return {
4499
5072
  version: null,
4500
5073
  verdict: null,
@@ -4508,14 +5081,14 @@ async function detectVersion2(context) {
4508
5081
  };
4509
5082
  }
4510
5083
  return {
4511
- version: version2,
4512
- verdict: classifyVersion(version2, MANIFEST.testedVersions),
5084
+ version: version3,
5085
+ verdict: classifyVersion(version3, MANIFEST.testedVersions),
4513
5086
  evidence: [
4514
5087
  evidence({
4515
5088
  kind: "version-output",
4516
5089
  source: command.executable,
4517
5090
  path: outcome2.executablePath,
4518
- detail: `reported ${version2}`
5091
+ detail: `reported ${version3}`
4519
5092
  })
4520
5093
  ]
4521
5094
  };
@@ -4523,7 +5096,7 @@ async function detectVersion2(context) {
4523
5096
  async function detect(context) {
4524
5097
  const evidenceItems = [];
4525
5098
  const warnings = [];
4526
- const { version: version2, verdict, evidence: versionEvidence } = await detectVersion2(context);
5099
+ const { version: version3, verdict, evidence: versionEvidence } = await detectVersion2(context);
4527
5100
  evidenceItems.push(...versionEvidence);
4528
5101
  const configs = await Promise.all(MANIFEST.configFiles.map((declaration) => resolveConfig(declaration, context)));
4529
5102
  const present = configs.filter((config) => config.exists);
@@ -4546,7 +5119,7 @@ async function detect(context) {
4546
5119
  remediation: "Repair the JSON syntax, then run the command again"
4547
5120
  }));
4548
5121
  }
4549
- if (version2 === null && present.length > 0) {
5122
+ if (version3 === null && present.length > 0) {
4550
5123
  warnings.push(diagnostic({
4551
5124
  severity: "warning",
4552
5125
  code: "harness-executable-missing",
@@ -4559,16 +5132,16 @@ async function detect(context) {
4559
5132
  warnings.push(diagnostic({
4560
5133
  severity: "warning",
4561
5134
  code: "harness-version-unknown-newer",
4562
- message: `Claude Code ${String(version2)} is newer than the versions whose configuration schema Token Harness has observed (up to ${String(MANIFEST.testedVersions.maximum)})`,
5135
+ message: `Claude Code ${String(version3)} is newer than the versions whose configuration schema Token Harness has observed (up to ${String(MANIFEST.testedVersions.maximum)})`,
4563
5136
  remediation: "Check the release notes for hook-schema changes before applying a plan"
4564
5137
  }));
4565
5138
  }
4566
- const state = unparsed.length > 0 ? "broken" : configured.length > 0 && version2 !== null ? "configured" : version2 !== null || present.length > 0 ? "detected" : "absent";
5139
+ const state = unparsed.length > 0 ? "broken" : configured.length > 0 && version3 !== null ? "configured" : version3 !== null || present.length > 0 ? "detected" : "absent";
4567
5140
  const primary = present.find((config) => config.declaration.primary) ?? present[0] ?? null;
4568
5141
  return {
4569
5142
  harnessId: CLAUDE,
4570
5143
  state,
4571
- version: version2,
5144
+ version: version3,
4572
5145
  versionVerdict: verdict,
4573
5146
  configPath: primary?.path ?? null,
4574
5147
  declaredVerificationTier: MANIFEST.verificationTier,
@@ -4822,22 +5395,22 @@ async function detectVersion3(context) {
4822
5395
  })
4823
5396
  ]
4824
5397
  };
4825
- const version2 = VERSION_PATTERN2.exec(outcome2.stdout)?.[1] ?? null;
5398
+ const version3 = VERSION_PATTERN2.exec(outcome2.stdout)?.[1] ?? null;
4826
5399
  return {
4827
- version: version2,
4828
- verdict: version2 === null ? null : classifyVersion(version2, MANIFEST2.testedVersions),
5400
+ version: version3,
5401
+ verdict: version3 === null ? null : classifyVersion(version3, MANIFEST2.testedVersions),
4829
5402
  evidence: [
4830
5403
  evidence({
4831
5404
  kind: "version-output",
4832
5405
  source: "codex",
4833
5406
  path: outcome2.executablePath,
4834
- detail: version2 === null ? "reported no recognisable semantic version" : `reported ${version2}`
5407
+ detail: version3 === null ? "reported no recognisable semantic version" : `reported ${version3}`
4835
5408
  })
4836
5409
  ]
4837
5410
  };
4838
5411
  }
4839
5412
  async function detect2(context) {
4840
- const { version: version2, verdict, evidence: versionEvidence } = await detectVersion3(context);
5413
+ const { version: version3, verdict, evidence: versionEvidence } = await detectVersion3(context);
4841
5414
  const configs = await Promise.all(MANIFEST2.configFiles.map((file) => resolveConfig2(file, context)));
4842
5415
  const present = configs.filter((config) => config.exists);
4843
5416
  const hooks = configs.find((config) => config.declaration.path.endsWith("hooks.json"));
@@ -4850,7 +5423,7 @@ async function detect2(context) {
4850
5423
  path: hooks.path,
4851
5424
  remediation: "Repair the JSON syntax, then run the command again"
4852
5425
  }));
4853
- if (version2 === null && present.length > 0)
5426
+ if (version3 === null && present.length > 0)
4854
5427
  warnings.push(diagnostic({
4855
5428
  severity: "warning",
4856
5429
  code: "harness-executable-missing",
@@ -4862,15 +5435,15 @@ async function detect2(context) {
4862
5435
  warnings.push(diagnostic({
4863
5436
  severity: "warning",
4864
5437
  code: "harness-version-unknown-newer",
4865
- message: `Codex ${String(version2)} is newer than the observed hook schema`,
5438
+ message: `Codex ${String(version3)} is newer than the observed hook schema`,
4866
5439
  remediation: "Check the release notes for hook-schema changes before applying a plan"
4867
5440
  }));
4868
5441
  const configured = (hooks?.configuredPoints.length ?? 0) > 0;
4869
5442
  const primary = configs.find((config) => config.declaration.primary && config.exists) ?? present[0] ?? null;
4870
5443
  return {
4871
5444
  harnessId: CODEX,
4872
- state: hooks?.exists && !hooks.parsed ? "broken" : configured && version2 !== null ? "configured" : version2 !== null || present.length > 0 ? "detected" : "absent",
4873
- version: version2,
5445
+ state: hooks?.exists && !hooks.parsed ? "broken" : configured && version3 !== null ? "configured" : version3 !== null || present.length > 0 ? "detected" : "absent",
5446
+ version: version3,
4874
5447
  versionVerdict: verdict,
4875
5448
  configPath: primary?.path ?? null,
4876
5449
  declaredVerificationTier: MANIFEST2.verificationTier,
@@ -4987,37 +5560,285 @@ async function verify2(context) {
4987
5560
  }
4988
5561
  var codexAdapter = { manifest: MANIFEST2, detect: detect2, inspect: inspect2, verify: verify2 };
4989
5562
 
4990
- // packages/adapters/dist/src/harnesses/opencode.js
4991
- var OPENCODE = harnessId("opencode");
5563
+ // packages/adapters/dist/src/harnesses/hermes.js
5564
+ var HERMES = harnessId("hermes");
4992
5565
  var VERSION_PATTERN3 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
5566
+ var PLUGIN = "harnesstrim";
4993
5567
  var MANIFEST3 = {
4994
5568
  schemaVersion: MANIFEST_SCHEMA_VERSION,
4995
- id: OPENCODE,
4996
- displayName: "OpenCode",
4997
- homepage: "https://opencode.ai",
4998
- /**
4999
- * The version actually observed, not a round number.
5000
- *
5001
- * This read `1.0.0`–`1.0.0`, and the OpenCode on the machine this was reviewed against is
5002
- * `1.18.9` — so every real user got `unknown-newer`, which `doctor` counts as a problem, and
5003
- * every real machine exited 3. RFC 0006 §Exit codes forbids exactly that: "A supported
5004
- * configuration must be able to exit 0. A declared limitation is not a problem, and reporting
5569
+ id: HERMES,
5570
+ displayName: "Hermes Agent",
5571
+ homepage: "https://hermes-agent.nousresearch.com/docs/",
5572
+ testedVersions: { minimum: "0.1.0", maximum: "0.1.0" },
5573
+ verificationTier: "config-only",
5574
+ versionCommand: { executable: "hermes", args: ["--version"] },
5575
+ interceptionPoints: [{ scopeId: "transform-tool-result", eventName: "transform_tool_result" }],
5576
+ configFiles: [
5577
+ {
5578
+ path: ".hermes/plugins/harnesstrim/plugin.yaml",
5579
+ scope: "user",
5580
+ parser: "markers",
5581
+ primary: true
5582
+ },
5583
+ {
5584
+ path: ".hermes/plugins/harnesstrim/config.json",
5585
+ scope: "user",
5586
+ parser: "json",
5587
+ primary: false
5588
+ }
5589
+ ],
5590
+ toolFamilies: [
5591
+ { id: "tool.result", platforms: ["windows", "macos", "linux"], executesShellCommands: false }
5592
+ ],
5593
+ requiresEnablement: true,
5594
+ enablementNote: "The plugin must be enabled with `hermes plugins enable harnesstrim`.",
5595
+ receiptFamily: "provider-telemetry"
5596
+ };
5597
+ async function resolveConfig3(declaration, context) {
5598
+ const path = resolveConfigPath(declaration, context);
5599
+ const stat3 = await context.fs.stat(path);
5600
+ if (stat3 === null || stat3.kind !== "file") {
5601
+ return {
5602
+ declaration,
5603
+ path,
5604
+ exists: false,
5605
+ parsed: false,
5606
+ configuredPoints: [],
5607
+ matchers: [],
5608
+ commands: []
5609
+ };
5610
+ }
5611
+ const text = new TextDecoder().decode(await context.fs.readFile(path));
5612
+ const parsed = declaration.parser === "json" ? (() => {
5613
+ try {
5614
+ return JSON.parse(text);
5615
+ } catch {
5616
+ return null;
5617
+ }
5618
+ })() : text;
5619
+ const valid = parsed !== null;
5620
+ return {
5621
+ declaration,
5622
+ path,
5623
+ exists: true,
5624
+ parsed: valid,
5625
+ configuredPoints: valid && declaration.path.endsWith("plugin.yaml") ? ["transform-tool-result"] : [],
5626
+ matchers: valid && declaration.path.endsWith("plugin.yaml") ? ["tool.result"] : [],
5627
+ commands: valid && declaration.path.endsWith("plugin.yaml") ? ["hermes plugins enable harnesstrim"] : []
5628
+ };
5629
+ }
5630
+ async function version2(context) {
5631
+ const result = await context.runner.run({
5632
+ executable: "hermes",
5633
+ args: ["--version"],
5634
+ cwd: context.projectRoot,
5635
+ timeoutMs: 2e4
5636
+ });
5637
+ if (result.failure !== null)
5638
+ return {
5639
+ value: null,
5640
+ verdict: null,
5641
+ evidence: [
5642
+ evidence({
5643
+ kind: "absence",
5644
+ source: "hermes",
5645
+ detail: `not runnable: ${result.failure.reason}`
5646
+ })
5647
+ ]
5648
+ };
5649
+ const value = VERSION_PATTERN3.exec(result.stdout)?.[1] ?? null;
5650
+ return {
5651
+ value,
5652
+ verdict: value === null ? null : classifyVersion(value, MANIFEST3.testedVersions),
5653
+ evidence: [
5654
+ evidence({
5655
+ kind: "version-output",
5656
+ source: "hermes",
5657
+ path: result.executablePath,
5658
+ detail: value ? `reported ${value}` : "reported no recognisable version"
5659
+ })
5660
+ ]
5661
+ };
5662
+ }
5663
+ async function enabled(context) {
5664
+ const result = await context.runner.run({
5665
+ executable: "hermes",
5666
+ args: ["plugins", "list"],
5667
+ cwd: context.projectRoot,
5668
+ timeoutMs: 2e4
5669
+ });
5670
+ if (result.failure !== null || result.exitCode !== 0)
5671
+ return null;
5672
+ const line = result.stdout.split("\n").find((entry) => entry.toLowerCase().includes(PLUGIN));
5673
+ return line === void 0 ? false : /enabled/i.test(line);
5674
+ }
5675
+ async function detect3(context) {
5676
+ const observed = await version2(context);
5677
+ const configs = await Promise.all(MANIFEST3.configFiles.map((file) => resolveConfig3(file, context)));
5678
+ const plugin = configs.find((file) => file.declaration.path.endsWith("plugin.yaml"));
5679
+ const isEnabled = await enabled(context);
5680
+ const warnings = [];
5681
+ if (observed.verdict === "unknown-newer")
5682
+ warnings.push(diagnostic({
5683
+ severity: "warning",
5684
+ code: "harness-version-unknown-newer",
5685
+ message: `Hermes ${observed.value} is newer than the observed plugin contract`,
5686
+ remediation: "Review the transform_tool_result plugin contract before applying changes"
5687
+ }));
5688
+ if (plugin?.exists && isEnabled === false)
5689
+ warnings.push(diagnostic({
5690
+ severity: "warning",
5691
+ code: "plugin-disabled",
5692
+ message: "HarnessTrim is installed in Hermes but is not enabled",
5693
+ path: plugin.path,
5694
+ remediation: "Run `hermes plugins enable harnesstrim`"
5695
+ }));
5696
+ const present = plugin?.exists === true;
5697
+ return {
5698
+ harnessId: HERMES,
5699
+ state: present && isEnabled === true && observed.value !== null ? "configured" : present || observed.value !== null ? "detected" : "absent",
5700
+ version: observed.value,
5701
+ versionVerdict: observed.verdict,
5702
+ configPath: plugin?.path ?? null,
5703
+ declaredVerificationTier: MANIFEST3.verificationTier,
5704
+ evidence: [
5705
+ ...observed.evidence,
5706
+ ...plugin ? [
5707
+ evidence({
5708
+ kind: "config-entry",
5709
+ source: "Hermes plugin directory",
5710
+ path: plugin.path,
5711
+ detail: plugin.exists ? "HarnessTrim plugin manifest present" : "plugin manifest absent"
5712
+ })
5713
+ ] : []
5714
+ ],
5715
+ warnings
5716
+ };
5717
+ }
5718
+ async function inspect3(context) {
5719
+ const configs = await Promise.all(MANIFEST3.configFiles.map((file) => resolveConfig3(file, context)));
5720
+ return {
5721
+ harnessId: HERMES,
5722
+ configs,
5723
+ activeToolFamilies: familiesOnThisPlatform(MANIFEST3, context.facts),
5724
+ uncoveredToolFamilies: [],
5725
+ enabled: await enabled(context),
5726
+ diagnostics: [],
5727
+ summaries: configs.filter((config) => config.configuredPoints.length > 0).map((config) => ({
5728
+ harnessId: HERMES,
5729
+ configPath: config.path,
5730
+ scope: config.declaration.scope,
5731
+ interceptionPoints: config.configuredPoints,
5732
+ matchers: config.matchers,
5733
+ commands: config.commands
5734
+ }))
5735
+ };
5736
+ }
5737
+ async function verify3(context) {
5738
+ const detection = await detect3(context);
5739
+ const inspection = await inspect3(context);
5740
+ const plugin = inspection.configs.some((config) => config.configuredPoints.length > 0);
5741
+ const checks = [
5742
+ {
5743
+ id: "executable-resolves",
5744
+ status: detection.version === null ? "fail" : "pass",
5745
+ summary: detection.version === null ? "hermes could not be run" : `Hermes ${detection.version}`,
5746
+ achievedTier: detection.version === null ? null : "presence",
5747
+ evidence: detection.evidence.filter((item) => item.kind === "version-output"),
5748
+ remediation: detection.version === null ? "Install Hermes or add it to PATH" : null
5749
+ },
5750
+ {
5751
+ id: "plugin-installed",
5752
+ status: plugin ? "pass" : "fail",
5753
+ summary: plugin ? "HarnessTrim plugin manifest is installed" : "HarnessTrim plugin manifest is missing",
5754
+ achievedTier: plugin ? "config-only" : null,
5755
+ evidence: [],
5756
+ remediation: plugin ? null : "Run `harnesstrim install hermes --apply`"
5757
+ },
5758
+ {
5759
+ id: "plugin-enabled",
5760
+ status: inspection.enabled === true ? "pass" : inspection.enabled === false ? "fail" : "not-exercised",
5761
+ summary: inspection.enabled === true ? "HarnessTrim is enabled" : inspection.enabled === false ? "HarnessTrim is installed but disabled" : "Hermes plugin state could not be read",
5762
+ achievedTier: inspection.enabled === true ? "config-only" : null,
5763
+ evidence: [],
5764
+ remediation: inspection.enabled === false ? "Run `hermes plugins enable harnesstrim`" : null
5765
+ },
5766
+ {
5767
+ id: "canary-intercepted",
5768
+ status: "not-exercised",
5769
+ summary: "Hermes exposes no independent receipt; HarnessTrim telemetry is verified by the provider adapter",
5770
+ achievedTier: null,
5771
+ evidence: [],
5772
+ remediation: null
5773
+ }
5774
+ ];
5775
+ return {
5776
+ harnessId: HERMES,
5777
+ declaredTier: MANIFEST3.verificationTier,
5778
+ achievedTier: checks.some((check) => check.achievedTier === "config-only") ? "config-only" : checks.some((check) => check.achievedTier === "presence") ? "presence" : null,
5779
+ checks
5780
+ };
5781
+ }
5782
+ var hermesAdapter = { manifest: MANIFEST3, detect: detect3, inspect: inspect3, verify: verify3 };
5783
+
5784
+ // packages/adapters/dist/src/harnesses/opencode.js
5785
+ var OPENCODE = harnessId("opencode");
5786
+ var VERSION_PATTERN4 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
5787
+ var MANIFEST4 = {
5788
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
5789
+ id: OPENCODE,
5790
+ displayName: "OpenCode",
5791
+ homepage: "https://opencode.ai",
5792
+ /**
5793
+ * The version actually observed, not a round number.
5794
+ *
5795
+ * This read `1.0.0`–`1.0.0`, and the OpenCode on the machine this was reviewed against is
5796
+ * `1.18.9` — so every real user got `unknown-newer`, which `doctor` counts as a problem, and
5797
+ * every real machine exited 3. RFC 0006 §Exit codes forbids exactly that: "A supported
5798
+ * configuration must be able to exit 0. A declared limitation is not a problem, and reporting
5005
5799
  * it as one is the fastest way to teach users to ignore the exit code."
5006
5800
  *
5007
- * A single point rather than a range, matching the Codex adapter: one version was observed, so
5008
- * one version is claimed. An older 1.x reports `unknown-older`, which is a warning and not a
5801
+ * A range now, because spike 9.1 observed two more: the CLI at `1.18.11` and OpenCode Desktop at
5802
+ * `1.18.14`, both loading plugins from the same directories. The span is what was watched, not a
5803
+ * guess that every 1.18.x behaves alike — and the two ends differ in a way that matters, which is
5804
+ * exactly why both are named. An older 1.x reports `unknown-older`, which is a warning and not a
5009
5805
  * problem, and a newer one reports `unknown-newer` — which is then a true statement.
5010
5806
  */
5011
- testedVersions: { minimum: "1.18.9", maximum: "1.18.9" },
5807
+ testedVersions: { minimum: "1.18.9", maximum: "1.18.14" },
5012
5808
  // RFC 0007: a managed provider can emit a receipt, but an adopted generated wrapper
5013
5809
  // has no externally observable receipt. The harness adapter alone therefore declares
5014
5810
  // the achievable baseline, not a promise about a provider it does not own.
5015
5811
  verificationTier: "config-only",
5016
5812
  versionCommand: { executable: "opencode", args: ["--version"] },
5017
- interceptionPoints: [{ scopeId: "tool-execute-after", eventName: "tool.execute.after" }],
5813
+ interceptionPoints: [
5814
+ { scopeId: "tool-execute-before", eventName: "tool.execute.before" },
5815
+ { scopeId: "tool-execute-after", eventName: "tool.execute.after" }
5816
+ ],
5817
+ /**
5818
+ * Two kinds of entry, because OpenCode has two ways to register a plugin and only one of them is
5819
+ * a document.
5820
+ *
5821
+ * The `opencode.jsonc` files carry a `plugin` string array. The directories are auto-loaded: any
5822
+ * module dropped in one is registered without being named anywhere, which is how both real
5823
+ * integrations install — `rtk init -g --opencode` writes `.config/opencode/plugins/rtk.ts` and
5824
+ * touches no JSON at all, and HarnessTrim's generated wrapper says in its own header not to
5825
+ * reference it from `opencode.json`. An adapter that read only the array would report every such
5826
+ * installation as absent, which is the state this file was in before spike 9.1.
5827
+ *
5828
+ * Both spellings are listed because both load. Spike 9.1 put a probe in each and one run fired
5829
+ * both; the upstream documentation names the plural, the binary carries both strings, and the two
5830
+ * shipped integrations disagree with each other about which to use.
5831
+ *
5832
+ * `markers` is the parser because there is nothing to parse: the presence of a file is the
5833
+ * configuration, and its contents are a plugin module rather than a document this build reads.
5834
+ */
5018
5835
  configFiles: [
5019
5836
  { path: ".config/opencode/opencode.jsonc", scope: "user", parser: "jsonc", primary: true },
5020
- { path: "opencode.jsonc", scope: "project", parser: "jsonc", primary: false }
5837
+ { path: "opencode.jsonc", scope: "project", parser: "jsonc", primary: false },
5838
+ { path: ".config/opencode/plugins", scope: "user", parser: "markers", primary: false },
5839
+ { path: ".config/opencode/plugin", scope: "user", parser: "markers", primary: false },
5840
+ { path: ".opencode/plugins", scope: "project", parser: "markers", primary: false },
5841
+ { path: ".opencode/plugin", scope: "project", parser: "markers", primary: false }
5021
5842
  ],
5022
5843
  toolFamilies: [
5023
5844
  { id: "tool.execute", platforms: ["windows", "macos", "linux"], executesShellCommands: true }
@@ -5034,7 +5855,38 @@ function pluginEntries(document2) {
5034
5855
  return [];
5035
5856
  return document2["plugin"].filter((item) => typeof item === "string");
5036
5857
  }
5037
- async function resolveConfig3(declaration, context) {
5858
+ var PLUGIN_MODULE_PATTERN = /\.(ts|js|mjs|cjs|mts|cts)$/i;
5859
+ var PLUGIN_POINTS = ["tool-execute-before", "tool-execute-after"];
5860
+ async function resolvePluginDirectory(declaration, context) {
5861
+ const path = resolveConfigPath(declaration, context);
5862
+ const stat3 = await context.fs.stat(path);
5863
+ if (stat3 === null || stat3.kind !== "directory") {
5864
+ return {
5865
+ declaration,
5866
+ path,
5867
+ exists: false,
5868
+ parsed: false,
5869
+ configuredPoints: [],
5870
+ matchers: [],
5871
+ commands: []
5872
+ };
5873
+ }
5874
+ const modules = (await context.fs.readDirectory(path)).filter((entry) => PLUGIN_MODULE_PATTERN.test(entry));
5875
+ return {
5876
+ declaration,
5877
+ path,
5878
+ exists: true,
5879
+ // A directory listing cannot fail to parse. `markers` declares that there is nothing to read.
5880
+ parsed: true,
5881
+ configuredPoints: modules.length > 0 ? [...PLUGIN_POINTS] : [],
5882
+ // The plugin API sees every tool result; this is not a shell matcher.
5883
+ matchers: modules.length > 0 ? ["tool.execute"] : [],
5884
+ commands: modules.map((entry) => context.fs.join(path, entry))
5885
+ };
5886
+ }
5887
+ async function resolveConfig4(declaration, context) {
5888
+ if (declaration.parser === "markers")
5889
+ return resolvePluginDirectory(declaration, context);
5038
5890
  const path = resolveConfigPath(declaration, context);
5039
5891
  const stat3 = await context.fs.stat(path);
5040
5892
  if (stat3 === null || stat3.kind !== "file") {
@@ -5066,7 +5918,9 @@ async function resolveConfig3(declaration, context) {
5066
5918
  path,
5067
5919
  exists: true,
5068
5920
  parsed: true,
5069
- configuredPoints: plugins.length > 0 ? ["tool-execute-after"] : [],
5921
+ // A `plugin` array entry is the same kind of thing as a module in a plugin directory: a
5922
+ // registration whose hooks are only known once it runs. Same claim, same reason.
5923
+ configuredPoints: plugins.length > 0 ? [...PLUGIN_POINTS] : [],
5070
5924
  // The plugin API sees every tool result; this is not a shell matcher.
5071
5925
  matchers: plugins.length > 0 ? ["tool.execute"] : [],
5072
5926
  commands: plugins
@@ -5091,23 +5945,23 @@ async function detectVersion4(context) {
5091
5945
  })
5092
5946
  ]
5093
5947
  };
5094
- const version2 = VERSION_PATTERN3.exec(outcome2.stdout)?.[1] ?? null;
5948
+ const version3 = VERSION_PATTERN4.exec(outcome2.stdout)?.[1] ?? null;
5095
5949
  return {
5096
- version: version2,
5097
- verdict: version2 === null ? null : classifyVersion(version2, MANIFEST3.testedVersions),
5950
+ version: version3,
5951
+ verdict: version3 === null ? null : classifyVersion(version3, MANIFEST4.testedVersions),
5098
5952
  evidence: [
5099
5953
  evidence({
5100
5954
  kind: "version-output",
5101
5955
  source: "opencode",
5102
5956
  path: outcome2.executablePath,
5103
- detail: version2 === null ? "reported no recognisable semantic version" : `reported ${version2}`
5957
+ detail: version3 === null ? "reported no recognisable semantic version" : `reported ${version3}`
5104
5958
  })
5105
5959
  ]
5106
5960
  };
5107
5961
  }
5108
- async function detect3(context) {
5109
- const { version: version2, verdict, evidence: versionEvidence } = await detectVersion4(context);
5110
- const configs = await Promise.all(MANIFEST3.configFiles.map((file) => resolveConfig3(file, context)));
5962
+ async function detect4(context) {
5963
+ const { version: version3, verdict, evidence: versionEvidence } = await detectVersion4(context);
5964
+ const configs = await Promise.all(MANIFEST4.configFiles.map((file) => resolveConfig4(file, context)));
5111
5965
  const present = configs.filter((config) => config.exists);
5112
5966
  const configured = present.filter((config) => config.configuredPoints.length > 0);
5113
5967
  const warnings = [];
@@ -5123,17 +5977,17 @@ async function detect3(context) {
5123
5977
  warnings.push(diagnostic({
5124
5978
  severity: "warning",
5125
5979
  code: "harness-version-unknown-newer",
5126
- message: `OpenCode ${String(version2)} is newer than the observed configuration schema`,
5980
+ message: `OpenCode ${String(version3)} is newer than the observed configuration schema`,
5127
5981
  remediation: "Check the release notes for plugin-schema changes before applying a plan"
5128
5982
  }));
5129
5983
  const primary = present.find((config) => config.declaration.primary) ?? present[0] ?? null;
5130
5984
  return {
5131
5985
  harnessId: OPENCODE,
5132
- state: present.some((config) => !config.parsed) ? "broken" : configured.length > 0 && version2 !== null ? "configured" : version2 !== null || present.length > 0 ? "detected" : "absent",
5133
- version: version2,
5986
+ state: present.some((config) => !config.parsed) ? "broken" : configured.length > 0 && version3 !== null ? "configured" : version3 !== null || present.length > 0 ? "detected" : "absent",
5987
+ version: version3,
5134
5988
  versionVerdict: verdict,
5135
5989
  configPath: primary?.path ?? null,
5136
- declaredVerificationTier: MANIFEST3.verificationTier,
5990
+ declaredVerificationTier: MANIFEST4.verificationTier,
5137
5991
  evidence: [
5138
5992
  ...versionEvidence,
5139
5993
  ...present.map((config) => evidence({
@@ -5146,18 +6000,224 @@ async function detect3(context) {
5146
6000
  warnings
5147
6001
  };
5148
6002
  }
5149
- async function inspect3(context) {
5150
- const configs = await Promise.all(MANIFEST3.configFiles.map((file) => resolveConfig3(file, context)));
5151
- const activeToolFamilies = familiesOnThisPlatform(MANIFEST3, context.facts);
6003
+ async function inspect4(context) {
6004
+ const configs = await Promise.all(MANIFEST4.configFiles.map((file) => resolveConfig4(file, context)));
6005
+ const activeToolFamilies = familiesOnThisPlatform(MANIFEST4, context.facts);
6006
+ return {
6007
+ harnessId: OPENCODE,
6008
+ configs,
6009
+ activeToolFamilies,
6010
+ uncoveredToolFamilies: [],
6011
+ enabled: null,
6012
+ diagnostics: [],
6013
+ summaries: configs.filter((config) => config.configuredPoints.length > 0).map((config) => ({
6014
+ harnessId: OPENCODE,
6015
+ configPath: config.path,
6016
+ scope: config.declaration.scope,
6017
+ interceptionPoints: config.configuredPoints,
6018
+ matchers: config.matchers,
6019
+ commands: config.commands
6020
+ }))
6021
+ };
6022
+ }
6023
+ async function verify4(context) {
6024
+ const detection = await detect4(context);
6025
+ const inspection = await inspect4(context);
6026
+ const configured = inspection.configs.some((config) => config.configuredPoints.length > 0);
6027
+ const readable = inspection.configs.filter((config) => config.exists).every((config) => config.parsed);
6028
+ const checks = [
6029
+ {
6030
+ id: "executable-resolves",
6031
+ status: detection.version === null ? "fail" : "pass",
6032
+ summary: detection.version === null ? "opencode could not be run" : `opencode ${detection.version} resolves`,
6033
+ achievedTier: detection.version === null ? null : "presence",
6034
+ evidence: detection.evidence.filter((item) => item.kind === "version-output"),
6035
+ remediation: detection.version === null ? "Install OpenCode or make it available on PATH" : null
6036
+ },
6037
+ {
6038
+ id: "config-readable",
6039
+ status: !readable ? "fail" : configured ? "pass" : "not-exercised",
6040
+ summary: !readable ? "OpenCode configuration is not valid JSONC" : configured ? "OpenCode plugin configuration is readable" : "No OpenCode plugin configuration is present",
6041
+ achievedTier: configured && readable ? "config-only" : null,
6042
+ evidence: [],
6043
+ remediation: !readable ? "Repair the JSONC syntax, then run verify again" : null
6044
+ },
6045
+ {
6046
+ id: "plugin-registered",
6047
+ status: configured ? "pass" : "not-exercised",
6048
+ summary: configured ? "A plugin entry is registered for tool.execute.after" : "No plugin entry is registered",
6049
+ achievedTier: configured ? "config-only" : null,
6050
+ evidence: [],
6051
+ remediation: configured ? null : "Configure the OpenCode plugin, or adopt an existing configuration"
6052
+ },
6053
+ {
6054
+ id: "canary-intercepted",
6055
+ status: "not-exercised",
6056
+ summary: "No observable receipt exists for an adopted generated plugin wrapper",
6057
+ achievedTier: null,
6058
+ evidence: [],
6059
+ remediation: null
6060
+ }
6061
+ ];
6062
+ return {
6063
+ harnessId: OPENCODE,
6064
+ declaredTier: MANIFEST4.verificationTier,
6065
+ achievedTier: detection.version !== null && configured && readable ? "config-only" : detection.version !== null ? "presence" : null,
6066
+ checks
6067
+ };
6068
+ }
6069
+ var opencodeAdapter = { manifest: MANIFEST4, detect: detect4, inspect: inspect4, verify: verify4 };
6070
+
6071
+ // packages/adapters/dist/src/harnesses/pi.js
6072
+ var PI = harnessId("pi");
6073
+ var VERSION_PATTERN5 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
6074
+ var MANIFEST5 = {
6075
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
6076
+ id: PI,
6077
+ displayName: "Pi",
6078
+ // The npm package declares no `homepage`; the repository it ships is the documented home.
6079
+ homepage: "https://github.com/earendil-works/pi",
6080
+ // Only the version actually observed — PLAN §15 item 31, "tested range of exactly the observed
6081
+ // version". `pi --version` on the machine this was written against printed `0.83.0`.
6082
+ testedVersions: { minimum: "0.83.0", maximum: "0.83.0" },
6083
+ // RFC 0007: the extension emits no receipt of its own, so nothing above `config-only` is
6084
+ // provable. The configuration is the extension module sitting in an auto-loaded directory.
6085
+ verificationTier: "config-only",
6086
+ versionCommand: { executable: "pi", args: ["--version"] },
6087
+ interceptionPoints: [{ scopeId: "tool-result", eventName: "tool_result" }],
6088
+ // `markers` because there is nothing to parse: Pi auto-loads any module in these directories,
6089
+ // and the presence of the HarnessTrim extension file is the whole configuration.
6090
+ configFiles: [
6091
+ {
6092
+ path: ".pi/agent/extensions/harnesstrim/index.ts",
6093
+ scope: "user",
6094
+ parser: "markers",
6095
+ primary: true
6096
+ },
6097
+ {
6098
+ path: ".pi/extensions/harnesstrim/harnesstrim.ts",
6099
+ scope: "project",
6100
+ parser: "markers",
6101
+ primary: false
6102
+ }
6103
+ ],
6104
+ // The extension intercepts `tool_result` and reduces text chunks — a content seam, never a
6105
+ // shell. Mirrors the OpenCode plugin API claim.
6106
+ toolFamilies: [
6107
+ { id: "tool.result", platforms: ["windows", "macos", "linux"], executesShellCommands: false }
6108
+ ],
6109
+ // There is no separate enable state: the module being present in the directory is the load
6110
+ // decision, so the adapter reads presence and nothing else.
6111
+ requiresEnablement: false,
6112
+ enablementNote: null,
6113
+ receiptFamily: "none"
6114
+ };
6115
+ async function resolveConfig5(declaration, context) {
6116
+ const path = resolveConfigPath(declaration, context);
6117
+ const stat3 = await context.fs.stat(path);
6118
+ if (stat3 === null || stat3.kind !== "file") {
6119
+ return {
6120
+ declaration,
6121
+ path,
6122
+ exists: false,
6123
+ parsed: false,
6124
+ configuredPoints: [],
6125
+ matchers: [],
6126
+ commands: []
6127
+ };
6128
+ }
6129
+ const text = new TextDecoder().decode(await context.fs.readFile(path));
6130
+ const isHarnesstrim = /harnesstrim/i.test(text);
6131
+ return {
6132
+ declaration,
6133
+ path,
6134
+ exists: true,
6135
+ parsed: isHarnesstrim,
6136
+ configuredPoints: isHarnesstrim ? ["tool-result"] : [],
6137
+ matchers: isHarnesstrim ? ["tool.result"] : [],
6138
+ // There is no enable command: Pi loads the module from the directory directly.
6139
+ commands: []
6140
+ };
6141
+ }
6142
+ async function detectVersion5(context) {
6143
+ const result = await context.runner.run({
6144
+ executable: "pi",
6145
+ args: ["--version"],
6146
+ cwd: context.projectRoot,
6147
+ timeoutMs: 2e4
6148
+ });
6149
+ if (result.failure !== null)
6150
+ return {
6151
+ value: null,
6152
+ verdict: null,
6153
+ evidence: [
6154
+ evidence({
6155
+ kind: "absence",
6156
+ source: "pi",
6157
+ detail: `not runnable: ${result.failure.reason}`
6158
+ })
6159
+ ]
6160
+ };
6161
+ const value = VERSION_PATTERN5.exec(result.stdout)?.[1] ?? null;
6162
+ return {
6163
+ value,
6164
+ verdict: value === null ? null : classifyVersion(value, MANIFEST5.testedVersions),
6165
+ evidence: [
6166
+ evidence({
6167
+ kind: "version-output",
6168
+ source: "pi",
6169
+ path: result.executablePath,
6170
+ detail: value ? `reported ${value}` : "reported no recognisable version"
6171
+ })
6172
+ ]
6173
+ };
6174
+ }
6175
+ async function detect5(context) {
6176
+ const observed = await detectVersion5(context);
6177
+ const configs = await Promise.all(MANIFEST5.configFiles.map((file) => resolveConfig5(file, context)));
6178
+ const module = configs.find((file) => file.parsed);
6179
+ const present = module !== void 0;
6180
+ const warnings = [];
6181
+ if (observed.verdict === "unknown-newer")
6182
+ warnings.push(diagnostic({
6183
+ severity: "warning",
6184
+ code: "harness-version-unknown-newer",
6185
+ message: `Pi ${observed.value} is newer than the observed extension contract`,
6186
+ remediation: "Review the tool_result extension contract before applying changes"
6187
+ }));
6188
+ return {
6189
+ harnessId: PI,
6190
+ state: present && observed.value !== null ? "configured" : present || observed.value !== null ? "detected" : "absent",
6191
+ version: observed.value,
6192
+ versionVerdict: observed.verdict,
6193
+ configPath: module?.path ?? null,
6194
+ declaredVerificationTier: MANIFEST5.verificationTier,
6195
+ evidence: [
6196
+ ...observed.evidence,
6197
+ ...module ? [
6198
+ evidence({
6199
+ kind: "config-entry",
6200
+ source: "HarnessTrim Pi extension",
6201
+ path: module.path,
6202
+ detail: "extension module present in an auto-loaded directory"
6203
+ })
6204
+ ] : []
6205
+ ],
6206
+ warnings
6207
+ };
6208
+ }
6209
+ async function inspect5(context) {
6210
+ const configs = await Promise.all(MANIFEST5.configFiles.map((file) => resolveConfig5(file, context)));
5152
6211
  return {
5153
- harnessId: OPENCODE,
6212
+ harnessId: PI,
5154
6213
  configs,
5155
- activeToolFamilies,
6214
+ activeToolFamilies: familiesOnThisPlatform(MANIFEST5, context.facts),
5156
6215
  uncoveredToolFamilies: [],
6216
+ // No separate enablement exists, so nothing is reported as enabled or disabled.
5157
6217
  enabled: null,
5158
6218
  diagnostics: [],
5159
6219
  summaries: configs.filter((config) => config.configuredPoints.length > 0).map((config) => ({
5160
- harnessId: OPENCODE,
6220
+ harnessId: PI,
5161
6221
  configPath: config.path,
5162
6222
  scope: config.declaration.scope,
5163
6223
  interceptionPoints: config.configuredPoints,
@@ -5166,56 +6226,61 @@ async function inspect3(context) {
5166
6226
  }))
5167
6227
  };
5168
6228
  }
5169
- async function verify3(context) {
5170
- const detection = await detect3(context);
5171
- const inspection = await inspect3(context);
5172
- const configured = inspection.configs.some((config) => config.configuredPoints.length > 0);
5173
- const readable = inspection.configs.filter((config) => config.exists).every((config) => config.parsed);
6229
+ async function verify5(context) {
6230
+ const detection = await detect5(context);
6231
+ const inspection = await inspect5(context);
6232
+ const module = inspection.configs.some((config) => config.configuredPoints.length > 0);
5174
6233
  const checks = [
5175
6234
  {
5176
6235
  id: "executable-resolves",
5177
6236
  status: detection.version === null ? "fail" : "pass",
5178
- summary: detection.version === null ? "opencode could not be run" : `opencode ${detection.version} resolves`,
6237
+ summary: detection.version === null ? "pi could not be run" : `Pi ${detection.version}`,
5179
6238
  achievedTier: detection.version === null ? null : "presence",
5180
6239
  evidence: detection.evidence.filter((item) => item.kind === "version-output"),
5181
- remediation: detection.version === null ? "Install OpenCode or make it available on PATH" : null
6240
+ remediation: detection.version === null ? "Install Pi or add it to PATH" : null
5182
6241
  },
5183
6242
  {
5184
- id: "config-readable",
5185
- status: !readable ? "fail" : configured ? "pass" : "not-exercised",
5186
- summary: !readable ? "OpenCode configuration is not valid JSONC" : configured ? "OpenCode plugin configuration is readable" : "No OpenCode plugin configuration is present",
5187
- achievedTier: configured && readable ? "config-only" : null,
6243
+ id: "module-installed",
6244
+ status: module ? "pass" : "fail",
6245
+ summary: module ? "HarnessTrim extension module is installed" : "No HarnessTrim extension module was found in the Pi extension directories",
6246
+ achievedTier: module ? "config-only" : null,
5188
6247
  evidence: [],
5189
- remediation: !readable ? "Repair the JSONC syntax, then run verify again" : null
6248
+ remediation: module ? null : "Run `harnesstrim install pi --apply`"
5190
6249
  },
5191
6250
  {
5192
- id: "plugin-registered",
5193
- status: configured ? "pass" : "not-exercised",
5194
- summary: configured ? "A plugin entry is registered for tool.execute.after" : "No plugin entry is registered",
5195
- achievedTier: configured ? "config-only" : null,
6251
+ id: "mode-unreadable",
6252
+ status: "info",
6253
+ summary: "The effective reduction mode (HARNESSTRIM_MODE) defaults to dryrun and is not observable from the extension alone",
6254
+ achievedTier: null,
5196
6255
  evidence: [],
5197
- remediation: configured ? null : "Configure the OpenCode plugin, or adopt an existing configuration"
6256
+ remediation: null
5198
6257
  },
5199
6258
  {
5200
6259
  id: "canary-intercepted",
5201
6260
  status: "not-exercised",
5202
- summary: "No observable receipt exists for an adopted generated plugin wrapper",
6261
+ summary: "Pi exposes no independent receipt; HarnessTrim telemetry is verified by the provider adapter",
5203
6262
  achievedTier: null,
5204
6263
  evidence: [],
5205
6264
  remediation: null
5206
6265
  }
5207
6266
  ];
5208
6267
  return {
5209
- harnessId: OPENCODE,
5210
- declaredTier: MANIFEST3.verificationTier,
5211
- achievedTier: detection.version !== null && configured && readable ? "config-only" : detection.version !== null ? "presence" : null,
6268
+ harnessId: PI,
6269
+ declaredTier: MANIFEST5.verificationTier,
6270
+ achievedTier: checks.some((check) => check.achievedTier === "config-only") ? "config-only" : checks.some((check) => check.achievedTier === "presence") ? "presence" : null,
5212
6271
  checks
5213
6272
  };
5214
6273
  }
5215
- var opencodeAdapter = { manifest: MANIFEST3, detect: detect3, inspect: inspect3, verify: verify3 };
6274
+ var piAdapter = { manifest: MANIFEST5, detect: detect5, inspect: inspect5, verify: verify5 };
5216
6275
 
5217
6276
  // packages/adapters/dist/src/harnesses/index.js
5218
- var HARNESS_ADAPTERS = [claudeAdapter, codexAdapter, opencodeAdapter];
6277
+ var HARNESS_ADAPTERS = [
6278
+ claudeAdapter,
6279
+ codexAdapter,
6280
+ hermesAdapter,
6281
+ opencodeAdapter,
6282
+ piAdapter
6283
+ ];
5219
6284
  function listHarnessAdapters() {
5220
6285
  return HARNESS_ADAPTERS;
5221
6286
  }
@@ -5223,8 +6288,11 @@ function listHarnessAdapters() {
5223
6288
  // packages/adapters/dist/src/providers/harnesstrim.js
5224
6289
  var HARNESSTRIM2 = providerId("harnesstrim");
5225
6290
  var CLAUDE2 = harnessId("claude");
6291
+ var HERMES2 = harnessId("hermes");
5226
6292
  var CODEX2 = harnessId("codex");
5227
- var CLAUDE_ARTIFACT_DIGESTS = {
6293
+ var PI2 = harnessId("pi");
6294
+ var SKILLS_UPSTREAM = "0.0.7";
6295
+ var SKILL_ARTIFACT_DIGESTS = {
5228
6296
  "compact-handoff/SKILL.md": "sha256:0efbf35581c559359e755b204778b64a289dab4d20dadc1cba3d5b5c995b5f01",
5229
6297
  "debug-log-slim/SKILL.md": "sha256:17f5ccc34d29d7aba083444e0e4d87fd3d49916ebcb4e6544caf8c89f13f0045",
5230
6298
  "delegate-bulk/SKILL.md": "sha256:3753de2ad18271c24832e4cda63115d353620515d5d80f40e93bc07d2a7257d7",
@@ -5233,9 +6301,18 @@ var CLAUDE_ARTIFACT_DIGESTS = {
5233
6301
  "review-delta/SKILL.md": "sha256:4dfbf9d6ec08dff27b4759726536b43928370e288706fa01f5b498648e741388",
5234
6302
  "scaffold-fast/SKILL.md": "sha256:1a18d52c4d335fbd3f74e2559bc20d6346193a40eef7b85149f51f44f697d182"
5235
6303
  };
6304
+ var SKILL_ARTIFACTS = Object.keys(SKILL_ARTIFACT_DIGESTS);
5236
6305
  var OPENCODE2 = harnessId("opencode");
6306
+ var MODE_CARRYING_HARNESSES = /* @__PURE__ */ new Set([
6307
+ OPENCODE2,
6308
+ HERMES2,
6309
+ harnessId("pi"),
6310
+ harnessId("omp")
6311
+ ]);
5237
6312
  var HOOK_COMMAND_PATTERN = /(^|[\\/\s"'])harnesstrim(\.cmd|\.exe)?([\s"']|$)/i;
5238
- var MANIFEST4 = {
6313
+ var PLUGIN_MODULE_PATTERN2 = /(^|[\\/])harnesstrim\.(ts|js|mjs|cjs|mts|cts)$/i;
6314
+ var PI_EXTENSION_PATTERN = /(^|[\\/])\.pi[\\/](agent[\\/])?extensions[\\/]harnesstrim[\\/]/i;
6315
+ var MANIFEST6 = {
5239
6316
  schemaVersion: MANIFEST_SCHEMA_VERSION,
5240
6317
  id: HARNESSTRIM2,
5241
6318
  displayName: "HarnessTrim",
@@ -5311,36 +6388,264 @@ var MANIFEST4 = {
5311
6388
  }
5312
6389
  ],
5313
6390
  metrics: {
5314
- // RFC 0005 §Importer degradation policy: `legacy` is the character-only `TrimEvent` JSONL, and
5315
- // it is "a supported steady state, not a warning". `native` would need upstream to expose token
5316
- // counts and native ids, which `0.0.5` does not.
6391
+ // RFC 0005 §Importer degradation policy. `native` since PLAN §15 item 43d: the `0.1.0`
6392
+ // telemetry lines carry a producer `eventId` and nullable token counts, so dedup uses the
6393
+ // native ID and a token count is a token count where one exists. Legacy schema 0 lines
6394
+ // still parse, stay `estimated-local`, and never merge with the native figures.
5317
6395
  source: "jsonl",
5318
- mode: "legacy",
5319
- locations: [".harnesstrim/metrics.jsonl", "~/.hermes/harnesstrim-metrics.jsonl"]
6396
+ mode: "native",
6397
+ // PLAN §15 item 25: Hermes has no adapter, tested range or tier, so the home-relative path that
6398
+ // names it is not read. Item 30 readmits it together with the adapter and a matrix row; the
6399
+ // registry assertion in `registries.test.ts` refuses a home-relative location that names a
6400
+ // harness the registry does not know.
6401
+ locations: [".harnesstrim/metrics.jsonl", ".hermes/harnesstrim-metrics.jsonl"]
5320
6402
  },
5321
- delegatedInstallReview: {
5322
- upstreamVersion: "0.0.7",
5323
- reviewedWriteSet: [
5324
- ...Object.keys(CLAUDE_ARTIFACT_DIGESTS).map((path) => `.claude/skills/${path}`)
5325
- ],
5326
- containmentBoundary: [".claude", "CLAUDE.md"],
5327
- upstreamUninstallAvailable: true
6403
+ /**
6404
+ * One review per harness — PLAN §15 item 46b.
6405
+ *
6406
+ * Codex was observed at `0.1.0`: `harnesstrim install codex <dir> --apply --no-instructions`
6407
+ * writes the same seven skill artifacts as the Claude invocation, byte for byte — the digests
6408
+ * below are shared because the files are the same files — under `.codex/skills/` instead, and
6409
+ * skips `AGENTS.md` exactly as the Claude one skips `CLAUDE.md`. What differs is the boundary and
6410
+ * the protected paths, which is precisely why one review could not describe both.
6411
+ *
6412
+ * OpenCode is absent on purpose. Its installer writes `.opencode/plugin/harnesstrim.ts` and
6413
+ * `.opencode/package.json` *and runs an npm install*, so the containment boundary would hold a
6414
+ * `node_modules` tree: a snapshot question, not another entry in this map.
6415
+ */
6416
+ delegatedInstallReviews: {
6417
+ claude: {
6418
+ upstreamVersion: SKILLS_UPSTREAM,
6419
+ reviewedWriteSet: SKILL_ARTIFACTS.map((path) => `.claude/skills/${path}`),
6420
+ containmentBoundary: [".claude", "CLAUDE.md"],
6421
+ upstreamUninstallAvailable: true
6422
+ },
6423
+ codex: {
6424
+ upstreamVersion: SKILLS_UPSTREAM,
6425
+ reviewedWriteSet: SKILL_ARTIFACTS.map((path) => `.codex/skills/${path}`),
6426
+ containmentBoundary: [".codex", "AGENTS.md"],
6427
+ upstreamUninstallAvailable: true
6428
+ }
6429
+ }
6430
+ };
6431
+ var SKILLS_INSTALL = {
6432
+ claude: {
6433
+ directory: ".claude",
6434
+ instructions: "CLAUDE.md",
6435
+ hook: "settings.json",
6436
+ args: ["--no-hook", "--no-instructions"]
6437
+ },
6438
+ codex: {
6439
+ directory: ".codex",
6440
+ instructions: "AGENTS.md",
6441
+ hook: "hooks.json",
6442
+ args: ["--no-instructions"]
5328
6443
  }
5329
6444
  };
5330
6445
  var TESTED_UPSTREAM = "0.0.7";
5331
6446
  var TESTED_VERSIONS = { minimum: "0.0.5", maximum: TESTED_UPSTREAM };
5332
6447
  function identifiesCommand(command) {
5333
- return HOOK_COMMAND_PATTERN.test(command);
6448
+ return HOOK_COMMAND_PATTERN.test(command) || PLUGIN_MODULE_PATTERN2.test(command);
6449
+ }
6450
+ function isStringArray(value) {
6451
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
6452
+ }
6453
+ function parseHarnessCapabilities(value) {
6454
+ if (typeof value !== "object" || value === null)
6455
+ return null;
6456
+ const record = value;
6457
+ const surfaces = record["surfaces"];
6458
+ const writeSet = record["writeSet"];
6459
+ const narrowing = record["narrowing"];
6460
+ if (!isStringArray(surfaces) || !isStringArray(writeSet) || !Array.isArray(narrowing)) {
6461
+ return null;
6462
+ }
6463
+ const flags = [];
6464
+ for (const item of narrowing) {
6465
+ if (typeof item !== "object" || item === null)
6466
+ return null;
6467
+ const flag = item["flag"];
6468
+ const produces = item["produces"];
6469
+ if (typeof flag !== "string" || typeof produces !== "string")
6470
+ return null;
6471
+ flags.push({ flag, produces });
6472
+ }
6473
+ return {
6474
+ adapter: typeof record["adapter"] === "string" ? record["adapter"] : "",
6475
+ surfaces,
6476
+ writeSet,
6477
+ narrowing: flags
6478
+ };
6479
+ }
6480
+ function parseCapabilities(stdout) {
6481
+ let parsed;
6482
+ try {
6483
+ parsed = JSON.parse(stdout);
6484
+ } catch {
6485
+ return null;
6486
+ }
6487
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
6488
+ return null;
6489
+ const record = parsed;
6490
+ const version3 = record["version"];
6491
+ const harnesses = record["harnesses"];
6492
+ if (typeof version3 !== "string" || typeof harnesses !== "object" || harnesses === null) {
6493
+ return null;
6494
+ }
6495
+ const entries = {};
6496
+ for (const [harness, value] of Object.entries(harnesses)) {
6497
+ const entry = parseHarnessCapabilities(value);
6498
+ if (entry === null)
6499
+ return null;
6500
+ entries[harness] = entry;
6501
+ }
6502
+ return { version: version3, harnesses: entries };
6503
+ }
6504
+ async function probeCapabilities(context) {
6505
+ const outcome2 = await context.runner.run({
6506
+ executable: "harnesstrim",
6507
+ args: ["capabilities"],
6508
+ cwd: context.projectRoot,
6509
+ timeoutMs: 2e4
6510
+ });
6511
+ if (outcome2.failure !== null || outcome2.exitCode !== 0) {
6512
+ return {
6513
+ capabilities: null,
6514
+ evidence: [
6515
+ evidence({
6516
+ kind: "absence",
6517
+ source: "harnesstrim capabilities",
6518
+ detail: "did not answer with a machine-readable declaration"
6519
+ })
6520
+ ]
6521
+ };
6522
+ }
6523
+ const capabilities = parseCapabilities(outcome2.stdout);
6524
+ if (capabilities === null) {
6525
+ return {
6526
+ capabilities: null,
6527
+ evidence: [
6528
+ evidence({
6529
+ kind: "version-output",
6530
+ source: "harnesstrim capabilities",
6531
+ path: outcome2.executablePath,
6532
+ detail: "answered, but not with a declaration this build can read"
6533
+ })
6534
+ ]
6535
+ };
6536
+ }
6537
+ return {
6538
+ capabilities,
6539
+ evidence: [
6540
+ evidence({
6541
+ kind: "version-output",
6542
+ source: "harnesstrim capabilities",
6543
+ path: outcome2.executablePath,
6544
+ detail: `declared ${capabilities.version} for ${Object.keys(capabilities.harnesses).join(", ")}`
6545
+ })
6546
+ ]
6547
+ };
6548
+ }
6549
+ var SURFACE_ANCHORS = {
6550
+ "post-tool-use": "PostToolUse",
6551
+ "tool-execute-after": "tool.execute.after"
6552
+ };
6553
+ function surfaceMatchesDeclaration(surface, declaration) {
6554
+ const pointAnchor = SURFACE_ANCHORS[declaration.interceptionPoint];
6555
+ if (pointAnchor !== void 0 && !surface.includes(pointAnchor))
6556
+ return false;
6557
+ if (declaration.toolFamily !== EVERY_TOOL_FAMILY && !surface.includes(declaration.toolFamily)) {
6558
+ return false;
6559
+ }
6560
+ return true;
6561
+ }
6562
+ function writeSetPath(entry) {
6563
+ return entry.replace(/\s*\([^)]*\)\s*$/, "").replaceAll("\\", "/");
6564
+ }
6565
+ function coveredBy(path, declared) {
6566
+ const base = declared.endsWith("/") ? declared : `${declared}/`;
6567
+ return path === declared || path.startsWith(base);
6568
+ }
6569
+ function driftWarning(message, remediation) {
6570
+ return diagnostic({
6571
+ severity: "warning",
6572
+ code: "provider-capabilities-drift",
6573
+ subject: "harnesstrim",
6574
+ message,
6575
+ remediation
6576
+ });
6577
+ }
6578
+ function formatSurface(surface) {
6579
+ return `${surface.toolFamily}/${surface.interceptionPoint}`;
6580
+ }
6581
+ function assignableOn(capabilities) {
6582
+ if (capabilities === null)
6583
+ return [];
6584
+ const reviews = MANIFEST6.delegatedInstallReviews ?? {};
6585
+ return Object.keys(reviews).filter((harness) => {
6586
+ const observed = capabilities.harnesses[harness];
6587
+ return observed !== void 0 && observed.narrowing.length > 0 && writeSetStillReviewed(capabilities, harnessId(harness));
6588
+ }).map((harness) => harnessId(harness));
6589
+ }
6590
+ function writeSetStillReviewed(capabilities, harness) {
6591
+ const review = MANIFEST6.delegatedInstallReviews?.[harness];
6592
+ if (review === void 0 || capabilities === null)
6593
+ return false;
6594
+ const observed = capabilities.harnesses[harness];
6595
+ if (observed === void 0)
6596
+ return false;
6597
+ const declared = observed.writeSet.map(writeSetPath);
6598
+ const boundary = review.containmentBoundary.map(writeSetPath);
6599
+ const everyDeclaredPathContained = declared.every((entry) => boundary.some((prefix) => coveredBy(entry, prefix)));
6600
+ const everyReviewedPathCovered = review.reviewedWriteSet.every((reviewed) => declared.some((entry) => coveredBy(reviewed, entry)));
6601
+ return everyDeclaredPathContained && everyReviewedPathCovered;
6602
+ }
6603
+ function compareCapabilities(manifest, capabilities) {
6604
+ const warnings = [];
6605
+ for (const declaration of manifest.capabilities) {
6606
+ for (const harness of declaration.harnesses) {
6607
+ const observed = capabilities.harnesses[harness];
6608
+ if (observed === void 0) {
6609
+ 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`));
6610
+ continue;
6611
+ }
6612
+ const matched = declaration.surfaces.some((surface) => observed.surfaces.some((entry) => surfaceMatchesDeclaration(entry, surface)));
6613
+ if (!matched) {
6614
+ 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`));
6615
+ }
6616
+ }
6617
+ }
6618
+ for (const [harness, review] of Object.entries(manifest.delegatedInstallReviews ?? {})) {
6619
+ const observed = capabilities.harnesses[harness];
6620
+ if (observed === void 0)
6621
+ continue;
6622
+ const declared = observed.writeSet.map(writeSetPath);
6623
+ for (const reviewed of review.reviewedWriteSet) {
6624
+ if (declared.some((entry) => coveredBy(reviewed, entry)))
6625
+ continue;
6626
+ warnings.push(driftWarning(`the reviewed ${harness} 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`));
6627
+ }
6628
+ const boundary = review.containmentBoundary.map(writeSetPath);
6629
+ for (const entry of declared) {
6630
+ if (boundary.some((prefix) => coveredBy(entry, prefix)))
6631
+ continue;
6632
+ warnings.push(driftWarning(`\`harnesstrim capabilities\` (${capabilities.version}) declares ${entry} for ${harness}, 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"));
6633
+ }
6634
+ }
6635
+ return warnings;
5334
6636
  }
5335
6637
  function harnessesWiredToHarnessTrim(configs) {
5336
6638
  const wired = /* @__PURE__ */ new Set();
5337
6639
  for (const config of configs) {
5338
- if (config.commands.some((command) => identifiesCommand(command)))
6640
+ const hermesPlugin = config.harnessId === HERMES2 && (config.configPath.toLowerCase().includes(".hermes/plugins/harnesstrim") || config.commands.some((command) => /hermes plugins enable harnesstrim/i.test(command)));
6641
+ const piExtension = config.harnessId === PI2 && PI_EXTENSION_PATTERN.test(config.configPath);
6642
+ if (piExtension || hermesPlugin || config.commands.some((command) => identifiesCommand(command))) {
5339
6643
  wired.add(config.harnessId);
6644
+ }
5340
6645
  }
5341
6646
  return [...wired];
5342
6647
  }
5343
- var VERSION_PATTERN4 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
6648
+ var VERSION_PATTERN6 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
5344
6649
  async function probeExecutable(context) {
5345
6650
  const asked = await context.runner.run({
5346
6651
  executable: "harnesstrim",
@@ -5363,17 +6668,17 @@ async function probeExecutable(context) {
5363
6668
  };
5364
6669
  }
5365
6670
  if (asked.exitCode === 0) {
5366
- const version2 = VERSION_PATTERN4.exec(asked.stdout)?.[1] ?? null;
6671
+ const version3 = VERSION_PATTERN6.exec(asked.stdout)?.[1] ?? null;
5367
6672
  return {
5368
6673
  installed: true,
5369
- version: version2,
6674
+ version: version3,
5370
6675
  path: asked.executablePath,
5371
6676
  evidence: [
5372
6677
  evidence({
5373
6678
  kind: "version-output",
5374
6679
  source: "harnesstrim --version",
5375
6680
  path: asked.executablePath,
5376
- detail: version2 === null ? "reported no recognisable version" : `reported ${version2}`
6681
+ detail: version3 === null ? "reported no recognisable version" : `reported ${version3}`
5377
6682
  })
5378
6683
  ]
5379
6684
  };
@@ -5418,11 +6723,15 @@ function metricsLocations(context) {
5418
6723
  context.fs.join(context.paths.home, ".hermes", "harnesstrim-metrics.jsonl")
5419
6724
  ];
5420
6725
  }
5421
- async function detect4(context) {
6726
+ async function detect6(context) {
5422
6727
  const probe = await probeExecutable(context);
6728
+ const observed = probe.installed ? await probeCapabilities(context) : null;
5423
6729
  const configured = harnessesWiredToHarnessTrim(context.harnessConfigs);
5424
6730
  const warnings = [];
5425
- const evidenceItems = [...probe.evidence];
6731
+ const evidenceItems = [...probe.evidence, ...observed?.evidence ?? []];
6732
+ if (observed !== null && observed.capabilities !== null) {
6733
+ warnings.push(...compareCapabilities(MANIFEST6, observed.capabilities));
6734
+ }
5426
6735
  for (const harness of configured) {
5427
6736
  evidenceItems.push(evidence({
5428
6737
  kind: "config-entry",
@@ -5452,7 +6761,7 @@ async function detect4(context) {
5452
6761
  // cannot answer.
5453
6762
  versionVerdict: probe.version === null ? null : classifyVersion(probe.version, TESTED_VERSIONS),
5454
6763
  configuredHarnesses: configured,
5455
- unmanagedHarnessesConfigured: configured.filter((harness) => !MANIFEST4.harnesses.some((entry) => entry.harness === harness)),
6764
+ unmanagedHarnessesConfigured: configured.filter((harness) => !MANIFEST6.harnesses.some((entry) => entry.harness === harness)),
5456
6765
  // RFC 0002 §Providers may exceed the managed surface: HarnessTrim ships adapters for Hermes and
5457
6766
  // Pi as well, which Token Harness does not manage, so a wired one is reported and left alone.
5458
6767
  supportsUnmanagedHarnesses: true,
@@ -5460,6 +6769,9 @@ async function detect4(context) {
5460
6769
  // circumstantial: PLAN §11 says Token Harness never installs it, so every installation it ever
5461
6770
  // sees is the user's.
5462
6771
  managedByTokenHarness: false,
6772
+ // PLAN §15 item 46, per harness. Decided by the build in front of us and by which harnesses
6773
+ // carry a reviewed write set: those are the two things that make the assignment producible.
6774
+ assignableHarnesses: assignableOn(observed?.capabilities ?? null),
5463
6775
  evidence: evidenceItems,
5464
6776
  warnings
5465
6777
  };
@@ -5481,6 +6793,12 @@ function parseTrimEvent(line) {
5481
6793
  if (typeof ts !== "string" || typeof harness !== "string" || typeof before !== "number" || typeof after !== "number") {
5482
6794
  return null;
5483
6795
  }
6796
+ const schemaVersion = typeof record["schemaVersion"] === "number" ? record["schemaVersion"] : 0;
6797
+ if (schemaVersion > 1)
6798
+ return null;
6799
+ const eventId = schemaVersion === 1 && typeof record["eventId"] === "string" && record["eventId"] !== "" ? record["eventId"] : null;
6800
+ if (schemaVersion === 1 && eventId === null)
6801
+ return null;
5484
6802
  return {
5485
6803
  ts,
5486
6804
  harness,
@@ -5488,6 +6806,11 @@ function parseTrimEvent(line) {
5488
6806
  reducer: typeof record["reducer"] === "string" ? record["reducer"] : null,
5489
6807
  beforeChars: before,
5490
6808
  afterChars: after,
6809
+ schemaVersion,
6810
+ eventId,
6811
+ changed: typeof record["changed"] === "boolean" ? record["changed"] : null,
6812
+ beforeTokens: typeof record["beforeTokens"] === "number" ? record["beforeTokens"] : null,
6813
+ afterTokens: typeof record["afterTokens"] === "number" ? record["afterTokens"] : null,
5491
6814
  ...typeof record["mode"] === "string" ? { mode: record["mode"] } : {}
5492
6815
  };
5493
6816
  }
@@ -5499,16 +6822,18 @@ function toEvent(event, sourceId, ordinal, line, context) {
5499
6822
  const instant = new Date(event.ts);
5500
6823
  if (Number.isNaN(instant.getTime()))
5501
6824
  return null;
6825
+ const native = event.schemaVersion === 1;
5502
6826
  const dryrun = event.mode === "dryrun";
5503
- const eventId = synthesizeEventId(sourceId, ordinal, line);
6827
+ const eventId = native ? event.eventId : synthesizeEventId(sourceId, ordinal, line);
6828
+ const passThrough = native && event.changed === false;
6829
+ const hasTokens = native && event.beforeTokens !== null && event.afterTokens !== null;
6830
+ const hasUnprovenMode = native && MODE_CARRYING_HARNESSES.has(event.harness) && !hasTokens && !passThrough;
5504
6831
  return {
5505
6832
  schemaVersion: OPTIMIZATION_EVENT_SCHEMA_VERSION,
5506
6833
  eventId,
5507
6834
  timestamp: instant.toISOString(),
5508
6835
  provider: { id: HARNESSTRIM2, version: null },
5509
6836
  context: {
5510
- // The project the metrics file belongs to, which for the default location is the project it
5511
- // sits inside.
5512
6837
  projectId: context.projectIdFor(context.projectRoot),
5513
6838
  // Unlike RTK's database, a `TrimEvent` names its harness, so this is read rather than left
5514
6839
  // unknown.
@@ -5518,31 +6843,40 @@ function toEvent(event, sourceId, ordinal, line, context) {
5518
6843
  pipelineId: null,
5519
6844
  pipelineOrder: null,
5520
6845
  toolFamily: event.tool === "" ? null : event.tool,
5521
- capability: event.harness === OPENCODE2 ? "tool.output.reduce" : "shell.output.reduce"
6846
+ capability: event.harness === OPENCODE2 || event.harness === HERMES2 || event.harness === PI2 ? "tool.output.reduce" : "shell.output.reduce"
5522
6847
  },
5523
6848
  measurement: {
5524
- class: dryrun ? "counterfactual" : "estimated-local",
6849
+ class: dryrun || hasUnprovenMode ? "counterfactual" : hasTokens ? "exact-local" : "estimated-local",
5525
6850
  beforeChars: event.beforeChars,
5526
6851
  afterChars: event.afterChars,
5527
- // RFC 0005: "never derived silently". `0.0.5` counts characters and nothing else.
5528
- beforeTokens: null,
5529
- afterTokens: null,
6852
+ // RFC 0005: "never derived silently". `0.0.5` counts characters and nothing else; the
6853
+ // `0.1.0` producer's counts are used as they are because a token count is a token count
6854
+ // where one exists.
6855
+ beforeTokens: hasTokens ? event.beforeTokens : null,
6856
+ afterTokens: hasTokens ? event.afterTokens : null,
5530
6857
  tokenizer: null,
5531
6858
  confidenceLow: null,
5532
6859
  confidenceHigh: null
5533
6860
  },
5534
6861
  outcome: {
5535
- // A dryrun leaves `output.output` untouched, so nothing the model saw changed.
5536
- changed: !dryrun && event.afterChars !== event.beforeChars,
5537
- bypassReason: dryrun ? "dryrun" : event.afterChars === event.beforeChars ? "no-reduction-applied" : null,
6862
+ // A dryrun leaves `output.output` untouched; a native line states whether its attempt
6863
+ // actually changed the output. A native line whose mode the envelope cannot carry is
6864
+ // deliberately not asserted as a change: the figure may describe a dryrun, and reporting
6865
+ // `changed: true` would claim the model received fewer bytes than it did.
6866
+ changed: dryrun || hasUnprovenMode ? false : native ? event.changed ?? true : event.afterChars !== event.beforeChars,
6867
+ bypassReason: dryrun ? "dryrun" : (
6868
+ // Same reason value as the diagnostic code for the residual ambiguity: nothing refutes a
6869
+ // dryrun, and nothing proves one, so the bypass is the envelope's own silence.
6870
+ hasUnprovenMode ? "mode-unresolved" : native ? passThrough ? "pass-through" : null : event.afterChars === event.beforeChars ? "no-reduction-applied" : null
6871
+ ),
5538
6872
  originalReference: null,
5539
6873
  latencyMs: null,
5540
6874
  errorCode: null
5541
6875
  },
5542
- source: { nativeEventId: null, importedAt: context.now() }
6876
+ source: { nativeEventId: native ? eventId : null, importedAt: context.now() }
5543
6877
  };
5544
6878
  }
5545
- async function verify4(context) {
6879
+ async function verify6(context) {
5546
6880
  const checks = [];
5547
6881
  const diagnostics = [];
5548
6882
  const probe = await probeExecutable(context);
@@ -5590,7 +6924,7 @@ async function verify4(context) {
5590
6924
  const agents = context.fs.join(context.projectRoot, "AGENTS.md");
5591
6925
  if (await context.fs.stat(agents) !== null) {
5592
6926
  const text = new TextDecoder().decode(await context.fs.readFile(agents));
5593
- if (identifiesCommand(text)) {
6927
+ if (HOOK_COMMAND_PATTERN.test(text)) {
5594
6928
  checks.push({
5595
6929
  id: "instruction-path-present",
5596
6930
  status: "info",
@@ -5626,6 +6960,8 @@ async function collectMetrics(context, store) {
5626
6960
  let skipped = 0;
5627
6961
  let lastCursor = null;
5628
6962
  let readAny = false;
6963
+ let sawNative = false;
6964
+ let unprovableModeEvents = 0;
5629
6965
  for (const path of metricsLocations(context)) {
5630
6966
  const stat3 = await context.fs.stat(path);
5631
6967
  if (stat3 === null)
@@ -5676,6 +7012,10 @@ async function collectMetrics(context, store) {
5676
7012
  ordinal += 1;
5677
7013
  continue;
5678
7014
  }
7015
+ if (parsed.schemaVersion === 1)
7016
+ sawNative = true;
7017
+ if (event.outcome.bypassReason === "mode-unresolved")
7018
+ unprovableModeEvents += 1;
5679
7019
  events.push(event);
5680
7020
  lastLine = trimmed;
5681
7021
  ordinal += 1;
@@ -5727,91 +7067,128 @@ async function collectMetrics(context, store) {
5727
7067
  diagnostics
5728
7068
  };
5729
7069
  }
7070
+ if (unprovableModeEvents > 0) {
7071
+ diagnostics.push(diagnostic({
7072
+ severity: "info",
7073
+ code: "provider-metrics-mode-unresolved",
7074
+ 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`,
7075
+ remediation: "Treat these figures as counterfactual; only OpenCode can later prove its mode from the configured plugin option"
7076
+ }));
7077
+ }
5730
7078
  return {
5731
7079
  providerId: HARNESSTRIM2,
5732
- // RFC 0005 §Importer degradation policy: character-only events are `legacy`, and "running in
5733
- // legacy mode is a supported steady state, not a warning".
5734
- mode: "legacy",
5735
- source: `harnesstrim TrimEvent JSONL (${TESTED_UPSTREAM} shape)`,
7080
+ // RFC 0005 §Importer degradation policy, decided by what this run read: `native` when the
7081
+ // file carried schema 1 events with producer IDs, `legacy` when it only had the character
7082
+ // stream. Legacy inside a native run is not a problem — those lines are older files.
7083
+ //
7084
+ // A native stream whose mode-carrying harness lines cannot prove a realized reduction (no
7085
+ // tokens, no pass-through) is `native-with-residue`: the import is native, but a part of it
7086
+ // could not be classed as realized, and the count above qualifies exactly how much. A bare
7087
+ // `native` must keep meaning "everything was classed".
7088
+ mode: sawNative && unprovableModeEvents > 0 ? "native-with-residue" : sawNative ? "native" : "legacy",
7089
+ source: `harnesstrim TrimEvent JSONL (schema 0 and schema 1)`,
5736
7090
  imported,
5737
7091
  skipped,
5738
7092
  cursor: lastCursor,
5739
7093
  diagnostics
5740
7094
  };
5741
7095
  }
5742
- async function plan(context, request) {
5743
- const claudeDirectory = context.fs.join(context.projectRoot, ".claude");
5744
- const skillsDirectory = context.fs.join(claudeDirectory, "skills");
5745
- const expectedArtifacts = Object.entries(CLAUDE_ARTIFACT_DIGESTS).map(([path, digest]) => ({
5746
- path: context.fs.join(skillsDirectory, ...path.split("/")),
7096
+ function skillArtifacts(context, harness) {
7097
+ const install = SKILLS_INSTALL[harness];
7098
+ if (install === void 0)
7099
+ return [];
7100
+ const skills = context.fs.join(context.projectRoot, install.directory, "skills");
7101
+ return Object.entries(SKILL_ARTIFACT_DIGESTS).map(([path, digest]) => ({
7102
+ path: context.fs.join(skills, ...path.split("/")),
5747
7103
  digest
5748
7104
  }));
5749
- if (!request.harnesses.some((harness) => harness.id === CLAUDE2)) {
7105
+ }
7106
+ async function plan(context, request) {
7107
+ const reviews = MANIFEST6.delegatedInstallReviews ?? {};
7108
+ const targets = request.harnesses.map((harness) => harness.id).filter((harness) => reviews[harness] !== void 0 && SKILLS_INSTALL[harness] !== void 0);
7109
+ if (targets.length === 0) {
5750
7110
  return { providerId: HARNESSTRIM2, desiredState: request.desiredState, actions: [] };
5751
7111
  }
5752
7112
  if (request.desiredState === "absent") {
5753
- const actions = expectedArtifacts.map((artifact) => ({
7113
+ const actions2 = targets.flatMap((harness) => skillArtifacts(context, harness).map((artifact) => ({
5754
7114
  kind: "remove-owned-change",
5755
- id: `harnesstrim-claude-skill-remove-${digestText(artifact.path).slice(7, 15)}`,
7115
+ id: `harnesstrim-${harness}-skill-remove-${digestText(artifact.path).slice(7, 15)}`,
5756
7116
  riskClass: "reversible",
5757
7117
  requiresNetwork: false,
5758
7118
  requiresElevation: false,
5759
7119
  affectedPaths: [artifact.path],
5760
7120
  affectedProcesses: [],
5761
- preconditions: ["the HarnessTrim skill still matches the reviewed 0.0.7 artifact"],
7121
+ preconditions: [
7122
+ `the HarnessTrim skill still matches the reviewed ${SKILLS_UPSTREAM} artifact`
7123
+ ],
5762
7124
  postconditions: ["the owned HarnessTrim skill is absent"],
5763
7125
  rollbackData: "file-snapshot",
5764
- explanation: `Remove the owned HarnessTrim Claude skill ${context.fs.basename(context.fs.dirname(artifact.path))}`,
7126
+ explanation: `Remove the owned HarnessTrim ${harness} skill ${context.fs.basename(context.fs.dirname(artifact.path))}`,
5765
7127
  path: artifact.path,
5766
- reverses: `harnesstrim-claude-skills-${digestText(context.projectRoot).slice(7, 15)}`,
5767
- target: { kind: "owned-file", path: artifact.path, digest: artifact.digest, mode: null }
5768
- }));
5769
- return { providerId: HARNESSTRIM2, desiredState: "absent", actions };
7128
+ reverses: `harnesstrim-${harness}-skills-${digestText(context.projectRoot).slice(7, 15)}`,
7129
+ target: {
7130
+ kind: "owned-file",
7131
+ path: artifact.path,
7132
+ digest: artifact.digest,
7133
+ mode: null
7134
+ }
7135
+ })));
7136
+ return { providerId: HARNESSTRIM2, desiredState: "absent", actions: actions2 };
5770
7137
  }
5771
7138
  const installed = await probeExecutable(context);
5772
- if (installed.version !== TESTED_UPSTREAM) {
7139
+ if (!installed.installed) {
5773
7140
  return { providerId: HARNESSTRIM2, desiredState: "configured", actions: [] };
5774
7141
  }
5775
- const review = MANIFEST4.delegatedInstallReview;
5776
- if (review === null)
5777
- throw new Error("HarnessTrim delegated-install review is missing");
5778
- const action = {
5779
- kind: "delegated-provider-install",
5780
- id: `harnesstrim-claude-skills-${digestText(context.projectRoot).slice(7, 15)}`,
5781
- riskClass: "delegated",
5782
- requiresNetwork: false,
5783
- requiresElevation: false,
5784
- affectedPaths: expectedArtifacts.map((artifact) => artifact.path),
5785
- affectedProcesses: ["harnesstrim"],
5786
- preconditions: [
5787
- "harnesstrim 0.0.7 is installed and on PATH",
5788
- "the reviewed installer writes only the declared Claude skill artifacts"
5789
- ],
5790
- postconditions: [
5791
- "HarnessTrim Claude skills match the reviewed 0.0.7 artifacts",
5792
- "no HarnessTrim hook or reduce-pipe instruction was added"
5793
- ],
5794
- rollbackData: "directory-snapshot",
5795
- explanation: "Install HarnessTrim Claude skills without a hook or reduce-pipe instruction",
5796
- executable: "harnesstrim",
5797
- args: ["install", "claude", context.projectRoot, "--apply", "--no-hook", "--no-instructions"],
5798
- containmentBoundary: review.containmentBoundary.map((path) => context.fs.join(context.projectRoot, path)),
5799
- expectedArtifacts,
5800
- protectedPaths: [
5801
- context.fs.join(claudeDirectory, "settings.json"),
5802
- context.fs.join(context.projectRoot, "CLAUDE.md")
5803
- ],
5804
- rollbackStrategy: "restore-snapshot",
5805
- snapshotSizeCapBytes: 1048576,
5806
- upstreamUninstallAvailable: review.upstreamUninstallAvailable
5807
- };
5808
- return { providerId: HARNESSTRIM2, desiredState: "configured", actions: [action] };
7142
+ const observed = await probeCapabilities(context);
7143
+ const actions = [];
7144
+ for (const harness of targets) {
7145
+ const review = reviews[harness];
7146
+ const install = SKILLS_INSTALL[harness];
7147
+ if (review === void 0 || install === void 0)
7148
+ continue;
7149
+ const reviewed = observed.capabilities === null ? installed.version === review.upstreamVersion : writeSetStillReviewed(observed.capabilities, harnessId(harness));
7150
+ if (!reviewed)
7151
+ continue;
7152
+ const expectedArtifacts = skillArtifacts(context, harness);
7153
+ actions.push({
7154
+ kind: "delegated-provider-install",
7155
+ id: `harnesstrim-${harness}-skills-${digestText(context.projectRoot).slice(7, 15)}`,
7156
+ riskClass: "delegated",
7157
+ requiresNetwork: false,
7158
+ requiresElevation: false,
7159
+ affectedPaths: expectedArtifacts.map((artifact) => artifact.path),
7160
+ affectedProcesses: ["harnesstrim"],
7161
+ preconditions: [
7162
+ "harnesstrim is installed, on PATH, and declares the reviewed write set",
7163
+ `the reviewed installer writes only the declared ${harness} skill artifacts`
7164
+ ],
7165
+ postconditions: [
7166
+ `HarnessTrim ${harness} skills match the reviewed ${review.upstreamVersion} artifacts`,
7167
+ "no HarnessTrim hook or reduce-pipe instruction was added"
7168
+ ],
7169
+ rollbackData: "directory-snapshot",
7170
+ explanation: `Install HarnessTrim ${harness} skills without a hook or reduce-pipe instruction`,
7171
+ executable: "harnesstrim",
7172
+ args: ["install", harness, context.projectRoot, "--apply", ...install.args],
7173
+ containmentBoundary: review.containmentBoundary.map((path) => context.fs.join(context.projectRoot, path)),
7174
+ expectedArtifacts,
7175
+ // The two files each installer reports skipping. Named here because they must not appear.
7176
+ protectedPaths: [
7177
+ context.fs.join(context.projectRoot, install.directory, install.hook),
7178
+ context.fs.join(context.projectRoot, install.instructions)
7179
+ ],
7180
+ rollbackStrategy: "restore-snapshot",
7181
+ snapshotSizeCapBytes: 1048576,
7182
+ upstreamUninstallAvailable: review.upstreamUninstallAvailable
7183
+ });
7184
+ }
7185
+ return { providerId: HARNESSTRIM2, desiredState: "configured", actions };
5809
7186
  }
5810
7187
  var harnesstrimAdapter = {
5811
- manifest: MANIFEST4,
5812
- detect: detect4,
7188
+ manifest: MANIFEST6,
7189
+ detect: detect6,
5813
7190
  identifiesCommand,
5814
- verify: verify4,
7191
+ verify: verify6,
5815
7192
  collectMetrics,
5816
7193
  plan,
5817
7194
  plansWithoutOwnership: true
@@ -5819,6 +7196,7 @@ var harnesstrimAdapter = {
5819
7196
 
5820
7197
  // packages/adapters/dist/src/providers/rtk-plan.js
5821
7198
  var RTK_HOOK_COMMAND_PREFIX = "rtk hook";
7199
+ var HOOK_LIST_HARNESSES = /* @__PURE__ */ new Set(["claude"]);
5822
7200
  function hookListPointer(eventName) {
5823
7201
  return `hooks.${eventName.replace(/\./g, "\\.")}`;
5824
7202
  }
@@ -5834,6 +7212,8 @@ function planTargets(context, request) {
5834
7212
  const harness = request.harnesses.find((entry) => entry.id === owned.scope.harness);
5835
7213
  if (harness === void 0)
5836
7214
  continue;
7215
+ if (!HOOK_LIST_HARNESSES.has(harness.id))
7216
+ continue;
5837
7217
  const point = harness.interceptionPoints.find((entry) => entry.scopeId === owned.scope.interceptionPoint);
5838
7218
  if (point === void 0)
5839
7219
  continue;
@@ -5966,11 +7346,11 @@ function installAction(context, manifest) {
5966
7346
  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"
5967
7347
  ],
5968
7348
  postconditions: ["`rtk --version` reports a version inside the tested range"],
5969
- // Not `package-inventory`: that promises an inventory-based reversal nothing implements. RFC
5970
- // 0004 allows rollback only by restoring snapshots, "never by inventing an uninstall command",
5971
- // and a package is not a file — so an installed package survives a rollback and the executor
5972
- // says so rather than letting the report imply otherwise.
5973
- rollbackData: "none",
7349
+ // The declared value the executor now implements (RFC 0009 §Initial delivery order item 1):
7350
+ // the executor captures whatever the channel has installed before the install runs, and a
7351
+ // rollback restores that captured version through the same channel. `none` is left only for
7352
+ // channels this build cannot ask about — there the executor says the package stayed instead.
7353
+ rollbackData: channelCanReportInventory(channel.id) ? "package-inventory" : "none",
5974
7354
  explanation: `Install RTK through ${channel.id}`,
5975
7355
  packageManager: channel.id,
5976
7356
  // The channel's own name for the package, because they differ: `rtk-ai.rtk` on winget and
@@ -6009,7 +7389,8 @@ function buildRtkPlan(input) {
6009
7389
  // packages/adapters/dist/src/providers/rtk.js
6010
7390
  var RTK2 = providerId("rtk");
6011
7391
  var CLAUDE3 = harnessId("claude");
6012
- var MANIFEST5 = {
7392
+ var OPENCODE3 = harnessId("opencode");
7393
+ var MANIFEST7 = {
6013
7394
  schemaVersion: MANIFEST_SCHEMA_VERSION,
6014
7395
  id: RTK2,
6015
7396
  displayName: "RTK",
@@ -6047,10 +7428,53 @@ var MANIFEST5 = {
6047
7428
  sourceReference: "docs/spikes/2.5-live-verification-log.md",
6048
7429
  upstreamVersion: "0.42.0"
6049
7430
  }
7431
+ },
7432
+ /**
7433
+ * OpenCode, from spike 9.1.
7434
+ *
7435
+ * Separate entries rather than adding `OPENCODE` to the two above, because the surface is not
7436
+ * the same one: RTK reaches Claude Code through a `PreToolUse` hook matching `Bash`, and
7437
+ * OpenCode through a plugin on `tool.execute.before`. A capability entry names one surface
7438
+ * list, and merging these would claim that RTK serves `Bash/pre-tool-use` on OpenCode — a
7439
+ * point OpenCode does not have.
7440
+ *
7441
+ * `shell.command.rewrite` and `shell.output.reduce` again arrive together, for the reason they
7442
+ * do on Claude Code: the plugin calls `rtk rewrite`, and the rewritten command is what filters
7443
+ * its own output. One interception point, two capabilities.
7444
+ */
7445
+ {
7446
+ capability: "shell.command.rewrite",
7447
+ mode: "exclusive",
7448
+ harnesses: [OPENCODE3],
7449
+ // `tool.execute` is the family the harness manifest declares as shell-executing. The plugin
7450
+ // narrows to `tool === "bash" || tool === "shell"` inside that family, which is every shell
7451
+ // route OpenCode exposes — unlike Claude Code on Windows, there is no second one to miss.
7452
+ surfaces: [{ toolFamily: "tool.execute", interceptionPoint: "tool-execute-before" }],
7453
+ evidence: {
7454
+ sourceReference: "docs/spikes/9.1-rtk-opencode-observation-log.md",
7455
+ upstreamVersion: "0.44.0"
7456
+ }
7457
+ },
7458
+ {
7459
+ capability: "shell.output.reduce",
7460
+ mode: "exclusive",
7461
+ harnesses: [OPENCODE3],
7462
+ surfaces: [{ toolFamily: "tool.execute", interceptionPoint: "tool-execute-before" }],
7463
+ evidence: {
7464
+ sourceReference: "docs/spikes/9.1-rtk-opencode-observation-log.md",
7465
+ upstreamVersion: "0.44.0"
7466
+ }
6050
7467
  }
6051
7468
  ],
6052
7469
  platforms: [
6053
- { os: "windows", wsl: false, supported: true, limitation: null },
7470
+ {
7471
+ os: "windows",
7472
+ wsl: false,
7473
+ supported: true,
7474
+ // Spike 9.1. Named on Windows alone because Windows is where Desktop was run; the same
7475
+ // defect on another platform's Desktop build would be a guess until someone watches it.
7476
+ 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"
7477
+ },
6054
7478
  { os: "linux", wsl: true, supported: true, limitation: null },
6055
7479
  { os: "macos", wsl: false, supported: true, limitation: null },
6056
7480
  { os: "linux", wsl: false, supported: true, limitation: null }
@@ -6060,6 +7484,31 @@ var MANIFEST5 = {
6060
7484
  harness: CLAUDE3,
6061
7485
  testedVersions: { minimum: "2.0.0", maximum: "2.1.212" },
6062
7486
  verificationTier: "canary"
7487
+ },
7488
+ {
7489
+ harness: OPENCODE3,
7490
+ // Spike 9.1 watched interception on the CLI at `1.18.11` and watched it fail on Desktop at
7491
+ // `1.18.14`. The range covers both because both were observed; the failure is a declared
7492
+ // platform limitation below rather than a narrower range, because it is a property of the
7493
+ // host application and not of the OpenCode version — the same 1.18.x plugin loads in both.
7494
+ testedVersions: { minimum: "1.18.11", maximum: "1.18.14" },
7495
+ /**
7496
+ * `config-only`, even though spike 9.1 watched a canary-grade interception here — the counter
7497
+ * moved by exactly the number of shell calls the OpenCode session made.
7498
+ *
7499
+ * A tier is what `verify` can prove on the user's machine, not what a spike proved once on a
7500
+ * clean one. RTK's receipt is its history database, and the `commands` table carries
7501
+ * `timestamp`, token counts, `exec_time_ms`, and `project_path` — no harness column, and no
7502
+ * column standing in for one. The spike could attribute those rows because it ran OpenCode
7503
+ * alone in a scratch directory and read the counter either side; `verify` runs where both
7504
+ * harnesses are wired to the same binary and writing into the same table, and cannot say
7505
+ * which of them a row came from.
7506
+ *
7507
+ * Declaring `canary` here would credit RTK's whole command history to whichever harness the
7508
+ * reader happened to be looking at. Claude Code keeps `canary` because its own hook receipt
7509
+ * is per-harness; this one is provider-wide, and RFC 0007 asks the question per harness.
7510
+ */
7511
+ verificationTier: "config-only"
6063
7512
  }
6064
7513
  ],
6065
7514
  installationChannels: [
@@ -6101,11 +7550,12 @@ var MANIFEST5 = {
6101
7550
  // location differs per platform; `rtkDatabasePath` is the single derivation.
6102
7551
  locations: ["<user data directory>/rtk/history.db"]
6103
7552
  },
6104
- delegatedInstallReview: null
7553
+ delegatedInstallReviews: null
6105
7554
  };
6106
- var TESTED_VERSIONS2 = { minimum: "0.40.0", maximum: "0.42.0" };
6107
- var VERSION_PATTERN5 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
7555
+ var TESTED_VERSIONS2 = { minimum: "0.40.0", maximum: "0.44.0" };
7556
+ var VERSION_PATTERN7 = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
6108
7557
  var HOOK_COMMAND_PATTERN2 = /(^|[\\/\s"'])rtk(\.exe)?([\s"']|$)/i;
7558
+ var PLUGIN_MODULE_PATTERN3 = /(^|[\\/])rtk\.(ts|js|mjs|cjs|mts|cts)$/i;
6109
7559
  function isRecord4(value) {
6110
7560
  return typeof value === "object" && value !== null && !Array.isArray(value);
6111
7561
  }
@@ -6148,7 +7598,7 @@ function parseRtkAnalytics(text) {
6148
7598
  function harnessesWiredToRtk(configs) {
6149
7599
  const wired = /* @__PURE__ */ new Set();
6150
7600
  for (const config of configs) {
6151
- if (config.commands.some((command) => HOOK_COMMAND_PATTERN2.test(command))) {
7601
+ if (config.commands.some((command) => identifiesCommand2(command))) {
6152
7602
  wired.add(config.harnessId);
6153
7603
  }
6154
7604
  }
@@ -6175,17 +7625,17 @@ async function readVersion(context) {
6175
7625
  ]
6176
7626
  };
6177
7627
  }
6178
- const version2 = VERSION_PATTERN5.exec(outcome2.stdout)?.[1] ?? null;
7628
+ const version3 = VERSION_PATTERN7.exec(outcome2.stdout)?.[1] ?? null;
6179
7629
  return {
6180
- version: version2,
6181
- verdict: version2 === null ? null : classifyVersion(version2, TESTED_VERSIONS2),
7630
+ version: version3,
7631
+ verdict: version3 === null ? null : classifyVersion(version3, TESTED_VERSIONS2),
6182
7632
  executable: outcome2.executablePath,
6183
7633
  evidence: [
6184
7634
  evidence({
6185
7635
  kind: "version-output",
6186
7636
  source: "rtk --version",
6187
7637
  path: outcome2.executablePath,
6188
- detail: version2 === null ? "reported no recognisable version" : `reported ${version2}`
7638
+ detail: version3 === null ? "reported no recognisable version" : `reported ${version3}`
6189
7639
  })
6190
7640
  ]
6191
7641
  };
@@ -6221,11 +7671,11 @@ async function readAnalytics(context) {
6221
7671
  ]
6222
7672
  };
6223
7673
  }
6224
- async function detect5(context) {
7674
+ async function detect7(context) {
6225
7675
  const evidenceItems = [];
6226
7676
  const warnings = [];
6227
- const version2 = await readVersion(context);
6228
- evidenceItems.push(...version2.evidence);
7677
+ const version3 = await readVersion(context);
7678
+ evidenceItems.push(...version3.evidence);
6229
7679
  const configured = harnessesWiredToRtk(context.harnessConfigs);
6230
7680
  for (const harness of configured) {
6231
7681
  evidenceItems.push(evidence({
@@ -6235,15 +7685,15 @@ async function detect5(context) {
6235
7685
  detail: "names rtk in a hook command"
6236
7686
  }));
6237
7687
  }
6238
- if (version2.verdict === "unknown-newer") {
7688
+ if (version3.verdict === "unknown-newer") {
6239
7689
  warnings.push(diagnostic({
6240
7690
  severity: "warning",
6241
7691
  code: "provider-version-unknown-newer",
6242
- message: `RTK ${String(version2.version)} is newer than the versions Token Harness has observed (up to ${TESTED_VERSIONS2.maximum})`,
7692
+ message: `RTK ${String(version3.version)} is newer than the versions Token Harness has observed (up to ${TESTED_VERSIONS2.maximum})`,
6243
7693
  remediation: "Check the release notes for analytics or hook changes before applying a plan"
6244
7694
  }));
6245
7695
  }
6246
- const state = version2.version === null ? configured.length > 0 ? "broken" : "absent" : configured.length > 0 ? "configured" : "installed";
7696
+ const state = version3.version === null ? configured.length > 0 ? "broken" : "absent" : configured.length > 0 ? "configured" : "installed";
6247
7697
  if (state === "broken") {
6248
7698
  warnings.push(diagnostic({
6249
7699
  severity: "error",
@@ -6256,36 +7706,61 @@ async function detect5(context) {
6256
7706
  return {
6257
7707
  providerId: RTK2,
6258
7708
  state,
6259
- version: version2.version,
6260
- executable: version2.executable,
7709
+ version: version3.version,
7710
+ executable: version3.executable,
6261
7711
  // Detection reports what it saw; which channel installed it is an installation-stage
6262
7712
  // question and guessing it from a path would be a guess.
6263
7713
  installationChannel: null,
6264
- versionVerdict: version2.verdict,
7714
+ versionVerdict: version3.verdict,
6265
7715
  configuredHarnesses: configured,
6266
7716
  unmanagedHarnessesConfigured: [],
6267
- // RFC 0002 §Providers may exceed the managed surface. RTK's manifest covers Claude
6268
- // Code only, so the qualifier RFC 0006's doctor transcript uses for HarnessTrim does
6269
- // not apply here.
7717
+ /**
7718
+ * RFC 0002 §Providers may exceed the managed surface.
7719
+ *
7720
+ * The field asks whether this manifest names a harness with no adapter behind it. It names two,
7721
+ * Claude Code and OpenCode, and both are managed — so the qualifier RFC 0006's doctor transcript
7722
+ * uses for HarnessTrim does not apply, and the report says "not configured for any harness"
7723
+ * without hedging.
7724
+ *
7725
+ * `rtk init --agent` separately targets Cursor, Windsurf, Cline, Kilo Code, Antigravity, Kimi,
7726
+ * Pi, Hermes, and Droid. That is RTK exceeding *Token Harness*, not this manifest exceeding the
7727
+ * managed set, and it stays out of the flag: claiming those here would make the resolver
7728
+ * responsible for harnesses this build has no adapter to inspect.
7729
+ */
6270
7730
  supportsUnmanagedHarnesses: false,
6271
7731
  // RFC 0004 §Brownfield adoption: nothing has been applied by Token Harness yet, so
6272
7732
  // every installation it finds is the user's.
6273
7733
  managedByTokenHarness: false,
7734
+ /**
7735
+ * Claude Code alone, and independent of the observed version.
7736
+ *
7737
+ * RTK's assignment is produced by this build writing the hook itself — `rtk-plan.ts` appends the
7738
+ * entry — so nothing about the installed `rtk` decides whether the state is reachable. What does
7739
+ * decide it is which harness schema that builder knows, and it knows one: `HOOK_LIST_HARNESSES`
7740
+ * holds `claude` and the builder refuses anything else, because a `{matcher, hooks:[…]}` object
7741
+ * at `hooks.<event>` is Claude Code's schema and nothing else's.
7742
+ *
7743
+ * RTK claims OpenCode too, from spike 9.1, and that claim is real — detected, adopted, verified
7744
+ * and measured there. It is simply not *written* there: its OpenCode integration is a plugin
7745
+ * module `rtk init -g --opencode` installs globally, which this build has no action for. Saying
7746
+ * so here is what stops the resolver assigning a scope nothing can produce.
7747
+ */
7748
+ assignableHarnesses: [CLAUDE3],
6274
7749
  evidence: evidenceItems,
6275
7750
  warnings
6276
7751
  };
6277
7752
  }
6278
- async function verify5(context) {
7753
+ async function verify7(context) {
6279
7754
  const checks = [];
6280
7755
  const diagnostics = [];
6281
- const version2 = await readVersion(context);
7756
+ const version3 = await readVersion(context);
6282
7757
  checks.push({
6283
7758
  id: "executable-resolves",
6284
- status: version2.version === null ? "fail" : "pass",
6285
- summary: version2.version === null ? "rtk could not be run" : `rtk ${version2.version}`,
6286
- achievedTier: version2.version === null ? null : "presence",
6287
- evidence: version2.evidence,
6288
- remediation: version2.version === null ? "Install RTK, or add it to PATH" : null
7759
+ status: version3.version === null ? "fail" : "pass",
7760
+ summary: version3.version === null ? "rtk could not be run" : `rtk ${version3.version}`,
7761
+ achievedTier: version3.version === null ? null : "presence",
7762
+ evidence: version3.evidence,
7763
+ remediation: version3.version === null ? "Install RTK, or add it to PATH" : null
6289
7764
  });
6290
7765
  const configured = harnessesWiredToRtk(context.harnessConfigs);
6291
7766
  checks.push({
@@ -6392,7 +7867,7 @@ function toEvent2(row2, context) {
6392
7867
  context: {
6393
7868
  // An empty `project_path` means RTK recorded no directory; attributing the event to
6394
7869
  // the directory `metrics` happens to run in would invent an attribution.
6395
- projectId: projectPath === null || projectPath === "" ? "p_unattributed" : context.projectIdFor(projectPath),
7870
+ projectId: projectPath === null || projectPath === "" ? UNATTRIBUTED_PROJECT_ID : context.projectIdFor(projectPath),
6396
7871
  // RTK proxies commands for whichever harness invoked it and does not record which.
6397
7872
  // The hook that wires it is per harness, but a *row* carries no harness, and reading
6398
7873
  // one off the current configuration would attribute months of history to today's
@@ -6582,22 +8057,22 @@ async function collectMetrics2(context, store) {
6582
8057
  }
6583
8058
  var SOURCE_LABEL = "rtk history.db (commands)";
6584
8059
  function identifiesCommand2(command) {
6585
- return HOOK_COMMAND_PATTERN2.test(command);
8060
+ return HOOK_COMMAND_PATTERN2.test(command) || PLUGIN_MODULE_PATTERN3.test(command);
6586
8061
  }
6587
8062
  async function plan2(context, request) {
6588
- const version2 = await readVersion(context);
8063
+ const version3 = await readVersion(context);
6589
8064
  return buildRtkPlan({
6590
8065
  context,
6591
8066
  request,
6592
- installed: version2.version !== null,
8067
+ installed: version3.version !== null,
6593
8068
  identifiesCommand: identifiesCommand2,
6594
- installationChannels: MANIFEST5.installationChannels
8069
+ installationChannels: MANIFEST7.installationChannels
6595
8070
  });
6596
8071
  }
6597
8072
  var rtkAdapter = {
6598
- manifest: MANIFEST5,
6599
- detect: detect5,
6600
- verify: verify5,
8073
+ manifest: MANIFEST7,
8074
+ detect: detect7,
8075
+ verify: verify7,
6601
8076
  collectMetrics: collectMetrics2,
6602
8077
  identifiesCommand: identifiesCommand2,
6603
8078
  plan: plan2
@@ -6611,7 +8086,6 @@ function listProviderAdapters() {
6611
8086
 
6612
8087
  // apps/cli/dist/src/commands/plan.js
6613
8088
  var DEFAULT_PROFILE = "safe";
6614
- var ASSIGNABLE_PROVIDERS = ["rtk"];
6615
8089
  async function computePlan(context) {
6616
8090
  const diagnostics = [];
6617
8091
  const providerAdapters = listProviderAdapters().filter((adapter) => context.provider === null || adapter.manifest.id === context.provider);
@@ -6638,11 +8112,6 @@ async function computePlan(context) {
6638
8112
  harnessConfigs.push(...inspection.summaries);
6639
8113
  }
6640
8114
  }
6641
- const providers = providerAdapters.map((adapter) => ({
6642
- id: adapter.manifest.id,
6643
- capabilities: adapter.manifest.capabilities,
6644
- assignable: ASSIGNABLE_PROVIDERS.includes(adapter.manifest.id)
6645
- }));
6646
8115
  const providerContext = detectionContext === null || context.adapters === null ? null : {
6647
8116
  ...detectionContext,
6648
8117
  harnessConfigs,
@@ -6650,12 +8119,19 @@ async function computePlan(context) {
6650
8119
  localDatabase: context.adapters.localDatabase,
6651
8120
  projectIdFor: context.adapters.projectIdFor
6652
8121
  };
8122
+ const assignable = /* @__PURE__ */ new Map();
6653
8123
  if (providerContext !== null) {
6654
8124
  for (const adapter of providerAdapters) {
6655
8125
  const detection = await adapter.detect(providerContext);
6656
8126
  versions.providers[adapter.manifest.id] = detection.version;
8127
+ assignable.set(adapter.manifest.id, new Set(detection.assignableHarnesses));
6657
8128
  }
6658
8129
  }
8130
+ const providers = providerAdapters.map((adapter) => ({
8131
+ id: adapter.manifest.id,
8132
+ capabilities: adapter.manifest.capabilities,
8133
+ assignableHarnesses: assignable.get(adapter.manifest.id) ?? /* @__PURE__ */ new Set()
8134
+ }));
6659
8135
  const resolution = resolveOwnership({
6660
8136
  profile,
6661
8137
  harnesses: present,
@@ -6665,7 +8141,9 @@ async function computePlan(context) {
6665
8141
  harness: context.harness
6666
8142
  });
6667
8143
  const actions = [];
8144
+ const blocked = [];
6668
8145
  if (providerContext !== null) {
8146
+ const rows = context.compatibilityRows ?? COMPATIBILITY_ROWS;
6669
8147
  for (const adapter of providerAdapters) {
6670
8148
  const owned = resolution.ownership.filter((entry) => entry.owner === adapter.manifest.id);
6671
8149
  if (owned.length === 0 && adapter.plansWithoutOwnership !== true)
@@ -6675,7 +8153,35 @@ async function computePlan(context) {
6675
8153
  harnesses: present,
6676
8154
  desiredState: "configured"
6677
8155
  });
6678
- actions.push(...providerPlan.actions);
8156
+ if (providerPlan.actions.length === 0)
8157
+ continue;
8158
+ const touched = [...new Set(owned.map((entry) => entry.scope.harness))];
8159
+ if (touched.length === 0)
8160
+ touched.push(...present.map((harness) => harness.id));
8161
+ let admitted = true;
8162
+ for (const harness of touched) {
8163
+ const admission = admitManagedMutation(rows, {
8164
+ provider: adapter.manifest.id,
8165
+ providerVersion: versions.providers[adapter.manifest.id] ?? null,
8166
+ harness,
8167
+ harnessVersion: versions.harnesses[harness] ?? null,
8168
+ os: context.platform.os,
8169
+ wsl: context.platform.isWsl
8170
+ });
8171
+ if (admission.state !== "admitted") {
8172
+ admitted = false;
8173
+ blocked.push({
8174
+ provider: adapter.manifest.id,
8175
+ providerVersion: versions.providers[adapter.manifest.id] ?? null,
8176
+ harness,
8177
+ harnessVersion: versions.harnesses[harness] ?? null,
8178
+ verdict: admission.verdict,
8179
+ missing: admission.missing
8180
+ });
8181
+ }
8182
+ }
8183
+ if (admitted)
8184
+ actions.push(...providerPlan.actions);
6679
8185
  }
6680
8186
  }
6681
8187
  const report = {
@@ -6733,7 +8239,7 @@ async function computePlan(context) {
6733
8239
  remediation: null
6734
8240
  }));
6735
8241
  }
6736
- if (resolution.ownership.length > 0 && actions.length === 0) {
8242
+ if (resolution.ownership.length > 0 && actions.length === 0 && blocked.length === 0) {
6737
8243
  diagnostics.push(diagnostic({
6738
8244
  severity: "info",
6739
8245
  code: "already-in-desired-state",
@@ -6741,6 +8247,15 @@ async function computePlan(context) {
6741
8247
  remediation: null
6742
8248
  }));
6743
8249
  }
8250
+ const refusalIsTheOutcome = actions.length === 0;
8251
+ for (const entry of blocked) {
8252
+ diagnostics.push(diagnostic({
8253
+ severity: refusalIsTheOutcome ? "error" : "warning",
8254
+ code: "managed-mutation-blocked",
8255
+ message: `plan refuses managed mutation of ${entry.harness} by ${entry.provider}${entry.providerVersion !== null ? ` at ${entry.providerVersion}` : ""}: ${entry.missing}`,
8256
+ remediation: "Add a compatibility row whose fixture proves this combination, or configure this integration by hand"
8257
+ }));
8258
+ }
6744
8259
  for (const conflict of resolution.conflicts) {
6745
8260
  diagnostics.push(diagnostic({
6746
8261
  severity: "error",
@@ -6749,7 +8264,7 @@ async function computePlan(context) {
6749
8264
  remediation: conflict.remediation
6750
8265
  }));
6751
8266
  }
6752
- return { report, versions, diagnostics, present, harnessConfigs };
8267
+ return { report, versions, diagnostics, present, harnessConfigs, blocked };
6753
8268
  }
6754
8269
  async function persist(context, computed) {
6755
8270
  if (context.adapters === null || context.stateRoot === null)
@@ -6774,7 +8289,11 @@ async function runPlan(context) {
6774
8289
  const computed = await computePlan(context);
6775
8290
  const persisted = await persist(context, computed);
6776
8291
  const report = { ...computed.report, persisted };
6777
- const exitCode = report.conflicts.length > 0 ? EXIT_CODES["blocked-by-conflict"] : EXIT_CODES.ok;
8292
+ const exitCode = planExitCode({
8293
+ blocked: computed.blocked.length,
8294
+ actions: report.actions.length,
8295
+ conflicts: report.conflicts.length
8296
+ });
6778
8297
  return commandResult({
6779
8298
  command: "plan",
6780
8299
  exitCode,
@@ -6782,6 +8301,13 @@ async function runPlan(context) {
6782
8301
  diagnostics: computed.diagnostics
6783
8302
  });
6784
8303
  }
8304
+ function planExitCode(counts) {
8305
+ if (counts.blocked > 0 && counts.actions === 0)
8306
+ return EXIT_CODES["unsupported-environment"];
8307
+ if (counts.conflicts > 0)
8308
+ return EXIT_CODES["blocked-by-conflict"];
8309
+ return EXIT_CODES.ok;
8310
+ }
6785
8311
 
6786
8312
  // apps/cli/dist/src/commands/apply.js
6787
8313
  var PLANS_DIRECTORY = "plans";
@@ -6845,6 +8371,9 @@ async function runApply(context) {
6845
8371
  const diagnostics = [];
6846
8372
  const computed = await computePlan(context);
6847
8373
  diagnostics.push(...computed.diagnostics);
8374
+ if (computed.blocked.length > 0 && computed.report.actions.length === 0) {
8375
+ return finish("rejected", EXIT_CODES["unsupported-environment"], emptyReport("rejected"), diagnostics);
8376
+ }
6848
8377
  if (computed.report.conflicts.length > 0) {
6849
8378
  return finish("rejected", EXIT_CODES["blocked-by-conflict"], emptyReport("rejected"), diagnostics);
6850
8379
  }
@@ -6999,10 +8528,40 @@ async function runDoctor(context) {
6999
8528
  projectIdFor: context.adapters?.projectIdFor ?? (() => "p_unattributed")
7000
8529
  })));
7001
8530
  const problemCount = harnesses.filter(isBroken).length + providers.filter(isBroken).length + [...harnesses, ...providers].filter((detection) => detection.versionVerdict === "unknown-newer").length;
8531
+ const rows = context.compatibilityRows ?? COMPATIBILITY_ROWS;
8532
+ const providersWithCoverage = providers.map((detection) => {
8533
+ if (detection.state !== "configured" || detection.configuredHarnesses.length === 0) {
8534
+ return detection;
8535
+ }
8536
+ const uncovered = detection.configuredHarnesses.flatMap((harness) => {
8537
+ const harnessVersion = harnesses.find((candidate) => candidate.harnessId === harness)?.version ?? null;
8538
+ const admission = admitManagedMutation(rows, {
8539
+ provider: detection.providerId,
8540
+ providerVersion: detection.version,
8541
+ harness,
8542
+ harnessVersion,
8543
+ os: context.platform.os,
8544
+ wsl: context.platform.isWsl
8545
+ });
8546
+ if (admission.state === "admitted")
8547
+ return [];
8548
+ return [
8549
+ diagnostic({
8550
+ severity: "warning",
8551
+ code: "no-compatibility-row",
8552
+ message: `no compatibility row covers ${detection.providerId} on ${harness}${detection.version !== null ? ` at ${detection.version}` : ""} \u2014 ${admission.missing}`,
8553
+ remediation: "Add a compatibility row whose fixture proves this combination, or configure this integration by hand"
8554
+ })
8555
+ ];
8556
+ });
8557
+ if (uncovered.length === 0)
8558
+ return detection;
8559
+ return { ...detection, warnings: [...detection.warnings, ...uncovered] };
8560
+ });
7002
8561
  const report = {
7003
8562
  platform: context.platform,
7004
8563
  harnesses,
7005
- providers,
8564
+ providers: providersWithCoverage,
7006
8565
  problemCount
7007
8566
  };
7008
8567
  return commandResult({
@@ -7094,14 +8653,42 @@ async function runMetrics(context) {
7094
8653
  }
7095
8654
  }
7096
8655
  }
8656
+ const projectId = context.adapters === null ? null : context.adapters.projectIdFor(context.projectRoot);
7097
8657
  const events = [];
7098
8658
  for await (const event of store.query({
7099
8659
  since: window.sinceInstant,
7100
8660
  until: window.untilInstant,
8661
+ ...projectId === null ? {} : { projectId },
7101
8662
  ...context.provider === null ? {} : { providerIds: [context.provider] }
7102
8663
  })) {
7103
8664
  events.push(event);
7104
8665
  }
8666
+ if (projectId === null) {
8667
+ diagnostics.push(diagnostic({
8668
+ severity: "info",
8669
+ code: "metrics-not-project-scoped",
8670
+ message: "This report covers every project in the store: no project identity was available",
8671
+ remediation: null
8672
+ }));
8673
+ } else {
8674
+ let unattributed = 0;
8675
+ for await (const _event of store.query({
8676
+ since: window.sinceInstant,
8677
+ until: window.untilInstant,
8678
+ projectId: UNATTRIBUTED_PROJECT_ID,
8679
+ ...context.provider === null ? {} : { providerIds: [context.provider] }
8680
+ })) {
8681
+ unattributed += 1;
8682
+ }
8683
+ if (unattributed > 0) {
8684
+ diagnostics.push(diagnostic({
8685
+ severity: "info",
8686
+ code: "metrics-unattributed-excluded",
8687
+ message: `${String(unattributed)} operation${unattributed === 1 ? "" : "s"} named no project and are excluded from this report`,
8688
+ remediation: null
8689
+ }));
8690
+ }
8691
+ }
7105
8692
  const report = aggregateEvents({
7106
8693
  events,
7107
8694
  windowStart: window.windowStart,
@@ -7226,7 +8813,12 @@ async function runRollback(context) {
7226
8813
  fs,
7227
8814
  snapshots: built.store,
7228
8815
  journal: built.journal,
7229
- now: context.now
8816
+ now: context.now,
8817
+ // Package inventories, when the transaction captured any: restoring a package needs the same
8818
+ // process runner the install used. Absent, the rollback reports those packages as not
8819
+ // restored instead of attempting a silent reinstall.
8820
+ runner: context.adapters.runner,
8821
+ cwd: context.projectRoot
7230
8822
  });
7231
8823
  diagnostics.push(...result.diagnostics);
7232
8824
  const report = empty(result.exitCode === 0 ? "rolled-back" : "dirty");
@@ -7348,7 +8940,6 @@ async function runUninstall(context) {
7348
8940
  }
7349
8941
 
7350
8942
  // apps/cli/dist/src/commands/status.js
7351
- var ASSIGNABLE_PROVIDERS2 = ["rtk"];
7352
8943
  async function runStatus(context) {
7353
8944
  const report = {
7354
8945
  platform: context.platform,
@@ -7386,12 +8977,8 @@ async function runStatus(context) {
7386
8977
  configs.push(...inspection.summaries);
7387
8978
  }
7388
8979
  const providerAdapters = listProviderAdapters();
7389
- const providers = providerAdapters.map((adapter) => ({
7390
- id: adapter.manifest.id,
7391
- capabilities: adapter.manifest.capabilities,
7392
- assignable: ASSIGNABLE_PROVIDERS2.includes(adapter.manifest.id)
7393
- }));
7394
8980
  const observedVersions = {};
8981
+ const providers = [];
7395
8982
  for (const adapter of providerAdapters) {
7396
8983
  const detection = await adapter.detect({
7397
8984
  ...detectionContext,
@@ -7401,6 +8988,11 @@ async function runStatus(context) {
7401
8988
  projectIdFor: context.adapters.projectIdFor
7402
8989
  });
7403
8990
  observedVersions[adapter.manifest.id] = detection.version;
8991
+ providers.push({
8992
+ id: adapter.manifest.id,
8993
+ capabilities: adapter.manifest.capabilities,
8994
+ assignableHarnesses: new Set(detection.assignableHarnesses)
8995
+ });
7404
8996
  }
7405
8997
  const resolution = resolveOwnership({
7406
8998
  profile: "safe",
@@ -7461,14 +9053,15 @@ function upgradeAction(input) {
7461
9053
  ],
7462
9054
  postconditions: [`${input.packageName} reports ${input.target}`],
7463
9055
  /**
7464
- * `none`, and the consequence is worth stating where a reviewer sees it.
9056
+ * `package-inventory` where the channel can report what is installed, `none` otherwise.
7465
9057
  *
7466
9058
  * An update replaces a binary. Rolling the transaction back restores files and leaves the new
7467
- * version in place, so the executor's `install-not-reversible` diagnostic is not boilerplate
7468
- * here — it is the difference between the machine being restored and the *configuration*
7469
- * being restored.
9059
+ * version in place, so the executor's receipt is not boilerplate here — it is the difference
9060
+ * between the machine being restored and the *configuration* being restored. With an
9061
+ * inventory-capturing channel the rollback also reinstalls the previous version, and the
9062
+ * receipt says per package whether that reinstall took.
7470
9063
  */
7471
- rollbackData: "none",
9064
+ rollbackData: channelCanReportInventory(input.channel) ? "package-inventory" : "none",
7472
9065
  explanation: `Update ${input.packageName} from ${input.installed} to ${input.target} through ${input.channel}`,
7473
9066
  packageManager: input.channel,
7474
9067
  packageName: input.packageName,
@@ -7741,11 +9334,12 @@ async function runVerify(context) {
7741
9334
  presentHarnesses
7742
9335
  );
7743
9336
  for (const harnessId2 of harnesses) {
9337
+ const declaredTier = adapter.manifest.harnesses.find((entry) => entry.harness === harnessId2)?.verificationTier ?? verification.declaredTier;
7744
9338
  results.push({
7745
9339
  providerId: adapter.manifest.id,
7746
9340
  harnessId: harnessId2,
7747
- status: statusFor(verification.achievedTier, verification.declaredTier),
7748
- declaredTier: verification.declaredTier,
9341
+ status: statusFor(verification.achievedTier, declaredTier),
9342
+ declaredTier,
7749
9343
  managedByTokenHarness: MANAGED_PROVIDERS.includes(adapter.manifest.id),
7750
9344
  checks: verification.checks
7751
9345
  });
@@ -7942,7 +9536,8 @@ function renderApplyReport(report, context, command = "apply") {
7942
9536
  lines.push(` ${displayPath(path, context.home)}`);
7943
9537
  lines.push("");
7944
9538
  }
7945
- lines.push((OUTCOME_LINES[command] ?? OUTCOME_LINES["apply"])?.[report.outcome] ?? "");
9539
+ const removedNothing = command === "uninstall" && report.outcome === "committed" && report.results.length > 0 && report.results.every((result) => result.status !== "applied");
9540
+ lines.push(removedNothing ? "Removed nothing: no owned entry was recognised. Everything is untouched." : (OUTCOME_LINES[command] ?? OUTCOME_LINES["apply"])?.[report.outcome] ?? "");
7946
9541
  return document(lines);
7947
9542
  }
7948
9543
 
@@ -8609,7 +10204,7 @@ function usageText(topic) {
8609
10204
  }
8610
10205
 
8611
10206
  // apps/cli/dist/src/version.js
8612
- var TOOL_VERSION = "0.1.1";
10207
+ var TOOL_VERSION = "0.1.3";
8613
10208
 
8614
10209
  // apps/cli/dist/src/run.js
8615
10210
  var DEFAULT_COMMANDS = {
@@ -8786,7 +10381,8 @@ async function run(options) {
8786
10381
  until: invocation.options.until,
8787
10382
  planId: invocation.options.plan,
8788
10383
  confirmed: invocation.options.yes,
8789
- metrics: options.metrics ?? null
10384
+ metrics: options.metrics ?? null,
10385
+ compatibilityRows: options.compatibilityRows ?? null
8790
10386
  };
8791
10387
  const table = options.commands ?? DEFAULT_COMMANDS;
8792
10388
  let result;