@osovv/vv-opencode 0.13.0 → 0.14.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/README.md +38 -34
- package/dist/commands/agent.js +23 -87
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/config-validate.d.ts +4 -24
- package/dist/commands/config-validate.js +52 -242
- package/dist/commands/config-validate.js.map +1 -1
- package/dist/commands/doctor.js +10 -9
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/guardian.js +6 -13
- package/dist/commands/guardian.js.map +1 -1
- package/dist/commands/init.js +11 -15
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts +1 -16
- package/dist/commands/install.js +8 -44
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/status.js +9 -9
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +1 -1
- package/dist/commands/sync.js +8 -10
- package/dist/commands/sync.js.map +1 -1
- package/dist/lib/opencode.d.ts +19 -52
- package/dist/lib/opencode.js +86 -336
- package/dist/lib/opencode.js.map +1 -1
- package/dist/lib/package.d.ts +2 -0
- package/dist/lib/package.js +14 -10
- package/dist/lib/package.js.map +1 -1
- package/dist/lib/vvoc-config.d.ts +209 -0
- package/dist/lib/vvoc-config.js +642 -0
- package/dist/lib/vvoc-config.js.map +1 -0
- package/dist/lib/vvoc-paths.d.ts +2 -0
- package/dist/lib/vvoc-paths.js +9 -3
- package/dist/lib/vvoc-paths.js.map +1 -1
- package/dist/plugins/guardian/index.js +24 -206
- package/dist/plugins/guardian/index.js.map +1 -1
- package/dist/plugins/memory/index.js +1 -1
- package/dist/plugins/memory/index.js.map +1 -1
- package/dist/plugins/memory-store.d.ts +2 -9
- package/dist/plugins/memory-store.js +20 -127
- package/dist/plugins/memory-store.js.map +1 -1
- package/dist/plugins/secrets-redaction/config.d.ts +3 -20
- package/dist/plugins/secrets-redaction/config.js +33 -134
- package/dist/plugins/secrets-redaction/config.js.map +1 -1
- package/package.json +3 -1
- package/schemas/vvoc/v1.json +94 -0
package/dist/lib/opencode.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// FILE: src/lib/opencode.ts
|
|
2
|
-
// VERSION: 0.
|
|
2
|
+
// VERSION: 0.6.0
|
|
3
3
|
// START_MODULE_CONTRACT
|
|
4
|
-
// PURPOSE: Manage OpenCode config mutation, provider patching, and vvoc
|
|
5
|
-
// SCOPE: Scope-aware path resolution, pinned plugin writes, provider baseURL patching, managed OpenCode agent registration/model overrides, managed agent prompt sync,
|
|
6
|
-
// DEPENDS: [jsonc-parser, node:fs/promises, node:path, src/lib/managed-agents.ts, src/lib/package.ts, src/lib/vvoc-
|
|
4
|
+
// PURPOSE: Manage OpenCode config mutation, provider patching, and the canonical vvoc.json config file.
|
|
5
|
+
// SCOPE: Scope-aware path resolution, pinned plugin writes, provider baseURL patching, managed OpenCode agent registration/model overrides, managed agent prompt sync, 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/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
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
// OPENCODE_SCHEMA_URL - OpenCode config schema URL.
|
|
16
16
|
// Scope - Supported installation scopes for vvoc config writes.
|
|
17
17
|
// ResolvedPaths - Scope-aware path bundle for OpenCode and vvoc config locations.
|
|
18
|
-
// GuardianConfigOverrides - Guardian config override shape parsed from managed JSONC.
|
|
19
18
|
// WriteResult - Result shape returned by managed config write operations.
|
|
20
19
|
// InstallationInspection - Current OpenCode and vvoc installation status snapshot.
|
|
21
20
|
// resolvePaths - Resolves OpenCode and vvoc config paths for global/project scopes.
|
|
22
21
|
// ensurePackageConfigText - Ensures OpenCode config contains the pinned vvoc plugin specifier.
|
|
23
22
|
// ensureProviderBaseUrlConfigText - Ensures OpenCode config contains the requested provider options.baseURL override.
|
|
24
23
|
// ensureManagedAgentRegistrationsConfigText - Ensures OpenCode config contains the vvoc-managed OpenCode agent registrations.
|
|
25
|
-
//
|
|
26
|
-
// renderGuardianConfig - Renders managed Guardian config JSONC.
|
|
24
|
+
// readVvocConfig - Loads the canonical vvoc.json document when present.
|
|
27
25
|
// ensurePackageInstalled - Writes the pinned vvoc plugin specifier into OpenCode config.
|
|
26
|
+
// installVvocConfig - Creates or refreshes the canonical vvoc.json document.
|
|
27
|
+
// syncVvocConfig - Rewrites the canonical vvoc.json document while preserving valid current values.
|
|
28
28
|
// writeProviderBaseUrl - Writes a provider options.baseURL override into OpenCode config.
|
|
29
29
|
// syncManagedAgentRegistrations - Syncs the canonical vvoc-managed OpenCode agent registrations into OpenCode config.
|
|
30
30
|
// installManagedAgentPrompts - Creates managed vvoc prompt files for the bundled Guardian and managed OpenCode agents when missing.
|
|
@@ -33,38 +33,27 @@
|
|
|
33
33
|
// writeOpenCodeAgentModel - Writes or removes a model override for any OpenCode agent in config.
|
|
34
34
|
// readManagedAgentModels - Reads model overrides for the bundled vvoc-managed OpenCode agents from OpenCode config.
|
|
35
35
|
// writeManagedAgentModel - Writes or removes a bundled vvoc-managed OpenCode agent model override in OpenCode config.
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// writeGuardianConfig - Writes explicit Guardian overrides to managed config.
|
|
39
|
-
// installMemoryConfig - Creates or preserves managed Memory config.
|
|
40
|
-
// syncMemoryConfig - Rewrites managed Memory config while preserving current values.
|
|
36
|
+
// writeGuardianConfig - Writes the guardian section into the canonical vvoc.json document.
|
|
37
|
+
// writeMemoryConfig - Writes the memory section into the canonical vvoc.json document.
|
|
41
38
|
// inspectInstallation - Reads current OpenCode/vvoc installation state for status and doctor commands.
|
|
42
39
|
// describeWriteResult - Formats config write outcomes for CLI output.
|
|
43
40
|
// END_MODULE_MAP
|
|
44
41
|
//
|
|
45
42
|
// START_CHANGE_SUMMARY
|
|
46
|
-
// LAST_CHANGE: [v0.
|
|
43
|
+
// LAST_CHANGE: [v0.6.0 - Replaced per-feature vvoc config files with a single canonical vvoc.json document.]
|
|
47
44
|
// END_CHANGE_SUMMARY
|
|
48
45
|
import { applyEdits, format, modify, parse } from "jsonc-parser";
|
|
49
46
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
50
47
|
import { dirname, join, relative } from "node:path";
|
|
51
48
|
import { MANAGED_AGENT_PROMPT_NAMES, MANAGED_OPENCODE_AGENTS, getManagedAgentPromptPath, getManagedOpenCodeAgentDefinition, loadManagedAgentPromptTemplate, } from "./managed-agents.js";
|
|
52
|
-
import {
|
|
49
|
+
import { createDefaultVvocConfig, createGuardianConfig, createMemoryConfig, parseVvocConfigText, renderVvocConfig, } from "./vvoc-config.js";
|
|
53
50
|
import { getPinnedPackageSpecifier, PACKAGE_NAME } from "./package.js";
|
|
54
|
-
import { getConfigHome, getGlobalOpencodeDir, getGlobalVvocDir, getProjectVvocDir, getVvocAgentsDir, } from "./vvoc-paths.js";
|
|
51
|
+
import { getConfigHome, getGlobalOpencodeDir, getGlobalVvocConfigPath, getGlobalVvocDir, getProjectVvocDir, getVvocAgentsDir, } from "./vvoc-paths.js";
|
|
55
52
|
export const CLI_NAME = "vvoc";
|
|
56
53
|
export { PACKAGE_NAME };
|
|
57
54
|
export const OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
58
55
|
const MANAGED_MARKER = "Managed by vvoc";
|
|
59
|
-
const DEFAULT_GUARDIAN_TIMEOUT_MS = 90_000;
|
|
60
|
-
const DEFAULT_GUARDIAN_APPROVAL_RISK_THRESHOLD = 80;
|
|
61
|
-
const GUARDIAN_CONFIG_FILE_NAMES = ["guardian.jsonc", "guardian.json"];
|
|
62
|
-
const MEMORY_CONFIG_FILE_NAMES = ["memory.jsonc", "memory.json"];
|
|
63
56
|
const OPENCODE_CONFIG_FILE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
64
|
-
const SECRETS_REDACTION_CONFIG_FILE_NAMES = [
|
|
65
|
-
"secrets-redaction.config.json",
|
|
66
|
-
"secrets-redaction.config.jsonc",
|
|
67
|
-
];
|
|
68
57
|
const JSON_FORMAT = {
|
|
69
58
|
insertSpaces: true,
|
|
70
59
|
tabSize: 2,
|
|
@@ -73,30 +62,21 @@ const JSON_FORMAT = {
|
|
|
73
62
|
// START_BLOCK_RESOLVE_CONFIG_PATHS
|
|
74
63
|
export async function resolvePaths(options) {
|
|
75
64
|
const configHome = getConfigHome(options.configDir);
|
|
65
|
+
const vvocBaseDir = getGlobalVvocDir(options.configDir);
|
|
76
66
|
const opencodeBaseDir = options.scope === "global" ? getGlobalOpencodeDir(options.configDir) : options.cwd;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
: getProjectVvocDir(options.cwd);
|
|
80
|
-
const managedAgentsDirPath = getVvocAgentsDir(vvocBaseDir);
|
|
67
|
+
const managedAgentsBaseDir = options.scope === "global" ? vvocBaseDir : getProjectVvocDir(options.cwd);
|
|
68
|
+
const managedAgentsDirPath = getVvocAgentsDir(managedAgentsBaseDir);
|
|
81
69
|
const opencodeSelection = await selectPrimaryPath(OPENCODE_CONFIG_FILE_NAMES.map((name) => join(opencodeBaseDir, name)));
|
|
82
|
-
const guardianSelection = await selectPrimaryPath(GUARDIAN_CONFIG_FILE_NAMES.map((name) => join(vvocBaseDir, name)));
|
|
83
|
-
const memorySelection = await selectPrimaryPath(MEMORY_CONFIG_FILE_NAMES.map((name) => join(vvocBaseDir, name)));
|
|
84
|
-
const secretsRedactionSelection = await selectPrimaryPath(SECRETS_REDACTION_CONFIG_FILE_NAMES.map((name) => join(vvocBaseDir, name)));
|
|
85
70
|
return {
|
|
86
71
|
scope: options.scope,
|
|
87
72
|
cwd: options.cwd,
|
|
88
73
|
configHome,
|
|
89
74
|
opencodeBaseDir,
|
|
90
75
|
vvocBaseDir,
|
|
76
|
+
vvocConfigPath: getGlobalVvocConfigPath(options.configDir),
|
|
91
77
|
managedAgentsDirPath,
|
|
92
78
|
opencodeConfigPath: opencodeSelection.primary,
|
|
93
79
|
opencodeAlternatePaths: opencodeSelection.alternates,
|
|
94
|
-
guardianConfigPath: guardianSelection.primary,
|
|
95
|
-
guardianAlternatePaths: guardianSelection.alternates,
|
|
96
|
-
memoryConfigPath: memorySelection.primary,
|
|
97
|
-
memoryConfigAlternates: memorySelection.alternates,
|
|
98
|
-
secretsRedactionConfigPath: secretsRedactionSelection.primary,
|
|
99
|
-
secretsRedactionConfigAlternates: secretsRedactionSelection.alternates,
|
|
100
80
|
};
|
|
101
81
|
}
|
|
102
82
|
// END_BLOCK_RESOLVE_CONFIG_PATHS
|
|
@@ -304,42 +284,7 @@ export async function writeManagedAgentModel(paths, agentName, options) {
|
|
|
304
284
|
};
|
|
305
285
|
}
|
|
306
286
|
// END_BLOCK_ENSURE_MANAGED_SUBAGENT_CONFIG
|
|
307
|
-
export
|
|
308
|
-
return normalizeGuardianOverrides(parseObjectDocument(text, label), label);
|
|
309
|
-
}
|
|
310
|
-
// START_BLOCK_RENDER_GUARDIAN_CONFIG
|
|
311
|
-
export function renderGuardianConfig(overrides = {}) {
|
|
312
|
-
const timeoutMs = overrides.timeoutMs ?? DEFAULT_GUARDIAN_TIMEOUT_MS;
|
|
313
|
-
const approvalRiskThreshold = overrides.approvalRiskThreshold ?? DEFAULT_GUARDIAN_APPROVAL_RISK_THRESHOLD;
|
|
314
|
-
const reviewToastDurationMs = overrides.reviewToastDurationMs ?? timeoutMs;
|
|
315
|
-
const lines = [
|
|
316
|
-
"// Managed by vvoc.",
|
|
317
|
-
"// `vvoc sync` rewrites files with this marker while preserving current values.",
|
|
318
|
-
"// Remove this header if you want to manage the file manually.",
|
|
319
|
-
"",
|
|
320
|
-
"{",
|
|
321
|
-
];
|
|
322
|
-
if (overrides.model) {
|
|
323
|
-
lines.push(` "model": ${JSON.stringify(overrides.model)},`);
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
lines.push(' // "model": "anthropic/claude-sonnet-4-5",');
|
|
327
|
-
}
|
|
328
|
-
lines.push("");
|
|
329
|
-
if (overrides.variant) {
|
|
330
|
-
lines.push(` "variant": ${JSON.stringify(overrides.variant)},`);
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
lines.push(' // "variant": "high",');
|
|
334
|
-
}
|
|
335
|
-
lines.push("");
|
|
336
|
-
lines.push(` "timeoutMs": ${timeoutMs},`);
|
|
337
|
-
lines.push(` "approvalRiskThreshold": ${approvalRiskThreshold},`);
|
|
338
|
-
lines.push(` "reviewToastDurationMs": ${reviewToastDurationMs}`);
|
|
339
|
-
lines.push("}");
|
|
340
|
-
return `${lines.join("\n")}\n`;
|
|
341
|
-
}
|
|
342
|
-
// END_BLOCK_RENDER_GUARDIAN_CONFIG
|
|
287
|
+
export { parseGuardianConfigText, renderGuardianConfig, } from "./vvoc-config.js";
|
|
343
288
|
// START_BLOCK_INSTALL_PACKAGE_AND_GUARDIAN_CONFIG
|
|
344
289
|
export async function ensurePackageInstalled(paths) {
|
|
345
290
|
const currentText = await readOptionalText(paths.opencodeConfigPath);
|
|
@@ -397,177 +342,47 @@ export async function writeProviderBaseUrl(paths, providerID, baseURL) {
|
|
|
397
342
|
};
|
|
398
343
|
}
|
|
399
344
|
// END_BLOCK_ENSURE_PROVIDER_BASE_URL_CONFIG
|
|
400
|
-
export async function
|
|
401
|
-
const currentText = await readOptionalText(paths.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (!options.force && !isManagedFile(currentText)) {
|
|
425
|
-
return {
|
|
426
|
-
action: "skipped",
|
|
427
|
-
path: paths.guardianConfigPath,
|
|
428
|
-
reason: "existing file is not managed by vvoc",
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
const nextText = renderGuardianConfig(parseGuardianConfigText(currentText, paths.guardianConfigPath));
|
|
432
|
-
if (currentText === nextText) {
|
|
433
|
-
return { action: "kept", path: paths.guardianConfigPath };
|
|
434
|
-
}
|
|
435
|
-
await writeText(paths.guardianConfigPath, nextText);
|
|
436
|
-
return { action: "updated", path: paths.guardianConfigPath };
|
|
437
|
-
}
|
|
438
|
-
export async function writeGuardianConfig(paths, overrides, options) {
|
|
439
|
-
const currentText = await readOptionalText(paths.guardianConfigPath);
|
|
440
|
-
if (currentText && !options.force && !isManagedFile(currentText)) {
|
|
441
|
-
return {
|
|
442
|
-
action: "skipped",
|
|
443
|
-
path: paths.guardianConfigPath,
|
|
444
|
-
reason: "existing file is not managed by vvoc",
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
const nextText = renderGuardianConfig(overrides);
|
|
448
|
-
if (currentText === nextText) {
|
|
449
|
-
return { action: "kept", path: paths.guardianConfigPath };
|
|
450
|
-
}
|
|
451
|
-
await writeText(paths.guardianConfigPath, nextText);
|
|
452
|
-
return {
|
|
453
|
-
action: currentText ? "updated" : "created",
|
|
454
|
-
path: paths.guardianConfigPath,
|
|
345
|
+
export async function readVvocConfig(paths) {
|
|
346
|
+
const currentText = await readOptionalText(paths.vvocConfigPath);
|
|
347
|
+
return currentText ? parseVvocConfigText(currentText, paths.vvocConfigPath) : undefined;
|
|
348
|
+
}
|
|
349
|
+
export async function installVvocConfig(paths) {
|
|
350
|
+
return syncVvocConfig(paths);
|
|
351
|
+
}
|
|
352
|
+
export async function syncVvocConfig(paths) {
|
|
353
|
+
const currentText = await readOptionalText(paths.vvocConfigPath);
|
|
354
|
+
const nextConfig = currentText
|
|
355
|
+
? parseVvocConfigText(currentText, paths.vvocConfigPath)
|
|
356
|
+
: createDefaultVvocConfig();
|
|
357
|
+
return writeResolvedVvocConfig(paths.vvocConfigPath, currentText, nextConfig);
|
|
358
|
+
}
|
|
359
|
+
export async function writeGuardianConfig(paths, overrides, options = {}) {
|
|
360
|
+
const currentText = await readOptionalText(paths.vvocConfigPath);
|
|
361
|
+
const currentConfig = currentText
|
|
362
|
+
? parseVvocConfigText(currentText, paths.vvocConfigPath)
|
|
363
|
+
: createDefaultVvocConfig();
|
|
364
|
+
const nextConfig = {
|
|
365
|
+
...currentConfig,
|
|
366
|
+
guardian: options.merge
|
|
367
|
+
? createGuardianConfig({ ...currentConfig.guardian, ...overrides })
|
|
368
|
+
: createGuardianConfig(overrides),
|
|
455
369
|
};
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
reason: "existing file is not managed by vvoc",
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
return { action: "kept", path: paths.memoryConfigPath };
|
|
474
|
-
}
|
|
475
|
-
return syncMemoryConfig(paths, options);
|
|
476
|
-
}
|
|
477
|
-
export async function syncMemoryConfig(paths, options) {
|
|
478
|
-
const currentText = await readOptionalText(paths.memoryConfigPath);
|
|
479
|
-
if (!currentText) {
|
|
480
|
-
await writeText(paths.memoryConfigPath, renderMemoryConfig());
|
|
481
|
-
return { action: "created", path: paths.memoryConfigPath };
|
|
482
|
-
}
|
|
483
|
-
if (!options.force && !isManagedFile(currentText)) {
|
|
484
|
-
return {
|
|
485
|
-
action: "skipped",
|
|
486
|
-
path: paths.memoryConfigPath,
|
|
487
|
-
reason: "existing file is not managed by vvoc",
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
const nextText = renderMemoryConfig(parseMemoryConfigText(currentText, paths.memoryConfigPath));
|
|
491
|
-
if (currentText === nextText) {
|
|
492
|
-
return { action: "kept", path: paths.memoryConfigPath };
|
|
493
|
-
}
|
|
494
|
-
await writeText(paths.memoryConfigPath, nextText);
|
|
495
|
-
return { action: "updated", path: paths.memoryConfigPath };
|
|
370
|
+
return writeResolvedVvocConfig(paths.vvocConfigPath, currentText, nextConfig);
|
|
371
|
+
}
|
|
372
|
+
export async function writeMemoryConfig(paths, overrides, options = {}) {
|
|
373
|
+
const currentText = await readOptionalText(paths.vvocConfigPath);
|
|
374
|
+
const currentConfig = currentText
|
|
375
|
+
? parseVvocConfigText(currentText, paths.vvocConfigPath)
|
|
376
|
+
: createDefaultVvocConfig();
|
|
377
|
+
const nextConfig = {
|
|
378
|
+
...currentConfig,
|
|
379
|
+
memory: options.merge
|
|
380
|
+
? createMemoryConfig({ ...currentConfig.memory, ...overrides })
|
|
381
|
+
: createMemoryConfig(overrides),
|
|
382
|
+
};
|
|
383
|
+
return writeResolvedVvocConfig(paths.vvocConfigPath, currentText, nextConfig);
|
|
496
384
|
}
|
|
497
385
|
// END_BLOCK_INSTALL_MEMORY_CONFIG
|
|
498
|
-
// START_BLOCK_INSTALL_SECRETS_REDACTION_CONFIG
|
|
499
|
-
export function renderSecretsRedactionConfig() {
|
|
500
|
-
const lines = [
|
|
501
|
-
"// Managed by vvoc.",
|
|
502
|
-
"// `vvoc sync` rewrites files with this marker while preserving current values.",
|
|
503
|
-
"// Remove this header if you want to manage the file manually.",
|
|
504
|
-
"",
|
|
505
|
-
"{",
|
|
506
|
-
' "enabled": true,',
|
|
507
|
-
' "secret": "${VVOC_SECRET}",',
|
|
508
|
-
' "ttlMs": 3600000,',
|
|
509
|
-
' "maxMappings": 10000,',
|
|
510
|
-
' "patterns": {',
|
|
511
|
-
' "keywords": [],',
|
|
512
|
-
' "regex": [],',
|
|
513
|
-
' "builtin": ["email", "uuid", "ipv4", "mac", "openai_key", "anthropic_key", "github_token", "aws_access_key", "stripe_key", "bearer_token", "bearer_dot", "syn_key", "hex_token"],',
|
|
514
|
-
' "exclude": []',
|
|
515
|
-
" },",
|
|
516
|
-
' "debug": false',
|
|
517
|
-
"}",
|
|
518
|
-
];
|
|
519
|
-
return `${lines.join("\n")}\n`;
|
|
520
|
-
}
|
|
521
|
-
export function parseSecretsRedactionConfigText(text, _filePath) {
|
|
522
|
-
const errors = [];
|
|
523
|
-
const result = parse(text, errors, { allowTrailingComma: true });
|
|
524
|
-
if (errors.length > 0) {
|
|
525
|
-
throw new Error(`parse error at offset ${errors[0].offset}`);
|
|
526
|
-
}
|
|
527
|
-
if (typeof result !== "object" || result === null) {
|
|
528
|
-
throw new Error("root must be an object");
|
|
529
|
-
}
|
|
530
|
-
return result;
|
|
531
|
-
}
|
|
532
|
-
export async function installSecretsRedactionConfig(paths, options) {
|
|
533
|
-
const currentText = await readOptionalText(paths.secretsRedactionConfigPath);
|
|
534
|
-
if (!currentText) {
|
|
535
|
-
await writeText(paths.secretsRedactionConfigPath, renderSecretsRedactionConfig());
|
|
536
|
-
return { action: "created", path: paths.secretsRedactionConfigPath };
|
|
537
|
-
}
|
|
538
|
-
if (!options.force) {
|
|
539
|
-
if (!isManagedFile(currentText)) {
|
|
540
|
-
return {
|
|
541
|
-
action: "skipped",
|
|
542
|
-
path: paths.secretsRedactionConfigPath,
|
|
543
|
-
reason: "existing file is not managed by vvoc",
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
return { action: "kept", path: paths.secretsRedactionConfigPath };
|
|
547
|
-
}
|
|
548
|
-
return syncSecretsRedactionConfig(paths, options);
|
|
549
|
-
}
|
|
550
|
-
export async function syncSecretsRedactionConfig(paths, options) {
|
|
551
|
-
const currentText = await readOptionalText(paths.secretsRedactionConfigPath);
|
|
552
|
-
if (!currentText) {
|
|
553
|
-
await writeText(paths.secretsRedactionConfigPath, renderSecretsRedactionConfig());
|
|
554
|
-
return { action: "created", path: paths.secretsRedactionConfigPath };
|
|
555
|
-
}
|
|
556
|
-
if (!options.force && !isManagedFile(currentText)) {
|
|
557
|
-
return {
|
|
558
|
-
action: "skipped",
|
|
559
|
-
path: paths.secretsRedactionConfigPath,
|
|
560
|
-
reason: "existing file is not managed by vvoc",
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
const nextText = renderSecretsRedactionConfig();
|
|
564
|
-
if (currentText === nextText) {
|
|
565
|
-
return { action: "kept", path: paths.secretsRedactionConfigPath };
|
|
566
|
-
}
|
|
567
|
-
await writeText(paths.secretsRedactionConfigPath, nextText);
|
|
568
|
-
return { action: "updated", path: paths.secretsRedactionConfigPath };
|
|
569
|
-
}
|
|
570
|
-
// END_BLOCK_INSTALL_SECRETS_REDACTION_CONFIG
|
|
571
386
|
// START_BLOCK_INSPECT_INSTALLATION_STATE
|
|
572
387
|
export async function inspectInstallation(paths) {
|
|
573
388
|
const warnings = [];
|
|
@@ -575,15 +390,6 @@ export async function inspectInstallation(paths) {
|
|
|
575
390
|
if (paths.opencodeAlternatePaths.length > 0) {
|
|
576
391
|
warnings.push(`multiple OpenCode config files exist: ${[paths.opencodeConfigPath, ...paths.opencodeAlternatePaths].join(", ")}`);
|
|
577
392
|
}
|
|
578
|
-
if (paths.guardianAlternatePaths.length > 0) {
|
|
579
|
-
warnings.push(`multiple Guardian config files exist: ${[paths.guardianConfigPath, ...paths.guardianAlternatePaths].join(", ")}`);
|
|
580
|
-
}
|
|
581
|
-
if (paths.memoryConfigAlternates.length > 0) {
|
|
582
|
-
warnings.push(`multiple Memory config files exist: ${[paths.memoryConfigPath, ...paths.memoryConfigAlternates].join(", ")}`);
|
|
583
|
-
}
|
|
584
|
-
if (paths.secretsRedactionConfigAlternates.length > 0) {
|
|
585
|
-
warnings.push(`multiple SecretsRedaction config files exist: ${[paths.secretsRedactionConfigPath, ...paths.secretsRedactionConfigAlternates].join(", ")}`);
|
|
586
|
-
}
|
|
587
393
|
const opencodeText = await readOptionalText(paths.opencodeConfigPath);
|
|
588
394
|
let opencodeParseError;
|
|
589
395
|
let plugins = [];
|
|
@@ -599,49 +405,24 @@ export async function inspectInstallation(paths) {
|
|
|
599
405
|
problems.push(opencodeParseError);
|
|
600
406
|
}
|
|
601
407
|
}
|
|
602
|
-
const
|
|
603
|
-
let
|
|
604
|
-
let
|
|
605
|
-
|
|
606
|
-
if (guardianText) {
|
|
408
|
+
const vvocText = await readOptionalText(paths.vvocConfigPath);
|
|
409
|
+
let vvocParseError;
|
|
410
|
+
let vvocConfig;
|
|
411
|
+
if (vvocText) {
|
|
607
412
|
try {
|
|
608
|
-
|
|
413
|
+
vvocConfig = parseVvocConfigText(vvocText, paths.vvocConfigPath);
|
|
609
414
|
}
|
|
610
415
|
catch (error) {
|
|
611
|
-
|
|
612
|
-
problems.push(
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
const memoryText = await readOptionalText(paths.memoryConfigPath);
|
|
616
|
-
let memoryParseError;
|
|
617
|
-
let memoryOverrides;
|
|
618
|
-
const memoryManaged = memoryText ? isManagedFile(memoryText) : false;
|
|
619
|
-
if (memoryText) {
|
|
620
|
-
try {
|
|
621
|
-
memoryOverrides = parseMemoryConfigText(memoryText, paths.memoryConfigPath);
|
|
622
|
-
}
|
|
623
|
-
catch (error) {
|
|
624
|
-
memoryParseError = error instanceof Error ? error.message : String(error);
|
|
625
|
-
problems.push(memoryParseError);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
const secretsRedactionText = await readOptionalText(paths.secretsRedactionConfigPath);
|
|
629
|
-
let secretsRedactionParseError;
|
|
630
|
-
const secretsRedactionManaged = secretsRedactionText
|
|
631
|
-
? isManagedFile(secretsRedactionText)
|
|
632
|
-
: false;
|
|
633
|
-
if (secretsRedactionText) {
|
|
634
|
-
try {
|
|
635
|
-
parseSecretsRedactionConfigText(secretsRedactionText, paths.secretsRedactionConfigPath);
|
|
636
|
-
}
|
|
637
|
-
catch (error) {
|
|
638
|
-
secretsRedactionParseError = error instanceof Error ? error.message : String(error);
|
|
639
|
-
problems.push(secretsRedactionParseError);
|
|
416
|
+
vvocParseError = error instanceof Error ? error.message : String(error);
|
|
417
|
+
problems.push(vvocParseError);
|
|
640
418
|
}
|
|
641
419
|
}
|
|
642
420
|
if (!pluginConfigured) {
|
|
643
421
|
problems.push(`${PACKAGE_NAME} is not configured in ${paths.opencodeConfigPath}`);
|
|
644
422
|
}
|
|
423
|
+
if (!vvocText) {
|
|
424
|
+
problems.push(`vvoc config is missing at ${paths.vvocConfigPath}`);
|
|
425
|
+
}
|
|
645
426
|
return {
|
|
646
427
|
scope: paths.scope,
|
|
647
428
|
opencode: {
|
|
@@ -652,28 +433,21 @@ export async function inspectInstallation(paths) {
|
|
|
652
433
|
pluginConfigured,
|
|
653
434
|
plugins,
|
|
654
435
|
},
|
|
436
|
+
vvoc: {
|
|
437
|
+
path: paths.vvocConfigPath,
|
|
438
|
+
exists: Boolean(vvocText),
|
|
439
|
+
parseError: vvocParseError,
|
|
440
|
+
schema: vvocConfig?.$schema,
|
|
441
|
+
version: vvocConfig?.version,
|
|
442
|
+
},
|
|
655
443
|
guardian: {
|
|
656
|
-
|
|
657
|
-
exists: Boolean(guardianText),
|
|
658
|
-
alternates: paths.guardianAlternatePaths,
|
|
659
|
-
managed: guardianManaged,
|
|
660
|
-
parseError: guardianParseError,
|
|
661
|
-
overrides: guardianOverrides,
|
|
444
|
+
config: vvocConfig?.guardian,
|
|
662
445
|
},
|
|
663
446
|
memory: {
|
|
664
|
-
|
|
665
|
-
exists: Boolean(memoryText),
|
|
666
|
-
alternates: paths.memoryConfigAlternates,
|
|
667
|
-
managed: memoryManaged,
|
|
668
|
-
parseError: memoryParseError,
|
|
669
|
-
overrides: memoryOverrides,
|
|
447
|
+
config: vvocConfig?.memory,
|
|
670
448
|
},
|
|
671
449
|
secretsRedaction: {
|
|
672
|
-
|
|
673
|
-
exists: Boolean(secretsRedactionText),
|
|
674
|
-
alternates: paths.secretsRedactionConfigAlternates,
|
|
675
|
-
managed: secretsRedactionManaged,
|
|
676
|
-
parseError: secretsRedactionParseError,
|
|
450
|
+
config: vvocConfig?.secretsRedaction,
|
|
677
451
|
},
|
|
678
452
|
warnings,
|
|
679
453
|
problems,
|
|
@@ -868,29 +642,6 @@ function updateAgentEntryText(text, agentName, entry) {
|
|
|
868
642
|
return ensureTrailingNewline(applyEdits(nextText, format(nextText, undefined, JSON_FORMAT)));
|
|
869
643
|
}
|
|
870
644
|
// END_BLOCK_MANAGED_AGENT_HELPERS
|
|
871
|
-
function normalizeGuardianOverrides(raw, label) {
|
|
872
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
873
|
-
throw new Error(`${label}: expected a top-level object`);
|
|
874
|
-
}
|
|
875
|
-
const record = raw;
|
|
876
|
-
const overrides = {};
|
|
877
|
-
if (Object.hasOwn(record, "model")) {
|
|
878
|
-
overrides.model = readNonEmptyString(record.model, `${label}: model`);
|
|
879
|
-
}
|
|
880
|
-
if (Object.hasOwn(record, "variant")) {
|
|
881
|
-
overrides.variant = readNonEmptyString(record.variant, `${label}: variant`);
|
|
882
|
-
}
|
|
883
|
-
if (Object.hasOwn(record, "timeoutMs")) {
|
|
884
|
-
overrides.timeoutMs = readPositiveInteger(record.timeoutMs, `${label}: timeoutMs`);
|
|
885
|
-
}
|
|
886
|
-
if (Object.hasOwn(record, "approvalRiskThreshold")) {
|
|
887
|
-
overrides.approvalRiskThreshold = readThreshold(record.approvalRiskThreshold, `${label}: approvalRiskThreshold`);
|
|
888
|
-
}
|
|
889
|
-
if (Object.hasOwn(record, "reviewToastDurationMs")) {
|
|
890
|
-
overrides.reviewToastDurationMs = readPositiveInteger(record.reviewToastDurationMs, `${label}: reviewToastDurationMs`);
|
|
891
|
-
}
|
|
892
|
-
return overrides;
|
|
893
|
-
}
|
|
894
645
|
function readNonEmptyString(value, label) {
|
|
895
646
|
if (typeof value !== "string" || !value.trim()) {
|
|
896
647
|
throw new Error(`${label}: expected a non-empty string`);
|
|
@@ -907,18 +658,6 @@ function readOptionalObject(document, key, label) {
|
|
|
907
658
|
}
|
|
908
659
|
return value;
|
|
909
660
|
}
|
|
910
|
-
function readPositiveInteger(value, label) {
|
|
911
|
-
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
912
|
-
return Math.round(value);
|
|
913
|
-
}
|
|
914
|
-
throw new Error(`${label}: expected a positive integer`);
|
|
915
|
-
}
|
|
916
|
-
function readThreshold(value, label) {
|
|
917
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
918
|
-
return Math.max(0, Math.min(100, Math.round(value)));
|
|
919
|
-
}
|
|
920
|
-
throw new Error(`${label}: expected a number between 0 and 100`);
|
|
921
|
-
}
|
|
922
661
|
// END_BLOCK_PARSE_AND_NORMALIZE_CONFIG_VALUES
|
|
923
662
|
// START_BLOCK_FILESYSTEM_HELPERS
|
|
924
663
|
function isManagedFile(text) {
|
|
@@ -934,6 +673,17 @@ function stripMarkdownFrontmatter(text) {
|
|
|
934
673
|
function ensureTrailingNewline(text) {
|
|
935
674
|
return text.endsWith("\n") ? text : `${text}\n`;
|
|
936
675
|
}
|
|
676
|
+
async function writeResolvedVvocConfig(path, currentText, config) {
|
|
677
|
+
const nextText = renderVvocConfig(config);
|
|
678
|
+
if ((currentText ?? "") === nextText) {
|
|
679
|
+
return { action: "kept", path };
|
|
680
|
+
}
|
|
681
|
+
await writeText(path, nextText);
|
|
682
|
+
return {
|
|
683
|
+
action: currentText ? "updated" : "created",
|
|
684
|
+
path,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
937
687
|
async function readOptionalText(path) {
|
|
938
688
|
try {
|
|
939
689
|
return await readFile(path, "utf8");
|