vgxness 1.20.13 → 1.20.15

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 (28) hide show
  1. package/README.md +2 -0
  2. package/dist/adapters/opencode/install/client-install-opencode-contract.js +4 -2
  3. package/dist/adapters/opencode/install/client-install-opencode.js +6 -2
  4. package/dist/application/agents/agent-resolver.js +9 -1
  5. package/dist/application/knowledge-workspace/canvas-renderer.js +36 -0
  6. package/dist/application/knowledge-workspace/filesystem-workspace-port.js +98 -0
  7. package/dist/application/knowledge-workspace/markdown-renderer.js +68 -0
  8. package/dist/application/knowledge-workspace/obsidian-export-service.js +157 -0
  9. package/dist/application/knowledge-workspace/obsidian-mcp-workspace-port.js +109 -0
  10. package/dist/application/knowledge-workspace/workspace-port.js +20 -0
  11. package/dist/application/provider-setup/provider-doctor.js +1 -1
  12. package/dist/domain/agents/canonical-agent-manifest.js +0 -1
  13. package/dist/domain/knowledge-workspace/schema.js +1 -0
  14. package/dist/interfaces/cli/cli-help.js +5 -1
  15. package/dist/interfaces/cli/commands/index.js +1 -0
  16. package/dist/interfaces/cli/commands/knowledge-workspace-dispatcher.js +252 -0
  17. package/dist/interfaces/cli/dispatcher.js +9 -1
  18. package/docs/architecture.md +1 -1
  19. package/docs/cli.md +13 -0
  20. package/docs/glossary.md +1 -1
  21. package/docs/knowledge-workspace.md +135 -0
  22. package/docs/obsidian-mcp.md +130 -0
  23. package/docs/sdd/sqlite-obsidian-knowledge-workflow/design.md +118 -0
  24. package/docs/sdd/sqlite-obsidian-knowledge-workflow/proposal.md +80 -0
  25. package/docs/sdd/sqlite-obsidian-knowledge-workflow/spec.md +88 -0
  26. package/docs/sdd/sqlite-obsidian-knowledge-workflow/tasks.md +70 -0
  27. package/docs/storage.md +2 -0
  28. package/package.json +1 -1
@@ -0,0 +1,252 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve, sep } from 'node:path';
3
+ import { normalizeSddArtifact } from '../../../domain/sdd/schema.js';
4
+ import { renderGeneratedFrontmatter, renderMemorySummaryMarkdown, renderSddBoardMarkdown } from '../../../application/knowledge-workspace/markdown-renderer.js';
5
+ import { renderObsidianCanvas } from '../../../application/knowledge-workspace/canvas-renderer.js';
6
+ import { assertSafeWorkspaceRelativePath, isGeneratedByVgxness } from '../../../application/knowledge-workspace/workspace-port.js';
7
+ import { MemoryService } from '../../../memory/memory-service.js';
8
+ import { optionalNumberFlag, optionalStringFlag, requiredFlag } from '../cli-flags.js';
9
+ import { jsonResult, resultFailure } from '../cli-helpers.js';
10
+ import { okText, usageFailure, validationFailure } from '../cli-help.js';
11
+ const defaultWorkspaceRoot = 'VGXNESS';
12
+ const defaultFolders = [
13
+ '00 Home',
14
+ '01 PRDs',
15
+ '02 Architecture',
16
+ '03 SDD',
17
+ '04 Research',
18
+ '05 Decisions',
19
+ '06 Memory Summaries',
20
+ '07 Canvas',
21
+ '08 Boards',
22
+ '09 Release Notes',
23
+ ];
24
+ export function runKnowledgeWorkspaceCommand(command, parsed, database, environment) {
25
+ const workspace = workspaceModeFlag(parsed.flags);
26
+ if (!workspace.ok)
27
+ return resultFailure(workspace);
28
+ if (command === 'status')
29
+ return runStatus(parsed, workspace.value, environment);
30
+ if (command === 'init')
31
+ return runInit(parsed, workspace.value, environment);
32
+ if (command === 'export')
33
+ return runExport(parsed, workspace.value, database, environment);
34
+ return usageFailure(`Unknown knowledge command: ${command}`);
35
+ }
36
+ function runStatus(parsed, workspace, environment) {
37
+ if (workspace === 'obsidian-mcp') {
38
+ return jsonOrText(parsed, obsidianMcpUnavailableStatus(), 'Obsidian MCP workspace: unavailable from standalone CLI. Configure Obsidian MCP in the agent host, or use --workspace filesystem --vault <path> for explicit fallback.\n');
39
+ }
40
+ const vault = vaultFlag(parsed.flags, environment);
41
+ if (!vault.ok)
42
+ return resultFailure(vault);
43
+ const workspaceRoot = resolve(vault.value, defaultWorkspaceRoot);
44
+ return jsonOrText(parsed, {
45
+ kind: 'knowledge-workspace-status',
46
+ status: existsSync(workspaceRoot) ? 'available' : 'unavailable',
47
+ mode: 'filesystem',
48
+ workspaceRoot,
49
+ diagnostics: existsSync(workspaceRoot) ? [] : ['Workspace root does not exist yet; run knowledge init.'],
50
+ safety: { readOnly: true, writesNotes: false, deletesNotes: false, executesObsidianCommands: false },
51
+ }, `${existsSync(workspaceRoot) ? 'available' : 'unavailable'} filesystem knowledge workspace: ${workspaceRoot}\n`);
52
+ }
53
+ function runInit(parsed, workspace, environment) {
54
+ if (workspace === 'obsidian-mcp') {
55
+ return jsonOrText(parsed, {
56
+ kind: 'knowledge-workspace-init',
57
+ workspace,
58
+ created: [],
59
+ manualActionRequired: true,
60
+ guidance: [
61
+ 'Install and enable the Obsidian Local REST API plugin.',
62
+ 'Configure an Obsidian MCP server in the agent host with OBSIDIAN_API_KEY stored outside the repo.',
63
+ 'Scope OBSIDIAN_READ_PATHS and OBSIDIAN_WRITE_PATHS to VGXNESS where supported.',
64
+ 'Keep OBSIDIAN_ENABLE_COMMANDS=false unless a human explicitly approves command execution.',
65
+ ],
66
+ }, 'Obsidian MCP init is manual: configure the Obsidian MCP server in the agent host with secrets outside the repo.\n');
67
+ }
68
+ const vault = vaultFlag(parsed.flags, environment);
69
+ if (!vault.ok)
70
+ return resultFailure(vault);
71
+ const root = resolve(vault.value, defaultWorkspaceRoot);
72
+ const created = [root, ...defaultFolders.map((folder) => resolve(root, folder))];
73
+ for (const path of created)
74
+ mkdirSync(path, { recursive: true });
75
+ return jsonOrText(parsed, { kind: 'knowledge-workspace-init', workspace, vaultRoot: vault.value, workspaceRoot: root, created }, `Initialized ${defaultWorkspaceRoot}/ in ${vault.value}\n`);
76
+ }
77
+ function runExport(parsed, workspace, database, environment) {
78
+ const project = requiredFlag(parsed.flags, 'project');
79
+ if (!project.ok)
80
+ return resultFailure(project);
81
+ const kind = exportKindFlag(parsed.flags);
82
+ if (!kind.ok)
83
+ return resultFailure(kind);
84
+ const limit = optionalNumberFlag(parsed.flags, 'limit');
85
+ if (!limit.ok)
86
+ return resultFailure(limit);
87
+ if (workspace === 'obsidian-mcp') {
88
+ return resultFailure(validationFailure('Obsidian MCP export requires an agent-host MCP client; standalone CLI cannot call host MCP tools yet. Use --workspace filesystem --vault <path> for explicit fallback, or run the sync from an MCP-capable agent host.'));
89
+ }
90
+ const vault = vaultFlag(parsed.flags, environment);
91
+ if (!vault.ok)
92
+ return resultFailure(vault);
93
+ if (database === undefined)
94
+ return resultFailure(validationFailure('Filesystem knowledge export requires a local SQLite database'));
95
+ const memory = new MemoryService(database);
96
+ const exported = exportFilesystemSync({
97
+ project: project.value,
98
+ requestedKind: kind.value,
99
+ vaultRoot: vault.value,
100
+ transport: 'filesystem',
101
+ memory,
102
+ ...(limit.value === undefined ? {} : { limit: limit.value }),
103
+ });
104
+ return jsonResult(exported);
105
+ }
106
+ function exportFilesystemSync(input) {
107
+ try {
108
+ const workspaceRoot = resolve(input.vaultRoot, defaultWorkspaceRoot);
109
+ if (!existsSync(workspaceRoot))
110
+ return validationFailure('Knowledge workspace root does not exist; run knowledge init first');
111
+ const kinds = input.requestedKind === 'all' ? ['docs', 'memory', 'sdd', 'canvas'] : [input.requestedKind];
112
+ const exportedNotes = [];
113
+ for (const kind of kinds) {
114
+ if (kind === 'docs')
115
+ exportedNotes.push(writeDocs({ project: input.project, vaultRoot: input.vaultRoot, transport: input.transport }));
116
+ if (kind === 'memory') {
117
+ const previews = input.memory.searchObservationPreviewsNoTrace({ project: input.project, limit: input.limit ?? 50 });
118
+ if (!previews.ok)
119
+ return previews;
120
+ exportedNotes.push(writeMemory({ project: input.project, vaultRoot: input.vaultRoot, transport: input.transport, observations: previews.value }));
121
+ }
122
+ if (kind === 'sdd') {
123
+ const artifacts = input.memory.listArtifactsByTopicPrefixNoTrace(input.project, 'sdd/');
124
+ if (!artifacts.ok)
125
+ return artifacts;
126
+ exportedNotes.push(...writeSdd({ project: input.project, vaultRoot: input.vaultRoot, transport: input.transport, artifacts: artifacts.value }));
127
+ }
128
+ if (kind === 'canvas')
129
+ exportedNotes.push(writeCanvas({ project: input.project, vaultRoot: input.vaultRoot }));
130
+ }
131
+ return {
132
+ ok: true,
133
+ value: {
134
+ kind: 'knowledge-workspace-export',
135
+ project: input.project,
136
+ requestedKind: input.requestedKind,
137
+ transport: input.transport,
138
+ exportedNotes,
139
+ safety: {
140
+ sqliteRemainsSourceOfTruth: true,
141
+ overwritesGeneratedNotesOnly: true,
142
+ exportsMemoryPreviewsOnly: true,
143
+ infersSddAcceptanceFromObsidian: false,
144
+ },
145
+ },
146
+ };
147
+ }
148
+ catch (cause) {
149
+ return { ok: false, error: { code: 'validation_failed', message: cause instanceof Error ? cause.message : String(cause), cause } };
150
+ }
151
+ }
152
+ function writeDocs(input) {
153
+ const content = `${renderGeneratedFrontmatter({ generatedBy: 'vgxness', sourceOfTruth: 'repository-docs', manualEdits: 'overwritten_on_export', transport: input.transport, generatedAt: new Date().toISOString(), project: input.project })}# ${input.project} Knowledge Workspace\n\nThis workspace is a human-facing projection generated by VGXNESS.\n\n## Boundaries\n\n- SQLite remains the source of truth for memory, SDD governance, runs, traces, and acceptance.\n- Obsidian is for review, planning, docs, boards, and canvas navigation.\n- Generated notes disclose that manual edits may be overwritten on export.\n`;
154
+ return { ...writeGeneratedFile(input.vaultRoot, `VGXNESS/00 Home/${input.project} Knowledge Workspace.md`, content), kind: 'docs' };
155
+ }
156
+ function writeMemory(input) {
157
+ const content = renderMemorySummaryMarkdown({
158
+ project: input.project,
159
+ generatedAt: new Date().toISOString(),
160
+ transport: input.transport,
161
+ observations: input.observations.map((observation) => ({
162
+ title: observation.title,
163
+ type: observation.type,
164
+ scope: observation.scope,
165
+ preview: observation.preview,
166
+ updatedAt: observation.updatedAt,
167
+ ...(observation.topicKey === undefined ? {} : { topicKey: observation.topicKey }),
168
+ })),
169
+ });
170
+ return { ...writeGeneratedFile(input.vaultRoot, `VGXNESS/06 Memory Summaries/${input.project}.md`, content), kind: 'memory' };
171
+ }
172
+ function writeSdd(input) {
173
+ const exported = [];
174
+ const boardItems = [];
175
+ for (const artifact of input.artifacts) {
176
+ const topic = parseSddTopicKey(artifact.topicKey);
177
+ if (topic === undefined)
178
+ continue;
179
+ const normalized = normalizeSddArtifact(artifact);
180
+ const notePath = `VGXNESS/03 SDD/${topic.change}/${topic.phase}.md`;
181
+ const content = `${renderGeneratedFrontmatter({ generatedBy: 'vgxness', sourceOfTruth: 'sqlite', manualEdits: 'overwritten_on_export', transport: input.transport, generatedAt: new Date().toISOString(), project: input.project })}# ${topic.change} / ${topic.phase}\n\n> Generated from SQLite SDD artifact state. Obsidian does not prove readiness or acceptance.\n\n## State\n\n- Status: ${normalized.metadata.status}\n- Accepted: ${normalized.metadata.status === 'accepted' ? 'yes' : 'no'}\n- Updated: ${artifact.updatedAt}\n\n## Artifact\n\n${artifact.content}\n`;
182
+ exported.push({ ...writeGeneratedFile(input.vaultRoot, notePath, content), kind: 'sdd' });
183
+ boardItems.push({ changeId: topic.change, phase: topic.phase, state: normalized.metadata.status, updatedAt: artifact.updatedAt, notePath });
184
+ }
185
+ const board = renderSddBoardMarkdown({ project: input.project, generatedAt: new Date().toISOString(), transport: input.transport, items: boardItems });
186
+ exported.push({ ...writeGeneratedFile(input.vaultRoot, 'VGXNESS/08 Boards/SDD Board.md', board), kind: 'sdd' });
187
+ return exported;
188
+ }
189
+ function writeCanvas(input) {
190
+ const content = renderObsidianCanvas({
191
+ nodes: [
192
+ { id: 'sqlite', type: 'text', label: 'SQLite\nOperational source of truth', x: 0, y: 0 },
193
+ { id: 'obsidian', type: 'text', label: 'Obsidian\nHuman-facing projection workspace', x: 520, y: 0 },
194
+ ],
195
+ edges: [{ id: 'project', fromNode: 'sqlite', toNode: 'obsidian', label: 'project via MCP/export' }],
196
+ });
197
+ return { ...writeGeneratedFile(input.vaultRoot, `VGXNESS/07 Canvas/${input.project} Knowledge Architecture.canvas`, content), kind: 'canvas' };
198
+ }
199
+ function writeGeneratedFile(vaultRoot, path, content) {
200
+ const safePath = assertSafeWorkspaceRelativePath(path, defaultWorkspaceRoot);
201
+ const workspaceRoot = resolve(vaultRoot, defaultWorkspaceRoot);
202
+ const absolute = resolve(vaultRoot, safePath);
203
+ if (absolute !== workspaceRoot && !absolute.startsWith(`${workspaceRoot}${sep}`))
204
+ throw new Error(`Path must stay under ${workspaceRoot}: ${path}`);
205
+ const created = !existsSync(absolute);
206
+ if (!created) {
207
+ const existing = readFileSync(absolute, 'utf8');
208
+ if (!isGeneratedByVgxness(existing))
209
+ throw new Error(`Refusing to overwrite non-generated Obsidian note: ${safePath}`);
210
+ }
211
+ mkdirSync(dirname(absolute), { recursive: true });
212
+ writeFileSync(absolute, content, 'utf8');
213
+ return { path: safePath, created };
214
+ }
215
+ function workspaceModeFlag(flags) {
216
+ const workspace = optionalStringFlag(flags, 'workspace') ?? 'obsidian-mcp';
217
+ if (workspace === 'obsidian-mcp' || workspace === 'filesystem')
218
+ return { ok: true, value: workspace };
219
+ return validationFailure('--workspace must be obsidian-mcp or filesystem');
220
+ }
221
+ function exportKindFlag(flags) {
222
+ const kind = optionalStringFlag(flags, 'kind') ?? 'all';
223
+ if (kind === 'docs' || kind === 'memory' || kind === 'sdd' || kind === 'canvas' || kind === 'all')
224
+ return { ok: true, value: kind };
225
+ return validationFailure('--kind must be docs, memory, sdd, canvas, or all');
226
+ }
227
+ function vaultFlag(flags, environment) {
228
+ const vault = requiredFlag(flags, 'vault');
229
+ if (!vault.ok)
230
+ return vault;
231
+ return { ok: true, value: resolve(environment.cwd, vault.value) };
232
+ }
233
+ function jsonOrText(parsed, value, text) {
234
+ return parsed.flags.json === true ? { exitCode: 0, stdout: `${JSON.stringify(value, null, 2)}\n`, stderr: '' } : okText(text);
235
+ }
236
+ function parseSddTopicKey(topicKey) {
237
+ const match = /^sdd\/([^/]+)\/([^/]+)$/u.exec(topicKey);
238
+ if (match === null)
239
+ return undefined;
240
+ return { change: match[1], phase: match[2] };
241
+ }
242
+ function obsidianMcpUnavailableStatus() {
243
+ return {
244
+ kind: 'knowledge-workspace-status',
245
+ status: 'unavailable',
246
+ mode: 'obsidian-mcp',
247
+ workspaceRoot: defaultWorkspaceRoot,
248
+ diagnostics: ['Standalone CLI cannot call agent-host Obsidian MCP tools yet.'],
249
+ guidance: ['Configure Obsidian MCP in the agent host.', 'Use --workspace filesystem --vault <path> for explicit fallback.'],
250
+ safety: { readOnly: true, writesNotes: false, deletesNotes: false, executesObsidianCommands: false },
251
+ };
252
+ }
@@ -5,7 +5,7 @@ import { runBootAgentSeedUpgrade } from '../../agents/boot-upgrade.js';
5
5
  import { databasePathFor, parseArgs, requiredFlag } from './cli-flags.js';
6
6
  import { okText, usageFailure, visibleHelpText } from './cli-help.js';
7
7
  import { openCliDatabase, resultFailure } from './cli-helpers.js';
8
- import { runAgentCommand, runApprovalsCommand, runCodeContextCommand, runDefaultInteractiveEntrypoint, runDoctorAliasCommand, runHomeTuiCommand, runInitCommand, runMcpDoctorCommand, runMcpInstallCommand, runMcpSetupCommand, runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runPermissionsCommand, runProductNextCommand, runProductResumeCommand, runProductStatusCommand, runRunsCommand, runSddCommand, runSetupApplyCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupReinstallApplyCommand, runSetupRollbackCommand, runSkillCommand, runSubagentCommand, runVerificationPlanCommand, runVerificationReportCommand, runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand, } from './commands/index.js';
8
+ import { runAgentCommand, runApprovalsCommand, runCodeContextCommand, runDefaultInteractiveEntrypoint, runDoctorAliasCommand, runHomeTuiCommand, runInitCommand, runKnowledgeWorkspaceCommand, runMcpDoctorCommand, runMcpInstallCommand, runMcpSetupCommand, runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runPermissionsCommand, runProductNextCommand, runProductResumeCommand, runProductStatusCommand, runRunsCommand, runSddCommand, runSetupApplyCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupReinstallApplyCommand, runSetupRollbackCommand, runSkillCommand, runSubagentCommand, runVerificationPlanCommand, runVerificationReportCommand, runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand, } from './commands/index.js';
9
9
  import { canRunInteractiveTui } from './tui/terminal-capabilities.js';
10
10
  const _promptBuffers = new WeakMap();
11
11
  const require = createRequire(import.meta.url);
@@ -51,6 +51,9 @@ export function dispatchCli(argv, environment) {
51
51
  }
52
52
  if (area === 'memory' && command === 'import')
53
53
  return runMemoryImportCommand(parsed, environment);
54
+ if (area === 'knowledge' && (command === 'status' || command === 'init' || parsed.flags.workspace !== 'filesystem')) {
55
+ return runKnowledgeWorkspaceCommand(command, parsed, undefined, environment);
56
+ }
54
57
  const selectedDatabasePath = databasePathFor(parsed.flags, environment);
55
58
  if (!selectedDatabasePath.ok)
56
59
  return resultFailure(selectedDatabasePath);
@@ -71,6 +74,8 @@ export function dispatchCli(argv, environment) {
71
74
  return runSubagentCommand(command, parsed, opened.value, environment);
72
75
  if (area === 'memory')
73
76
  return runMemoryCommand(command, parsed, opened.value);
77
+ if (area === 'knowledge')
78
+ return runKnowledgeWorkspaceCommand(command, parsed, opened.value, environment);
74
79
  if (area === 'approvals')
75
80
  return runApprovalsCommand(command, parsed, opened.value);
76
81
  if (area === 'runs')
@@ -148,6 +153,9 @@ function validateCommand(area, command) {
148
153
  if (area === 'memory') {
149
154
  return command === 'save' || command === 'search' || command === 'import' ? { ok: true } : { ok: false, message: `Unknown memory command: ${command}` };
150
155
  }
156
+ if (area === 'knowledge') {
157
+ return command === 'status' || command === 'init' || command === 'export' ? { ok: true } : { ok: false, message: `Unknown knowledge command: ${command}` };
158
+ }
151
159
  if (area === 'runs') {
152
160
  return command === 'create' ||
153
161
  command === 'list' ||
@@ -471,7 +471,7 @@ The envelope is always `installable:false` and `readOnly:true`. It does **not**
471
471
 
472
472
  ### OpenCode manager orchestration
473
473
 
474
- The checked-in OpenCode default config and `seeds/agents/agent-seed-v1.json` define `vgxness-manager` as an MCP-first orchestrator. The manager should use `vgxness_session_restore` with the project and workspace directory when starting, resuming, or recovering context, then use SDD artifact/status tools, memory tools, agent resolution, run/preflight tools, and read-only payload/profile previews before delegating substantial work to exact capability subagents. Before ending, pausing, handing off, or compacting, it should call `vgxness_session_close` with the current session id, actor `manager`, and an actionable summary; if no current session id is available, it must not invent one and should preserve the summary in its final response. Its OpenCode `permission.task` remains deny-by-default: `*` is denied and only the canonical capability subagent names plus legacy `vgxness-sdd-*` aliases are allowed explicitly. The seed/default prompts are self-contained and do not require external provider-native skill files; optional guidance should live in VGXNESS registry assets unless a future provider-native integration is explicitly designed.
474
+ The checked-in OpenCode default config and `seeds/agents/agent-seed-v1.json` define `vgxness-manager` as an MCP-first orchestrator. The manager should use `vgxness_session_restore` with the project and workspace directory when starting, resuming, or recovering context, then use SDD artifact/status tools, memory tools, agent resolution, run/preflight tools, and read-only payload/profile previews before delegating substantial work to exact capability subagents. Before ending, pausing, handing off, or compacting, it should call `vgxness_session_close` with the current session id, actor `manager`, and an actionable summary; if no current session id is available, it must not invent one and should preserve the summary in its final response. Its OpenCode `permission.task` remains deny-by-default: `*` is denied and only the canonical capability subagent names are allowed explicitly. The seed/default prompts are self-contained and do not require external provider-native skill files; optional guidance should live in VGXNESS registry assets unless a future provider-native integration is explicitly designed.
475
475
 
476
476
  ## Run lifecycle
477
477
 
package/docs/cli.md CHANGED
@@ -208,6 +208,19 @@ bun run package:bun:evidence
208
208
 
209
209
  When reporting a Bun failure, include the Bun version, OS/CPU, Node version, whether `bun install --frozen-lockfile` completed, which `bun run verify:*` command failed, and whether the error points to `bun:sqlite`, migration application, package evidence, or another install step.
210
210
 
211
+ ## Knowledge workspace CLI
212
+
213
+ VGXNESS exposes one grouped knowledge surface instead of mirroring every Obsidian MCP note operation:
214
+
215
+ ```bash
216
+ vgxness knowledge status --workspace obsidian-mcp --json
217
+ vgxness knowledge init --workspace obsidian-mcp --json
218
+ vgxness knowledge init --workspace filesystem --vault /path/to/vault --json
219
+ vgxness knowledge export --project vgxness --workspace filesystem --vault /path/to/vault --kind docs|memory|sdd|canvas|all --db /path/to/memory.sqlite
220
+ ```
221
+
222
+ `obsidian-mcp` is the preferred workspace mode for agent-host integrations. The standalone CLI cannot directly call host MCP tools, so `knowledge export --workspace obsidian-mcp` fails fast with configuration guidance. `filesystem` writes are available only when the caller explicitly passes `--workspace filesystem --vault <path>`.
223
+
211
224
  ## Repository quick path
212
225
 
213
226
  These examples are for a source checkout. They use repo-local seeds and `bun run cli:bun -- ...`; they are not installed-package quick-start commands.
package/docs/glossary.md CHANGED
@@ -32,7 +32,7 @@ The canonical installed CLI/MCP runtime and verification path. Required `>= 1.3.
32
32
 
33
33
  ## Canonical agent manifest
34
34
 
35
- The built-in, validated manifest that defines the manager agent and generic capability subagents (`vgxness-explore`, `vgxness-propose`, `vgxness-requirements`, `vgxness-design`, `vgxness-plan`, `vgxness-apply`, `vgxness-verify`, `vgxness-archive`, `vgxness-init`, and `vgxness-onboard`). Legacy `vgxness-sdd-*` task names remain callable as compatibility aliases, but the canonical provider-facing names are no longer SDD-prefixed. Lives in `src/domain/agents/canonical-agent-manifest.ts` with old-path compatibility through `src/agents/canonical-agent-manifest.ts`. `promptContractVersion` increments on breaking contract changes.
35
+ The built-in, validated manifest that defines the manager agent and generic capability subagents (`vgxness-explore`, `vgxness-propose`, `vgxness-requirements`, `vgxness-design`, `vgxness-plan`, `vgxness-apply`, `vgxness-verify`, `vgxness-archive`, `vgxness-init`, and `vgxness-onboard`). Legacy `vgxness-sdd-*` task names are no longer provider-callable; the canonical provider-facing names are not SDD-prefixed. Lives in `src/domain/agents/canonical-agent-manifest.ts` with old-path compatibility through `src/agents/canonical-agent-manifest.ts`. `promptContractVersion` increments on breaking contract changes.
36
36
 
37
37
  ## Change (SDD)
38
38
 
@@ -0,0 +1,135 @@
1
+ # Knowledge Workspace
2
+
3
+ VGXNESS separates operational memory from human-facing knowledge work.
4
+
5
+ SQLite remains the authoritative local state engine for agent memory, SDD artifacts, sessions, runs, traces, approvals, and governance state. Obsidian is a human workspace for reading, planning, visualizing, and curating derived views of that state.
6
+
7
+ ## Source-of-truth boundary
8
+
9
+ | Layer | Source of truth? | Purpose |
10
+ |---|---:|---|
11
+ | SQLite | Yes | Operational memory, SDD artifact state, acceptance, run history, traces, search indexes, revisions, and governance state. |
12
+ | Obsidian vault | No | Human-facing docs, research, boards, canvas maps, and curated summaries. |
13
+ | Obsidian MCP | No | Controlled transport for VGXNESS/agents to read, create, patch, search, and open Obsidian notes. |
14
+ | Repository docs | Yes for shipped product docs | Versioned product documentation and implementation guidance. |
15
+
16
+ The practical rule is:
17
+
18
+ ```text
19
+ If an agent needs it to retrieve or decide quickly, store it in SQLite.
20
+ If a human needs it to read, organize, or visualize, project it into Obsidian.
21
+ If both need it, SQLite stays authoritative and Obsidian gets a generated or curated projection.
22
+ ```
23
+
24
+ ## What belongs in SQLite
25
+
26
+ Use SQLite-backed VGXNESS services for:
27
+
28
+ - project and personal memory observations;
29
+ - observation revisions and topic-key upserts;
30
+ - SDD artifacts and their governance metadata;
31
+ - explicit human artifact acceptance/reopen state;
32
+ - run records, events, checkpoints, approvals, and traces;
33
+ - skill/agent registry state;
34
+ - any future search or embedding indexes used by agents.
35
+
36
+ Memory observations should stay compact and reusable. Full transcripts, raw logs, secrets, and large documents should not be copied into memory just because they exist in Obsidian or the repo.
37
+
38
+ ## What belongs in Obsidian
39
+
40
+ Use Obsidian for human-facing material:
41
+
42
+ - PRDs and product notes;
43
+ - roadmaps and release planning;
44
+ - architecture notes and diagrams;
45
+ - Obsidian Canvas maps;
46
+ - SDD board projections;
47
+ - research notes and curated decision writeups;
48
+ - memory summaries that are safe for human review.
49
+
50
+ Generated Obsidian files are projections. They do not approve SDD artifacts, verify work, grant permissions, or replace SQLite state.
51
+
52
+ ## Recommended vault layout
53
+
54
+ VGXNESS writes under a dedicated `VGXNESS/` folder in the configured vault:
55
+
56
+ ```text
57
+ VGXNESS/
58
+ ├── 00 Inbox/
59
+ ├── 01 Product/
60
+ │ ├── PRD.md
61
+ │ ├── Roadmap.md
62
+ │ └── Glossary.md
63
+ ├── 02 Architecture/
64
+ │ ├── Architecture.md
65
+ │ ├── Storage.md
66
+ │ ├── Module Boundaries.md
67
+ │ └── Architecture.canvas
68
+ ├── 03 SDD/
69
+ │ ├── Active/<change-id>/
70
+ │ └── Archive/
71
+ ├── 04 Research/
72
+ ├── 05 Decisions/
73
+ ├── 06 Memory Summaries/
74
+ │ ├── Project Memory.md
75
+ │ └── Personal Patterns.md
76
+ ├── 07 Canvas/
77
+ │ ├── Knowledge System.canvas
78
+ │ └── SDD Flow.canvas
79
+ ├── 08 Boards/
80
+ │ ├── SDD Board.md
81
+ │ └── Release Board.md
82
+ └── 09 Release Notes/
83
+ ```
84
+
85
+ Generated notes should include frontmatter similar to:
86
+
87
+ ```yaml
88
+ ---
89
+ generated_by: vgxness
90
+ source_of_truth: sqlite
91
+ manual_edits: overwritten_on_export
92
+ transport: obsidian-mcp
93
+ ---
94
+ ```
95
+
96
+ VGXNESS should overwrite only generated files carrying this marker. Curated human notes should either live outside generated paths or omit the generated marker.
97
+
98
+ ## Obsidian MCP transport
99
+
100
+ The preferred integration path is an Obsidian MCP server. VGXNESS should use a workspace port so the product is not coupled to a specific community server package:
101
+
102
+ ```text
103
+ SQLite services
104
+ -> Markdown/Canvas renderers
105
+ -> KnowledgeWorkspacePort
106
+ -> Obsidian MCP adapter (preferred)
107
+ -> filesystem adapter (tests/offline fallback)
108
+ -> Obsidian vault / VGXNESS/
109
+ ```
110
+
111
+ The Obsidian MCP adapter may map high-level VGXNESS operations to MCP tools such as note read/write/search/patch/frontmatter operations. VGXNESS should not mirror every Obsidian MCP tool into its own MCP surface; if needed later, expose one grouped sync operation instead.
112
+
113
+ See [Obsidian MCP integration](./obsidian-mcp.md) for setup and safety guidance.
114
+
115
+ ## CLI workflow
116
+
117
+ Use the grouped `knowledge` command for high-level workspace operations:
118
+
119
+ ```bash
120
+ vgxness knowledge status --workspace obsidian-mcp --json
121
+ vgxness knowledge init --workspace obsidian-mcp --json
122
+ vgxness knowledge init --workspace filesystem --vault /path/to/vault --json
123
+ vgxness knowledge export --project vgxness --workspace filesystem --vault /path/to/vault --kind all --db /path/to/memory.sqlite
124
+ ```
125
+
126
+ Standalone CLI export intentionally does not call host-configured Obsidian MCP tools directly yet. When `--workspace obsidian-mcp` is selected for export, it reports an actionable configuration error. Use `--workspace filesystem --vault <path>` only as an explicit fallback/test path, or run the eventual grouped sync from an MCP-capable agent host.
127
+
128
+ ## Safety invariants
129
+
130
+ - SQLite is authoritative for SDD readiness, artifact acceptance, verification, and permissions.
131
+ - Obsidian exports are projections and may be regenerated.
132
+ - Destructive or opaque Obsidian command execution stays disabled by default.
133
+ - Obsidian MCP read/write allowlists should be scoped to `VGXNESS/` where supported.
134
+ - Memory exports should use safe summaries/previews by default, not raw transcripts or traces.
135
+ - Read-only/status commands must not write notes, provider config, or repository files.
@@ -0,0 +1,130 @@
1
+ # Obsidian MCP Integration
2
+
3
+ VGXNESS can use an Obsidian MCP server as the preferred transport for its human-facing knowledge workspace. SQLite remains the source of truth; the Obsidian vault receives generated or curated projections for human review.
4
+
5
+ ## Recommended server for the first spike
6
+
7
+ Use `obsidian-mcp-server` for the initial implementation spike unless a blocker appears during integration testing.
8
+
9
+ At the time this document was written, the npm package `obsidian-mcp-server@3.2.8` exposes note read/list/search/write/append/patch/replace/frontmatter/tag/open-in-ui tools over STDIO or Streamable HTTP.
10
+
11
+ ## Prerequisites
12
+
13
+ - Obsidian desktop is installed and the target vault is open.
14
+ - Obsidian community plugin **Local REST API** v4 or newer is installed and enabled.
15
+ - A Local REST API key is generated in Obsidian settings.
16
+ - The Local REST API HTTP endpoint is reachable, normally `http://127.0.0.1:27123`.
17
+ - Node/npm or Bun is available for launching the MCP server.
18
+
19
+ Keep the Local REST API key out of git. Store it in the invoking client configuration or local secret store.
20
+
21
+ ## Hermes native MCP configuration
22
+
23
+ Hermes can connect to the Obsidian MCP server directly. Add a server entry similar to this to the active Hermes profile config and restart Hermes:
24
+
25
+ ```yaml
26
+ mcp_servers:
27
+ obsidian:
28
+ command: "npx"
29
+ args: ["-y", "obsidian-mcp-server@latest"]
30
+ env:
31
+ MCP_TRANSPORT_TYPE: "stdio"
32
+ MCP_LOG_LEVEL: "info"
33
+ OBSIDIAN_API_KEY: "<local-rest-api-key>"
34
+ OBSIDIAN_BASE_URL: "http://127.0.0.1:27123"
35
+ OBSIDIAN_READ_PATHS: "VGXNESS"
36
+ OBSIDIAN_WRITE_PATHS: "VGXNESS"
37
+ OBSIDIAN_ENABLE_COMMANDS: "false"
38
+ timeout: 120
39
+ connect_timeout: 60
40
+ ```
41
+
42
+ After restart, Hermes discovers tools with names prefixed by the server name, for example:
43
+
44
+ ```text
45
+ mcp_obsidian_obsidian_get_note
46
+ mcp_obsidian_obsidian_search_notes
47
+ mcp_obsidian_obsidian_write_note
48
+ mcp_obsidian_obsidian_patch_note
49
+ mcp_obsidian_obsidian_manage_frontmatter
50
+ mcp_obsidian_obsidian_manage_tags
51
+ mcp_obsidian_obsidian_open_in_ui
52
+ ```
53
+
54
+ Use the real discovered names from `hermes mcp list` / `hermes mcp test obsidian`; exact names depend on the MCP server and Hermes tool naming normalization.
55
+
56
+ ## VGXNESS integration contract
57
+
58
+ VGXNESS should treat Obsidian MCP as an adapter behind a small workspace port:
59
+
60
+ ```ts
61
+ interface KnowledgeWorkspacePort {
62
+ status(): Promise<KnowledgeWorkspaceStatus>;
63
+ listNotes(input: { pathPrefix: string; recursive?: boolean }): Promise<KnowledgeWorkspaceNoteRef[]>;
64
+ getNote(input: { path: string }): Promise<KnowledgeWorkspaceNote>;
65
+ writeGeneratedNote(input: { path: string; content: string; overwriteGeneratedOnly: boolean }): Promise<KnowledgeWorkspaceWriteResult>;
66
+ patchNote(input: { path: string; section: string; operation: 'append' | 'prepend' | 'replace'; content: string }): Promise<KnowledgeWorkspaceWriteResult>;
67
+ openInUi?(input: { path: string }): Promise<void>;
68
+ }
69
+ ```
70
+
71
+ The adapter should translate these operations to the configured Obsidian MCP server. The rest of VGXNESS should call the port, not raw filesystem writes and not package-specific MCP tool functions directly.
72
+
73
+ ## Generated-note policy
74
+
75
+ Generated files should include frontmatter that makes the source and overwrite behavior obvious:
76
+
77
+ ```yaml
78
+ ---
79
+ generated_by: vgxness
80
+ source_of_truth: sqlite
81
+ manual_edits: overwritten_on_export
82
+ transport: obsidian-mcp
83
+ ---
84
+ ```
85
+
86
+ VGXNESS may overwrite generated files only when the existing note contains `generated_by: vgxness`. It should not overwrite curated human notes by default.
87
+
88
+ ## Path and command safety
89
+
90
+ For the first version:
91
+
92
+ - scope `OBSIDIAN_READ_PATHS` and `OBSIDIAN_WRITE_PATHS` to `VGXNESS` when the MCP server supports path allowlists;
93
+ - leave `OBSIDIAN_ENABLE_COMMANDS=false`;
94
+ - do not call delete or command-palette execution tools;
95
+ - keep status/preview operations non-mutating;
96
+ - return the Obsidian note paths written by export operations;
97
+ - export memory summaries/previews, not traces, raw transcripts, provider config, or secrets.
98
+
99
+ ## Smoke test checklist
100
+
101
+ With Obsidian open and Local REST API configured:
102
+
103
+ ```bash
104
+ hermes mcp test obsidian
105
+ ```
106
+
107
+ Then verify the configured client can:
108
+
109
+ 1. list notes under `VGXNESS/`;
110
+ 2. create `VGXNESS/00 Inbox/VGXNESS MCP Smoke.md`;
111
+ 3. read the created note;
112
+ 4. patch a section in that note;
113
+ 5. refuse or fail a write outside `VGXNESS/` when path allowlists are enabled.
114
+
115
+ Remove the smoke note or leave it under `00 Inbox/` as visible test evidence.
116
+
117
+ ## Relationship to VGXNESS MCP
118
+
119
+ Do not mirror the Obsidian MCP server's detailed note tools inside VGXNESS MCP. If agents need a VGXNESS-facing operation later, add one grouped tool such as:
120
+
121
+ ```ts
122
+ knowledge_workspace_sync({
123
+ project: string,
124
+ target: 'obsidian-mcp',
125
+ kind: 'docs' | 'memory' | 'sdd' | 'all',
126
+ mode: 'preview' | 'write' | 'open-ui'
127
+ })
128
+ ```
129
+
130
+ That keeps VGXNESS responsible for product-level orchestration while Obsidian MCP remains responsible for vault-level note operations.