@osovv/vv-opencode 0.35.19 → 0.35.21

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +67 -24
  3. package/dist/cli.js +3 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/completion.js +2 -0
  6. package/dist/commands/completion.js.map +1 -1
  7. package/dist/commands/config-validate.d.ts +8 -1
  8. package/dist/commands/config-validate.js +31 -8
  9. package/dist/commands/config-validate.js.map +1 -1
  10. package/dist/commands/doctor.d.ts +3 -3
  11. package/dist/commands/doctor.js +14 -13
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/init.js +10 -12
  14. package/dist/commands/init.js.map +1 -1
  15. package/dist/commands/install.js +6 -6
  16. package/dist/commands/install.js.map +1 -1
  17. package/dist/commands/launch.d.ts +35 -0
  18. package/dist/commands/launch.js +123 -0
  19. package/dist/commands/launch.js.map +1 -0
  20. package/dist/commands/patch-provider.d.ts +8 -0
  21. package/dist/commands/patch-provider.js +13 -4
  22. package/dist/commands/patch-provider.js.map +1 -1
  23. package/dist/commands/plugin-list.d.ts +6 -6
  24. package/dist/commands/plugin-list.js +30 -24
  25. package/dist/commands/plugin-list.js.map +1 -1
  26. package/dist/commands/plugin-toggle.js +5 -3
  27. package/dist/commands/plugin-toggle.js.map +1 -1
  28. package/dist/commands/preset.d.ts +7 -0
  29. package/dist/commands/preset.js +37 -9
  30. package/dist/commands/preset.js.map +1 -1
  31. package/dist/commands/role.d.ts +3 -0
  32. package/dist/commands/role.js +45 -11
  33. package/dist/commands/role.js.map +1 -1
  34. package/dist/commands/status.d.ts +3 -3
  35. package/dist/commands/status.js +14 -13
  36. package/dist/commands/status.js.map +1 -1
  37. package/dist/commands/sync.js +6 -7
  38. package/dist/commands/sync.js.map +1 -1
  39. package/dist/commands/upgrade.js +4 -3
  40. package/dist/commands/upgrade.js.map +1 -1
  41. package/dist/lib/config-layers.d.ts +56 -0
  42. package/dist/lib/config-layers.js +256 -0
  43. package/dist/lib/config-layers.js.map +1 -0
  44. package/dist/lib/opencode.d.ts +13 -5
  45. package/dist/lib/opencode.js +105 -39
  46. package/dist/lib/opencode.js.map +1 -1
  47. package/dist/lib/plugin-toggle-config.d.ts +5 -1
  48. package/dist/lib/plugin-toggle-config.js +20 -33
  49. package/dist/lib/plugin-toggle-config.js.map +1 -1
  50. package/dist/lib/vvoc-config.js +14 -1
  51. package/dist/lib/vvoc-config.js.map +1 -1
  52. package/dist/lib/vvoc-paths.d.ts +2 -2
  53. package/dist/lib/vvoc-paths.js +12 -9
  54. package/dist/lib/vvoc-paths.js.map +1 -1
  55. package/dist/plugins/guardian/index.js +30 -37
  56. package/dist/plugins/guardian/index.js.map +1 -1
  57. package/dist/plugins/model-roles/index.js +11 -27
  58. package/dist/plugins/model-roles/index.js.map +1 -1
  59. package/dist/plugins/secrets-redaction/config.d.ts +1 -1
  60. package/dist/plugins/secrets-redaction/config.js +28 -39
  61. package/dist/plugins/secrets-redaction/config.js.map +1 -1
  62. package/dist/plugins/system-context-injection/index.js +19 -7
  63. package/dist/plugins/system-context-injection/index.js.map +1 -1
  64. package/package.json +1 -1
  65. package/schemas/vvoc/v3.json +1 -1
  66. package/templates/agents/vv-controller.md +9 -8
  67. package/templates/skills/vv-execute/SKILL.md +8 -8
  68. package/templates/skills/vv-plan/SKILL.md +11 -6
  69. package/templates/skills/vv-plan/references/plan-template.xml +1 -0
  70. package/templates/skills/vv-spec/SKILL.md +29 -3
  71. package/templates/skills/vv-spec/references/design-context-template.xml +62 -0
@@ -0,0 +1,56 @@
1
+ import { type VvocConfig } from "./vvoc-config.js";
2
+ export declare const VVOC_CONFIG_ENV = "VVOC_CONFIG";
3
+ export declare const OPENCODE_CONFIG_ENV = "OPENCODE_CONFIG";
4
+ export type ConfigWriteScope = "global" | "project";
5
+ export type ConfigReadScope = ConfigWriteScope | "effective";
6
+ export type ConfigSourceKind = "env" | "project" | "global" | "default" | "missing";
7
+ export type ConfigSource = {
8
+ kind: ConfigSourceKind;
9
+ path?: string;
10
+ rootDir?: string;
11
+ reason?: string;
12
+ };
13
+ export type ProjectConfigRoot = {
14
+ rootDir: string;
15
+ vvocConfigPath?: string;
16
+ opencodeConfigPath?: string;
17
+ };
18
+ export type ConfigLayerOptions = {
19
+ cwd: string;
20
+ configDir?: string;
21
+ env?: NodeJS.ProcessEnv;
22
+ };
23
+ export type ConfigWriteTargets = {
24
+ scope: ConfigWriteScope;
25
+ projectRoot?: string;
26
+ opencodeBaseDir: string;
27
+ vvocBaseDir: string;
28
+ opencodeConfigPath: string;
29
+ vvocConfigPath: string;
30
+ };
31
+ export declare function findNearestProjectConfigRoot(cwd: string): Promise<ProjectConfigRoot | undefined>;
32
+ export declare function resolveProjectWriteRoot(cwd: string): Promise<string>;
33
+ export declare function resolveProjectOpenCodeConfigPath(projectRoot: string): Promise<string>;
34
+ export declare function resolveVvocConfigSource(options: ConfigLayerOptions & {
35
+ scope: ConfigReadScope;
36
+ allowDefault: boolean;
37
+ }): Promise<ConfigSource>;
38
+ export declare function resolveOpenCodeConfigSource(options: ConfigLayerOptions & {
39
+ scope: ConfigReadScope;
40
+ }): Promise<ConfigSource>;
41
+ export declare function resolveConfigWriteTargets(options: ConfigLayerOptions & {
42
+ scope: ConfigWriteScope;
43
+ }): Promise<ConfigWriteTargets>;
44
+ export declare function loadVvocConfigForRead(options: ConfigLayerOptions & {
45
+ scope: ConfigReadScope;
46
+ allowDefault: boolean;
47
+ }): Promise<{
48
+ config: VvocConfig;
49
+ source: ConfigSource;
50
+ warnings: string[];
51
+ }>;
52
+ export declare function loadEffectiveVvocConfigForRuntime(options?: Partial<ConfigLayerOptions>): Promise<{
53
+ config: VvocConfig;
54
+ source: ConfigSource;
55
+ warnings: string[];
56
+ }>;
@@ -0,0 +1,256 @@
1
+ // FILE: src/lib/config-layers.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Resolve vvoc and OpenCode config layers for global, project, and effective scopes.
5
+ // SCOPE: Env override handling, ancestor project-root discovery, project write-root selection, global fallback paths, runtime vvoc loading, and source metadata.
6
+ // DEPENDS: [node:fs/promises, node:path, src/lib/vvoc-config.ts, src/lib/vvoc-paths.ts]
7
+ // LINKS: [M-CONFIG-LAYERS, M-CLI-CONFIG]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // VVOC_CONFIG_ENV - Environment variable name for explicit vvoc config selection.
14
+ // OPENCODE_CONFIG_ENV - Environment variable name for explicit OpenCode config selection.
15
+ // ConfigWriteScope - Supported write scopes for mutating commands.
16
+ // ConfigReadScope - Supported read scopes for list/show/diagnostic commands.
17
+ // ConfigSourceKind - Source kind labels for selected config sources.
18
+ // ConfigSource - Metadata describing a selected config source.
19
+ // ConfigWriteTargets - Resolved global or project write target paths.
20
+ // ProjectConfigRoot - Nearest project config layer metadata.
21
+ // ConfigLayerOptions - Common layered config resolution inputs.
22
+ // findNearestProjectConfigRoot - Finds the closest ancestor with .vvoc/vvoc.json or .opencode/opencode.json(c).
23
+ // resolveProjectWriteRoot - Selects the project root that project-scope mutations write to.
24
+ // resolveProjectOpenCodeConfigPath - Selects the canonical project .opencode/opencode.json(c) path.
25
+ // resolveVvocConfigSource - Resolves vvoc source metadata for global, project, or effective reads.
26
+ // resolveOpenCodeConfigSource - Resolves OpenCode source metadata for global, project, or effective reads.
27
+ // resolveConfigWriteTargets - Returns canonical global or project write paths.
28
+ // loadVvocConfigForRead - Loads vvoc config for CLI read/list/show commands without creating files.
29
+ // loadEffectiveVvocConfigForRuntime - Loads the effective vvoc config for runtime plugins.
30
+ // END_MODULE_MAP
31
+ //
32
+ // START_CHANGE_SUMMARY
33
+ // LAST_CHANGE: [v1.0.0 - Added layered config source discovery, write target resolution, and runtime vvoc loading.]
34
+ // END_CHANGE_SUMMARY
35
+ import { access, readFile } from "node:fs/promises";
36
+ import { dirname, join, resolve } from "node:path";
37
+ import { createDefaultVvocConfig, loadLenientVvocConfigText, parseVvocConfigText, } from "./vvoc-config.js";
38
+ import { getGlobalOpencodeDir, getGlobalVvocConfigPath, getGlobalVvocDir, getProjectOpencodeDir, getProjectVvocConfigPath, } from "./vvoc-paths.js";
39
+ export const VVOC_CONFIG_ENV = "VVOC_CONFIG";
40
+ export const OPENCODE_CONFIG_ENV = "OPENCODE_CONFIG";
41
+ const OPENCODE_CONFIG_FILE_NAMES = ["opencode.json", "opencode.jsonc"];
42
+ // START_BLOCK_PROJECT_LAYER_DISCOVERY
43
+ export async function findNearestProjectConfigRoot(cwd) {
44
+ let currentDir = resolve(cwd);
45
+ while (true) {
46
+ const vvocConfigPath = getProjectVvocConfigPath(currentDir);
47
+ const opencodeConfigPath = await findExistingProjectOpenCodeConfigPath(currentDir);
48
+ const hasVvocConfig = await pathExists(vvocConfigPath);
49
+ if (hasVvocConfig || opencodeConfigPath) {
50
+ return {
51
+ rootDir: currentDir,
52
+ vvocConfigPath: hasVvocConfig ? vvocConfigPath : undefined,
53
+ opencodeConfigPath,
54
+ };
55
+ }
56
+ const parentDir = dirname(currentDir);
57
+ if (parentDir === currentDir) {
58
+ return undefined;
59
+ }
60
+ currentDir = parentDir;
61
+ }
62
+ }
63
+ export async function resolveProjectWriteRoot(cwd) {
64
+ return (await findNearestProjectConfigRoot(cwd))?.rootDir ?? resolve(cwd);
65
+ }
66
+ export async function resolveProjectOpenCodeConfigPath(projectRoot) {
67
+ return ((await findExistingProjectOpenCodeConfigPath(projectRoot)) ??
68
+ join(getProjectOpencodeDir(projectRoot), "opencode.json"));
69
+ }
70
+ // END_BLOCK_PROJECT_LAYER_DISCOVERY
71
+ // START_BLOCK_SOURCE_RESOLUTION
72
+ export async function resolveVvocConfigSource(options) {
73
+ if (options.scope === "global") {
74
+ return resolveGlobalVvocSource(options.configDir);
75
+ }
76
+ if (options.scope === "project") {
77
+ return resolveProjectVvocSource(options.cwd);
78
+ }
79
+ const envSource = readEnvConfigSource(options.env, VVOC_CONFIG_ENV);
80
+ if (envSource) {
81
+ return envSource;
82
+ }
83
+ const projectSource = await resolveProjectVvocSource(options.cwd);
84
+ if (projectSource.kind === "project") {
85
+ return projectSource;
86
+ }
87
+ const globalSource = await resolveGlobalVvocSource(options.configDir);
88
+ if (globalSource.kind === "global") {
89
+ return globalSource;
90
+ }
91
+ if (options.allowDefault) {
92
+ return { kind: "default", reason: "no vvoc config found" };
93
+ }
94
+ return globalSource.kind === "missing" ? globalSource : projectSource;
95
+ }
96
+ export async function resolveOpenCodeConfigSource(options) {
97
+ if (options.scope === "global") {
98
+ return resolveGlobalOpenCodeSource(options.configDir);
99
+ }
100
+ if (options.scope === "project") {
101
+ return resolveProjectOpenCodeSource(options.cwd);
102
+ }
103
+ const envSource = readEnvConfigSource(options.env, OPENCODE_CONFIG_ENV);
104
+ if (envSource) {
105
+ return envSource;
106
+ }
107
+ const projectSource = await resolveProjectOpenCodeSource(options.cwd);
108
+ if (projectSource.kind === "project") {
109
+ return projectSource;
110
+ }
111
+ return resolveGlobalOpenCodeSource(options.configDir);
112
+ }
113
+ export async function resolveConfigWriteTargets(options) {
114
+ if (options.scope === "global") {
115
+ const opencodeBaseDir = getGlobalOpencodeDir(options.configDir);
116
+ const opencodeConfigPath = await selectExistingPath(OPENCODE_CONFIG_FILE_NAMES.map((name) => join(opencodeBaseDir, name)));
117
+ return {
118
+ scope: "global",
119
+ opencodeBaseDir,
120
+ vvocBaseDir: getGlobalVvocDir(options.configDir),
121
+ opencodeConfigPath,
122
+ vvocConfigPath: getGlobalVvocConfigPath(options.configDir),
123
+ };
124
+ }
125
+ const projectRoot = await resolveProjectWriteRoot(options.cwd);
126
+ const opencodeBaseDir = getProjectOpencodeDir(projectRoot);
127
+ return {
128
+ scope: "project",
129
+ projectRoot,
130
+ opencodeBaseDir,
131
+ vvocBaseDir: dirname(getProjectVvocConfigPath(projectRoot)),
132
+ opencodeConfigPath: await resolveProjectOpenCodeConfigPath(projectRoot),
133
+ vvocConfigPath: getProjectVvocConfigPath(projectRoot),
134
+ };
135
+ }
136
+ // END_BLOCK_SOURCE_RESOLUTION
137
+ // START_BLOCK_RUNTIME_VVOC_LOADING
138
+ export async function loadVvocConfigForRead(options) {
139
+ const source = await resolveVvocConfigSource(options);
140
+ if (source.kind === "default" ||
141
+ (source.kind === "missing" && options.scope === "global" && options.allowDefault)) {
142
+ return {
143
+ config: createDefaultVvocConfig(),
144
+ source: { kind: "default", reason: source.reason },
145
+ warnings: [],
146
+ };
147
+ }
148
+ if (source.kind === "missing") {
149
+ throw new Error(source.reason ?? `vvoc config missing${source.path ? ` at ${source.path}` : ""}`);
150
+ }
151
+ if (!source.path) {
152
+ throw new Error(`selected vvoc config source has no path (${source.kind})`);
153
+ }
154
+ return {
155
+ config: parseVvocConfigText(await readFile(source.path, "utf8"), source.path),
156
+ source,
157
+ warnings: [],
158
+ };
159
+ }
160
+ export async function loadEffectiveVvocConfigForRuntime(options = {}) {
161
+ const source = await resolveVvocConfigSource({
162
+ scope: "effective",
163
+ allowDefault: true,
164
+ cwd: options.cwd ?? process.cwd(),
165
+ configDir: options.configDir,
166
+ env: options.env,
167
+ });
168
+ if (source.kind === "default") {
169
+ return { config: createDefaultVvocConfig(), source, warnings: [] };
170
+ }
171
+ if (!source.path) {
172
+ throw new Error(`selected vvoc config source has no path (${source.kind})`);
173
+ }
174
+ const text = await readFile(source.path, "utf8");
175
+ if (source.kind === "env" || source.kind === "project") {
176
+ return { config: parseVvocConfigText(text, source.path), source, warnings: [] };
177
+ }
178
+ const warnings = [];
179
+ const config = loadLenientVvocConfigText(text, source.path, warnings);
180
+ return { config, source, warnings };
181
+ }
182
+ // END_BLOCK_RUNTIME_VVOC_LOADING
183
+ async function resolveProjectVvocSource(cwd) {
184
+ const root = await findNearestProjectConfigRoot(cwd);
185
+ if (root?.vvocConfigPath) {
186
+ return { kind: "project", path: root.vvocConfigPath, rootDir: root.rootDir };
187
+ }
188
+ return {
189
+ kind: "missing",
190
+ reason: "project vvoc config missing; run vvoc install --scope project",
191
+ };
192
+ }
193
+ async function resolveProjectOpenCodeSource(cwd) {
194
+ const root = await findNearestProjectConfigRoot(cwd);
195
+ if (root?.opencodeConfigPath) {
196
+ return { kind: "project", path: root.opencodeConfigPath, rootDir: root.rootDir };
197
+ }
198
+ return {
199
+ kind: "missing",
200
+ reason: "project OpenCode config missing; run vvoc install --scope project",
201
+ };
202
+ }
203
+ async function resolveGlobalVvocSource(configDir) {
204
+ const path = getGlobalVvocConfigPath(configDir);
205
+ return (await pathExists(path))
206
+ ? { kind: "global", path }
207
+ : { kind: "missing", path, reason: "global vvoc config missing" };
208
+ }
209
+ async function resolveGlobalOpenCodeSource(configDir) {
210
+ const baseDir = getGlobalOpencodeDir(configDir);
211
+ const path = await findExistingOpenCodeConfigPath(baseDir);
212
+ return path
213
+ ? { kind: "global", path }
214
+ : {
215
+ kind: "missing",
216
+ path: join(baseDir, "opencode.json"),
217
+ reason: "global OpenCode config missing",
218
+ };
219
+ }
220
+ function readEnvConfigSource(env, name) {
221
+ const value = env ? env[name] : process.env[name];
222
+ if (typeof value !== "string" || !value.trim()) {
223
+ return undefined;
224
+ }
225
+ return { kind: "env", path: resolve(value.trim()), reason: name };
226
+ }
227
+ async function findExistingProjectOpenCodeConfigPath(projectRoot) {
228
+ return findExistingOpenCodeConfigPath(getProjectOpencodeDir(projectRoot));
229
+ }
230
+ async function findExistingOpenCodeConfigPath(baseDir) {
231
+ for (const name of OPENCODE_CONFIG_FILE_NAMES) {
232
+ const candidate = join(baseDir, name);
233
+ if (await pathExists(candidate)) {
234
+ return candidate;
235
+ }
236
+ }
237
+ return undefined;
238
+ }
239
+ async function selectExistingPath(candidates) {
240
+ for (const candidate of candidates) {
241
+ if (await pathExists(candidate)) {
242
+ return candidate;
243
+ }
244
+ }
245
+ return candidates[0];
246
+ }
247
+ async function pathExists(path) {
248
+ try {
249
+ await access(path);
250
+ return true;
251
+ }
252
+ catch {
253
+ return false;
254
+ }
255
+ }
256
+ //# sourceMappingURL=config-layers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-layers.js","sourceRoot":"","sources":["../../src/lib/config-layers.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { type ManagedOpenCodeAgentName } from "./managed-agents.js";
2
2
  import { type GuardianConfig, type GuardianConfigOverrides, type SecretsRedactionConfig, type VvocConfig } from "./vvoc-config.js";
3
3
  import { PACKAGE_NAME } from "./package.js";
4
+ import { type ConfigReadScope, type ConfigSource } from "./config-layers.js";
4
5
  export declare const CLI_NAME = "vvoc";
5
6
  export { PACKAGE_NAME };
6
7
  export declare const OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
@@ -11,11 +12,11 @@ export type ResolvedPaths = {
11
12
  scope: Scope;
12
13
  cwd: string;
13
14
  configHome: string;
15
+ projectRoot?: string;
14
16
  opencodeBaseDir: string;
15
17
  vvocBaseDir: string;
16
18
  vvocConfigPath: string;
17
19
  managedAgentsDirPath: string;
18
- managedPlansDirPath: string;
19
20
  managedSkillsDirPath: string;
20
21
  opencodeConfigPath: string;
21
22
  opencodeAlternatePaths: string[];
@@ -31,7 +32,7 @@ export type OpenCodeAgentOverride = {
31
32
  };
32
33
  export type ManagedAgentOverrideMap = Record<ManagedOpenCodeAgentName, OpenCodeAgentOverride>;
33
34
  export type InstallationInspection = {
34
- scope: Scope;
35
+ scope: ConfigReadScope;
35
36
  opencode: {
36
37
  path: string;
37
38
  exists: boolean;
@@ -74,7 +75,7 @@ export declare function resolvePaths(options: {
74
75
  configDir?: string;
75
76
  }): Promise<ResolvedPaths>;
76
77
  export declare function ensurePackageConfigText(text: string | undefined, packageSpecifier?: string): string;
77
- export declare function ensureManagedAgentRegistrationsConfigText(text: string | undefined, paths: Pick<ResolvedPaths, "managedAgentsDirPath" | "opencodeConfigPath">): string;
78
+ export declare function ensureManagedAgentRegistrationsConfigText(text: string | undefined, paths: Pick<ResolvedPaths, "managedAgentsDirPath" | "managedSkillsDirPath" | "opencodeConfigPath">): string;
78
79
  export declare function syncManagedAgentRegistrations(paths: ResolvedPaths): Promise<{
79
80
  path: string;
80
81
  changed: boolean;
@@ -85,7 +86,6 @@ export declare function installManagedAgentPrompts(paths: ResolvedPaths, options
85
86
  export declare function syncManagedAgentPrompts(paths: ResolvedPaths, options: {
86
87
  force: boolean;
87
88
  }): Promise<WriteResult[]>;
88
- export declare function ensureManagedPlanDirectory(paths: Pick<ResolvedPaths, "managedPlansDirPath">): Promise<WriteResult>;
89
89
  export declare function installManagedSkillFiles(paths: ResolvedPaths, options: {
90
90
  force: boolean;
91
91
  }): Promise<WriteResult[]>;
@@ -107,7 +107,7 @@ export declare function writeOpenCodeDefaultModel(paths: Pick<ResolvedPaths, "op
107
107
  ensureEntry: boolean;
108
108
  }): Promise<WriteResult>;
109
109
  export declare function writeOpenCodeProviderObject(paths: Pick<ResolvedPaths, "opencodeConfigPath">, providerID: string, value: JsonObject): Promise<WriteResult>;
110
- export declare function writeManagedAgentModel(paths: Pick<ResolvedPaths, "managedAgentsDirPath" | "opencodeConfigPath">, agentName: ManagedOpenCodeAgentName, options: {
110
+ export declare function writeManagedAgentModel(paths: Pick<ResolvedPaths, "managedAgentsDirPath" | "managedSkillsDirPath" | "opencodeConfigPath">, agentName: ManagedOpenCodeAgentName, options: {
111
111
  model?: string;
112
112
  ensureEntry: boolean;
113
113
  }): Promise<WriteResult>;
@@ -125,4 +125,12 @@ export declare function writeGuardianConfig(paths: Pick<ResolvedPaths, "vvocConf
125
125
  merge?: boolean;
126
126
  }): Promise<WriteResult>;
127
127
  export declare function inspectInstallation(paths: ResolvedPaths): Promise<InstallationInspection>;
128
+ export declare function inspectInstallationForScope(options: {
129
+ scope: ConfigReadScope;
130
+ cwd: string;
131
+ configDir?: string;
132
+ }): Promise<InstallationInspection & {
133
+ opencodeSource: ConfigSource;
134
+ vvocSource: ConfigSource;
135
+ }>;
128
136
  export declare function describeWriteResult(result: WriteResult): string;
@@ -1,9 +1,9 @@
1
1
  // FILE: src/lib/opencode.ts
2
2
  // VERSION: 1.0.0
3
3
  // START_MODULE_CONTRACT
4
- // PURPOSE: Manage OpenCode config mutation, provider patching, and the canonical vvoc.json config file.
5
- // SCOPE: Scope-aware path resolution, pinned plugin writes, top-level OpenCode model/default writes, managed OpenCode default-agent, command, and tool gating, provider baseURL patching, provider object patching, managed OpenCode agent registration/model overrides, managed agent prompt sync, managed skill file install/sync, version-aware canonical vvoc config rendering and sync, and installation inspection.
6
- // DEPENDS: [jsonc-parser, node:fs/promises, node:path, src/lib/managed-agents.ts, src/lib/managed-skills.ts, src/lib/package.ts, src/lib/vvoc-config.ts, src/lib/vvoc-paths.ts]
4
+ // PURPOSE: Manage OpenCode config mutation, provider patching, and scoped vvoc.json config files.
5
+ // SCOPE: Layer-aware path resolution, pinned plugin writes, managed OpenCode defaults, local skills path registration, provider patching, managed prompts/skills, version-aware vvoc config rendering, and installation inspection.
6
+ // DEPENDS: [jsonc-parser, node:fs/promises, node:path, src/lib/config-layers.ts, src/lib/managed-agents.ts, src/lib/managed-skills.ts, src/lib/package.ts, src/lib/vvoc-config.ts, src/lib/vvoc-paths.ts]
7
7
  // LINKS: [M-CLI-CONFIG]
8
8
  // ROLE: RUNTIME
9
9
  // MAP_MODE: EXPORTS
@@ -33,7 +33,6 @@
33
33
  // syncManagedAgentRegistrations - Syncs the canonical vvoc-managed OpenCode agent registrations and tool gating into OpenCode config.
34
34
  // installManagedAgentPrompts - Creates managed vvoc prompt files for the bundled Guardian and managed OpenCode agents when missing.
35
35
  // syncManagedAgentPrompts - Rewrites managed vvoc prompt files for the bundled Guardian and managed OpenCode agents.
36
- // ensureManagedPlanDirectory - Creates the managed vvoc planning artifact directory when missing.
37
36
  // installManagedSkillFiles - Creates managed vvoc skill files from bundled templates.
38
37
  // syncManagedSkillFiles - Rewrites managed vvoc skill files from bundled templates.
39
38
  // ensureManagedSkillSymlink - Creates symlink from OpenCode skills dir to vvoc skills dir for skill discovery.
@@ -45,6 +44,7 @@
45
44
  // writeManagedAgentModel - Writes or removes a bundled vvoc-managed OpenCode agent model override in OpenCode config.
46
45
  // writeGuardianConfig - Writes the guardian section into the canonical vvoc.json document.
47
46
  // inspectInstallation - Reads current OpenCode/vvoc installation state for status and doctor commands.
47
+ // inspectInstallationForScope - Reads installation state using strict/effective layered source resolution.
48
48
  // describeWriteResult - Formats config write outcomes for CLI output.
49
49
  // ManagedAgentModelMap - Map of managed agent names to model selections.
50
50
  // OpenCodeAgentOverride - Agent override config for OpenCode.
@@ -56,8 +56,10 @@
56
56
  // END_MODULE_MAP
57
57
  //
58
58
  // START_CHANGE_SUMMARY
59
+ // LAST_CHANGE: [v1.1.1 - Fixed syncManagedSkillFiles to not sync references when parent skill is skipped (user-owned/custom). Added regression coverage.]
60
+ // LAST_CHANGE: [v1.1.0 - Switched project-scope writes to .opencode/.vvoc layers and added local managed skills path registration.]
59
61
  // LAST_CHANGE: [v0.5.1 - Added ensureManagedSkillSymlink. Fixed renderManagedSkill to preserve YAML frontmatter.]
60
- // LAST_CHANGE: [v1.0.1 - Added managed plan directory resolution and creation for analyst/architect artifacts.]
62
+ // LAST_CHANGE: [v1.0.1 - Added managed plan directory resolution and creation for analyst/architect artifacts. Removed in v1.2.0 — superseded by spec package layout under .vvoc/specs/<id>/.]
61
63
  // LAST_CHANGE: [v0.5.0 - Replaced managed command registrations (vv-plan/vv-review) with managed skills system. Added managedSkillsDirPath to ResolvedPaths. Added installManagedSkillFiles and syncManagedSkillFiles.]
62
64
  // LAST_CHANGE: [v0.9.7 - Removed variant splitting from agent model read/write helpers so provider/model:free passes through unchanged.]
63
65
  // LAST_CHANGE: [v0.9.6 - Added managed OpenCode `tools.apply_patch = false` writes during install/init/sync so sessions stay on the hashline-backed `edit` override.]
@@ -76,7 +78,8 @@ import { MANAGED_SKILL_NAMES, getManagedSkillFilePath, listManagedSkillReference
76
78
  import { BUILTIN_ROLE_NAMES, getBuiltInRoleBindings, ROLE_REFERENCE_PREFIX, } from "./model-roles.js";
77
79
  import { createDefaultVvocConfig, createGuardianConfig, parseVersionedVvocConfigText, parseVvocConfigText, renderVvocConfig, } from "./vvoc-config.js";
78
80
  import { getPinnedPackageSpecifier, PACKAGE_NAME } from "./package.js";
79
- import { getConfigHome, getGlobalOpencodeDir, getGlobalOpencodeSkillsDir, getGlobalVvocConfigPath, getGlobalVvocDir, getProjectVvocDir, getVvocAgentsDir, getVvocPlansDir, getVvocSkillsDir, } from "./vvoc-paths.js";
81
+ import { resolveConfigWriteTargets, resolveOpenCodeConfigSource, resolveVvocConfigSource, } from "./config-layers.js";
82
+ import { getConfigHome, getGlobalOpencodeSkillsDir, getGlobalVvocDir, getVvocAgentsDir, getVvocSkillsDir, } from "./vvoc-paths.js";
80
83
  export const CLI_NAME = "vvoc";
81
84
  export { PACKAGE_NAME };
82
85
  export const OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
@@ -111,26 +114,22 @@ const JSON_FORMAT = {
111
114
  };
112
115
  // START_BLOCK_RESOLVE_CONFIG_PATHS
113
116
  export async function resolvePaths(options) {
117
+ const targets = await resolveConfigWriteTargets(options);
114
118
  const configHome = getConfigHome(options.configDir);
115
- const vvocBaseDir = getGlobalVvocDir(options.configDir);
116
- const opencodeBaseDir = options.scope === "global" ? getGlobalOpencodeDir(options.configDir) : options.cwd;
117
- const managedAgentsBaseDir = options.scope === "global" ? vvocBaseDir : getProjectVvocDir(options.cwd);
118
- const managedAgentsDirPath = getVvocAgentsDir(managedAgentsBaseDir);
119
- const managedPlansDirPath = getVvocPlansDir(managedAgentsBaseDir);
120
- const managedSkillsDirPath = getVvocSkillsDir(managedAgentsBaseDir);
121
- const opencodeSelection = await selectPrimaryPath(OPENCODE_CONFIG_FILE_NAMES.map((name) => join(opencodeBaseDir, name)));
119
+ const managedAgentsDirPath = getVvocAgentsDir(targets.vvocBaseDir);
120
+ const managedSkillsDirPath = getVvocSkillsDir(targets.vvocBaseDir);
122
121
  return {
123
122
  scope: options.scope,
124
123
  cwd: options.cwd,
125
124
  configHome,
126
- opencodeBaseDir,
127
- vvocBaseDir,
128
- vvocConfigPath: getGlobalVvocConfigPath(options.configDir),
125
+ projectRoot: targets.projectRoot,
126
+ opencodeBaseDir: targets.opencodeBaseDir,
127
+ vvocBaseDir: targets.vvocBaseDir,
128
+ vvocConfigPath: targets.vvocConfigPath,
129
129
  managedAgentsDirPath,
130
- managedPlansDirPath,
131
130
  managedSkillsDirPath,
132
- opencodeConfigPath: opencodeSelection.primary,
133
- opencodeAlternatePaths: opencodeSelection.alternates,
131
+ opencodeConfigPath: targets.opencodeConfigPath,
132
+ opencodeAlternatePaths: await resolveOpenCodeAlternates(targets.opencodeBaseDir, targets.opencodeConfigPath),
134
133
  };
135
134
  }
136
135
  // END_BLOCK_RESOLVE_CONFIG_PATHS
@@ -187,6 +186,9 @@ export function ensureManagedAgentRegistrationsConfigText(text, paths) {
187
186
  ...Object.fromEntries(Object.entries(builtInAgentModelRefs).map(([name, model]) => [name, { model }])),
188
187
  ...managedRegistrations,
189
188
  },
189
+ skills: {
190
+ paths: [getManagedSkillsPathReference(paths)],
191
+ },
190
192
  command: {},
191
193
  });
192
194
  }
@@ -252,7 +254,7 @@ export function ensureManagedAgentRegistrationsConfigText(text, paths) {
252
254
  }));
253
255
  }
254
256
  }
255
- return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
257
+ return ensureManagedSkillsPathConfigText(ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT))), paths);
256
258
  }
257
259
  export async function syncManagedAgentRegistrations(paths) {
258
260
  const currentText = await readOptionalText(paths.opencodeConfigPath);
@@ -300,13 +302,6 @@ export async function syncManagedAgentPrompts(paths, options) {
300
302
  }
301
303
  return results;
302
304
  }
303
- export async function ensureManagedPlanDirectory(paths) {
304
- const createdPath = await mkdir(paths.managedPlansDirPath, { recursive: true });
305
- return {
306
- action: createdPath ? "created" : "kept",
307
- path: paths.managedPlansDirPath,
308
- };
309
- }
310
305
  // START_BLOCK_MANAGED_SKILL_FUNCTIONS
311
306
  export async function installManagedSkillFiles(paths, options) {
312
307
  const results = [];
@@ -341,9 +336,13 @@ export async function installManagedSkillFiles(paths, options) {
341
336
  export async function syncManagedSkillFiles(paths, options) {
342
337
  const results = [];
343
338
  for (const skillName of MANAGED_SKILL_NAMES) {
344
- results.push(await syncManagedSkill(paths, skillName, options));
345
- const refResults = await syncManagedSkillReferences(paths.managedSkillsDirPath, skillName);
346
- results.push(...refResults);
339
+ const skillResult = await syncManagedSkill(paths, skillName, options);
340
+ results.push(skillResult);
341
+ // Only sync references when the parent skill was not skipped (user-owned/custom)
342
+ if (skillResult.action !== "skipped") {
343
+ const refResults = await syncManagedSkillReferences(paths.managedSkillsDirPath, skillName);
344
+ results.push(...refResults);
345
+ }
347
346
  }
348
347
  return results;
349
348
  }
@@ -682,6 +681,51 @@ export async function inspectInstallation(paths) {
682
681
  problems,
683
682
  };
684
683
  }
684
+ export async function inspectInstallationForScope(options) {
685
+ const [opencodeSource, vvocSource] = await Promise.all([
686
+ resolveOpenCodeConfigSource({
687
+ scope: options.scope,
688
+ cwd: options.cwd,
689
+ configDir: options.configDir,
690
+ }),
691
+ resolveVvocConfigSource({
692
+ scope: options.scope,
693
+ cwd: options.cwd,
694
+ configDir: options.configDir,
695
+ allowDefault: options.scope === "effective",
696
+ }),
697
+ ]);
698
+ if (options.scope === "project") {
699
+ const missingSource = [opencodeSource, vvocSource].find((source) => source.kind === "missing");
700
+ if (missingSource) {
701
+ throw new Error(missingSource.reason ?? "project config missing; run vvoc install --scope project");
702
+ }
703
+ }
704
+ const fallbackPaths = await resolvePaths({
705
+ scope: options.scope === "global" ? "global" : "project",
706
+ cwd: options.cwd,
707
+ configDir: options.configDir,
708
+ });
709
+ const opencodeConfigPath = opencodeSource.path ?? fallbackPaths.opencodeConfigPath;
710
+ const vvocConfigPath = vvocSource.path ?? fallbackPaths.vvocConfigPath;
711
+ const scopedPaths = {
712
+ ...fallbackPaths,
713
+ opencodeBaseDir: dirname(opencodeConfigPath),
714
+ vvocBaseDir: dirname(vvocConfigPath),
715
+ opencodeConfigPath,
716
+ vvocConfigPath,
717
+ opencodeAlternatePaths: [],
718
+ managedAgentsDirPath: getVvocAgentsDir(dirname(vvocConfigPath)),
719
+ managedSkillsDirPath: getVvocSkillsDir(dirname(vvocConfigPath)),
720
+ };
721
+ const inspection = await inspectInstallation(scopedPaths);
722
+ return {
723
+ ...inspection,
724
+ scope: options.scope,
725
+ opencodeSource,
726
+ vvocSource,
727
+ };
728
+ }
685
729
  // END_BLOCK_INSPECT_INSTALLATION_STATE
686
730
  export function describeWriteResult(result) {
687
731
  let message = "";
@@ -726,6 +770,12 @@ function readPluginList(document, label) {
726
770
  }
727
771
  return raw.slice();
728
772
  }
773
+ function readStringArray(value, label) {
774
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
775
+ throw new Error(`${label}: expected an array of strings`);
776
+ }
777
+ return value.slice();
778
+ }
729
779
  function normalizePluginList(currentPlugins, packageSpecifier) {
730
780
  const nextPlugins = [];
731
781
  const seen = new Set();
@@ -873,6 +923,25 @@ function getManagedOpenCodeAgentPromptReference(paths, agentName) {
873
923
  const promptRef = relative(dirname(paths.opencodeConfigPath), promptPath).replaceAll("\\", "/");
874
924
  return `{file:${promptRef.startsWith(".") ? promptRef : `./${promptRef}`}}`;
875
925
  }
926
+ function getManagedSkillsPathReference(paths) {
927
+ const skillsRef = relative(dirname(paths.opencodeConfigPath), paths.managedSkillsDirPath).replaceAll("\\", "/");
928
+ return skillsRef.startsWith(".") ? skillsRef : `./${skillsRef}`;
929
+ }
930
+ function ensureManagedSkillsPathConfigText(text, paths) {
931
+ const document = parseObjectDocument(text, "OpenCode config");
932
+ const skills = readOptionalObject(document, "skills", "OpenCode config");
933
+ const rawPaths = skills?.paths;
934
+ const currentPaths = rawPaths === undefined ? [] : readStringArray(rawPaths, "OpenCode config: skills.paths");
935
+ const managedSkillsPath = getManagedSkillsPathReference(paths);
936
+ if (currentPaths.includes(managedSkillsPath)) {
937
+ return text;
938
+ }
939
+ const nextPaths = [...currentPaths, managedSkillsPath];
940
+ const nextText = applyEdits(text, modify(text, ["skills", "paths"], nextPaths, {
941
+ formattingOptions: JSON_FORMAT,
942
+ }));
943
+ return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
944
+ }
876
945
  function getLegacyTrackedManagedPromptReferences(paths, agentName) {
877
946
  const promptPath = join(paths.managedAgentsDirPath, LEGACY_TRACKED_PROMPT_FILE_NAMES[agentName]);
878
947
  const promptRef = relative(dirname(paths.opencodeConfigPath), promptPath).replaceAll("\\", "/");
@@ -1188,17 +1257,14 @@ async function writeText(path, text) {
1188
1257
  await mkdir(dirname(path), { recursive: true });
1189
1258
  await writeFile(path, text, "utf8");
1190
1259
  }
1191
- async function selectPrimaryPath(candidates) {
1192
- const existing = [];
1193
- for (const candidate of candidates) {
1194
- if ((await readOptionalText(candidate)) !== undefined) {
1195
- existing.push(candidate);
1260
+ async function resolveOpenCodeAlternates(opencodeBaseDir, selectedPath) {
1261
+ const alternates = [];
1262
+ for (const candidate of OPENCODE_CONFIG_FILE_NAMES.map((name) => join(opencodeBaseDir, name))) {
1263
+ if (candidate !== selectedPath && (await readOptionalText(candidate)) !== undefined) {
1264
+ alternates.push(candidate);
1196
1265
  }
1197
1266
  }
1198
- return {
1199
- primary: existing[0] ?? candidates[0],
1200
- alternates: existing.slice(1),
1201
- };
1267
+ return alternates;
1202
1268
  }
1203
1269
  // END_BLOCK_FILESYSTEM_HELPERS
1204
1270
  //# sourceMappingURL=opencode.js.map