feat(voyage)!: marketplace handoff — rename plugins/ultraplan-local to plugins/voyage [skip-docs]

Session 5 of voyage-rebrand (V6). Operator-authorized cross-plugin scope.

- git mv plugins/ultraplan-local plugins/voyage (rename detected, history preserved)
- .claude-plugin/marketplace.json: voyage entry replaces ultraplan-local
- CLAUDE.md: voyage row in plugin list, voyage in design-system consumer list
- README.md: bulk rename ultra*-local commands -> trek* commands; ultraplan-local refs -> voyage; type discriminators (type: trekbrief/trekreview); session-title pattern (voyage:<command>:<slug>); v4.0.0 release-note paragraph
- plugins/voyage/.claude-plugin/plugin.json: homepage/repository URLs point to monorepo voyage path
- plugins/voyage/verify.sh: drop URL whitelist exception (no longer needed)

Closes voyage-rebrand. bash plugins/voyage/verify.sh PASS 7/7. npm test 361/361.
This commit is contained in:
Kjell Tore Guttormsen 2026-05-05 15:37:52 +02:00
commit 7a90d348ad
149 changed files with 26 additions and 33 deletions

View file

@ -0,0 +1,41 @@
// lib/parsers/jaccard.mjs
// Jaccard similarity for SC4 determinism floor.
//
// jaccard(A, B) = |A ∩ B| / |A B|
// Inputs are arrays of strings; deduplicated internally.
// Both empty → 1.0 (vacuously identical). One empty → 0.0.
/**
* Compute Jaccard similarity between two string sets.
* @param {string[]} setA
* @param {string[]} setB
* @returns {number} similarity in [0, 1]
*/
export function jaccardSimilarity(setA, setB) {
if (!Array.isArray(setA) || !Array.isArray(setB)) {
throw new TypeError('jaccardSimilarity: both inputs must be arrays');
}
const a = new Set(setA);
const b = new Set(setB);
if (a.size === 0 && b.size === 0) return 1.0;
if (a.size === 0 || b.size === 0) return 0.0;
let intersection = 0;
for (const x of a) {
if (b.has(x)) intersection += 1;
}
const union = a.size + b.size - intersection;
return intersection / union;
}
/**
* Check whether a similarity meets a threshold.
* @param {number} similarity
* @param {number} threshold
* @returns {boolean}
*/
export function meetsThreshold(similarity, threshold) {
if (typeof similarity !== 'number' || typeof threshold !== 'number') return false;
if (!Number.isFinite(similarity) || !Number.isFinite(threshold)) return false;
return similarity >= threshold;
}