@osovv/vv-opencode 0.2.5 → 0.3.3

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 (55) hide show
  1. package/README.md +29 -13
  2. package/dist/cli.js +21 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/doctor.js +20 -0
  5. package/dist/commands/doctor.js.map +1 -1
  6. package/dist/commands/guardian.js +22 -0
  7. package/dist/commands/guardian.js.map +1 -1
  8. package/dist/commands/install.js +20 -0
  9. package/dist/commands/install.js.map +1 -1
  10. package/dist/commands/status.js +20 -0
  11. package/dist/commands/status.js.map +1 -1
  12. package/dist/commands/sync.js +20 -0
  13. package/dist/commands/sync.js.map +1 -1
  14. package/dist/commands/version.js +18 -0
  15. package/dist/commands/version.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +19 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/lib/opencode.js +53 -0
  20. package/dist/lib/opencode.js.map +1 -1
  21. package/dist/lib/package.js +22 -0
  22. package/dist/lib/package.js.map +1 -1
  23. package/dist/lib/vvoc-paths.js +26 -0
  24. package/dist/lib/vvoc-paths.js.map +1 -1
  25. package/dist/plugins/guardian.js +42 -0
  26. package/dist/plugins/guardian.js.map +1 -1
  27. package/dist/plugins/memory-store.js +57 -0
  28. package/dist/plugins/memory-store.js.map +1 -1
  29. package/dist/plugins/memory.js +34 -0
  30. package/dist/plugins/memory.js.map +1 -1
  31. package/dist/plugins/secrets-redaction/config.d.ts +26 -0
  32. package/dist/plugins/secrets-redaction/config.js +158 -0
  33. package/dist/plugins/secrets-redaction/config.js.map +1 -0
  34. package/dist/plugins/secrets-redaction/deep.d.ts +4 -0
  35. package/dist/plugins/secrets-redaction/deep.js +78 -0
  36. package/dist/plugins/secrets-redaction/deep.js.map +1 -0
  37. package/dist/plugins/secrets-redaction/engine.d.ts +14 -0
  38. package/dist/plugins/secrets-redaction/engine.js +113 -0
  39. package/dist/plugins/secrets-redaction/engine.js.map +1 -0
  40. package/dist/plugins/secrets-redaction/index.d.ts +2 -0
  41. package/dist/plugins/secrets-redaction/index.js +124 -0
  42. package/dist/plugins/secrets-redaction/index.js.map +1 -0
  43. package/dist/plugins/secrets-redaction/patterns.d.ts +26 -0
  44. package/dist/plugins/secrets-redaction/patterns.js +85 -0
  45. package/dist/plugins/secrets-redaction/patterns.js.map +1 -0
  46. package/dist/plugins/secrets-redaction/restore.d.ts +2 -0
  47. package/dist/plugins/secrets-redaction/restore.js +25 -0
  48. package/dist/plugins/secrets-redaction/restore.js.map +1 -0
  49. package/dist/plugins/secrets-redaction/session.d.ts +30 -0
  50. package/dist/plugins/secrets-redaction/session.js +113 -0
  51. package/dist/plugins/secrets-redaction/session.js.map +1 -0
  52. package/dist/plugins/secrets-redaction.d.ts +1 -0
  53. package/dist/plugins/secrets-redaction.js +16 -0
  54. package/dist/plugins/secrets-redaction.js.map +1 -0
  55. package/package.json +5 -2
@@ -0,0 +1,113 @@
1
+ // FILE: src/plugins/secrets-redaction/engine.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Core redaction engine — performs find/replace of secrets with placeholders in text.
5
+ // SCOPE: text scanning, match sorting, interval arithmetic, replacement
6
+ // DEPENDS: session, patterns
7
+ // LINKS: knowledge-graph://plugins/secrets-redaction
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // redactText - replaces secrets in text with placeholders, returns changed text + match list
14
+ // END_MODULE_MAP
15
+ function subtractCovered(intervals) {
16
+ if (intervals.length === 0)
17
+ return [];
18
+ intervals.sort((a, b) => a[0] - b[0]);
19
+ const result = [];
20
+ let currentEnd = intervals[0][0];
21
+ for (const [start, end] of intervals) {
22
+ if (start > currentEnd) {
23
+ result.push([currentEnd, start]);
24
+ }
25
+ if (end > currentEnd) {
26
+ currentEnd = end;
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+ function insertCovered(intervals, newInterval) {
32
+ const merged = [...intervals, newInterval];
33
+ merged.sort((a, b) => a[0] - b[0]);
34
+ const result = [];
35
+ for (const interval of merged) {
36
+ if (result.length > 0 && interval[0] <= result[result.length - 1][1]) {
37
+ result[result.length - 1][1] = Math.max(result[result.length - 1][1], interval[1]);
38
+ }
39
+ else {
40
+ result.push([...interval]);
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+ function sortByPositionDesc(rules, text) {
46
+ const allMatches = [];
47
+ for (const rule of rules) {
48
+ const re = new RegExp(rule.pattern.source, rule.pattern.flags.includes("g") ? rule.pattern.flags : `${rule.pattern.flags}g`);
49
+ let match;
50
+ while ((match = re.exec(text)) !== null) {
51
+ allMatches.push({ rule, match: match });
52
+ }
53
+ }
54
+ allMatches.sort((a, b) => {
55
+ const aStart = a.match.index;
56
+ const bStart = b.match.index;
57
+ if (aStart !== bStart)
58
+ return bStart - aStart;
59
+ return (b.match[0].length - a.match[0].length);
60
+ });
61
+ return allMatches.map((x) => ({ rule: x.rule, matches: x.match }));
62
+ }
63
+ export function redactText(input, patternSet, session) {
64
+ if (!input || patternSet.rules.length === 0) {
65
+ return { text: input, matches: [] };
66
+ }
67
+ const sorted = sortByPositionDesc(patternSet.rules, input);
68
+ const covered = [];
69
+ const matches = [];
70
+ for (const { rule, matches: match } of sorted) {
71
+ const start = match.index;
72
+ const end = start + match[0].length;
73
+ const value = match[0];
74
+ if (patternSet.exclude.has(value.toLowerCase())) {
75
+ continue;
76
+ }
77
+ const gaps = subtractCovered(covered);
78
+ const isInside = gaps.every(([gStart, gEnd]) => start >= gStart && end <= gEnd);
79
+ if (isInside)
80
+ continue;
81
+ const placeholder = session.getOrCreatePlaceholder(value, rule.category);
82
+ matches.push({ start, end, original: value, placeholder, category: rule.category });
83
+ const remainingGaps = [];
84
+ for (const [gStart, gEnd] of gaps) {
85
+ if (start >= gEnd || end <= gStart) {
86
+ remainingGaps.push([gStart, gEnd]);
87
+ }
88
+ else {
89
+ if (start > gStart) {
90
+ remainingGaps.push([gStart, start]);
91
+ }
92
+ if (end < gEnd) {
93
+ remainingGaps.push([end, gEnd]);
94
+ }
95
+ }
96
+ }
97
+ covered.length = 0;
98
+ for (const g of remainingGaps) {
99
+ insertCovered(covered, g);
100
+ }
101
+ }
102
+ const sortedMatches = [...matches].sort((a, b) => a.start - b.start);
103
+ let result = "";
104
+ let lastIndex = 0;
105
+ for (const m of sortedMatches) {
106
+ result += input.slice(lastIndex, m.start);
107
+ result += m.placeholder;
108
+ lastIndex = m.end;
109
+ }
110
+ result += input.slice(lastIndex);
111
+ return { text: result, matches: sortedMatches };
112
+ }
113
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/plugins/secrets-redaction/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const SecretsRedactionPlugin: Plugin;
@@ -0,0 +1,124 @@
1
+ // FILE: src/plugins/secrets-redaction/index.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: OpenCode plugin that redacts secrets from messages before LLM requests and restores them after.
5
+ // SCOPE: 3 hook handlers — chat.messages.transform, text.complete, tool.execute.before
6
+ // DEPENDS: session, engine, patterns, restore, deep, config
7
+ // LINKS: knowledge-graph://plugins/secrets-redaction
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // SecretsRedactionPlugin - main plugin factory function
14
+ // END_MODULE_MAP
15
+ import { loadConfig } from "./config.js";
16
+ import { buildPatternSet } from "./patterns.js";
17
+ import { redactText } from "./engine.js";
18
+ import { restoreText } from "./restore.js";
19
+ import { redactDeep, restoreDeep } from "./deep.js";
20
+ import { PlaceholderSession } from "./session.js";
21
+ const PLACEHOLDER_PREFIX = "__VVOC_SECRET_";
22
+ function isTextPart(part) {
23
+ return part.type === "text";
24
+ }
25
+ function isReasoningPart(part) {
26
+ return part.type === "reasoning";
27
+ }
28
+ function redactMessageParts(parts, patternSet, session) {
29
+ for (const part of parts) {
30
+ if (isTextPart(part)) {
31
+ const result = redactText(part.text, patternSet, session);
32
+ part.text = result.text;
33
+ }
34
+ if (isReasoningPart(part)) {
35
+ const result = redactText(part.text, patternSet, session);
36
+ part.text = result.text;
37
+ }
38
+ }
39
+ }
40
+ function redactAssistantState(msg, patternSet, session) {
41
+ const state = msg.state;
42
+ if (state) {
43
+ if (state.input) {
44
+ redactDeep(state.input, patternSet, session);
45
+ }
46
+ if (state.output) {
47
+ redactDeep(state.output, patternSet, session);
48
+ }
49
+ if (state.error) {
50
+ redactDeep(state.error, patternSet, session);
51
+ }
52
+ if (state.raw) {
53
+ redactDeep(state.raw, patternSet, session);
54
+ }
55
+ }
56
+ }
57
+ export const SecretsRedactionPlugin = async (ctx) => {
58
+ const { config, path, warnings } = await loadConfig(ctx.directory);
59
+ if (config.debug) {
60
+ await ctx.client.app.log({
61
+ body: {
62
+ service: "secrets-redaction",
63
+ level: "debug",
64
+ message: `config loaded from: ${path ?? "none"}`,
65
+ },
66
+ });
67
+ }
68
+ for (const warning of warnings) {
69
+ await ctx.client.app.log({
70
+ body: {
71
+ service: "secrets-redaction",
72
+ level: "warn",
73
+ message: warning,
74
+ },
75
+ });
76
+ }
77
+ if (!config.enabled) {
78
+ return {};
79
+ }
80
+ const patternSet = buildPatternSet(config.patterns);
81
+ const session = new PlaceholderSession({
82
+ prefix: PLACEHOLDER_PREFIX,
83
+ ttlMs: config.ttlMs,
84
+ maxMappings: config.maxMappings,
85
+ secret: config.secret,
86
+ });
87
+ if (config.ttlMs > 0) {
88
+ setInterval(() => {
89
+ const evicted = session.cleanup(Date.now());
90
+ if (config.debug && evicted > 0) {
91
+ ctx.client.app.log({
92
+ body: {
93
+ service: "secrets-redaction",
94
+ level: "debug",
95
+ message: `evicted ${evicted} expired placeholders`,
96
+ },
97
+ });
98
+ }
99
+ }, Math.min(config.ttlMs, 60_000));
100
+ }
101
+ return {
102
+ config: async () => { },
103
+ event: async () => { },
104
+ "tool.execute.before": async (_input, output) => {
105
+ if (output.args) {
106
+ restoreDeep(output.args, session);
107
+ }
108
+ },
109
+ "experimental.chat.messages.transform": async (_input, output) => {
110
+ for (const msg of output.messages) {
111
+ if (msg.info.role === "assistant") {
112
+ redactAssistantState(msg.info, patternSet, session);
113
+ }
114
+ redactMessageParts(msg.parts, patternSet, session);
115
+ }
116
+ },
117
+ "experimental.text.complete": async (_input, output) => {
118
+ if (output.text) {
119
+ output.text = restoreText(output.text, session);
120
+ }
121
+ },
122
+ };
123
+ };
124
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/secrets-redaction/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface PatternRule {
2
+ pattern: RegExp;
3
+ category: string;
4
+ }
5
+ export interface PatternSet {
6
+ rules: PatternRule[];
7
+ exclude: Set<string>;
8
+ }
9
+ export interface PatternsConfig {
10
+ keywords?: Array<{
11
+ value: string;
12
+ category?: string;
13
+ }>;
14
+ regex?: Array<{
15
+ pattern: string;
16
+ category: string;
17
+ }>;
18
+ builtin?: string[];
19
+ exclude?: string[];
20
+ }
21
+ declare const BUILTIN_PATTERNS: Map<string, {
22
+ pattern: string;
23
+ category: string;
24
+ }>;
25
+ export declare function buildPatternSet(config: PatternsConfig): PatternSet;
26
+ export { BUILTIN_PATTERNS };
@@ -0,0 +1,85 @@
1
+ // FILE: src/plugins/secrets-redaction/patterns.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Builds the internal pattern set from config — keywords, regex rules, and builtin patterns.
5
+ // SCOPE: pattern parsing, normalization, deduplication
6
+ // DEPENDS: node:crypto (for hashing)
7
+ // LINKS: knowledge-graph://plugins/secrets-redaction
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // buildPatternSet - builds pattern set from config object
14
+ // BUILTIN_PATTERNS - Map of 6 builtin pattern definitions
15
+ // END_MODULE_MAP
16
+ const BUILTIN_PATTERNS = new Map([
17
+ ["email", { pattern: "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}", category: "EMAIL" }],
18
+ [
19
+ "china_phone",
20
+ { pattern: "(?<!\\d)1[3-9]\\d{9}(?!\\d)", category: "CHINA_PHONE" },
21
+ ],
22
+ [
23
+ "china_id",
24
+ { pattern: "(?<!\\d)\\d{17}[\\dXx](?!\\d)", category: "CHINA_ID" },
25
+ ],
26
+ [
27
+ "uuid",
28
+ {
29
+ pattern: "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}",
30
+ category: "UUID",
31
+ },
32
+ ],
33
+ [
34
+ "ipv4",
35
+ { pattern: "(?:\\d{1,3}\\.){3}\\d{1,3}", category: "IPV4" },
36
+ ],
37
+ ["mac", { pattern: "(?:[0-9a-f]{2}:){5}[0-9a-f]{2}", category: "MAC" }],
38
+ ]);
39
+ function peelFlags(pattern) {
40
+ const inlineFlags = [];
41
+ let p = pattern;
42
+ const iMatch = p.match(/^\(\?([a-z]+)\)/);
43
+ if (iMatch) {
44
+ const captured = iMatch[1];
45
+ if (captured.includes("i"))
46
+ inlineFlags.push("i");
47
+ if (captured.includes("m"))
48
+ inlineFlags.push("m");
49
+ if (captured.includes("s"))
50
+ inlineFlags.push("s");
51
+ p = p.slice(iMatch[0].length);
52
+ }
53
+ return { pattern: p, flags: inlineFlags.join("") };
54
+ }
55
+ function buildRegex(pattern, defaultFlags = "gi") {
56
+ const { pattern: raw, flags: peeled } = peelFlags(pattern);
57
+ const flags = peeled ? `${defaultFlags}${peeled}` : defaultFlags;
58
+ return new RegExp(raw, flags);
59
+ }
60
+ export function buildPatternSet(config) {
61
+ const rules = [];
62
+ if (config.builtin) {
63
+ for (const name of config.builtin) {
64
+ const builtin = BUILTIN_PATTERNS.get(name);
65
+ if (builtin) {
66
+ rules.push({ pattern: buildRegex(builtin.pattern), category: builtin.category });
67
+ }
68
+ }
69
+ }
70
+ if (config.regex) {
71
+ for (const { pattern, category } of config.regex) {
72
+ rules.push({ pattern: buildRegex(pattern), category });
73
+ }
74
+ }
75
+ if (config.keywords) {
76
+ for (const { value, category = "KEYWORD" } of config.keywords) {
77
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
78
+ rules.push({ pattern: new RegExp(escaped, "gi"), category });
79
+ }
80
+ }
81
+ const exclude = new Set(config.exclude ?? []);
82
+ return { rules, exclude };
83
+ }
84
+ export { BUILTIN_PATTERNS };
85
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/plugins/secrets-redaction/patterns.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,iBAAiB;AACjB,wBAAwB;AACxB,wGAAwG;AACxG,yDAAyD;AACzD,uCAAuC;AACvC,uDAAuD;AACvD,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,4DAA4D;AAC5D,4DAA4D;AAC5D,iBAAiB;AAmBjB,MAAM,gBAAgB,GAAuD,IAAI,GAAG,CAAC;IACnF,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,wCAAwC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACnF;QACE,aAAa;QACb,EAAE,OAAO,EAAE,6BAA6B,EAAE,QAAQ,EAAE,aAAa,EAAE;KACpE;IACD;QACE,UAAU;QACV,EAAE,OAAO,EAAE,+BAA+B,EAAE,QAAQ,EAAE,UAAU,EAAE;KACnE;IACD;QACE,MAAM;QACN;YACE,OAAO,EAAE,0FAA0F;YACnG,QAAQ,EAAE,MAAM;SACjB;KACF;IACD;QACE,MAAM;QACN,EAAE,OAAO,EAAE,4BAA4B,EAAE,QAAQ,EAAE,MAAM,EAAE;KAC5D;IACD,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,gCAAgC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;CACxE,CAAC,CAAC;AAEH,SAAS,SAAS,CAAC,OAAe;IAChC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,CAAC,GAAG,OAAO,CAAC;IAEhB,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC1C,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,YAAY,GAAG,IAAI;IACtD,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;IACjE,OAAO,IAAI,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAsB;IACpD,MAAM,KAAK,GAAkB,EAAE,CAAC;IAEhC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC3C,IAAI,OAAO,EAAE,CAAC;gBACZ,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnF,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,KAAK,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjD,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,KAAK,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,SAAS,EAAE,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YAC9D,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;YAC7D,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAEtD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type PlaceholderSession } from "./session.js";
2
+ export declare function restoreText(input: string, session: PlaceholderSession): string;
@@ -0,0 +1,25 @@
1
+ // FILE: src/plugins/secrets-redaction/restore.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Restores placeholders in a single string back to original secret values.
5
+ // SCOPE: single-string placeholder → original lookup
6
+ // DEPENDS: session
7
+ // LINKS: knowledge-graph://plugins/secrets-redaction
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // restoreText - restores all placeholders in a string to original values
14
+ // END_MODULE_MAP
15
+ import { getPlaceholderRegex } from "./session.js";
16
+ export function restoreText(input, session) {
17
+ if (!input)
18
+ return input;
19
+ const regex = getPlaceholderRegex(session["prefix"]);
20
+ return input.replace(regex, (placeholder) => {
21
+ const original = session.lookup(placeholder);
22
+ return original ?? placeholder;
23
+ });
24
+ }
25
+ //# sourceMappingURL=restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore.js","sourceRoot":"","sources":["../../../src/plugins/secrets-redaction/restore.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,iBAAiB;AACjB,wBAAwB;AACxB,sFAAsF;AACtF,uDAAuD;AACvD,qBAAqB;AACrB,uDAAuD;AACvD,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,2EAA2E;AAC3E,iBAAiB;AAGjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,OAA2B;IACpE,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAEzB,MAAM,KAAK,GAAG,mBAAmB,CAAC,OAAO,CAAC,QAAQ,CAAsB,CAAC,CAAC;IAC1E,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,EAAE;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC7C,OAAO,QAAQ,IAAI,WAAW,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,30 @@
1
+ export interface PlaceholderSessionOptions {
2
+ prefix: string;
3
+ ttlMs: number;
4
+ maxMappings: number;
5
+ secret: string;
6
+ }
7
+ export interface PlaceholderEntry {
8
+ original: string;
9
+ placeholder: string;
10
+ category: string;
11
+ createdAt: number;
12
+ }
13
+ export declare class PlaceholderSession {
14
+ private readonly prefix;
15
+ private readonly ttlMs;
16
+ private readonly maxMappings;
17
+ private readonly secret;
18
+ private readonly forward;
19
+ private readonly reverse;
20
+ private readonly created;
21
+ constructor(options: PlaceholderSessionOptions);
22
+ private computeHash;
23
+ getOrCreatePlaceholder(original: string, category: string): string;
24
+ lookup(placeholder: string): string | undefined;
25
+ cleanup(now: number): number;
26
+ private evictIfNeeded;
27
+ get size(): number;
28
+ }
29
+ export declare function getPlaceholderRegex(prefix: string): RegExp;
30
+ export declare function generateFallbackSecret(): string;
@@ -0,0 +1,113 @@
1
+ // FILE: src/plugins/secrets-redaction/session.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Manages placeholder lifecycle for secret redaction — generates stable HMAC-based placeholders,
5
+ // maintains forward/reverse mappings, handles TTL eviction and max-mappings limits.
6
+ // SCOPE: placeholder creation, lookup, cleanup
7
+ // DEPENDS: node:crypto
8
+ // LINKS: knowledge-graph://plugins/secrets-redaction
9
+ // ROLE: RUNTIME
10
+ // MAP_MODE: EXPORTS
11
+ // END_MODULE_CONTRACT
12
+ //
13
+ // START_MODULE_MAP
14
+ // PlaceholderSession - manages secret → placeholder mappings with HMAC hashing
15
+ // getPlaceholderRegex - returns RegExp to find all placeholders in text
16
+ // END_MODULE_MAP
17
+ import { createHmac, randomBytes } from "node:crypto";
18
+ export class PlaceholderSession {
19
+ prefix;
20
+ ttlMs;
21
+ maxMappings;
22
+ secret;
23
+ forward = new Map();
24
+ reverse = new Map();
25
+ created = new Map();
26
+ constructor(options) {
27
+ this.prefix = options.prefix;
28
+ this.ttlMs = options.ttlMs;
29
+ this.maxMappings = options.maxMappings;
30
+ this.secret = Buffer.from(options.secret, "utf-8");
31
+ }
32
+ computeHash(original) {
33
+ return createHmac("sha256", this.secret).update(original, "utf-8").digest("hex");
34
+ }
35
+ getOrCreatePlaceholder(original, category) {
36
+ if (this.reverse.has(original)) {
37
+ return this.reverse.get(original);
38
+ }
39
+ this.evictIfNeeded();
40
+ const hash = this.computeHash(original);
41
+ const hash12 = hash.substring(0, 12);
42
+ let placeholder = `${this.prefix}${category}_${hash12}__`;
43
+ if (this.forward.has(placeholder)) {
44
+ let counter = 1;
45
+ while (this.forward.has(placeholder)) {
46
+ placeholder = `${this.prefix}${category}_${hash12}_${counter}__`;
47
+ counter++;
48
+ }
49
+ }
50
+ const entry = {
51
+ original,
52
+ placeholder,
53
+ category,
54
+ createdAt: Date.now(),
55
+ };
56
+ this.forward.set(placeholder, entry);
57
+ this.reverse.set(original, placeholder);
58
+ this.created.set(placeholder, Date.now());
59
+ return placeholder;
60
+ }
61
+ lookup(placeholder) {
62
+ return this.forward.get(placeholder)?.original;
63
+ }
64
+ cleanup(now) {
65
+ let evicted = 0;
66
+ const expired = [];
67
+ for (const [placeholder, createdAt] of this.created) {
68
+ if (now - createdAt > this.ttlMs) {
69
+ expired.push(placeholder);
70
+ }
71
+ }
72
+ for (const placeholder of expired) {
73
+ const entry = this.forward.get(placeholder);
74
+ if (entry) {
75
+ this.reverse.delete(entry.original);
76
+ this.forward.delete(placeholder);
77
+ this.created.delete(placeholder);
78
+ evicted++;
79
+ }
80
+ }
81
+ return evicted;
82
+ }
83
+ evictIfNeeded() {
84
+ if (this.forward.size < this.maxMappings)
85
+ return;
86
+ let oldestPlaceholder = null;
87
+ let oldestCreated = Infinity;
88
+ for (const [placeholder, createdAt] of this.created) {
89
+ if (createdAt < oldestCreated) {
90
+ oldestCreated = createdAt;
91
+ oldestPlaceholder = placeholder;
92
+ }
93
+ }
94
+ if (oldestPlaceholder) {
95
+ const entry = this.forward.get(oldestPlaceholder);
96
+ if (entry) {
97
+ this.reverse.delete(entry.original);
98
+ this.forward.delete(oldestPlaceholder);
99
+ this.created.delete(oldestPlaceholder);
100
+ }
101
+ }
102
+ }
103
+ get size() {
104
+ return this.forward.size;
105
+ }
106
+ }
107
+ export function getPlaceholderRegex(prefix) {
108
+ return new RegExp(`${prefix}[A-Z_]+_[0-9a-f]{12}(?:_\\d+)?__`, "g");
109
+ }
110
+ export function generateFallbackSecret() {
111
+ return randomBytes(32).toString("hex");
112
+ }
113
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/plugins/secrets-redaction/session.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,iBAAiB;AACjB,wBAAwB;AACxB,4GAA4G;AAC5G,+FAA+F;AAC/F,iDAAiD;AACjD,yBAAyB;AACzB,uDAAuD;AACvD,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,iFAAiF;AACjF,0EAA0E;AAC1E,iBAAiB;AAEjB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgBtD,MAAM,OAAO,kBAAkB;IACZ,MAAM,CAAS;IACf,KAAK,CAAS;IACd,WAAW,CAAS;IACpB,MAAM,CAAS;IACf,OAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;IACnD,OAAO,GAAwB,IAAI,GAAG,EAAE,CAAC;IACzC,OAAO,GAAwB,IAAI,GAAG,EAAE,CAAC;IAE1D,YAAY,OAAkC;QAC5C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAEO,WAAW,CAAC,QAAgB;QAClC,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC;IAED,sBAAsB,CAAC,QAAgB,EAAE,QAAgB;QACvD,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC;QACrC,CAAC;QAED,IAAI,CAAC,aAAa,EAAE,CAAC;QAErB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrC,IAAI,WAAW,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,IAAI,MAAM,IAAI,CAAC;QAE1D,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;gBACrC,WAAW,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,IAAI,MAAM,IAAI,OAAO,IAAI,CAAC;gBACjE,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAqB;YAC9B,QAAQ;YACR,WAAW;YACX,QAAQ;YACR,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAE1C,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,CAAC,WAAmB;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC;IACjD,CAAC;IAED,OAAO,CAAC,GAAW;QACjB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,MAAM,OAAO,GAAa,EAAE,CAAC;QAE7B,KAAK,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACpD,IAAI,GAAG,GAAG,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,KAAK,MAAM,WAAW,IAAI,OAAO,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,KAAK,EAAE,CAAC;gBACV,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACpC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;gBACjC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;gBACjC,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,aAAa;QACnB,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW;YAAE,OAAO;QAEjD,IAAI,iBAAiB,GAAkB,IAAI,CAAC;QAC5C,IAAI,aAAa,GAAG,QAAQ,CAAC;QAE7B,KAAK,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACpD,IAAI,SAAS,GAAG,aAAa,EAAE,CAAC;gBAC9B,aAAa,GAAG,SAAS,CAAC;gBAC1B,iBAAiB,GAAG,WAAW,CAAC;YAClC,CAAC;QACH,CAAC;QAED,IAAI,iBAAiB,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;YAClD,IAAI,KAAK,EAAE,CAAC;gBACV,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACpC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;gBACvC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;YACzC,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,IAAI,MAAM,CAAC,GAAG,MAAM,kCAAkC,EAAE,GAAG,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1 @@
1
+ export { SecretsRedactionPlugin } from "./secrets-redaction/index.js";
@@ -0,0 +1,16 @@
1
+ // FILE: src/plugins/secrets-redaction.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Re-export barrel for SecretsRedactionPlugin
5
+ // SCOPE: plugin re-exports
6
+ // DEPENDS: secrets-redaction/index
7
+ // LINKS: knowledge-graph://plugins/secrets-redaction
8
+ // ROLE: BARREL
9
+ // MAP_MODE: SUMMARY
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // SecretsRedactionPlugin - main plugin export
14
+ // END_MODULE_MAP
15
+ export { SecretsRedactionPlugin } from "./secrets-redaction/index.js";
16
+ //# sourceMappingURL=secrets-redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-redaction.js","sourceRoot":"","sources":["../../src/plugins/secrets-redaction.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,iBAAiB;AACjB,wBAAwB;AACxB,yDAAyD;AACzD,6BAA6B;AAC7B,qCAAqC;AACrC,uDAAuD;AACvD,iBAAiB;AACjB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,gDAAgD;AAChD,iBAAiB;AAEjB,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@osovv/vv-opencode",
3
- "version": "0.2.5",
4
- "description": "Portable OpenCode workflow plugins and CLI tooling.",
3
+ "version": "0.3.3",
4
+ "description": "Portable OpenCode workflow plugins, explicit memory, and CLI tooling.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -15,6 +15,8 @@
15
15
  "workflow",
16
16
  "guardian",
17
17
  "memory",
18
+ "xdg",
19
+ "grace",
18
20
  "bun",
19
21
  "citty"
20
22
  ],
@@ -47,6 +49,7 @@
47
49
  "fmt:check": "oxfmt --check src",
48
50
  "test": "bun test",
49
51
  "check": "bun run typecheck && bun run lint && bun run fmt:check && bun test",
52
+ "pack:check": "npm pack --dry-run",
50
53
  "prepare": "lefthook install --force"
51
54
  },
52
55
  "dependencies": {