opencode-swarm 7.114.9 → 7.115.0
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/dist/cli/{config-doctor-jy3mdh9t.js → config-doctor-htzxe394.js} +2 -2
- package/dist/cli/{curator-5xt89z05.js → curator-hpc4tsjv.js} +4 -4
- package/dist/cli/{curator-llm-factory-pnhvf3s3.js → curator-llm-factory-6n9sgaj8.js} +4 -4
- package/dist/cli/{guardrail-explain-7aztgtyh.js → guardrail-explain-hecpd738.js} +5 -5
- package/dist/cli/{guardrail-log-84wnx273.js → guardrail-log-dzbqcgz9.js} +3 -3
- package/dist/cli/{hive-promoter-q899q2m6.js → hive-promoter-0kfb4vjk.js} +4 -4
- package/dist/cli/{index-54sevh81.js → index-2etc05tv.js} +6 -6
- package/dist/cli/{index-yfedche9.js → index-8j5d3ytd.js} +1 -1
- package/dist/cli/{index-m9m7p49w.js → index-m0rce3x6.js} +2 -2
- package/dist/cli/{index-7jmrbp68.js → index-m43zgtjn.js} +1 -1
- package/dist/cli/{index-typ408f4.js → index-m766gvdt.js} +16 -16
- package/dist/cli/{index-9bsmzfk3.js → index-m7hc7nn7.js} +5 -1
- package/dist/cli/{index-a45jq4b7.js → index-nvc0nsvg.js} +6 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/{schema-aymfdrsb.js → schema-bqn7g3ez.js} +3 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +9 -0
- package/dist/index.js +84 -51
- package/dist/tools/apply-patch.d.ts +21 -0
- package/dist/utils/fuzzy-match.d.ts +115 -0
- package/dist/utils/sequence-matcher.d.ts +83 -0
- package/package.json +1 -1
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
* Pure TypeScript, no shell/git/external binaries, standard node:fs sync I/O.
|
|
8
8
|
*/
|
|
9
9
|
import type { ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
10
|
+
import { loadPluginConfigWithMeta } from '../config';
|
|
11
|
+
/**
|
|
12
|
+
* DI seam for hermetic config-load substitution in tests (AGENTS.md invariant 7).
|
|
13
|
+
* Mirrors the pattern at `src/tools/context-status.ts:81-88`. Tests override
|
|
14
|
+
* `_internals.loadPluginConfigWithMeta` and restore it in `afterEach` instead
|
|
15
|
+
* of writing real config files or using `mock.module` (which leaks across
|
|
16
|
+
* test files in Bun's shared runner).
|
|
17
|
+
*/
|
|
18
|
+
export declare const _internals: {
|
|
19
|
+
loadPluginConfigWithMeta: typeof loadPluginConfigWithMeta;
|
|
20
|
+
};
|
|
10
21
|
/** Per-file error detail in the structured output. */
|
|
11
22
|
export interface ApplyPatchFileError {
|
|
12
23
|
hunkIndex: number;
|
|
@@ -45,6 +56,16 @@ export interface ApplyPatchArgs {
|
|
|
45
56
|
allowCreates?: boolean;
|
|
46
57
|
allowDeletes?: boolean;
|
|
47
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Opt-in fuzzy-matching options threaded from `execute` → `processFileDiff`
|
|
61
|
+
* → `applyHunks`. Both flags default false (exact-match-only, B3 decision).
|
|
62
|
+
*/
|
|
63
|
+
export interface ApplyHunksOptions {
|
|
64
|
+
/** Enable fuzzy fallback (strategies 1-8) on exact-match failure. */
|
|
65
|
+
fuzzyMatch: boolean;
|
|
66
|
+
/** Additionally enable strategy 9 (context_aware). Only effective with fuzzyMatch. */
|
|
67
|
+
fuzzyMatchContextAware: boolean;
|
|
68
|
+
}
|
|
48
69
|
/**
|
|
49
70
|
* Swarm unified-diff patch tool (formerly registered as apply_patch).
|
|
50
71
|
* Renamed to swarm_apply_patch so it no longer shadows the native opencode
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy text-matching engine — faithful TypeScript port of hermes-agent's
|
|
3
|
+
* `tools/fuzzy_match.py` (issue #1718).
|
|
4
|
+
*
|
|
5
|
+
* Implements a 9-strategy matching chain that finds and replaces text,
|
|
6
|
+
* tolerating the whitespace, indentation, escape-sequence, and Unicode
|
|
7
|
+
* drift common in LLM-generated patches. Integrated into `apply-patch`
|
|
8
|
+
* as an **opt-in fallback** (default off) gated by the `apply_patch.fuzzy_match`
|
|
9
|
+
* and `apply_patch.fuzzy_match_context_aware` config flags.
|
|
10
|
+
*
|
|
11
|
+
* Porting conventions:
|
|
12
|
+
* - Operates on **UTF-16 code units** consistently (`s[i]`, `s.length`).
|
|
13
|
+
* Matches `src/utils/sequence-matcher.ts`. Round-trip safe.
|
|
14
|
+
* - Per-line stripping uses JS `String.prototype.trim()`, which strips the
|
|
15
|
+
* Unicode WhiteSpace + LineTerminator set. Coincides with Python
|
|
16
|
+
* `str.strip()` for the ASCII/BMP content in the test suite; minor
|
|
17
|
+
* divergence on exotic whitespace categories is acceptable.
|
|
18
|
+
* - Strategy 9 (`contextAware`) is opt-in via the `includeContextAware`
|
|
19
|
+
* option to `fuzzyFindAndReplace`. It is the loosest, most-false-positive-
|
|
20
|
+
* prone strategy and is separately gated in apply-patch by
|
|
21
|
+
* `apply_patch.fuzzy_match_context_aware`.
|
|
22
|
+
*/
|
|
23
|
+
/** A `[start, end)` character span in the content string. */
|
|
24
|
+
type Span = [number, number];
|
|
25
|
+
/** Result of {@link fuzzyFindAndReplace}. */
|
|
26
|
+
export interface FuzzyResult {
|
|
27
|
+
/** Modified content on success; original content on failure. */
|
|
28
|
+
content: string;
|
|
29
|
+
/** Number of replacements made (0 on failure). */
|
|
30
|
+
matchCount: number;
|
|
31
|
+
/** Name of the strategy that matched, or `null` on failure. */
|
|
32
|
+
strategy: string | null;
|
|
33
|
+
/** `null` on success; an error description on failure. */
|
|
34
|
+
error: string | null;
|
|
35
|
+
}
|
|
36
|
+
/** Options for {@link fuzzyFindAndReplace}. */
|
|
37
|
+
export interface FuzzyOptions {
|
|
38
|
+
/**
|
|
39
|
+
* When true (default at the utility level), the `context_aware` strategy
|
|
40
|
+
* (9) is included in the chain. Strategy 9 is the loosest/most-false-
|
|
41
|
+
* positive-prone strategy (requires 50% of lines to reach 0.80 per-line
|
|
42
|
+
* similarity) and has quadratic cost, so it is bounded by an internal
|
|
43
|
+
* cell-count cap.
|
|
44
|
+
*
|
|
45
|
+
* **Default divergence (intentional):** the utility defaults this to
|
|
46
|
+
* `true` so the byte-faithful ported hermes test suite passes unmodified
|
|
47
|
+
* (strategy 9 is a legitimate part of the matching chain and some test
|
|
48
|
+
* cases depend on it). The `apply-patch` integration — the only consumer
|
|
49
|
+
* today — explicitly passes `false` unless the separate
|
|
50
|
+
* `apply_patch.fuzzy_match_context_aware` config flag is set, honoring
|
|
51
|
+
* issue #1718's Non-Goal "do not port strategy 9 as default-on" at the
|
|
52
|
+
* integration layer. New callers should pass `false` unless they
|
|
53
|
+
* specifically want strategy 9.
|
|
54
|
+
*/
|
|
55
|
+
includeContextAware?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Maps Unicode typographic characters to their ASCII equivalents.
|
|
59
|
+
* Some replacements EXPAND a single code point into multiple ASCII chars
|
|
60
|
+
* (em-dash → "--", ellipsis → "..."); the position-remap helpers handle the
|
|
61
|
+
* resulting offset divergence.
|
|
62
|
+
*/
|
|
63
|
+
export declare const UNICODE_MAP: Record<string, string>;
|
|
64
|
+
/** Normalize Unicode typographic characters to ASCII equivalents. */
|
|
65
|
+
export declare function unicodeNormalize(text: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Find and replace text using a chain of increasingly fuzzy strategies.
|
|
68
|
+
*
|
|
69
|
+
* Strategies are tried in order; the first that yields matches wins. On a
|
|
70
|
+
* unique match (or when `replaceAll` is true), the replacement is applied.
|
|
71
|
+
* On failure, returns the original content with an error description.
|
|
72
|
+
*
|
|
73
|
+
* Guards (ported verbatim from hermes):
|
|
74
|
+
* - Ambiguity: `>1 match && !replaceAll` → fail with a helpful message.
|
|
75
|
+
* - Escape-drift: `\'`/`\"` present in both old+new but absent from the
|
|
76
|
+
* matched file region → block (transport serialization artifact).
|
|
77
|
+
* - Selective unescape: `\t`/`\r` in new_string → real bytes only when the
|
|
78
|
+
* matched file region contains the corresponding control char. `\n` excluded.
|
|
79
|
+
* - Unicode preservation: under strategy 7, unchanged spans keep the file's
|
|
80
|
+
* original Unicode characters rather than the ASCII-normalized equivalents.
|
|
81
|
+
*/
|
|
82
|
+
export declare function fuzzyFindAndReplace(content: string, oldString: string, newString: string, replaceAll?: boolean, options?: FuzzyOptions): FuzzyResult;
|
|
83
|
+
/** Strategy 1: exact string match, non-overlapping. */
|
|
84
|
+
export declare function strategyExact(content: string, pattern: string): Span[];
|
|
85
|
+
/** Strategy 2: per-line `.trim()` + block equality. */
|
|
86
|
+
export declare function strategyLineTrimmed(content: string, pattern: string): Span[];
|
|
87
|
+
/** Strategy 3: collapse `[ \t]+` → single space, preserve newlines. */
|
|
88
|
+
export declare function strategyWhitespaceNormalized(content: string, pattern: string): Span[];
|
|
89
|
+
/** Strategy 4: strip all leading whitespace per line (lstrip). */
|
|
90
|
+
export declare function strategyIndentationFlexible(content: string, pattern: string): Span[];
|
|
91
|
+
/** Strategy 5: unescape `\n`/`\t`/`\r` literals → bytes, then exact match. */
|
|
92
|
+
export declare function strategyEscapeNormalized(content: string, pattern: string): Span[];
|
|
93
|
+
/** Strategy 6: trim only first and last lines, sliding window. */
|
|
94
|
+
export declare function strategyTrimmedBoundary(content: string, pattern: string): Span[];
|
|
95
|
+
/** Strategy 7: Unicode normalization (smart quotes, em/en dash, ellipsis, NBSP). */
|
|
96
|
+
export declare function strategyUnicodeNormalized(content: string, pattern: string): Span[];
|
|
97
|
+
/** Strategy 8: anchor on first+last lines, similarity for the middle. */
|
|
98
|
+
export declare function strategyBlockAnchor(content: string, pattern: string): Span[];
|
|
99
|
+
export declare function strategyContextAware(content: string, pattern: string): Span[];
|
|
100
|
+
/**
|
|
101
|
+
* Find lines in `content` most similar to `oldString` for "did you mean?" feedback.
|
|
102
|
+
*
|
|
103
|
+
* Returns a formatted string showing the closest matching lines with context
|
|
104
|
+
* and line numbers, or `''` if no useful match is found.
|
|
105
|
+
*/
|
|
106
|
+
export declare function findClosestLines(oldString: string, content: string, contextLines?: number, maxResults?: number): string;
|
|
107
|
+
/**
|
|
108
|
+
* Return a "Did you mean..." snippet for plain no-match errors.
|
|
109
|
+
*
|
|
110
|
+
* Gated so the hint only fires for actual "old_string not found" failures.
|
|
111
|
+
* Ambiguous-match, escape-drift, and identical-strings errors all have
|
|
112
|
+
* `matchCount === 0` but a did-you-mean snippet would be misleading.
|
|
113
|
+
*/
|
|
114
|
+
export declare function formatNoMatchHint(error: string | null, matchCount: number, oldString: string, content: string): string;
|
|
115
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Faithful TypeScript port of CPython's `difflib.SequenceMatcher`.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Ratcliff/Obershelp pattern-matching algorithm with the
|
|
5
|
+
* "popular element" (autojunk) guard, so that `.ratio()` produces values
|
|
6
|
+
* identical to CPython 3.x for the BMP character content used by the
|
|
7
|
+
* fuzzy-match strategies. The thresholds in `src/utils/fuzzy-match.ts`
|
|
8
|
+
* (0.50 / 0.70 / 0.80) were tuned against CPython's exact `2*M/T` ratio
|
|
9
|
+
* formula, so substituting a Levenshtein-based library would silently
|
|
10
|
+
* break those thresholds.
|
|
11
|
+
*
|
|
12
|
+
* Porting notes:
|
|
13
|
+
* - Operates on **UTF-16 code units** (`s[i]`, `s.length`, `s.charCodeAt(i)`)
|
|
14
|
+
* consistently throughout. CPython operates on Unicode code points; for
|
|
15
|
+
* ASCII/BMP content the two coincide. Astral-plane characters (emoji)
|
|
16
|
+
* consume two UTF-16 units, so ratios for emoji-heavy input may differ
|
|
17
|
+
* marginally from CPython. This is acceptable for the fuzzy-match use
|
|
18
|
+
* case (source-code patches) and is round-trip-safe — no index mixing,
|
|
19
|
+
* no file corruption.
|
|
20
|
+
* - `autojunk` defaults to `true`, matching CPython. When `b.length >= 200`
|
|
21
|
+
* and an element appears more than 1% of the time in `b`, it is treated
|
|
22
|
+
* as "popular" and excluded as an anchor candidate in `find_longest_match`.
|
|
23
|
+
* This is essential for ratio fidelity on real file sections.
|
|
24
|
+
*/
|
|
25
|
+
export interface Match {
|
|
26
|
+
/** Start index in sequence `a`. */
|
|
27
|
+
a: number;
|
|
28
|
+
/** Start index in sequence `b`. */
|
|
29
|
+
b: number;
|
|
30
|
+
/** Length of the matching block. */
|
|
31
|
+
size: number;
|
|
32
|
+
}
|
|
33
|
+
export type OpcodeTag = 'equal' | 'replace' | 'delete' | 'insert';
|
|
34
|
+
export interface Opcode {
|
|
35
|
+
tag: OpcodeTag;
|
|
36
|
+
i1: number;
|
|
37
|
+
i2: number;
|
|
38
|
+
j1: number;
|
|
39
|
+
j2: number;
|
|
40
|
+
}
|
|
41
|
+
/** A predicate that marks a character as "junk" (never a sync point). */
|
|
42
|
+
export type IsJunk = ((ch: string) => boolean) | null;
|
|
43
|
+
/**
|
|
44
|
+
* Difflib-compatible sequence matcher. Construct with two strings, then
|
|
45
|
+
* call `.ratio()`, `.get_matching_blocks()`, or `.get_opcodes()`.
|
|
46
|
+
*/
|
|
47
|
+
export declare class SequenceMatcher {
|
|
48
|
+
private a;
|
|
49
|
+
private b;
|
|
50
|
+
private isJunk;
|
|
51
|
+
private autoJunk;
|
|
52
|
+
private b2j;
|
|
53
|
+
private popularSet;
|
|
54
|
+
private matchingBlocks;
|
|
55
|
+
constructor(isJunk: IsJunk, a: string, b: string, autoJunk?: boolean);
|
|
56
|
+
/** Allow changing the input sequences (mirrors CPython `set_seqs`/`set_seq2`). */
|
|
57
|
+
setSeqs(a: string, b: string): void;
|
|
58
|
+
/** Precompute the `b` index map and the popular-element set. */
|
|
59
|
+
private chainB;
|
|
60
|
+
/**
|
|
61
|
+
* Find the longest matching block in `a[alo:ahi]` and `b[blo:bhi]`.
|
|
62
|
+
*
|
|
63
|
+
* Returns `{ a, b, size }` where `size` is maximal; ties are broken by
|
|
64
|
+
* smallest `a`, then smallest `b` (CPython canonical tiebreak).
|
|
65
|
+
*
|
|
66
|
+
* A "match" means `a[i] === b[j]` and the run extends as far as possible.
|
|
67
|
+
* Elements in the popular set are only used as anchor candidates when no
|
|
68
|
+
* non-popular match exists (CPython behavior: popular elements are
|
|
69
|
+
* excluded from `b2j`, so they only match opportunistically via the
|
|
70
|
+
* extending run after a non-popular anchor).
|
|
71
|
+
*/
|
|
72
|
+
findLongestMatch(alo: number, ahi: number, blo: number, bhi: number): Match;
|
|
73
|
+
/** Recursively compute the list of matching blocks (descending order). */
|
|
74
|
+
getMatchingBlocks(): Match[];
|
|
75
|
+
/**
|
|
76
|
+
* Return a float in [0, 1]: `2*M / T`, where `M` is the total matched
|
|
77
|
+
* characters and `T` is the sum of the two sequence lengths.
|
|
78
|
+
* Returns `1.0` when both sequences are empty (matches CPython).
|
|
79
|
+
*/
|
|
80
|
+
ratio(): number;
|
|
81
|
+
/** Compute the opcodes describing how to turn `a` into `b`. */
|
|
82
|
+
getOpcodes(): Opcode[];
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.115.0",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|