@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.
Files changed (49) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +8 -2
  3. package/.github/workflows/claude-issue-agent.yml +1 -2
  4. package/CHANGELOG.md +14 -0
  5. package/dist/tools/droid/.claude-plugin/plugin.json +1 -1
  6. package/dist/tools/droid/TOOL.yaml +1 -1
  7. package/dist/tools/droid/skills/droid/SKILL.md +1 -0
  8. package/dist/tools/droid/skills/droid-bootstrap/SKILL.md +1 -0
  9. package/dist/tools/edi-schema/.claude-plugin/plugin.json +25 -0
  10. package/dist/tools/edi-schema/TOOL.yaml +29 -0
  11. package/dist/tools/edi-schema/agents/edi-schema-agent.md +97 -0
  12. package/dist/tools/edi-schema/commands/edi-schema.md +33 -0
  13. package/dist/tools/edi-schema/skills/edi-schema/SKILL.md +86 -0
  14. package/dist/tools/pii/.claude-plugin/plugin.json +25 -0
  15. package/dist/tools/pii/TOOL.yaml +22 -0
  16. package/dist/tools/pii/agents/pii-scanner.md +85 -0
  17. package/dist/tools/pii/commands/pii.md +33 -0
  18. package/dist/tools/pii/skills/pii/SKILL.md +97 -0
  19. package/dist/tools/pii/skills/pii/references/supported-entities.md +90 -0
  20. package/dist/tools/pii/skills/pii/scripts/presidio-analyze.d.ts +18 -0
  21. package/dist/tools/pii/skills/pii/scripts/presidio-analyze.d.ts.map +1 -0
  22. package/dist/tools/pii/skills/pii/scripts/presidio-analyze.ts +258 -0
  23. package/dist/tools/pii/skills/pii/scripts/presidio-init.d.ts +17 -0
  24. package/dist/tools/pii/skills/pii/scripts/presidio-init.d.ts.map +1 -0
  25. package/dist/tools/pii/skills/pii/scripts/presidio-init.ts +151 -0
  26. package/dist/tools/pii/skills/pii/scripts/presidio-redact.d.ts +21 -0
  27. package/dist/tools/pii/skills/pii/scripts/presidio-redact.d.ts.map +1 -0
  28. package/dist/tools/pii/skills/pii/scripts/presidio-redact.ts +294 -0
  29. package/dist/tools/pii/skills/pii/scripts/presidio.test.ts +444 -0
  30. package/package.json +1 -1
  31. package/src/tools/droid/.claude-plugin/plugin.json +1 -1
  32. package/src/tools/droid/TOOL.yaml +1 -1
  33. package/src/tools/droid/skills/droid/SKILL.md +1 -0
  34. package/src/tools/droid/skills/droid-bootstrap/SKILL.md +1 -0
  35. package/src/tools/edi-schema/.claude-plugin/plugin.json +25 -0
  36. package/src/tools/edi-schema/TOOL.yaml +29 -0
  37. package/src/tools/edi-schema/agents/edi-schema-agent.md +97 -0
  38. package/src/tools/edi-schema/commands/edi-schema.md +33 -0
  39. package/src/tools/edi-schema/skills/edi-schema/SKILL.md +86 -0
  40. package/src/tools/pii/.claude-plugin/plugin.json +25 -0
  41. package/src/tools/pii/TOOL.yaml +22 -0
  42. package/src/tools/pii/agents/pii-scanner.md +85 -0
  43. package/src/tools/pii/commands/pii.md +33 -0
  44. package/src/tools/pii/skills/pii/SKILL.md +97 -0
  45. package/src/tools/pii/skills/pii/references/supported-entities.md +90 -0
  46. package/src/tools/pii/skills/pii/scripts/presidio-analyze.ts +258 -0
  47. package/src/tools/pii/skills/pii/scripts/presidio-init.ts +151 -0
  48. package/src/tools/pii/skills/pii/scripts/presidio-redact.ts +294 -0
  49. 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": "@orderful/droid",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "droid",
3
- "version": "0.7.0",
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.0
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
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: droid-bootstrap
3
3
  description: "Lightweight droid bootstrap for update awareness. Checks for droid updates once per session."
4
+ alwaysApply: true
4
5
  allowed-tools: [Bash]
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.