vgxness 1.11.0 → 1.12.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.
@@ -537,7 +537,7 @@ function validateMemorySearchInput(input, tool) {
537
537
  return type;
538
538
  if (type.value !== undefined)
539
539
  result.type = type.value;
540
- const topicKey = readOptionalTopicKey(record.value, tool);
540
+ const topicKey = readOptionalSearchTopicKey(record.value, tool);
541
541
  if (!topicKey.ok)
542
542
  return topicKey;
543
543
  if (topicKey.value !== undefined)
@@ -1239,6 +1239,18 @@ function readOptionalTopicKey(record, tool) {
1239
1239
  return validationFailure('topicKey must contain only letters, numbers, dots, underscores, slashes, or hyphens', tool);
1240
1240
  return topicKey;
1241
1241
  }
1242
+ function readOptionalSearchTopicKey(record, tool) {
1243
+ if (record.topicKey === undefined)
1244
+ return { ok: true, value: undefined };
1245
+ const topicKey = readLimitedNonEmptyString(record, 'topicKey', tool, maxMemoryTopicKeyLength);
1246
+ if (!topicKey.ok)
1247
+ return topicKey;
1248
+ if (topicKey.value === '*')
1249
+ return topicKey;
1250
+ if (!validMemoryTopicKeyPattern.test(topicKey.value))
1251
+ return validationFailure('topicKey must be * or contain only letters, numbers, dots, underscores, slashes, or hyphens', tool);
1252
+ return topicKey;
1253
+ }
1242
1254
  function readMemorySearchLimit(record, tool) {
1243
1255
  return readBoundedLimit(record, tool);
1244
1256
  }
@@ -84,7 +84,6 @@ export class ObservationRepository {
84
84
  ['project', 'project'],
85
85
  ['scope', 'scope'],
86
86
  ['type', 'type'],
87
- ['topicKey', 'topic_key'],
88
87
  ]) {
89
88
  const value = filters[key];
90
89
  if (value) {
@@ -92,6 +91,18 @@ export class ObservationRepository {
92
91
  params[key] = value;
93
92
  }
94
93
  }
94
+ if (filters.topicKey) {
95
+ const topicKeyFilter = normalizeTopicKeySearchFilter(filters.topicKey);
96
+ if (topicKeyFilter.kind === 'exact') {
97
+ where.push('o.topic_key=@topicKey');
98
+ params.topicKey = topicKeyFilter.value;
99
+ }
100
+ else if (topicKeyFilter.kind === 'prefix') {
101
+ where.push(`(o.topic_key=@topicKey OR o.topic_key LIKE @topicKeyPrefix ESCAPE '\\')`);
102
+ params.topicKey = topicKeyFilter.value;
103
+ params.topicKeyPrefix = `${escapeLike(topicKeyFilter.value)}/%`;
104
+ }
105
+ }
95
106
  const rows = this.db.connection
96
107
  .prepare(`SELECT o.* FROM ${from} ${where.length ? `WHERE ${where.join(' AND ')}` : ''} ORDER BY o.updated_at DESC LIMIT @limit`)
97
108
  .all(params);
@@ -138,6 +149,16 @@ export class ObservationRepository {
138
149
  });
139
150
  }
140
151
  }
152
+ function normalizeTopicKeySearchFilter(topicKey) {
153
+ if (topicKey === '*')
154
+ return { kind: 'all' };
155
+ if (!topicKey.includes('/'))
156
+ return { kind: 'prefix', value: topicKey };
157
+ return { kind: 'exact', value: topicKey };
158
+ }
159
+ function escapeLike(value) {
160
+ return value.replace(/[\\%_]/g, (match) => `\\${match}`);
161
+ }
141
162
  function map(row) {
142
163
  const value = {
143
164
  id: row.id,
@@ -2,6 +2,7 @@ import { existsSync, realpathSync } from 'node:fs';
2
2
  import { dirname, isAbsolute, resolve, sep } from 'node:path';
3
3
  import { classifyOperationRisk } from '../governance/risk-classifier.js';
4
4
  import { isSddPhase, sddPhases } from '../sdd/schema.js';
5
+ import { commandAllowlistIds } from '../workflows/command-allowlist-adapter.js';
5
6
  export { isRiskyPermissionCategory, permissionCategories, riskyPermissionCategories } from './schema.js';
6
7
  export const permissionDecisions = ['allow', 'ask', 'deny'];
7
8
  export const permissionModes = ['allow', 'audit', 'require-preflight', 'deny'];
@@ -94,6 +95,9 @@ export function evaluatePermission(request, policy = defaultPermissionPolicy) {
94
95
  const workspaceEscape = workspaceEscapeDecision(request);
95
96
  if (workspaceEscape !== undefined)
96
97
  return workspaceEscape;
98
+ const verifyAllowlistedCommand = verifyAllowlistedCommandDecision(request, risk);
99
+ if (verifyAllowlistedCommand !== undefined)
100
+ return verifyAllowlistedCommand;
97
101
  const sddPhaseDecision = phaseMatrixDecision(request);
98
102
  if (sddPhaseDecision !== undefined && sddPhaseDecision.mode !== 'allow' && sddPhaseDecision.mode !== 'audit')
99
103
  return sddPhaseDecision;
@@ -214,6 +218,28 @@ function phaseMatrixDecision(request) {
214
218
  ...(mode === 'audit' ? { warnings: ['opencode-native-tools-audit-only'] } : {}),
215
219
  });
216
220
  }
221
+ function verifyAllowlistedCommandDecision(request, risk) {
222
+ if (!isSddManagedWorkflow(request.workflow) || request.phase !== 'verify' || request.category !== 'shell')
223
+ return undefined;
224
+ const commandId = parseKnownCommandAllowlistOperation(request.operation);
225
+ if (commandId === undefined)
226
+ return undefined;
227
+ return result(request, 'allow', 'allowlisted_command', `SDD verify allows known command allowlist operation ${commandId} as audit-only.`, {
228
+ mode: 'audit',
229
+ auditOnly: true,
230
+ matrixVersion: sddPhasePermissionMatrixVersion,
231
+ auditEvidence: [...risk.evidence, `commandAllowlistId:${commandId}`],
232
+ }, risk);
233
+ }
234
+ function parseKnownCommandAllowlistOperation(operation) {
235
+ const match = /^command-allowlist:([A-Za-z0-9._/-]+)$/.exec(operation);
236
+ if (match === null)
237
+ return undefined;
238
+ const commandId = match[1];
239
+ if (commandId === undefined)
240
+ return undefined;
241
+ return commandAllowlistIds().includes(commandId) ? commandId : undefined;
242
+ }
217
243
  function isSddManagedWorkflow(workflow) {
218
244
  if (workflow === undefined)
219
245
  return false;
package/docs/mcp.md CHANGED
@@ -37,7 +37,7 @@ All tools:
37
37
  | `vgxness_memory_get` | Read one observation by id. | `id` |
38
38
  | `vgxness_memory_update` | Patch an existing observation. | `id`; optional `type`, `title`, `content`, `topicKey` |
39
39
 
40
- `topicKey` is the durable upsert key: re-saving with the same key updates the existing observation rather than creating a duplicate.
40
+ `topicKey` is the durable upsert key: re-saving with the same key updates the existing observation rather than creating a duplicate. For `vgxness_memory_search` only, `topicKey: "*"` searches across all topic keys, and a top-level key such as `"release"` also matches observations under that prefix, for example `release/...`.
41
41
 
42
42
  ## Sessions (5)
43
43
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "CLI and MCP control plane for guided AI-agent workflows, SDD, memory, and OpenCode setup.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {