opencode-swarm 7.15.0 → 7.17.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.
@@ -0,0 +1,199 @@
1
+ /**
2
+ * LanguageBackend — behavior-bearing extension of LanguageProfile.
3
+ *
4
+ * `LanguageProfile` (in `./profiles.ts`) is a passive data record: it
5
+ * declares which build commands, test frameworks, linters etc. exist for a
6
+ * language, but does not know how to run them. A `LanguageBackend` adds
7
+ * optional behavior hooks. Every hook has a registry-driven default in
8
+ * `./default-backend.ts`, so a backend that overrides nothing still works.
9
+ *
10
+ * Invariant boundaries (per AGENTS.md):
11
+ * - Backends NEVER spawn subprocesses. They return command-arrays only.
12
+ * The single spawn site stays in `src/tools/test-runner.ts` (and the
13
+ * existing helpers in `src/build/discovery.ts:isCommandAvailable`),
14
+ * each of which already satisfies invariant 3 (cwd, stdin: 'ignore',
15
+ * timeout, bounded stdio, killable). This rule is enforced by
16
+ * `tests/unit/lang/backend-purity.test.ts`.
17
+ * - Backends do no top-level `bun:` imports and no direct `Bun.*` calls
18
+ * (invariant 2 — runtime portability). Same purity test enforces this.
19
+ *
20
+ * Extension model: a new language is a single new file under
21
+ * `src/lang/backends/<id>.ts` plus one import line in
22
+ * `src/lang/backends/index.ts`. The default backend handles everything the
23
+ * new file does not override.
24
+ */
25
+ import type { LanguageProfile } from './profiles';
26
+ /**
27
+ * Selected test framework for a project, including the concrete spawn argv
28
+ * and explicit cwd. Returned by `LanguageBackend.selectTestFramework`.
29
+ */
30
+ export interface TestFrameworkSelection {
31
+ /** Framework id matching one of LanguageProfile.test.frameworks[*].name. */
32
+ name: string;
33
+ /**
34
+ * Spawn-arg array. Never includes shell metacharacters or relies on shell
35
+ * interpretation — passed directly to `bunSpawn(cmd, ...)`. Backends that
36
+ * cannot avoid a shell-mediated invocation (e.g. PowerShell `-EncodedCommand`)
37
+ * still produce an array; the array's first element is the binary and the
38
+ * rest are individual arguments.
39
+ */
40
+ cmd: string[];
41
+ /** Explicit cwd for the spawn (invariant 3). */
42
+ cwd: string;
43
+ /** Human-readable note: "package.json scripts.test", "Cargo.toml", etc. */
44
+ detectedVia: string;
45
+ /**
46
+ * When true, the `files` argument to `buildTestCommand` is ignored — the
47
+ * framework runs all tests in the project by default (e.g. cargo test,
48
+ * go test ./..., swift test). Per-file selection is the framework's
49
+ * concern, not the backend's.
50
+ */
51
+ filesIgnored?: boolean;
52
+ }
53
+ /**
54
+ * Structured summary of a test run. The default backend returns only
55
+ * exit-code-driven `ok` and the raw streams; richer parsing is opt-in per
56
+ * backend (e.g. the TypeScript backend parses bun:test JSON output).
57
+ */
58
+ export interface TestRunSummary {
59
+ ok: boolean;
60
+ raw: {
61
+ stdout: string;
62
+ stderr: string;
63
+ exitCode: number;
64
+ };
65
+ passed?: number;
66
+ failed?: number;
67
+ skipped?: number;
68
+ durationMs?: number;
69
+ /**
70
+ * Total tests reported by the framework. When undefined the caller may
71
+ * compute `passed + failed + skipped`.
72
+ */
73
+ total?: number;
74
+ /**
75
+ * Coverage percentage parsed from the framework's output. Optional —
76
+ * frameworks without a uniform coverage-line format (mocha, go-test,
77
+ * etc.) leave this undefined.
78
+ */
79
+ coveragePercent?: number;
80
+ }
81
+ /**
82
+ * Scope strings used by the test-runner tool. Re-exported here so backends
83
+ * can accept them in `buildTestCommand` without a circular import back to
84
+ * `src/tools/test-runner.ts`.
85
+ */
86
+ export type TestScope = 'all' | 'convention' | 'graph' | 'impact';
87
+ /**
88
+ * Options influencing build-command construction. Backends may ignore
89
+ * unrecognized opts; the default backend honors all of these.
90
+ */
91
+ export interface BuildTestCommandOpts {
92
+ scope?: TestScope;
93
+ coverage?: boolean;
94
+ }
95
+ /**
96
+ * Selected web/UI framework for a project (PROJECT_FRAMEWORK template
97
+ * variable). Best-effort detection — backends return null when no
98
+ * framework signal is available, and the architect's prompt then ships
99
+ * with the `unresolved (run /swarm preflight)` sentinel.
100
+ */
101
+ export interface FrameworkSelection {
102
+ /** Display name, e.g. "react", "vue", "django", "gin". */
103
+ name: string;
104
+ /** Human-readable note describing what evidence supports this. */
105
+ detectedVia: string;
106
+ }
107
+ /**
108
+ * Selected build command for a project.
109
+ */
110
+ export interface BuildCommandSelection {
111
+ /** Display name matching `LanguageProfile.build.commands[*].name`. */
112
+ name: string;
113
+ /** Spawn-arg array. Same constraints as TestFrameworkSelection.cmd. */
114
+ cmd: string[];
115
+ /** Explicit cwd. */
116
+ cwd: string;
117
+ /** Human-readable note: "Cargo.toml", "package.json#scripts.build", etc. */
118
+ detectedVia: string;
119
+ }
120
+ /**
121
+ * The behavior surface for a language. Every method is optional; the
122
+ * default-backend implementation in `./default-backend.ts` provides
123
+ * registry-driven fallbacks that work for most languages out of the box.
124
+ */
125
+ export interface LanguageBackend extends LanguageProfile {
126
+ /**
127
+ * Stronger signal than extension matching alone. Default behavior
128
+ * (provided by the default backend) checks that any of
129
+ * `profile.build.detectFiles` is present in `dir`. A backend may override
130
+ * to add language-specific heuristics (e.g. the TypeScript backend reads
131
+ * `package.json#scripts.test` to confirm a test runner is configured).
132
+ */
133
+ detectProject?(dir: string): Promise<boolean>;
134
+ /**
135
+ * Pick the highest-priority test framework whose detect file exists in
136
+ * `dir` AND whose binary is on PATH. Returns `null` if no framework is
137
+ * configured + available. Default behavior consults
138
+ * `profile.test.frameworks` sorted by priority and uses
139
+ * `isCommandAvailable` from `src/build/discovery.ts`.
140
+ */
141
+ selectTestFramework?(dir: string): Promise<TestFrameworkSelection | null>;
142
+ /**
143
+ * Build the spawn argv for a given framework + file list. Default
144
+ * behavior implements the full 14-framework legacy switch from
145
+ * `src/tools/test-runner.ts` (coverage flags, scope-dependent file
146
+ * inclusion, platform-specific python/python3, pester -EncodedCommand,
147
+ * gradle wrapper detection, ctest build-dir detection, dart/flutter
148
+ * selection, bundle/rspec detection, minitest require_relative).
149
+ * Backends with non-trivial language-specific shape override this.
150
+ *
151
+ * Returns `null` when the framework is unknown to this backend; callers
152
+ * (test-runner dispatch) treat that as "no test command available".
153
+ */
154
+ buildTestCommand?(framework: string, files: string[], dir: string, opts?: BuildTestCommandOpts): string[] | null;
155
+ /**
156
+ * Parse stdout/stderr into a structured summary. Default behavior
157
+ * returns only `{ ok: exitCode === 0, raw: { stdout, stderr, exitCode } }`
158
+ * — no regex, no framework-specific assumptions. Backends that want
159
+ * pass/fail counts (e.g. the TypeScript backend's bun:test JSON parser)
160
+ * override this.
161
+ */
162
+ parseTestOutput?(framework: string, stdout: string, stderr: string, exitCode: number): TestRunSummary;
163
+ /**
164
+ * Map a source file to candidate test files (convention scope). Default
165
+ * behavior: swap `src/` ↔ `tests/` and the extension to one of the
166
+ * profile's test-file conventions. Returns the candidate paths sorted by
167
+ * likelihood.
168
+ */
169
+ testFilesFor?(sourceFile: string, dir: string): Promise<string[]>;
170
+ /**
171
+ * Extract import paths from a source file (graph/impact scope). Default
172
+ * behavior: returns `[]` — the analyzer falls back to convention scope
173
+ * with an explicit "graph scope unavailable for {lang}" notice. Backends
174
+ * with import-graph support (TypeScript, Python, Go in this phase set)
175
+ * override this.
176
+ */
177
+ extractImports?(sourceFile: string, source: string): string[];
178
+ /**
179
+ * Pick the build command for this project. Default behavior consults
180
+ * `profile.build.commands` sorted by priority + binary-on-PATH check.
181
+ */
182
+ selectBuildCommand?(dir: string): Promise<BuildCommandSelection | null>;
183
+ /**
184
+ * Detect the dominant web/UI framework in this project (React, Django,
185
+ * Gin, etc.) for the architect's PROJECT_FRAMEWORK template variable.
186
+ * Returns null when no framework signal is present — the default
187
+ * backend's implementation looks for common framework manifest fields
188
+ * (package.json deps, requirements.txt entries, go.mod requires).
189
+ */
190
+ selectFramework?(dir: string): Promise<FrameworkSelection | null>;
191
+ /**
192
+ * Identify primary entry-point files for this project (ENTRY_POINTS
193
+ * template variable). Default behavior: read profile-specific manifests
194
+ * (package.json `main`/`bin`, pyproject `[project.scripts]`, go.mod
195
+ * + main.go, etc.). Returns absolute or repo-relative paths sorted by
196
+ * confidence. Empty list maps to the sentinel.
197
+ */
198
+ selectEntryPoints?(dir: string): Promise<string[]>;
199
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Go backend.
3
+ *
4
+ * Phase 5 of language-agnostic plugin work. Overrides `extractImports`
5
+ * with Go-specific import regexes — both single-line `import "x"` and
6
+ * grouped `import (\n "a"\n "b"\n)` forms — so the test-impact analyzer
7
+ * can build a graph for Go projects.
8
+ *
9
+ * Invariants identical to other backends — see `python.ts` and
10
+ * `typescript.ts` for the rationale; backend-purity test enforces.
11
+ */
12
+ import type { LanguageBackend } from '../backend';
13
+ declare function extractImports(_sourceFile: string, source: string): string[];
14
+ /**
15
+ * Build the Go backend from the registered profile.
16
+ */
17
+ export declare function buildGoBackend(): LanguageBackend;
18
+ export declare const _internals: {
19
+ extractImports: typeof extractImports;
20
+ };
21
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Backend registration surface.
3
+ *
4
+ * Adding a new language with first-class behavior is a single new file
5
+ * under this directory plus one `import` line + one `register(...)` call
6
+ * here. Importing this module triggers all registrations.
7
+ *
8
+ * Phase 2 ships only the TypeScript backend. Phase 5 will add Python and
9
+ * Go backends with import-graph extractors.
10
+ */
11
+ /**
12
+ * Register all known backends. Idempotent via the module-level `registered`
13
+ * flag.
14
+ *
15
+ * The flag is load-bearing, not redundant with the registry's own duplicate
16
+ * guard: `buildTypescriptBackend()` constructs a fresh object every call, so
17
+ * a second call without the flag would produce a different reference and
18
+ * trip `LanguageBackendRegistry.register`'s `existing !== backend` throw.
19
+ * The flag prevents that by short-circuiting before constructing.
20
+ */
21
+ export declare function registerAllBackends(): void;
22
+ /**
23
+ * Test-only: reset the registration flag and unregister all known
24
+ * backends. Allows tests to verify the registration logic itself without
25
+ * cross-file singleton pollution.
26
+ */
27
+ export declare function _resetForTesting(): void;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Python backend.
3
+ *
4
+ * Phase 5 of language-agnostic plugin work. Overrides `extractImports`
5
+ * with Python-specific import regexes (`import x`, `from x import y`)
6
+ * so the test-impact analyzer can build a graph for Python projects.
7
+ * Other hooks (selectTestFramework, selectBuildCommand, parseTestOutput,
8
+ * testFilesFor) inherit the registry-driven defaults.
9
+ *
10
+ * Invariants (same as typescript.ts):
11
+ * - No subprocess calls; defers binary checks to `isCommandAvailable`.
12
+ * - No `bun:` imports, no `Bun.*` calls.
13
+ * - Backend-purity test in `tests/unit/lang/backend-purity.test.ts`
14
+ * enforces both at PR time.
15
+ */
16
+ import type { LanguageBackend } from '../backend';
17
+ declare function extractImports(_sourceFile: string, source: string): string[];
18
+ /**
19
+ * Build the Python backend from the registered profile.
20
+ */
21
+ export declare function buildPythonBackend(): LanguageBackend;
22
+ export declare const _internals: {
23
+ extractImports: typeof extractImports;
24
+ };
25
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * TypeScript / JavaScript backend.
3
+ *
4
+ * Overrides the default backend's `selectTestFramework` to honor
5
+ * `package.json#scripts.test` (the canonical signal in the JS ecosystem)
6
+ * and `extractImports` to parse ES6 + CommonJS imports for the
7
+ * graph/impact analyzer.
8
+ *
9
+ * Phase 2 deliverable: this backend exists and registers itself, but
10
+ * `src/tools/test-runner.ts` and `src/test-impact/analyzer.ts` do not yet
11
+ * call into it — they still use their existing switch-statement helpers.
12
+ * Phase 3 wires the test-runner dispatch through this backend.
13
+ *
14
+ * Invariants:
15
+ * - No subprocess calls (defers to `isCommandAvailable` from
16
+ * `../../build/discovery` for binary checks; that helper already
17
+ * satisfies invariant 3).
18
+ * - No `bun:` imports, no `Bun.*` calls (invariant 2).
19
+ * - No mutation of LANGUAGE_REGISTRY at import time — only registers a
20
+ * backend in LANGUAGE_BACKEND_REGISTRY via `backends/index.ts`.
21
+ */
22
+ import type { LanguageBackend } from '../backend';
23
+ interface PackageJsonShape {
24
+ scripts?: Record<string, string>;
25
+ dependencies?: Record<string, string>;
26
+ devDependencies?: Record<string, string>;
27
+ }
28
+ /**
29
+ * Read package.json. Returns null when missing or malformed. Bounded by a
30
+ * single sync `fs.readFileSync` — no subprocess.
31
+ *
32
+ * Routed through `_internals.readPackageJsonRaw` so tests can substitute a
33
+ * different reader without touching the filesystem. The adversarial review
34
+ * (PR #825) flagged that this seam was advertised but unused.
35
+ */
36
+ declare function readPackageJsonRaw(dir: string): PackageJsonShape | null;
37
+ /** Convenience: read just `scripts.test` (used by tests). */
38
+ declare function readPackageJsonTestScript(dir: string): string | null;
39
+ /**
40
+ * Map a `package.json#scripts.test` invocation to a framework name. The
41
+ * mapping mirrors `detectTestFramework` in `src/tools/test-runner.ts:286–326`.
42
+ */
43
+ declare function frameworkFromScriptsTest(script: string): string | null;
44
+ /**
45
+ * Build the TypeScript backend from the registered profile. Backend
46
+ * registration happens in `./index.ts` (the single import-and-register
47
+ * surface) — this module just exports the factory so the registration
48
+ * site is explicit.
49
+ */
50
+ export declare function buildTypescriptBackend(): LanguageBackend;
51
+ export declare const _internals: {
52
+ readPackageJsonRaw: typeof readPackageJsonRaw;
53
+ readPackageJsonTestScript: typeof readPackageJsonTestScript;
54
+ frameworkFromScriptsTest: typeof frameworkFromScriptsTest;
55
+ };
56
+ export {};
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Default backend — registry-driven implementations of every optional hook
3
+ * on `LanguageBackend`. A backend that overrides nothing still works for
4
+ * common cases: any profile with build.commands + test.frameworks +
5
+ * lint.linters declared correctly will get a working `selectTestFramework`,
6
+ * `selectBuildCommand`, etc. without writing any backend code.
7
+ *
8
+ * No subprocess calls happen here — `isCommandAvailable` is the only seam
9
+ * to the environment, and it lives in `src/build/discovery.ts` with full
10
+ * invariant-3 properties (cwd, stdin: 'ignore', timeout, bounded stdio).
11
+ */
12
+ import type { BuildCommandSelection, BuildTestCommandOpts, FrameworkSelection, LanguageBackend, TestFrameworkSelection, TestRunSummary } from './backend';
13
+ import type { LanguageProfile } from './profiles';
14
+ /**
15
+ * Tokenize a string command into an array. Splits on whitespace; respects
16
+ * single and double quotes for argument grouping. Used to convert profile
17
+ * `cmd` strings (which today are written as "npx tsc --noEmit" etc.) into
18
+ * the array form `bunSpawn` expects.
19
+ *
20
+ * This deliberately does NOT support shell metacharacters (`;`, `&`, `|`,
21
+ * `>`, `<`, backticks, `$()`) — backends with non-trivial commands must
22
+ * override `buildTestCommand`/`selectBuildCommand` to return a custom
23
+ * `cmd: string[]`. Splitting a profile string into words is a 90% case;
24
+ * the 10% override their backend.
25
+ */
26
+ export declare function tokenizeCommand(cmd: string): string[];
27
+ /**
28
+ * Default selectTestFramework: highest-priority framework whose detect
29
+ * file exists AND whose binary is on PATH. Returns null if none.
30
+ */
31
+ export declare function defaultSelectTestFramework(profile: LanguageProfile, dir: string): Promise<TestFrameworkSelection | null>;
32
+ /**
33
+ * Default buildTestCommand: full 14-framework switch ported verbatim from
34
+ * the legacy logic that lived in `src/tools/test-runner.ts:buildTestCommand`
35
+ * (pre-Phase-3b). Handles per-framework coverage flags, scope-dependent
36
+ * file inclusion, platform-specific python/python3, pester
37
+ * `-EncodedCommand` for safe path passing, gradlew detection, ctest build-
38
+ * directory probing, flutter-vs-dart selection, bundle/rspec detection,
39
+ * and the minitest `require_relative` trick for multi-file runs.
40
+ *
41
+ * Backends are free to override individual framework cases via their own
42
+ * `buildTestCommand` — this default is a single source of truth so adding
43
+ * a 15th framework only requires one switch arm.
44
+ *
45
+ * `dir` is the base directory used for gradlew/ctest manifest probing.
46
+ * `opts.scope` defaults to `'all'`; `opts.coverage` defaults to `false`.
47
+ * Returns null when the framework name is not in the supported set.
48
+ */
49
+ export declare function defaultBuildTestCommand(profile: LanguageProfile, framework: string, files: string[], dir?: string, opts?: BuildTestCommandOpts): string[] | null;
50
+ /**
51
+ * Default parseTestOutput: full 14-framework switch ported verbatim from
52
+ * `src/tools/test-runner.ts:parseTestOutput`. Returns a TestRunSummary
53
+ * with `passed`/`failed`/`skipped`/`total`/`coveragePercent` populated
54
+ * for every supported framework. Unknown frameworks return an
55
+ * exit-code-only summary.
56
+ *
57
+ * `framework` is the union-name string (e.g. 'bun', 'vitest', 'pytest').
58
+ * Callers pass the combined stdout+stderr as `stdout` and an empty
59
+ * string for `stderr` per the legacy convention — the legacy parser
60
+ * always concatenated streams before parsing.
61
+ */
62
+ export declare function defaultParseTestOutput(framework: string, stdout: string, stderr: string, exitCode: number): TestRunSummary;
63
+ /**
64
+ * Default detectProject: any of `profile.build.detectFiles` is present in
65
+ * `dir`. Honors simple glob patterns the same way `detectFileExists` does.
66
+ */
67
+ export declare function defaultDetectProject(profile: LanguageProfile, dir: string): Promise<boolean>;
68
+ /**
69
+ * Default selectBuildCommand: highest-priority command whose detectFile
70
+ * (if specified) exists AND whose binary is on PATH. Returns null if none.
71
+ */
72
+ export declare function defaultSelectBuildCommand(profile: LanguageProfile, dir: string): Promise<BuildCommandSelection | null>;
73
+ /**
74
+ * Default testFilesFor: convention swap `src/<x>.<ext>` ↔ `tests/<x>.<ext>`
75
+ * (and `tests/<x>_test.<ext>`, `tests/<x>.test.<ext>`). Returns candidates
76
+ * sorted by likelihood. Best-effort — backends with established patterns
77
+ * (e.g. Python's `tests/test_<x>.py`) override.
78
+ */
79
+ export declare function defaultTestFilesFor(profile: LanguageProfile, sourceFile: string, dir: string): Promise<string[]>;
80
+ /**
81
+ * Default extractImports: returns []. The analyzer treats this as
82
+ * "graph scope unavailable for {lang}" and falls back to convention scope
83
+ * with an explicit notice. Backends with parser-driven extraction
84
+ * (TypeScript, Python, Go in the language-agnostic plan's Phase 5) override.
85
+ */
86
+ export declare function defaultExtractImports(): string[];
87
+ /**
88
+ * Default selectFramework: returns null. Frameworks (React, Django, Gin)
89
+ * are not detectable from a profile alone — a concrete backend must read
90
+ * its language-specific manifest. Returning null causes the architect's
91
+ * PROJECT_FRAMEWORK placeholder to resolve to the `unresolved` sentinel.
92
+ */
93
+ export declare function defaultSelectFramework(): Promise<FrameworkSelection | null>;
94
+ /**
95
+ * Default selectEntryPoints: returns []. Concrete backends override per
96
+ * language. Empty list maps to the `unresolved` sentinel.
97
+ */
98
+ export declare function defaultSelectEntryPoints(): Promise<string[]>;
99
+ /**
100
+ * Build a backend object that delegates every hook to the registry-driven
101
+ * defaults. Used by `pickBackend` when no language-specific override has
102
+ * been registered. The returned object is a structural `LanguageBackend`
103
+ * (it spreads the profile, then attaches default method bindings).
104
+ */
105
+ export declare function defaultBackendFor(profile: LanguageProfile): LanguageBackend;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Dispatch: pick the right `LanguageBackend` for a directory.
3
+ *
4
+ * `pickBackend(dir)` walks up from `dir` to find the nearest project
5
+ * manifest, runs language detection on that root, and returns the
6
+ * registered (or defaulted) backend for the dominant language. Caches
7
+ * results in a bounded LRU keyed by (dir, manifest-hash) so repeated calls
8
+ * during a session do not re-walk the filesystem.
9
+ *
10
+ * Per the language-agnostic plan, hot-path callers (hooks, tools) wrap
11
+ * this in `withTimeout(200ms)` and fail open on the cache miss; session-
12
+ * start callers use `withTimeout(2000ms)`. Both budgets are caller-set —
13
+ * the dispatch function itself does not impose timeouts.
14
+ *
15
+ * Invariant 4: this module never writes to `.swarm/`. All caching is
16
+ * in-process. `dir` is treated as caller-supplied and not validated as a
17
+ * project root — callers are responsible for passing the right directory.
18
+ */
19
+ import type { LanguageBackend } from './backend';
20
+ import './backends';
21
+ import { detectProjectLanguages } from './detector';
22
+ declare const _internals: {
23
+ detectProjectLanguages: typeof detectProjectLanguages;
24
+ cacheCapacity: number;
25
+ };
26
+ export { _internals };
27
+ /**
28
+ * Pick the most appropriate `LanguageBackend` for `dir`. Walks up to find
29
+ * the manifest root, detects languages there, returns the highest-tier
30
+ * backend (with the default backend synthesized for ids that have no
31
+ * registered override). Returns null if no language is detected.
32
+ *
33
+ * The dispatch is cached by `(manifestRoot, manifestHash)`; cache entries
34
+ * are invalidated automatically when any manifest's size or mtime changes.
35
+ */
36
+ export declare function pickBackend(dir: string): Promise<LanguageBackend | null>;
37
+ /**
38
+ * Return the ranked language profile list `pickBackend` last detected for
39
+ * `dir`. Used by `buildProjectContext` to populate
40
+ * `PROJECT_CONTEXT_SECONDARY_LANGUAGES` without re-running
41
+ * `detectProjectLanguages`. Returns an empty array when no cached entry
42
+ * matches (caller should invoke `pickBackend(dir)` first to warm the
43
+ * cache).
44
+ */
45
+ export declare function pickedProfiles(dir: string): ReadonlyArray<{
46
+ id: string;
47
+ }>;
48
+ /**
49
+ * Test-only: clear the dispatch cache. Production code should never call
50
+ * this — the cache is invalidated automatically by manifest hashes.
51
+ */
52
+ export declare function clearDispatchCache(): void;
@@ -27,9 +27,29 @@ export interface LanguageProfile {
27
27
  displayName: string;
28
28
  tier: 1 | 2 | 3;
29
29
  extensions: string[];
30
+ /**
31
+ * Reserved for future "parser-only" entries (e.g. css, bash, ini, regex,
32
+ * and the tsx parser-grammar split) that should register a tree-sitter
33
+ * parser but never participate in test/build/lint dispatch. Currently
34
+ * unused — populated in a later phase.
35
+ */
36
+ parserOnly?: boolean;
30
37
  treeSitter: {
31
38
  grammarId: string;
32
39
  wasmFile: string;
40
+ /**
41
+ * Tree-sitter node names that represent comments for this language.
42
+ * Used by tools that strip comments (e.g. ast-diff, syntax-check).
43
+ *
44
+ * Optional in the type because tests construct ad-hoc profiles for
45
+ * fixtures that don't exercise comment-stripping. Production profiles
46
+ * MUST populate this — enforced by
47
+ * `tests/unit/lang/profile-registry-parity.test.ts` against every
48
+ * profile in `LANGUAGE_REGISTRY`. Source of truth lives here;
49
+ * `src/lang/registry.ts` exposes a parity subset for the parser-only
50
+ * registry.
51
+ */
52
+ commentNodes?: string[];
33
53
  };
34
54
  build: {
35
55
  detectFiles: string[];
@@ -62,6 +82,14 @@ export declare class LanguageRegistry {
62
82
  private profiles;
63
83
  private extensionIndex;
64
84
  constructor();
85
+ /**
86
+ * Remove a previously registered profile and any extensions it claimed.
87
+ * Primarily used by tests to clean up after registering fixture profiles
88
+ * into the shared singleton — without this, fixture entries leak across
89
+ * test files in Bun's per-file-but-shared-process test runner. No-op if
90
+ * the id is not registered.
91
+ */
92
+ unregister(id: string): void;
65
93
  register(profile: LanguageProfile): void;
66
94
  get(id: string): LanguageProfile | undefined;
67
95
  getById(id: string): LanguageProfile | undefined;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Backend registry — maps language id → concrete `LanguageBackend`. Sits
3
+ * alongside `LANGUAGE_REGISTRY` (the data registry) and `languageDefinitions`
4
+ * (the parser registry). When a backend is not registered for a language id,
5
+ * the default backend (registry-driven defaults) is synthesized on demand.
6
+ *
7
+ * Adding a new language with first-class behavior is a single new file
8
+ * under `src/lang/backends/<id>.ts` plus one import line in
9
+ * `src/lang/backends/index.ts`. The new file calls
10
+ * `LANGUAGE_BACKEND_REGISTRY.register(myBackend)` at module load.
11
+ */
12
+ import type { LanguageBackend } from './backend';
13
+ declare class LanguageBackendRegistry {
14
+ private backends;
15
+ register(backend: LanguageBackend): void;
16
+ /**
17
+ * Get a registered backend by id, or `undefined` if no backend is
18
+ * registered. Callers usually want `getOrDefault` instead.
19
+ */
20
+ get(id: string): LanguageBackend | undefined;
21
+ /**
22
+ * Get the registered backend for `id`, or synthesize a default backend
23
+ * by wrapping the matching `LanguageProfile`. Returns `undefined` only
24
+ * when no profile exists for the id (i.e. unknown language).
25
+ */
26
+ getOrDefault(id: string): LanguageBackend | undefined;
27
+ /**
28
+ * Test-only: remove a registered backend. Mirrors
29
+ * `LANGUAGE_REGISTRY.unregister` for the same singleton-pollution
30
+ * rationale (see comment there).
31
+ */
32
+ unregister(id: string): void;
33
+ }
34
+ export declare const LANGUAGE_BACKEND_REGISTRY: LanguageBackendRegistry;
35
+ export {};
@@ -40,6 +40,7 @@ export interface SkillImproveRequest {
40
40
  maxCalls?: number;
41
41
  now?: Date;
42
42
  sessionId?: string;
43
+ signal?: AbortSignal;
43
44
  /** Test-only seam: inject a delegate. When undefined, the service uses
44
45
  * createSkillImproverLLMDelegate(directory, sessionId) which returns
45
46
  * undefined unless swarmState.opencodeClient is wired. */
@@ -7,6 +7,12 @@ export interface TestImpactResult {
7
7
  declare function normalizePath(p: string): string;
8
8
  declare function isCacheStale(impactMap: Record<string, string[]>, generatedAtMs: number): boolean;
9
9
  declare function resolveRelativeImport(fromDir: string, importPath: string): string | null;
10
+ /**
11
+ * Test-only: clear the go-module memoization cache. Production code
12
+ * should never need this — the cache is per-call-graph scoped, but tests
13
+ * that reuse the same tempDir benefit from a fresh start.
14
+ */
15
+ declare function _clearGoModuleCache(): void;
10
16
  declare function findTestFilesSync(cwd: string): string[];
11
17
  declare function extractImports(content: string): string[];
12
18
  declare function buildImpactMapInternal(cwd: string): Promise<Record<string, string[]>>;
@@ -21,6 +27,7 @@ export declare const _internals: {
21
27
  loadImpactMap: typeof loadImpactMap;
22
28
  saveImpactMap: typeof saveImpactMap;
23
29
  analyzeImpact: typeof analyzeImpact;
30
+ _clearGoModuleCache: typeof _clearGoModuleCache;
24
31
  };
25
32
  export declare function buildImpactMap(cwd: string): Promise<Record<string, string[]>>;
26
33
  export declare function loadImpactMap(cwd: string): Promise<Record<string, string[]>>;
@@ -49,6 +49,28 @@ export interface TestErrorResult {
49
49
  attempted_scope?: 'graph';
50
50
  }
51
51
  export type TestResult = TestSuccessResult | TestErrorResult;
52
+ export declare function detectTestFrameworkViaDispatch(cwd: string): Promise<TestFramework>;
53
+ /**
54
+ * Build a test command via the LanguageBackend dispatch path. Reverse-maps
55
+ * the union TestFramework string back to the profile name and asks the
56
+ * matching backend to produce a command. Falls back to the legacy switch
57
+ * (via `defaultBuildTestCommand` import) when no backend is registered or
58
+ * the backend has no `buildTestCommand` hook.
59
+ *
60
+ * Returns null on framework=`none` or when dispatch fails — callers (the
61
+ * test-runner) then surface "no test command available".
62
+ */
63
+ export declare function buildTestCommandViaDispatch(framework: TestFramework, scope: 'all' | 'convention' | 'graph' | 'impact', files: string[], coverage: boolean, baseDir: string): Promise<string[] | null>;
64
+ /**
65
+ * Parse test output via the LanguageBackend dispatch path. Calls
66
+ * `backend.parseTestOutput` for the directory's resolved backend and
67
+ * returns the legacy-shaped `{ totals, coveragePercent? }` for the
68
+ * test-runner. Returns null when dispatch fails.
69
+ */
70
+ export declare function parseTestOutputViaDispatch(framework: TestFramework, output: string, baseDir: string): Promise<{
71
+ totals: TestTotals;
72
+ coveragePercent?: number;
73
+ } | null>;
52
74
  export declare function detectTestFramework(cwd: string): Promise<TestFramework>;
53
75
  /**
54
76
  * Returns true when `basename` matches a language-specific test file naming
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.15.0",
3
+ "version": "7.17.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",