@orderful/droid 0.45.0 → 0.46.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +8 -2
- package/.github/workflows/claude-issue-agent.yml +1 -2
- package/CHANGELOG.md +14 -0
- package/dist/tools/droid/.claude-plugin/plugin.json +1 -1
- package/dist/tools/droid/TOOL.yaml +1 -1
- package/dist/tools/droid/skills/droid/SKILL.md +1 -0
- package/dist/tools/droid/skills/droid-bootstrap/SKILL.md +1 -0
- package/dist/tools/edi-schema/.claude-plugin/plugin.json +25 -0
- package/dist/tools/edi-schema/TOOL.yaml +29 -0
- package/dist/tools/edi-schema/agents/edi-schema-agent.md +97 -0
- package/dist/tools/edi-schema/commands/edi-schema.md +33 -0
- package/dist/tools/edi-schema/skills/edi-schema/SKILL.md +86 -0
- package/dist/tools/pii/.claude-plugin/plugin.json +25 -0
- package/dist/tools/pii/TOOL.yaml +22 -0
- package/dist/tools/pii/agents/pii-scanner.md +85 -0
- package/dist/tools/pii/commands/pii.md +33 -0
- package/dist/tools/pii/skills/pii/SKILL.md +97 -0
- package/dist/tools/pii/skills/pii/references/supported-entities.md +90 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-analyze.d.ts +18 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-analyze.d.ts.map +1 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-analyze.ts +258 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-init.d.ts +17 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-init.d.ts.map +1 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-init.ts +151 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-redact.d.ts +21 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-redact.d.ts.map +1 -0
- package/dist/tools/pii/skills/pii/scripts/presidio-redact.ts +294 -0
- package/dist/tools/pii/skills/pii/scripts/presidio.test.ts +444 -0
- package/package.json +1 -1
- package/src/tools/droid/.claude-plugin/plugin.json +1 -1
- package/src/tools/droid/TOOL.yaml +1 -1
- package/src/tools/droid/skills/droid/SKILL.md +1 -0
- package/src/tools/droid/skills/droid-bootstrap/SKILL.md +1 -0
- package/src/tools/edi-schema/.claude-plugin/plugin.json +25 -0
- package/src/tools/edi-schema/TOOL.yaml +29 -0
- package/src/tools/edi-schema/agents/edi-schema-agent.md +97 -0
- package/src/tools/edi-schema/commands/edi-schema.md +33 -0
- package/src/tools/edi-schema/skills/edi-schema/SKILL.md +86 -0
- package/src/tools/pii/.claude-plugin/plugin.json +25 -0
- package/src/tools/pii/TOOL.yaml +22 -0
- package/src/tools/pii/agents/pii-scanner.md +85 -0
- package/src/tools/pii/commands/pii.md +33 -0
- package/src/tools/pii/skills/pii/SKILL.md +97 -0
- package/src/tools/pii/skills/pii/references/supported-entities.md +90 -0
- package/src/tools/pii/skills/pii/scripts/presidio-analyze.ts +258 -0
- package/src/tools/pii/skills/pii/scripts/presidio-init.ts +151 -0
- package/src/tools/pii/skills/pii/scripts/presidio-redact.ts +294 -0
- package/src/tools/pii/skills/pii/scripts/presidio.test.ts +444 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { tmpdir } from 'os';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Integration tests for presidio scripts.
|
|
9
|
+
*
|
|
10
|
+
* These tests validate argument parsing, error paths, and JSON output shape.
|
|
11
|
+
* They do NOT invoke Presidio/Python directly — all Python-dependent paths
|
|
12
|
+
* are gated by existsSync checks in the scripts, so tests run without Python.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SCRIPTS_DIR = __dirname;
|
|
16
|
+
|
|
17
|
+
interface ScriptResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
already_existed?: boolean;
|
|
20
|
+
initialized?: boolean;
|
|
21
|
+
python_path?: string;
|
|
22
|
+
venv_path?: string;
|
|
23
|
+
entities?: Array<{ type: string; start: number; end: number; score: number; line: number; text?: string }>;
|
|
24
|
+
dry_run?: boolean;
|
|
25
|
+
original_path?: string;
|
|
26
|
+
output_path?: string;
|
|
27
|
+
entities_found?: number;
|
|
28
|
+
entities_redacted?: number;
|
|
29
|
+
redacted_text?: string;
|
|
30
|
+
error?: string;
|
|
31
|
+
init_required?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function runScript(scriptName: string, args: string[]): ScriptResult {
|
|
35
|
+
const scriptPath = join(SCRIPTS_DIR, `${scriptName}.ts`);
|
|
36
|
+
|
|
37
|
+
const result = spawnSync('bun', ['run', scriptPath, ...args], {
|
|
38
|
+
encoding: 'utf-8',
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (result.error) {
|
|
43
|
+
return { success: false, error: result.error.message };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(result.stdout.trim());
|
|
48
|
+
} catch {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: `Failed to parse output: ${result.stdout}\nStderr: ${result.stderr}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createFakeVenvWithPython(tempHome: string, scriptContent: string): string {
|
|
57
|
+
const venvPath = join(tempHome, '.droid', 'runtimes', 'presidio');
|
|
58
|
+
const binDir = join(venvPath, 'bin');
|
|
59
|
+
mkdirSync(binDir, { recursive: true });
|
|
60
|
+
writeFileSync(join(binDir, 'python3'), scriptContent, { mode: 0o755 });
|
|
61
|
+
return venvPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── presidio-init ───────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
describe('presidio-init', () => {
|
|
67
|
+
let tempHome: string;
|
|
68
|
+
let originalHome: string;
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
originalHome = process.env.HOME || '';
|
|
72
|
+
tempHome = mkdtempSync(join(tmpdir(), 'pii-test-home-'));
|
|
73
|
+
process.env.HOME = tempHome;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
process.env.HOME = originalHome;
|
|
78
|
+
try {
|
|
79
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
80
|
+
} catch {
|
|
81
|
+
// Ignore cleanup errors
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns already_existed:true when marker file and venv binary both exist', () => {
|
|
86
|
+
// Pre-create the venv structure
|
|
87
|
+
const venvPath = join(tempHome, '.droid', 'runtimes', 'presidio');
|
|
88
|
+
const binDir = join(venvPath, 'bin');
|
|
89
|
+
mkdirSync(binDir, { recursive: true });
|
|
90
|
+
writeFileSync(join(venvPath, '.droid-initialized'), new Date().toISOString());
|
|
91
|
+
writeFileSync(join(binDir, 'python3'), '#!/bin/bash\necho "Python 3.9.0"', { mode: 0o755 });
|
|
92
|
+
|
|
93
|
+
const result = spawnSync(
|
|
94
|
+
'bun',
|
|
95
|
+
['run', join(SCRIPTS_DIR, 'presidio-init.ts')],
|
|
96
|
+
{
|
|
97
|
+
encoding: 'utf-8',
|
|
98
|
+
env: { ...process.env, HOME: tempHome },
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
let parsed: ScriptResult;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
105
|
+
} catch {
|
|
106
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expect(parsed.success).toBe(true);
|
|
110
|
+
expect(parsed.already_existed).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('returns success:false with clear error when python3 is not available', () => {
|
|
114
|
+
// Override PATH to have no python3
|
|
115
|
+
const result = spawnSync(
|
|
116
|
+
'bun',
|
|
117
|
+
['run', join(SCRIPTS_DIR, 'presidio-init.ts')],
|
|
118
|
+
{
|
|
119
|
+
encoding: 'utf-8',
|
|
120
|
+
env: { ...process.env, HOME: tempHome, PATH: '/nonexistent' },
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
let parsed: ScriptResult;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
127
|
+
} catch {
|
|
128
|
+
// If Python is truly not found, the error may be in stderr
|
|
129
|
+
parsed = { success: false, error: 'python3 not found' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
expect(parsed.success).toBe(false);
|
|
133
|
+
expect(parsed.error).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ─── presidio-analyze ────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
describe('presidio-analyze', () => {
|
|
140
|
+
let tempDir: string;
|
|
141
|
+
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
tempDir = mkdtempSync(join(tmpdir(), 'pii-analyze-test-'));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
afterEach(() => {
|
|
147
|
+
try {
|
|
148
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
149
|
+
} catch {
|
|
150
|
+
// Ignore
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns success:false with init_required when venv does not exist', () => {
|
|
155
|
+
const result = spawnSync(
|
|
156
|
+
'bun',
|
|
157
|
+
['run', join(SCRIPTS_DIR, 'presidio-analyze.ts'), '--file', join(tempDir, 'test.md')],
|
|
158
|
+
{
|
|
159
|
+
encoding: 'utf-8',
|
|
160
|
+
env: { ...process.env, HOME: join(tempDir, 'nonexistent-home') },
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
let parsed: ScriptResult;
|
|
165
|
+
try {
|
|
166
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
167
|
+
} catch {
|
|
168
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
expect(parsed.success).toBe(false);
|
|
172
|
+
expect(parsed.init_required).toBe(true);
|
|
173
|
+
expect(parsed.error).toContain('presidio-init');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('returns success:false when neither --file nor --text is provided', () => {
|
|
177
|
+
// Use a fake home so venv won't be found — still validates arg parsing
|
|
178
|
+
const result = spawnSync(
|
|
179
|
+
'bun',
|
|
180
|
+
['run', join(SCRIPTS_DIR, 'presidio-analyze.ts')],
|
|
181
|
+
{
|
|
182
|
+
encoding: 'utf-8',
|
|
183
|
+
env: { ...process.env, HOME: join(tempDir, 'nonexistent-home') },
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
let parsed: ScriptResult;
|
|
188
|
+
try {
|
|
189
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
190
|
+
} catch {
|
|
191
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Either init_required or missing arg error — both are valid failures
|
|
195
|
+
expect(parsed.success).toBe(false);
|
|
196
|
+
expect(parsed.error).toBeDefined();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('returns success:false when --file does not exist (and venv exists)', () => {
|
|
200
|
+
// Pre-create a fake venv so the script gets past the venv check
|
|
201
|
+
const fakeHome = join(tempDir, 'fake-home');
|
|
202
|
+
createFakeVenvWithPython(fakeHome, '#!/bin/bash\necho ""');
|
|
203
|
+
|
|
204
|
+
const nonExistentFile = join(tempDir, 'does-not-exist.md');
|
|
205
|
+
|
|
206
|
+
const result = spawnSync(
|
|
207
|
+
'bun',
|
|
208
|
+
['run', join(SCRIPTS_DIR, 'presidio-analyze.ts'), '--file', nonExistentFile],
|
|
209
|
+
{
|
|
210
|
+
encoding: 'utf-8',
|
|
211
|
+
env: { ...process.env, HOME: fakeHome },
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
let parsed: ScriptResult;
|
|
216
|
+
try {
|
|
217
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
218
|
+
} catch {
|
|
219
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
expect(parsed.success).toBe(false);
|
|
223
|
+
expect(parsed.error).toContain('not found');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('rejects invalid entity names before invoking Python', () => {
|
|
227
|
+
const fakeHome = join(tempDir, 'fake-home');
|
|
228
|
+
createFakeVenvWithPython(fakeHome, '#!/bin/bash\necho \'[]\'');
|
|
229
|
+
|
|
230
|
+
const result = spawnSync(
|
|
231
|
+
'bun',
|
|
232
|
+
[
|
|
233
|
+
'run',
|
|
234
|
+
join(SCRIPTS_DIR, 'presidio-analyze.ts'),
|
|
235
|
+
'--text',
|
|
236
|
+
'Call me at 555-1234',
|
|
237
|
+
'--entities',
|
|
238
|
+
'EMAIL_ADDRESS,__import__("os").system("whoami")',
|
|
239
|
+
],
|
|
240
|
+
{
|
|
241
|
+
encoding: 'utf-8',
|
|
242
|
+
env: { ...process.env, HOME: fakeHome },
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const parsed = JSON.parse(result.stdout.trim()) as ScriptResult;
|
|
247
|
+
expect(parsed.success).toBe(false);
|
|
248
|
+
expect(parsed.error).toContain('Invalid entity type');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('does not include raw text in analyzer output entities', () => {
|
|
252
|
+
const fakeHome = join(tempDir, 'fake-home-no-text');
|
|
253
|
+
createFakeVenvWithPython(
|
|
254
|
+
fakeHome,
|
|
255
|
+
`#!/bin/bash
|
|
256
|
+
if grep -q "'text': text\\[r.start:r.end\\]" "$1"; then
|
|
257
|
+
echo '[{"type":"EMAIL_ADDRESS","start":11,"end":27,"score":0.99,"text":"jane@example.com"}]'
|
|
258
|
+
else
|
|
259
|
+
echo '[{"type":"EMAIL_ADDRESS","start":11,"end":27,"score":0.99}]'
|
|
260
|
+
fi`,
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const result = spawnSync(
|
|
264
|
+
'bun',
|
|
265
|
+
['run', join(SCRIPTS_DIR, 'presidio-analyze.ts'), '--text', 'Contact me: jane@example.com'],
|
|
266
|
+
{
|
|
267
|
+
encoding: 'utf-8',
|
|
268
|
+
env: { ...process.env, HOME: fakeHome },
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const parsed = JSON.parse(result.stdout.trim()) as ScriptResult;
|
|
273
|
+
expect(parsed.success).toBe(true);
|
|
274
|
+
expect(parsed.entities?.[0]?.type).toBe('EMAIL_ADDRESS');
|
|
275
|
+
expect(parsed.entities?.[0]).not.toHaveProperty('text');
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// ─── presidio-redact ─────────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
describe('presidio-redact', () => {
|
|
282
|
+
let tempDir: string;
|
|
283
|
+
|
|
284
|
+
beforeEach(() => {
|
|
285
|
+
tempDir = mkdtempSync(join(tmpdir(), 'pii-redact-test-'));
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
afterEach(() => {
|
|
289
|
+
try {
|
|
290
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
291
|
+
} catch {
|
|
292
|
+
// Ignore
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('returns success:false when --file is missing', () => {
|
|
297
|
+
const result = spawnSync(
|
|
298
|
+
'bun',
|
|
299
|
+
['run', join(SCRIPTS_DIR, 'presidio-redact.ts')],
|
|
300
|
+
{
|
|
301
|
+
encoding: 'utf-8',
|
|
302
|
+
env: { ...process.env, HOME: join(tempDir, 'nonexistent-home') },
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
let parsed: ScriptResult;
|
|
307
|
+
try {
|
|
308
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
309
|
+
} catch {
|
|
310
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
expect(parsed.success).toBe(false);
|
|
314
|
+
// Either init_required or missing --file error
|
|
315
|
+
expect(parsed.error).toBeDefined();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('returns success:false with init_required when venv does not exist', () => {
|
|
319
|
+
const testFile = join(tempDir, 'test.md');
|
|
320
|
+
writeFileSync(testFile, 'Hello, my email is test@example.com');
|
|
321
|
+
|
|
322
|
+
const result = spawnSync(
|
|
323
|
+
'bun',
|
|
324
|
+
['run', join(SCRIPTS_DIR, 'presidio-redact.ts'), '--file', testFile],
|
|
325
|
+
{
|
|
326
|
+
encoding: 'utf-8',
|
|
327
|
+
env: { ...process.env, HOME: join(tempDir, 'nonexistent-home') },
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
let parsed: ScriptResult;
|
|
332
|
+
try {
|
|
333
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
334
|
+
} catch {
|
|
335
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
expect(parsed.success).toBe(false);
|
|
339
|
+
expect(parsed.init_required).toBe(true);
|
|
340
|
+
expect(parsed.error).toContain('presidio-init');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('does not write output file with --dry-run (venv missing path)', () => {
|
|
344
|
+
const testFile = join(tempDir, 'test.md');
|
|
345
|
+
writeFileSync(testFile, 'Hello, my email is test@example.com');
|
|
346
|
+
const expectedOutput = join(tempDir, 'test-redacted.md');
|
|
347
|
+
|
|
348
|
+
const result = spawnSync(
|
|
349
|
+
'bun',
|
|
350
|
+
['run', join(SCRIPTS_DIR, 'presidio-redact.ts'), '--file', testFile, '--dry-run'],
|
|
351
|
+
{
|
|
352
|
+
encoding: 'utf-8',
|
|
353
|
+
env: { ...process.env, HOME: join(tempDir, 'nonexistent-home') },
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// With no venv, it will fail before writing
|
|
358
|
+
expect(existsSync(expectedOutput)).toBe(false);
|
|
359
|
+
|
|
360
|
+
let parsed: ScriptResult;
|
|
361
|
+
try {
|
|
362
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
363
|
+
} catch {
|
|
364
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
365
|
+
}
|
|
366
|
+
expect(parsed.success).toBe(false);
|
|
367
|
+
expect(parsed.init_required).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('correctly parses --output path argument', () => {
|
|
371
|
+
const testFile = join(tempDir, 'source.md');
|
|
372
|
+
writeFileSync(testFile, 'Contact: jane@example.com');
|
|
373
|
+
const customOutput = join(tempDir, 'custom-output.md');
|
|
374
|
+
|
|
375
|
+
// Without venv this will fail, but we can check the argument is parsed
|
|
376
|
+
const result = spawnSync(
|
|
377
|
+
'bun',
|
|
378
|
+
['run', join(SCRIPTS_DIR, 'presidio-redact.ts'), '--file', testFile, '--output', customOutput],
|
|
379
|
+
{
|
|
380
|
+
encoding: 'utf-8',
|
|
381
|
+
env: { ...process.env, HOME: join(tempDir, 'nonexistent-home') },
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
let parsed: ScriptResult;
|
|
386
|
+
try {
|
|
387
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
388
|
+
} catch {
|
|
389
|
+
parsed = { success: false, error: `Parse error: ${result.stdout}` };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// With no venv this will fail with init_required
|
|
393
|
+
expect(parsed.success).toBe(false);
|
|
394
|
+
expect(parsed.init_required).toBe(true);
|
|
395
|
+
// Confirm the output file was NOT created
|
|
396
|
+
expect(existsSync(customOutput)).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('omits redacted_text in non-dry-run output', () => {
|
|
400
|
+
const fakeHome = join(tempDir, 'fake-home-no-redacted-text');
|
|
401
|
+
createFakeVenvWithPython(fakeHome, '#!/bin/bash\necho \'{"redacted_text":"masked text","entities_found":1,"entities_redacted":1}\'');
|
|
402
|
+
|
|
403
|
+
const testFile = join(tempDir, 'source.md');
|
|
404
|
+
const outputFile = join(tempDir, 'source-redacted.md');
|
|
405
|
+
writeFileSync(testFile, 'Contact: jane@example.com');
|
|
406
|
+
|
|
407
|
+
const result = spawnSync(
|
|
408
|
+
'bun',
|
|
409
|
+
['run', join(SCRIPTS_DIR, 'presidio-redact.ts'), '--file', testFile],
|
|
410
|
+
{
|
|
411
|
+
encoding: 'utf-8',
|
|
412
|
+
env: { ...process.env, HOME: fakeHome },
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
const parsed = JSON.parse(result.stdout.trim()) as ScriptResult;
|
|
417
|
+
expect(parsed.success).toBe(true);
|
|
418
|
+
expect(parsed.dry_run).toBe(false);
|
|
419
|
+
expect(parsed.redacted_text).toBeUndefined();
|
|
420
|
+
expect(parsed.output_path).toBe(outputFile);
|
|
421
|
+
expect(existsSync(outputFile)).toBe(true);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('rejects unsupported entity names', () => {
|
|
425
|
+
const fakeHome = join(tempDir, 'fake-home');
|
|
426
|
+
createFakeVenvWithPython(fakeHome, '#!/bin/bash\necho \'{"redacted_text":"ok","entities_found":0,"entities_redacted":0}\'');
|
|
427
|
+
|
|
428
|
+
const testFile = join(tempDir, 'source.md');
|
|
429
|
+
writeFileSync(testFile, 'Contact: jane@example.com');
|
|
430
|
+
|
|
431
|
+
const result = spawnSync(
|
|
432
|
+
'bun',
|
|
433
|
+
['run', join(SCRIPTS_DIR, 'presidio-redact.ts'), '--file', testFile, '--entities', 'NOT_A_REAL_ENTITY'],
|
|
434
|
+
{
|
|
435
|
+
encoding: 'utf-8',
|
|
436
|
+
env: { ...process.env, HOME: fakeHome },
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const parsed = JSON.parse(result.stdout.trim()) as ScriptResult;
|
|
441
|
+
expect(parsed.success).toBe(false);
|
|
442
|
+
expect(parsed.error).toContain('Unsupported entity type');
|
|
443
|
+
});
|
|
444
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "droid",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Core droid meta-skill for update awareness, tool discovery, and usage help. Checks for updates, helps users find tools, and answers 'how do I...' questions about droid workflows.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Orderful",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: droid
|
|
2
2
|
description: "Core droid meta-skill for update awareness, tool discovery, and usage help. Checks for updates, helps users find tools, and answers 'how do I...' questions about droid workflows."
|
|
3
|
-
version: 0.7.
|
|
3
|
+
version: 0.7.1
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
# System tool - always stays current regardless of auto-update settings
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: droid
|
|
3
3
|
description: "Core droid meta-skill for updates, tool discovery, usage help, and skill overrides. Use when checking for droid updates, discovering available tools, answering 'how do I...' questions about droid workflows, or customizing skill behaviour. User prompts like 'any droid updates?', 'what tools do I have?', 'what tools do you have?', 'remind me what tools you have', 'hey droid what can you do', 'how do I add context?', 'remind me how to use codex', 'what's the best way to start a session?', 'customize brain search', 'create an override for brain'."
|
|
4
|
+
alwaysApply: true
|
|
4
5
|
allowed-tools: [Bash, Read, Write]
|
|
5
6
|
---
|
|
6
7
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "droid-edi-schema",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Query EDI schemas without context overhead. Uses a sub-agent to inspect large schema and code-list files, then returns concise results.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Orderful",
|
|
7
|
+
"url": "https://github.com/orderful"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/orderful/droid",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"droid",
|
|
13
|
+
"ai",
|
|
14
|
+
"edi-schema"
|
|
15
|
+
],
|
|
16
|
+
"skills": [
|
|
17
|
+
"./skills/edi-schema/SKILL.md"
|
|
18
|
+
],
|
|
19
|
+
"commands": [
|
|
20
|
+
"./commands/edi-schema.md"
|
|
21
|
+
],
|
|
22
|
+
"agents": [
|
|
23
|
+
"./agents/edi-schema-agent.md"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: edi-schema
|
|
2
|
+
description: "Query EDI schemas without context overhead. Uses a sub-agent to inspect large schema and code-list files, then returns concise results."
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
status: beta
|
|
5
|
+
|
|
6
|
+
includes:
|
|
7
|
+
skills:
|
|
8
|
+
- name: edi-schema
|
|
9
|
+
required: true
|
|
10
|
+
commands:
|
|
11
|
+
- name: edi-schema
|
|
12
|
+
is_alias: false
|
|
13
|
+
agents:
|
|
14
|
+
- edi-schema-agent
|
|
15
|
+
|
|
16
|
+
dependencies: []
|
|
17
|
+
|
|
18
|
+
prerequisites:
|
|
19
|
+
- name: jq
|
|
20
|
+
description: JSON query tool used for targeted schema extraction
|
|
21
|
+
check: "jq --version"
|
|
22
|
+
install_hint: "brew install jq"
|
|
23
|
+
|
|
24
|
+
config_schema:
|
|
25
|
+
schemas_dir:
|
|
26
|
+
type: string
|
|
27
|
+
description: "Optional absolute path to a repository containing libs/schemas/. If empty, auto-detect from current workspace."
|
|
28
|
+
required: false
|
|
29
|
+
default: ""
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: edi-schema-agent
|
|
3
|
+
description: "Specialized EDI schema query agent. Uses jq and targeted reads to extract precise schema details with minimal output."
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Glob
|
|
7
|
+
- Grep
|
|
8
|
+
- Bash
|
|
9
|
+
color: blue
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are an EDI schema analysis agent. Your job is to query large schema files and return concise, accurate findings.
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
You will receive:
|
|
17
|
+
|
|
18
|
+
1. `schemasRoot`: Absolute repo path containing `libs/schemas/src/lib/data`
|
|
19
|
+
2. `mode`: One of `field-search`, `segments`, `loops`, `codes`, `versions`, `types`
|
|
20
|
+
3. `transactionType`: Full or short transaction type identifier
|
|
21
|
+
4. `schemaFile` (optional): Resolved schema file path for schema-specific modes
|
|
22
|
+
5. `query` (optional): Search text for `field-search`
|
|
23
|
+
6. `elementId` (optional): Element identifier for `codes`
|
|
24
|
+
|
|
25
|
+
## Rules
|
|
26
|
+
|
|
27
|
+
- Prioritize correctness over broad output.
|
|
28
|
+
- Never output full JSON blobs.
|
|
29
|
+
- Keep returned content compact and actionable.
|
|
30
|
+
- Prefer jq for extraction. If jq is missing, use `rg` + targeted reads.
|
|
31
|
+
|
|
32
|
+
## Query Strategies
|
|
33
|
+
|
|
34
|
+
### `field-search`
|
|
35
|
+
|
|
36
|
+
Run exact identifier and title-first queries before fuzzy search:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
jq '.. | objects | select(.elementIdentifier? == $id)' --arg id "{query}" "{schemaFile}"
|
|
40
|
+
jq '.. | objects | select(.title? == $title)' --arg title "{query}" "{schemaFile}"
|
|
41
|
+
jq '.. | objects | select(.title? | test($q; "i"))' --arg q "{query}" "{schemaFile}"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Return top matches including:
|
|
45
|
+
|
|
46
|
+
- `elementIdentifier` (if present)
|
|
47
|
+
- `title`
|
|
48
|
+
- `schemaType`
|
|
49
|
+
- `X12Requirement` / relevant requirement field
|
|
50
|
+
- a concise path hint when derivable
|
|
51
|
+
|
|
52
|
+
### `segments`
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
jq '[.. | objects | select(.schemaType? == "segmentInstance") | {title, requirement: .X12Requirement}] | unique' "{schemaFile}"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Return deduplicated segment names and requirement info.
|
|
59
|
+
|
|
60
|
+
### `loops`
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
jq '[.. | objects | select(.schemaType? | test("loop")) | {title, schemaType}]' "{schemaFile}"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Return loop hierarchy summary. Include parent-child nesting hints if visible.
|
|
67
|
+
|
|
68
|
+
### `codes`
|
|
69
|
+
|
|
70
|
+
Find likely code-list file (for example `x12_004010_codeList.json`), then:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
jq --arg id "{elementId}" '.[$id]' "{codeListFile}"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Return code values and descriptions. If no entry exists, return "not found" and suggest checking identifier/version.
|
|
77
|
+
|
|
78
|
+
### `versions`
|
|
79
|
+
|
|
80
|
+
List available schema filenames matching type in x12 + edifact directories, then normalize to type/version list.
|
|
81
|
+
|
|
82
|
+
### `types`
|
|
83
|
+
|
|
84
|
+
Read `transactionTypes.json` and return a concise sorted list (or filtered list if query provided).
|
|
85
|
+
|
|
86
|
+
## Output Format
|
|
87
|
+
|
|
88
|
+
Respond in markdown with this structure:
|
|
89
|
+
|
|
90
|
+
1. `Resolved:` line (type/version and file used)
|
|
91
|
+
2. `Results:` compact bullets or table
|
|
92
|
+
3. `Notes:` any ambiguity, fallbacks, or missing data
|
|
93
|
+
|
|
94
|
+
If no match:
|
|
95
|
+
|
|
96
|
+
- state that clearly
|
|
97
|
+
- provide 2-5 nearest alternatives when possible
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: edi-schema
|
|
3
|
+
description: "Query EDI schemas (X12/EDIFACT) without context blowup. Find fields, segments, loops, and code-list values."
|
|
4
|
+
argument-hint: "{transactionType|types|versions {transactionType}} [{query|--segments|--loops|--codes {elementId}}]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /edi-schema
|
|
8
|
+
|
|
9
|
+
**User invoked:** `/edi-schema $ARGUMENTS`
|
|
10
|
+
|
|
11
|
+
**Your task:** Invoke the **edi-schema skill** with these arguments.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
- `/edi-schema 944_004010 freeFormDescription`
|
|
16
|
+
- `/edi-schema 850 --segments`
|
|
17
|
+
- `/edi-schema 856_004010 --loops`
|
|
18
|
+
- `/edi-schema 850 --codes BEG01`
|
|
19
|
+
- `/edi-schema types`
|
|
20
|
+
- `/edi-schema versions 850`
|
|
21
|
+
|
|
22
|
+
## Quick Reference
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
/edi-schema {transactionType} {query} # Search fields/elements
|
|
26
|
+
/edi-schema {transactionType} --segments # List segment set
|
|
27
|
+
/edi-schema {transactionType} --loops # Show loop structure
|
|
28
|
+
/edi-schema {transactionType} --codes ID # Code-list values for element
|
|
29
|
+
/edi-schema types # List transaction types
|
|
30
|
+
/edi-schema versions {transactionType} # List available versions
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
See the **edi-schema skill** for full behaviour and resolution rules.
|