@orderful/droid 0.47.0 → 0.50.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 (102) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/CHANGELOG.md +34 -0
  4. package/bun.lock +137 -3
  5. package/dist/bin/droid.js +355 -90
  6. package/dist/commands/pack.d.ts +5 -0
  7. package/dist/commands/pack.d.ts.map +1 -0
  8. package/dist/index.js +11 -0
  9. package/dist/lib/pack.d.ts +31 -0
  10. package/dist/lib/pack.d.ts.map +1 -0
  11. package/dist/lib/types.d.ts +17 -0
  12. package/dist/lib/types.d.ts.map +1 -1
  13. package/dist/tools/brain/.claude-plugin/plugin.json +1 -1
  14. package/dist/tools/brain/TOOL.yaml +3 -1
  15. package/dist/tools/brain/skills/brain/SKILL.md +3 -1
  16. package/dist/tools/brain/skills/brain/references/workflows.md +4 -2
  17. package/dist/tools/coach/TOOL.yaml +4 -0
  18. package/dist/tools/code-review/.claude-plugin/plugin.json +3 -2
  19. package/dist/tools/code-review/TOOL.yaml +4 -1
  20. package/dist/tools/code-review/agents/codex-context-researcher.md +99 -0
  21. package/dist/tools/code-review/skills/code-review/SKILL.md +20 -1
  22. package/dist/tools/codex/.claude-plugin/plugin.json +1 -1
  23. package/dist/tools/codex/TOOL.yaml +3 -1
  24. package/dist/tools/codex/skills/codex/SKILL.md +5 -1
  25. package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts +61 -0
  26. package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts.map +1 -0
  27. package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
  28. package/dist/tools/comments/.claude-plugin/plugin.json +1 -1
  29. package/dist/tools/comments/TOOL.yaml +3 -1
  30. package/dist/tools/comments/skills/comments/SKILL.md +14 -0
  31. package/dist/tools/droid/.claude-plugin/plugin.json +1 -1
  32. package/dist/tools/droid/TOOL.yaml +3 -1
  33. package/dist/tools/droid/skills/droid/SKILL.md +48 -2
  34. package/dist/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
  35. package/dist/tools/edi-schema/TOOL.yaml +2 -0
  36. package/dist/tools/excalidraw/.claude-plugin/plugin.json +22 -0
  37. package/dist/tools/excalidraw/TOOL.yaml +18 -0
  38. package/dist/tools/excalidraw/commands/excalidraw.md +34 -0
  39. package/dist/tools/excalidraw/skills/excalidraw/SKILL.md +100 -0
  40. package/dist/tools/excalidraw/skills/excalidraw/references/element-templates.md +336 -0
  41. package/dist/tools/excalidraw/skills/excalidraw/references/format-spec.md +102 -0
  42. package/dist/tools/meeting/TOOL.yaml +2 -0
  43. package/dist/tools/pii/TOOL.yaml +2 -0
  44. package/dist/tools/plan/.claude-plugin/plugin.json +1 -1
  45. package/dist/tools/plan/TOOL.yaml +5 -1
  46. package/dist/tools/plan/skills/plan/SKILL.md +2 -0
  47. package/dist/tools/plan/skills/plan/references/workflows.md +11 -2
  48. package/dist/tools/project/TOOL.yaml +2 -0
  49. package/dist/tools/release/TOOL.yaml +2 -0
  50. package/dist/tools/share/TOOL.yaml +2 -0
  51. package/dist/tools/status-update/TOOL.yaml +4 -0
  52. package/dist/tools/tech-design/TOOL.yaml +2 -0
  53. package/dist/tools/wrapup/TOOL.yaml +2 -0
  54. package/package.json +3 -1
  55. package/scripts/build.ts +3 -2
  56. package/src/bin/droid.ts +9 -0
  57. package/src/commands/pack.ts +77 -0
  58. package/src/lib/pack.test.ts +85 -0
  59. package/src/lib/pack.ts +293 -0
  60. package/src/lib/types.ts +19 -0
  61. package/src/tools/brain/.claude-plugin/plugin.json +1 -1
  62. package/src/tools/brain/TOOL.yaml +3 -1
  63. package/src/tools/brain/skills/brain/SKILL.md +3 -1
  64. package/src/tools/brain/skills/brain/references/workflows.md +4 -2
  65. package/src/tools/coach/TOOL.yaml +4 -0
  66. package/src/tools/code-review/.claude-plugin/plugin.json +3 -2
  67. package/src/tools/code-review/TOOL.yaml +4 -1
  68. package/src/tools/code-review/agents/codex-context-researcher.md +99 -0
  69. package/src/tools/code-review/skills/code-review/SKILL.md +20 -1
  70. package/src/tools/codex/.claude-plugin/plugin.json +1 -1
  71. package/src/tools/codex/TOOL.yaml +3 -1
  72. package/src/tools/codex/skills/codex/SKILL.md +5 -1
  73. package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.test.ts +331 -0
  74. package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
  75. package/src/tools/comments/.claude-plugin/plugin.json +1 -1
  76. package/src/tools/comments/TOOL.yaml +3 -1
  77. package/src/tools/comments/skills/comments/SKILL.md +14 -0
  78. package/src/tools/droid/.claude-plugin/plugin.json +1 -1
  79. package/src/tools/droid/TOOL.yaml +3 -1
  80. package/src/tools/droid/skills/droid/SKILL.md +48 -2
  81. package/src/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
  82. package/src/tools/edi-schema/TOOL.yaml +2 -0
  83. package/src/tools/excalidraw/.claude-plugin/plugin.json +22 -0
  84. package/src/tools/excalidraw/TOOL.yaml +18 -0
  85. package/src/tools/excalidraw/commands/excalidraw.md +34 -0
  86. package/src/tools/excalidraw/skills/excalidraw/SKILL.md +100 -0
  87. package/src/tools/excalidraw/skills/excalidraw/references/element-templates.md +336 -0
  88. package/src/tools/excalidraw/skills/excalidraw/references/format-spec.md +102 -0
  89. package/src/tools/meeting/TOOL.yaml +2 -0
  90. package/src/tools/pii/TOOL.yaml +2 -0
  91. package/src/tools/plan/.claude-plugin/plugin.json +1 -1
  92. package/src/tools/plan/TOOL.yaml +5 -1
  93. package/src/tools/plan/skills/plan/SKILL.md +2 -0
  94. package/src/tools/plan/skills/plan/references/workflows.md +11 -2
  95. package/src/tools/project/TOOL.yaml +2 -0
  96. package/src/tools/release/TOOL.yaml +2 -0
  97. package/src/tools/share/TOOL.yaml +2 -0
  98. package/src/tools/status-update/TOOL.yaml +4 -0
  99. package/src/tools/tech-design/TOOL.yaml +2 -0
  100. package/src/tools/wrapup/TOOL.yaml +2 -0
  101. package/dist/tools/codex/skills/codex/scripts/git-scripts.test.ts +0 -364
  102. package/dist/tools/pii/skills/pii/scripts/presidio.test.ts +0 -444
@@ -15,6 +15,8 @@ Code-review has no configuration of its own. Optional integration with other too
15
15
 
16
16
  - **Brain skill** (optional): If installed, offers to save review results to a `/brain review` doc
17
17
  - Check with: `droid config --get tools.brain` to see if `brain_dir` is configured
18
+ - **Codex skill** (optional): If configured, enriches EDI findings with organisational domain knowledge
19
+ - Check with: `droid config --get tools.codex` to see if `codex_repo` is configured
18
20
 
19
21
  **Overrides:** This skill supports user-defined overrides. See `/droid` skill § Skill Overrides.
20
22
 
@@ -32,6 +34,7 @@ The `/code-review` command orchestrates multiple specialized agents in parallel:
32
34
  2. **test-coverage-analyzer** - Test completeness and edge cases
33
35
  3. **error-handling-reviewer** - Silent failures, missing error handling
34
36
  4. **type-reviewer** - TypeScript type design, interface contracts
37
+ 5. **codex-context-researcher** (optional) - Domain knowledge from codex for EDI enrichment
35
38
 
36
39
  Each agent returns issues with confidence scores (0-100). Issues below 80% confidence are filtered out to reduce noise.
37
40
 
@@ -117,14 +120,19 @@ For PR reviews, also fetch:
117
120
 
118
121
  ### Step 3: Parallel Agent Reviews
119
122
 
123
+ **Check codex availability** before launching agents:
124
+
125
+ Run `droid config --get tools.codex`. If `codex_repo` is configured, you will launch 5 agents. If not, launch the standard 4.
126
+
120
127
  Launch these agents in parallel using the Task tool with `run_in_background: true`:
121
128
 
122
129
  1. **edi-standards-reviewer**: EDI patterns, partnership handling, billing concerns
123
130
  2. **test-coverage-analyzer**: Test completeness and edge cases
124
131
  3. **error-handling-reviewer**: Silent failures, missing error handling
125
132
  4. **type-reviewer**: Type design, interface contracts
133
+ 5. **codex-context-researcher** (only if codex is configured): Pass changed file paths and a brief summary of the diff (module names, domain concepts extracted from paths and PR description). Do NOT pass full diff content — this agent reads codex, not code.
126
134
 
127
- Pass each agent:
135
+ Pass agents 1–4:
128
136
 
129
137
  1. The diff content
130
138
  2. The full file content for changed files (for context)
@@ -139,6 +147,17 @@ Filter out issues with confidence < 80.
139
147
 
140
148
  ### Step 5: Synthesize Report
141
149
 
150
+ **Codex enrichment** (if codex-context-researcher returned `status: "ok"` with non-empty `domain_context`):
151
+
152
+ Before compiling the EDI findings, prefix the EDI section with a "Domain Context" block:
153
+
154
+ > **Domain context** (from codex: entry-name-1, entry-name-2)
155
+ > - Bullet points from the researcher's domain_context
156
+
157
+ Use this context to strengthen or re-prioritise EDI findings. For example, if codex says "partnership billing events must always include account_id" and the EDI reviewer flagged a missing field, elevate that finding's priority.
158
+
159
+ If codex returned `status: "not_configured"` or `"no_relevant_entries"`, omit the domain context block silently.
160
+
142
161
  Compile findings into a prioritized report:
143
162
 
144
163
  **PR #123: "Add partnership billing events"** (if reviewing a PR)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "droid-codex",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Shared organizational knowledge - PRDs, tech designs, domains, proposals, patterns, and explored topics. Self-describing: structure and workflows defined in codex repo. Use when loading project context, searching codex, capturing decisions, or creating new entries.",
5
5
  "author": {
6
6
  "name": "Orderful",
@@ -1,7 +1,9 @@
1
1
  name: codex
2
2
  description: "Shared organizational knowledge - PRDs, tech designs, domains, proposals, patterns, and explored topics. Self-describing: structure and workflows defined in codex repo. Use when loading project context, searching codex, capturing decisions, or creating new entries."
3
- version: 0.3.1
3
+ version: 0.4.0
4
4
  status: beta
5
+ audience:
6
+ - all
5
7
 
6
8
  includes:
7
9
  skills:
@@ -145,6 +145,7 @@ The codex skill includes three git scripts. **Always use these instead of raw gi
145
145
  |--------|---------|-------------|
146
146
  | git-preamble | Ensure clean main + pull latest | Before ANY operation |
147
147
  | git-start-write | Preamble + create branch | Before write operations |
148
+ | normalize-frontmatter | Normalize YAML frontmatter format | After writing, before commit |
148
149
  | git-finish-write | Commit + PR + return to main | After write operations |
149
150
 
150
151
  ### Read Operations
@@ -159,7 +160,10 @@ The codex skill includes three git scripts. **Always use these instead of raw gi
159
160
 
160
161
  # 2. Make your changes (write files)
161
162
 
162
- # 3. Finish write (commit + PR + return to main)
163
+ # 3. Normalize frontmatter (prevents fix-frontmatter PRs)
164
+ droid config --get tools.codex | droid exec codex normalize-frontmatter --config -
165
+
166
+ # 4. Finish write (commit + PR + return to main)
163
167
  droid config --get tools.codex | droid exec codex git-finish-write --config - \
164
168
  --message "{commit message}" \
165
169
  --pr-title "{PR title}" \
@@ -0,0 +1,331 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import {
3
+ extractFrontmatter,
4
+ needsQuoting,
5
+ quoteValue,
6
+ unquoteDate,
7
+ normalizeFrontmatter,
8
+ normalizeFile,
9
+ } from './normalize-frontmatter';
10
+
11
+ describe('extractFrontmatter', () => {
12
+ it('extracts frontmatter from valid markdown', () => {
13
+ const content = '---\ntitle: Test\nstatus: active\n---\n\n# Body';
14
+ const { frontmatter, body } = extractFrontmatter(content);
15
+ expect(frontmatter).toEqual(['title: Test', 'status: active']);
16
+ expect(body).toBe('\n\n# Body');
17
+ });
18
+
19
+ it('returns null for files without frontmatter', () => {
20
+ const content = '# Just a heading\n\nSome text.';
21
+ const { frontmatter, body } = extractFrontmatter(content);
22
+ expect(frontmatter).toBeNull();
23
+ expect(body).toBe(content);
24
+ });
25
+
26
+ it('returns null for unclosed frontmatter', () => {
27
+ const content = '---\ntitle: Test\nstatus: active\n';
28
+ const { frontmatter } = extractFrontmatter(content);
29
+ expect(frontmatter).toBeNull();
30
+ });
31
+ });
32
+
33
+ describe('needsQuoting', () => {
34
+ it('does not quote simple strings', () => {
35
+ expect(needsQuoting('active')).toBe(false);
36
+ expect(needsQuoting('hello world')).toBe(false);
37
+ });
38
+
39
+ it('does not quote booleans or null', () => {
40
+ expect(needsQuoting('true')).toBe(false);
41
+ expect(needsQuoting('false')).toBe(false);
42
+ expect(needsQuoting('null')).toBe(false);
43
+ });
44
+
45
+ it('does not quote dates', () => {
46
+ expect(needsQuoting('2026-03-04')).toBe(false);
47
+ });
48
+
49
+ it('quotes strings with em dashes', () => {
50
+ expect(needsQuoting('AI-powered — fast delivery')).toBe(true);
51
+ });
52
+
53
+ it('quotes strings with en dashes', () => {
54
+ expect(needsQuoting('pages 1\u20135')).toBe(true);
55
+ });
56
+
57
+ it('quotes strings with accented characters', () => {
58
+ expect(needsQuoting('résumé of changes')).toBe(true);
59
+ });
60
+
61
+ it('quotes strings with @ symbol', () => {
62
+ expect(needsQuoting('@tylerfry')).toBe(true);
63
+ });
64
+
65
+ it('quotes strings with colon-space', () => {
66
+ expect(needsQuoting('Note: this is important')).toBe(true);
67
+ });
68
+
69
+ it('quotes strings with inline # comment', () => {
70
+ expect(needsQuoting('value #comment')).toBe(true);
71
+ });
72
+
73
+ it('quotes strings starting with special YAML chars', () => {
74
+ expect(needsQuoting('*bold*')).toBe(true);
75
+ expect(needsQuoting('!important')).toBe(true);
76
+ });
77
+ });
78
+
79
+ describe('quoteValue', () => {
80
+ it('wraps value in double quotes', () => {
81
+ expect(quoteValue('hello')).toBe('"hello"');
82
+ });
83
+
84
+ it('escapes internal double quotes', () => {
85
+ expect(quoteValue('say "hello"')).toBe('"say \\"hello\\""');
86
+ });
87
+
88
+ it('does not double-quote already quoted values', () => {
89
+ expect(quoteValue('"already quoted"')).toBe('"already quoted"');
90
+ expect(quoteValue("'single quoted'")).toBe("'single quoted'");
91
+ });
92
+ });
93
+
94
+ describe('unquoteDate', () => {
95
+ it('unquotes a quoted date', () => {
96
+ expect(unquoteDate('"2026-03-04"')).toBe('2026-03-04');
97
+ expect(unquoteDate("'2026-03-04'")).toBe('2026-03-04');
98
+ });
99
+
100
+ it('leaves non-date quoted values alone', () => {
101
+ expect(unquoteDate('"not a date"')).toBe('"not a date"');
102
+ });
103
+
104
+ it('leaves bare dates alone', () => {
105
+ expect(unquoteDate('2026-03-04')).toBe('2026-03-04');
106
+ });
107
+ });
108
+
109
+ describe('normalizeFrontmatter', () => {
110
+ it('flattens folded scalar (>) into single line', () => {
111
+ const lines = [
112
+ 'title: >',
113
+ ' This is a long',
114
+ ' description that spans',
115
+ ' multiple lines',
116
+ 'status: active',
117
+ ];
118
+ const result = normalizeFrontmatter(lines);
119
+ expect(result).toEqual([
120
+ 'title: This is a long description that spans multiple lines',
121
+ 'status: active',
122
+ ]);
123
+ });
124
+
125
+ it('flattens literal scalar (|) into single line', () => {
126
+ const lines = [
127
+ 'description: |',
128
+ ' Line one',
129
+ ' Line two',
130
+ 'status: active',
131
+ ];
132
+ const result = normalizeFrontmatter(lines);
133
+ expect(result).toEqual([
134
+ 'description: Line one Line two',
135
+ 'status: active',
136
+ ]);
137
+ });
138
+
139
+ it('flattens >- (strip) into single line', () => {
140
+ const lines = [
141
+ 'title: >-',
142
+ ' Stripped trailing',
143
+ ' newline content',
144
+ 'status: active',
145
+ ];
146
+ const result = normalizeFrontmatter(lines);
147
+ expect(result).toEqual([
148
+ 'title: Stripped trailing newline content',
149
+ 'status: active',
150
+ ]);
151
+ });
152
+
153
+ it('quotes values with em dashes after flattening', () => {
154
+ const lines = [
155
+ 'description: >',
156
+ ' AI-powered platform —',
157
+ ' fast and reliable',
158
+ ];
159
+ const result = normalizeFrontmatter(lines);
160
+ expect(result).toEqual([
161
+ 'description: "AI-powered platform \u2014 fast and reliable"',
162
+ ]);
163
+ });
164
+
165
+ it('quotes inline values with special characters', () => {
166
+ const lines = [
167
+ 'title: Overview — EDI Platform',
168
+ 'author: @tylerfry',
169
+ ];
170
+ const result = normalizeFrontmatter(lines);
171
+ expect(result).toEqual([
172
+ 'title: "Overview \u2014 EDI Platform"',
173
+ 'author: "@tylerfry"',
174
+ ]);
175
+ });
176
+
177
+ it('unquotes dates that are wrapped in quotes', () => {
178
+ const lines = [
179
+ 'created: "2026-03-04"',
180
+ "updated: '2026-01-15'",
181
+ ];
182
+ const result = normalizeFrontmatter(lines);
183
+ expect(result).toEqual([
184
+ 'created: 2026-03-04',
185
+ 'updated: 2026-01-15',
186
+ ]);
187
+ });
188
+
189
+ it('preserves already-correct frontmatter', () => {
190
+ const lines = [
191
+ 'title: Simple Title',
192
+ 'type: domain',
193
+ 'status: active',
194
+ 'created: 2026-03-04',
195
+ ];
196
+ const result = normalizeFrontmatter(lines);
197
+ expect(result).toEqual(lines);
198
+ });
199
+
200
+ it('preserves list items', () => {
201
+ const lines = [
202
+ 'related_domains:',
203
+ '- overview',
204
+ '- transaction',
205
+ ];
206
+ const result = normalizeFrontmatter(lines);
207
+ expect(result).toEqual([
208
+ 'related_domains:',
209
+ '- overview',
210
+ '- transaction',
211
+ ]);
212
+ });
213
+
214
+ it('preserves comments', () => {
215
+ const lines = [
216
+ '# This is a comment',
217
+ 'title: Test',
218
+ ];
219
+ const result = normalizeFrontmatter(lines);
220
+ expect(result).toEqual(lines);
221
+ });
222
+
223
+ it('preserves blank lines', () => {
224
+ const lines = [
225
+ 'title: Test',
226
+ '',
227
+ 'status: active',
228
+ ];
229
+ const result = normalizeFrontmatter(lines);
230
+ expect(result).toEqual(lines);
231
+ });
232
+
233
+ it('handles key with empty value (list follows)', () => {
234
+ const lines = [
235
+ 'codebase_paths:',
236
+ '- ~/src/app',
237
+ '- ~/src/lib',
238
+ ];
239
+ const result = normalizeFrontmatter(lines);
240
+ expect(result).toEqual(lines);
241
+ });
242
+
243
+ it('quotes list items with special characters', () => {
244
+ const lines = [
245
+ 'participants:',
246
+ '- @tylerfry',
247
+ '- @thea',
248
+ ];
249
+ const result = normalizeFrontmatter(lines);
250
+ expect(result).toEqual([
251
+ 'participants:',
252
+ '- "@tylerfry"',
253
+ '- "@thea"',
254
+ ]);
255
+ });
256
+ });
257
+
258
+ describe('normalizeFile', () => {
259
+ it('returns null when no changes needed', () => {
260
+ const content = '---\ntitle: Test\nstatus: active\ncreated: 2026-03-04\n---\n\n# Body';
261
+ expect(normalizeFile(content)).toBeNull();
262
+ });
263
+
264
+ it('returns null when no frontmatter exists', () => {
265
+ const content = '# Just a heading';
266
+ expect(normalizeFile(content)).toBeNull();
267
+ });
268
+
269
+ it('normalizes and reassembles the file', () => {
270
+ const content = '---\ntitle: "2026-03-04"\ndescription: >\n A long\n description\n---\n\n# Body';
271
+ const result = normalizeFile(content);
272
+ expect(result).toBe('---\ntitle: 2026-03-04\ndescription: A long description\n---\n\n# Body');
273
+ });
274
+
275
+ it('is idempotent — second run returns null', () => {
276
+ const content = '---\ntitle: >\n Hello — world\nstatus: active\n---\n\n# Body';
277
+ const first = normalizeFile(content);
278
+ expect(first).not.toBeNull();
279
+
280
+ const second = normalizeFile(first!);
281
+ expect(second).toBeNull();
282
+ });
283
+
284
+ it('handles real-world codex frontmatter', () => {
285
+ const content = [
286
+ '---',
287
+ 'title: Droid - AI Workflow Toolkit',
288
+ 'type: context',
289
+ 'status: active',
290
+ 'created: 2025-12-09',
291
+ 'updated: 2026-01-29',
292
+ 'source: implementation',
293
+ 'participants:',
294
+ ' tech_lead: tylerfry',
295
+ 'codebase_paths:',
296
+ '- ~/src/github.com/droid',
297
+ 'slack:',
298
+ ' channel: \'#droid\'',
299
+ ' canvas_id: F0AD41VF71V',
300
+ '---',
301
+ '',
302
+ '# Droid',
303
+ ].join('\n');
304
+
305
+ // Already well-formed — should return null
306
+ expect(normalizeFile(content)).toBeNull();
307
+ });
308
+
309
+ it('normalizes multiline scalar in real-world pattern', () => {
310
+ const content = [
311
+ '---',
312
+ 'title: >',
313
+ ' Automated Partner Testing —',
314
+ ' End-to-end EDI validation',
315
+ 'type: project',
316
+ 'status: active',
317
+ 'created: 2026-02-01',
318
+ '---',
319
+ '',
320
+ '# Content',
321
+ ].join('\n');
322
+
323
+ const result = normalizeFile(content);
324
+ expect(result).toContain('title: "Automated Partner Testing \u2014 End-to-end EDI validation"');
325
+ expect(result).toContain('type: project');
326
+ expect(result).toContain('created: 2026-02-01');
327
+
328
+ // Idempotent
329
+ expect(normalizeFile(result!)).toBeNull();
330
+ });
331
+ });