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.
package/dist/mcp/validation.js
CHANGED
|
@@ -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 =
|
|
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
|
|