@syntesseraai/opencode-feature-factory 0.6.4 → 0.6.5
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/index.js +9 -0
- package/dist/plugin-config.d.ts +49 -0
- package/dist/plugin-config.js +140 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StopQualityGateHooksPlugin } from './stop-quality-gate.js';
|
|
2
2
|
import { updateMCPConfig } from './mcp-config.js';
|
|
3
3
|
import { updateAgentConfig } from './agent-config.js';
|
|
4
|
+
import { updatePluginConfig } from './plugin-config.js';
|
|
4
5
|
import { $ } from 'bun';
|
|
5
6
|
// Import tool creator functions
|
|
6
7
|
import { createFFAgentsCurrentTool } from './plugins/ff-agents-current-plugin.js';
|
|
@@ -53,6 +54,14 @@ export const FeatureFactoryPlugin = async (input) => {
|
|
|
53
54
|
catch {
|
|
54
55
|
console.error('Failed to update agent config in OpenCode plugin');
|
|
55
56
|
}
|
|
57
|
+
// Update plugin list in global OpenCode config
|
|
58
|
+
// This ensures required companion plugins are present
|
|
59
|
+
try {
|
|
60
|
+
await updatePluginConfig($);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
console.error('Failed to update plugin config in OpenCode plugin');
|
|
64
|
+
}
|
|
56
65
|
// Load hooks from the quality gate plugin
|
|
57
66
|
const qualityGateHooks = await StopQualityGateHooksPlugin(input).catch(() => ({}));
|
|
58
67
|
// Create all tools
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
type BunShell = any;
|
|
2
|
+
/**
|
|
3
|
+
* Default plugins that should be present in the global OpenCode config.
|
|
4
|
+
* These will be merged into the existing plugin array without removing
|
|
5
|
+
* any user-added entries.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_PLUGINS: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* Extract the base package name from a plugin specifier, stripping the
|
|
10
|
+
* version suffix. Handles both scoped (`@scope/name@version`) and
|
|
11
|
+
* unscoped (`name@version`) packages.
|
|
12
|
+
*
|
|
13
|
+
* Examples:
|
|
14
|
+
* "@scope/pkg@latest" → "@scope/pkg"
|
|
15
|
+
* "@scope/pkg@1.2.3" → "@scope/pkg"
|
|
16
|
+
* "pkg@latest" → "pkg"
|
|
17
|
+
* "pkg" → "pkg"
|
|
18
|
+
*/
|
|
19
|
+
export declare function getPackageBaseName(plugin: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Check whether a plugin (by base name) is already present in the array,
|
|
22
|
+
* regardless of the version suffix. If a user has pinned a specific version
|
|
23
|
+
* (e.g. `@scope/pkg@1.2.3`), we treat the package as already present and
|
|
24
|
+
* do NOT add the `@latest` variant.
|
|
25
|
+
*/
|
|
26
|
+
export declare function hasPlugin(existing: string[], plugin: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Merge plugin arrays, preserving existing entries and appending new ones.
|
|
29
|
+
* Existing entries are never removed or reordered.
|
|
30
|
+
*
|
|
31
|
+
* Matching is done by base package name (without version suffix), so a
|
|
32
|
+
* user-pinned `@scope/pkg@1.0.0` will prevent `@scope/pkg@latest` from
|
|
33
|
+
* being added.
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergePlugins(existing: string[] | undefined, defaults: readonly string[]): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Update the plugin list in global opencode.json.
|
|
38
|
+
*
|
|
39
|
+
* This function:
|
|
40
|
+
* 1. Reads existing config from ~/.config/opencode/opencode.json
|
|
41
|
+
* 2. Preserves all existing plugins in the array
|
|
42
|
+
* 3. Appends default Feature Factory plugins that aren't already present
|
|
43
|
+
* (matched by base package name, so pinned versions are respected)
|
|
44
|
+
* 4. Writes updated config back to ~/.config/opencode/opencode.json
|
|
45
|
+
*
|
|
46
|
+
* @param $ - Bun shell instance
|
|
47
|
+
*/
|
|
48
|
+
export declare function updatePluginConfig($: BunShell): Promise<void>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { readJsonFile } from './quality-gate-config.js';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const GLOBAL_OPENCODE_DIR = join(homedir(), '.config', 'opencode');
|
|
5
|
+
const GLOBAL_OPENCODE_CONFIG_PATH = join(GLOBAL_OPENCODE_DIR, 'opencode.json');
|
|
6
|
+
/**
|
|
7
|
+
* Default plugins that should be present in the global OpenCode config.
|
|
8
|
+
* These will be merged into the existing plugin array without removing
|
|
9
|
+
* any user-added entries.
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_PLUGINS = [
|
|
12
|
+
'@syntesseraai/opencode-feature-factory@latest',
|
|
13
|
+
'@nick-vi/opencode-type-inject@latest',
|
|
14
|
+
'@franlol/opencode-md-table-formatter@latest',
|
|
15
|
+
'@spoons-and-mirrors/subtask2@latest',
|
|
16
|
+
'opencode-pty@latest',
|
|
17
|
+
'@angdrew/opencode-hashline-plugin@latest',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Extract the base package name from a plugin specifier, stripping the
|
|
21
|
+
* version suffix. Handles both scoped (`@scope/name@version`) and
|
|
22
|
+
* unscoped (`name@version`) packages.
|
|
23
|
+
*
|
|
24
|
+
* Examples:
|
|
25
|
+
* "@scope/pkg@latest" → "@scope/pkg"
|
|
26
|
+
* "@scope/pkg@1.2.3" → "@scope/pkg"
|
|
27
|
+
* "pkg@latest" → "pkg"
|
|
28
|
+
* "pkg" → "pkg"
|
|
29
|
+
*/
|
|
30
|
+
export function getPackageBaseName(plugin) {
|
|
31
|
+
if (plugin.startsWith('@')) {
|
|
32
|
+
// Scoped package: the version delimiter is the '@' after the '/'
|
|
33
|
+
const slashIndex = plugin.indexOf('/');
|
|
34
|
+
if (slashIndex !== -1) {
|
|
35
|
+
const versionAt = plugin.indexOf('@', slashIndex + 1);
|
|
36
|
+
if (versionAt !== -1) {
|
|
37
|
+
return plugin.substring(0, versionAt);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// No slash or no version suffix — return as-is
|
|
41
|
+
return plugin;
|
|
42
|
+
}
|
|
43
|
+
// Unscoped package: version delimiter is the first '@'
|
|
44
|
+
const atIndex = plugin.indexOf('@');
|
|
45
|
+
if (atIndex !== -1) {
|
|
46
|
+
return plugin.substring(0, atIndex);
|
|
47
|
+
}
|
|
48
|
+
return plugin;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check whether a plugin (by base name) is already present in the array,
|
|
52
|
+
* regardless of the version suffix. If a user has pinned a specific version
|
|
53
|
+
* (e.g. `@scope/pkg@1.2.3`), we treat the package as already present and
|
|
54
|
+
* do NOT add the `@latest` variant.
|
|
55
|
+
*/
|
|
56
|
+
export function hasPlugin(existing, plugin) {
|
|
57
|
+
const baseName = getPackageBaseName(plugin);
|
|
58
|
+
return existing.some((entry) => getPackageBaseName(entry) === baseName);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Merge plugin arrays, preserving existing entries and appending new ones.
|
|
62
|
+
* Existing entries are never removed or reordered.
|
|
63
|
+
*
|
|
64
|
+
* Matching is done by base package name (without version suffix), so a
|
|
65
|
+
* user-pinned `@scope/pkg@1.0.0` will prevent `@scope/pkg@latest` from
|
|
66
|
+
* being added.
|
|
67
|
+
*/
|
|
68
|
+
export function mergePlugins(existing, defaults) {
|
|
69
|
+
const existingPlugins = existing ?? [];
|
|
70
|
+
const result = [...existingPlugins];
|
|
71
|
+
for (const plugin of defaults) {
|
|
72
|
+
if (!hasPlugin(result, plugin)) {
|
|
73
|
+
result.push(plugin);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Update the plugin list in global opencode.json.
|
|
80
|
+
*
|
|
81
|
+
* This function:
|
|
82
|
+
* 1. Reads existing config from ~/.config/opencode/opencode.json
|
|
83
|
+
* 2. Preserves all existing plugins in the array
|
|
84
|
+
* 3. Appends default Feature Factory plugins that aren't already present
|
|
85
|
+
* (matched by base package name, so pinned versions are respected)
|
|
86
|
+
* 4. Writes updated config back to ~/.config/opencode/opencode.json
|
|
87
|
+
*
|
|
88
|
+
* @param $ - Bun shell instance
|
|
89
|
+
*/
|
|
90
|
+
export async function updatePluginConfig($) {
|
|
91
|
+
// Read existing global config
|
|
92
|
+
const globalJson = await readJsonFile($, GLOBAL_OPENCODE_CONFIG_PATH);
|
|
93
|
+
// Get existing plugins from global config
|
|
94
|
+
const existingPlugins = Array.isArray(globalJson?.plugin)
|
|
95
|
+
? globalJson.plugin
|
|
96
|
+
: undefined;
|
|
97
|
+
// Check if any changes are needed (by base name)
|
|
98
|
+
const existingArray = existingPlugins ?? [];
|
|
99
|
+
const hasChanges = DEFAULT_PLUGINS.some((plugin) => !hasPlugin(existingArray, plugin));
|
|
100
|
+
if (!hasChanges) {
|
|
101
|
+
// All default plugins already present (possibly with pinned versions)
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Merge with default plugins
|
|
105
|
+
const updatedPlugins = mergePlugins(existingPlugins, DEFAULT_PLUGINS);
|
|
106
|
+
// Prepare updated global config
|
|
107
|
+
const updatedGlobalConfig = {
|
|
108
|
+
...(globalJson ?? {}),
|
|
109
|
+
plugin: updatedPlugins,
|
|
110
|
+
};
|
|
111
|
+
// Ensure global config directory exists
|
|
112
|
+
try {
|
|
113
|
+
await $ `mkdir -p ${GLOBAL_OPENCODE_DIR}`.quiet();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Directory might already exist, ignore
|
|
117
|
+
}
|
|
118
|
+
// Backup existing global config if it exists and has content
|
|
119
|
+
if (globalJson && Object.keys(globalJson).length > 0) {
|
|
120
|
+
try {
|
|
121
|
+
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
|
122
|
+
const backupPath = `${GLOBAL_OPENCODE_CONFIG_PATH}.backup.${timestamp}`;
|
|
123
|
+
const backupContent = JSON.stringify(globalJson, null, 2);
|
|
124
|
+
await $ `echo ${backupContent} > ${backupPath}`.quiet();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
// Backup failed, but continue anyway
|
|
128
|
+
console.warn('[feature-factory] Could not create backup:', error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Write updated config to global opencode.json
|
|
132
|
+
const configContent = JSON.stringify(updatedGlobalConfig, null, 2);
|
|
133
|
+
try {
|
|
134
|
+
await $ `echo ${configContent} > ${GLOBAL_OPENCODE_CONFIG_PATH}`.quiet();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
// Silently fail - don't block if we can't write the config
|
|
138
|
+
console.warn('[feature-factory] Could not update plugin config:', error);
|
|
139
|
+
}
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@syntesseraai/opencode-feature-factory",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "OpenCode plugin for Feature Factory agents - provides sub-agents and skills for validation, review, security, and architecture assessment",
|
|
7
7
|
"license": "MIT",
|