@orderful/droid 0.48.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/CHANGELOG.md +24 -0
- package/bun.lock +137 -3
- package/dist/bin/droid.js +355 -90
- package/dist/commands/pack.d.ts +5 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/lib/pack.d.ts +31 -0
- package/dist/lib/pack.d.ts.map +1 -0
- package/dist/lib/types.d.ts +17 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/brain/TOOL.yaml +2 -0
- package/dist/tools/coach/TOOL.yaml +4 -0
- package/dist/tools/code-review/.claude-plugin/plugin.json +3 -2
- package/dist/tools/code-review/TOOL.yaml +4 -1
- package/dist/tools/code-review/agents/codex-context-researcher.md +99 -0
- package/dist/tools/code-review/skills/code-review/SKILL.md +20 -1
- package/dist/tools/codex/.claude-plugin/plugin.json +1 -1
- package/dist/tools/codex/TOOL.yaml +3 -1
- package/dist/tools/codex/skills/codex/SKILL.md +5 -1
- package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts +61 -0
- package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts.map +1 -0
- package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
- package/dist/tools/comments/TOOL.yaml +2 -0
- package/dist/tools/droid/.claude-plugin/plugin.json +1 -1
- package/dist/tools/droid/TOOL.yaml +3 -1
- package/dist/tools/droid/skills/droid/SKILL.md +48 -2
- package/dist/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
- package/dist/tools/edi-schema/TOOL.yaml +2 -0
- package/dist/tools/excalidraw/TOOL.yaml +2 -0
- package/dist/tools/meeting/TOOL.yaml +2 -0
- package/dist/tools/pii/TOOL.yaml +2 -0
- package/dist/tools/plan/TOOL.yaml +4 -0
- package/dist/tools/project/TOOL.yaml +2 -0
- package/dist/tools/release/TOOL.yaml +2 -0
- package/dist/tools/share/TOOL.yaml +2 -0
- package/dist/tools/status-update/TOOL.yaml +4 -0
- package/dist/tools/tech-design/TOOL.yaml +2 -0
- package/dist/tools/wrapup/TOOL.yaml +2 -0
- package/package.json +3 -1
- package/scripts/build.ts +3 -2
- package/src/bin/droid.ts +9 -0
- package/src/commands/pack.ts +77 -0
- package/src/lib/pack.test.ts +85 -0
- package/src/lib/pack.ts +293 -0
- package/src/lib/types.ts +19 -0
- package/src/tools/brain/TOOL.yaml +2 -0
- package/src/tools/coach/TOOL.yaml +4 -0
- package/src/tools/code-review/.claude-plugin/plugin.json +3 -2
- package/src/tools/code-review/TOOL.yaml +4 -1
- package/src/tools/code-review/agents/codex-context-researcher.md +99 -0
- package/src/tools/code-review/skills/code-review/SKILL.md +20 -1
- package/src/tools/codex/.claude-plugin/plugin.json +1 -1
- package/src/tools/codex/TOOL.yaml +3 -1
- package/src/tools/codex/skills/codex/SKILL.md +5 -1
- package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.test.ts +331 -0
- package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
- package/src/tools/comments/TOOL.yaml +2 -0
- package/src/tools/droid/.claude-plugin/plugin.json +1 -1
- package/src/tools/droid/TOOL.yaml +3 -1
- package/src/tools/droid/skills/droid/SKILL.md +48 -2
- package/src/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
- package/src/tools/edi-schema/TOOL.yaml +2 -0
- package/src/tools/excalidraw/TOOL.yaml +2 -0
- package/src/tools/meeting/TOOL.yaml +2 -0
- package/src/tools/pii/TOOL.yaml +2 -0
- package/src/tools/plan/TOOL.yaml +4 -0
- package/src/tools/project/TOOL.yaml +2 -0
- package/src/tools/release/TOOL.yaml +2 -0
- package/src/tools/share/TOOL.yaml +2 -0
- package/src/tools/status-update/TOOL.yaml +4 -0
- package/src/tools/tech-design/TOOL.yaml +2 -0
- package/src/tools/wrapup/TOOL.yaml +2 -0
- package/dist/tools/codex/skills/codex/scripts/git-scripts.test.ts +0 -364
- package/dist/tools/pii/skills/pii/scripts/presidio.test.ts +0 -444
|
@@ -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
|
+
});
|