@polderlabs/bizar 2.3.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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +364 -0
  3. package/cli/audit.mjs +144 -0
  4. package/cli/banner.mjs +41 -0
  5. package/cli/bin.mjs +186 -0
  6. package/cli/copy.mjs +508 -0
  7. package/cli/export.mjs +87 -0
  8. package/cli/init.mjs +147 -0
  9. package/cli/install.mjs +390 -0
  10. package/cli/plan-templates.mjs +523 -0
  11. package/cli/plan.mjs +2087 -0
  12. package/cli/prompts.mjs +163 -0
  13. package/cli/update.mjs +273 -0
  14. package/cli/utils.mjs +153 -0
  15. package/config/AGENTS.md +282 -0
  16. package/config/agents/baldr.md +148 -0
  17. package/config/agents/forseti.md +112 -0
  18. package/config/agents/frigg.md +101 -0
  19. package/config/agents/heimdall.md +157 -0
  20. package/config/agents/hermod.md +144 -0
  21. package/config/agents/mimir.md +115 -0
  22. package/config/agents/odin.md +309 -0
  23. package/config/agents/quick.md +78 -0
  24. package/config/agents/semble-search.md +44 -0
  25. package/config/agents/thor.md +97 -0
  26. package/config/agents/tyr.md +96 -0
  27. package/config/agents/vidarr.md +100 -0
  28. package/config/agents/vor.md +140 -0
  29. package/config/commands/audit.md +1 -0
  30. package/config/commands/explain.md +1 -0
  31. package/config/commands/init.md +1 -0
  32. package/config/commands/learn.md +1 -0
  33. package/config/commands/pr-review.md +1 -0
  34. package/config/commands/tailscale-serve.md +96 -0
  35. package/config/hooks/README.md +29 -0
  36. package/config/hooks/post-tool-use.md +16 -0
  37. package/config/hooks/pre-tool-use.md +16 -0
  38. package/config/opencode.json +52 -0
  39. package/config/opencode.json.template +52 -0
  40. package/config/rules/general.md +8 -0
  41. package/config/rules/git.md +11 -0
  42. package/config/rules/javascript.md +10 -0
  43. package/config/rules/python.md +10 -0
  44. package/config/rules/testing.md +10 -0
  45. package/config/skills/bizar/README.md +9 -0
  46. package/config/skills/bizar/SKILL.md +187 -0
  47. package/config/skills/cpp-coding-standards/README.md +28 -0
  48. package/config/skills/cpp-coding-standards/SKILL.md +634 -0
  49. package/config/skills/cpp-coding-standards/agents/openai.yaml +4 -0
  50. package/config/skills/cpp-coding-standards/references/concurrency.md +320 -0
  51. package/config/skills/cpp-coding-standards/references/error-handling.md +229 -0
  52. package/config/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  53. package/config/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  54. package/config/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  55. package/config/skills/cpp-testing/README.md +28 -0
  56. package/config/skills/cpp-testing/SKILL.md +304 -0
  57. package/config/skills/cpp-testing/agents/openai.yaml +4 -0
  58. package/config/skills/cpp-testing/references/coverage.md +370 -0
  59. package/config/skills/cpp-testing/references/framework-compare.md +175 -0
  60. package/config/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  61. package/config/skills/cpp-testing/references/mocking.md +364 -0
  62. package/config/skills/cpp-testing/references/tdd-workflow.md +308 -0
  63. package/config/skills/embedded-esp-idf/README.md +41 -0
  64. package/config/skills/embedded-esp-idf/SKILL.md +439 -0
  65. package/config/skills/embedded-esp-idf/agents/openai.yaml +4 -0
  66. package/config/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  67. package/config/skills/embedded-esp-idf/references/host-tests.md +164 -0
  68. package/config/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  69. package/config/skills/embedded-esp-idf/references/kconfig.md +159 -0
  70. package/config/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  71. package/config/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  72. package/config/skills/embedded-esp-idf/references/nvs.md +121 -0
  73. package/config/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  74. package/config/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  75. package/config/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  76. package/config/skills/self-improvement/SKILL.md +64 -0
  77. package/package.json +47 -0
  78. package/templates/plan/htmx.min.js +1 -0
  79. package/templates/plan/library/bug-investigation.mdx +79 -0
  80. package/templates/plan/library/decision-record.mdx +71 -0
  81. package/templates/plan/library/feature-design.mdx +92 -0
  82. package/templates/plan/meta.json.template +8 -0
  83. package/templates/plan/plan.canvas.template +1711 -0
  84. package/templates/plan/plan.html.template +937 -0
  85. package/templates/plan/plan.mdx.template +46 -0
@@ -0,0 +1,523 @@
1
+ /**
2
+ * bizar plan-templates.mjs
3
+ *
4
+ * Built-in template library for the visual planner v2.
5
+ *
6
+ * Templates are stored in two places:
7
+ * 1. JS-embedded defaults (so the CLI works without filesystem reads)
8
+ * 2. templates/plan/library/*.mdx (so users can edit/add templates)
9
+ *
10
+ * At lookup time we prefer the on-disk .mdx file (if present) and fall
11
+ * back to the embedded string. The "blank" template is special: it
12
+ * delegates to the existing plan.mdx.template file via the CLI caller.
13
+ *
14
+ * Exports:
15
+ * - getTemplate(name) → { name, description, content } | null
16
+ * - getTemplateNames() → string[] (sorted)
17
+ * - listTemplates() → [{ name, description, source }, ...]
18
+ * - printTemplates() → writes a friendly CLI listing to stdout
19
+ *
20
+ * The names "blank", "feature-design", "bug-investigation", and
21
+ * "decision-record" are reserved (case-insensitive).
22
+ */
23
+
24
+ import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
25
+ import { dirname, join, resolve, basename } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+
28
+ const __dirname = dirname(fileURLToPath(import.meta.url));
29
+ const PROJECT_ROOT = resolve(__dirname, '..');
30
+ const TEMPLATES_DIR = join(PROJECT_ROOT, 'templates', 'plan');
31
+ const LIBRARY_DIR = join(TEMPLATES_DIR, 'library');
32
+
33
+ // ─── Embedded defaults ──────────────────────────────────────────────────────
34
+ //
35
+ // These are kept in sync with templates/plan/library/*.mdx. If the
36
+ // library directory is missing a file, the embedded version is used.
37
+ // To change a built-in template, edit BOTH (or just the .mdx file if
38
+ // the file is present — the loader prefers files).
39
+
40
+ const BUILT_IN_TEMPLATES = {
41
+ 'blank': {
42
+ description: 'Empty starter — the same template the v1 planner used',
43
+ content: null, // signals: caller should use plan.mdx.template
44
+ },
45
+ 'feature-design': {
46
+ description: 'For designing a new feature (problem, goals, design, tradeoffs)',
47
+ content: `# Feature: {{title}}
48
+
49
+ **Status:** \`[STATUS:draft]\` · **Author:** {{author}} · **Created:** {{created}}
50
+
51
+ > [!INFO]
52
+ > This is a v2 plan with the **feature-design** template. It uses callouts
53
+ > (\`> [!INFO]\`, \`> [!WARNING]\`, etc.), status badges, and GFM task lists.
54
+
55
+ ## Problem
56
+
57
+ _What problem are we solving? Why now? What happens if we don't?_
58
+
59
+ ## Goals
60
+
61
+ - [ ] Goal 1 — a concrete, measurable outcome
62
+ - [ ] Goal 2 — another concrete, measurable outcome
63
+ - [ ] Goal 3 — and one more for good measure
64
+
65
+ ## Non-goals
66
+
67
+ - This feature does NOT do X (deferred to a future plan)
68
+ - This feature does NOT cover Y (out of scope for this iteration)
69
+
70
+ ## Design
71
+
72
+ The proposed approach. Walk through the high-level shape, then drill into specifics.
73
+
74
+ ### Architecture
75
+
76
+ \`\`\`mermaid
77
+ graph LR
78
+ Client --> API --> DB
79
+ API --> Cache
80
+ \`\`\`
81
+
82
+ ### Data model
83
+
84
+ Tables, schemas, types. Show diffs or new shapes.
85
+
86
+ ### API
87
+
88
+ _Endpoints, request/response shapes, error handling._
89
+
90
+ ### Edge cases
91
+
92
+ - What if X is empty?
93
+ - What if Y is malformed?
94
+ - What if the user is offline?
95
+
96
+ ## Tradeoffs
97
+
98
+ | Option | Pros | Cons |
99
+ |--------|------|------|
100
+ | A | Simple, easy to roll back | Limited to use case 1 |
101
+ | B | Handles more cases | More complex, more tests |
102
+
103
+ ## Files affected
104
+
105
+ - \`path/to/file.ts\` — what changes and why
106
+ - \`path/to/other.ts\` — what changes and why
107
+ - \`path/to/migration.sql\` — schema changes
108
+
109
+ ## Open questions
110
+
111
+ 1. Question 1?
112
+ 2. Question 2?
113
+ 3. Question 3?
114
+
115
+ ## Test plan
116
+
117
+ - [ ] Unit tests for the new module
118
+ - [ ] Integration tests for the API surface
119
+ - [ ] E2E test for the user flow
120
+ - [ ] Manual QA in staging
121
+
122
+ ## Rollout
123
+
124
+ - [ ] Dev
125
+ - [ ] Staging
126
+ - [ ] Production (% rollout)
127
+
128
+ ## Decision
129
+
130
+ > [!TIP]
131
+ > Pick option **A** or **B** and state the rationale here. Reference the
132
+ > Tradeoffs table above. Link to relevant docs / RFCs.
133
+
134
+ ## References
135
+
136
+ - [Design doc](https://example.com/design)
137
+ - [Related plan](#)
138
+ `,
139
+ },
140
+ 'bug-investigation': {
141
+ description: 'For investigating a bug (repro, root cause, fix, regression test)',
142
+ content: `# Bug: {{title}}
143
+
144
+ **Status:** \`[STATUS:draft]\` · **Author:** {{author}} · **Created:** {{created}}
145
+
146
+ > [!DANGER]
147
+ > Severity: <high/medium/low> · Reported: <date> · Reporter: <name>
148
+
149
+ ## Summary
150
+
151
+ _One-paragraph description of the bug._
152
+
153
+ ## Reproduction
154
+
155
+ Steps to reproduce:
156
+ 1. Step 1
157
+ 2. Step 2
158
+ 3. ...
159
+
160
+ ## Expected vs Actual
161
+
162
+ **Expected:** What should happen.
163
+
164
+ **Actual:** What actually happens.
165
+
166
+ ## Environment
167
+
168
+ - App version: <version>
169
+ - OS: <os>
170
+ - Browser: <browser>
171
+ - Account: <test-account-id> (if reproducible only on certain accounts)
172
+
173
+ ## Investigation
174
+
175
+ > [!NOTE]
176
+ > Document the timeline of what you tried, what you found, and what
177
+ > you ruled out. Link to relevant logs, traces, or screenshots.
178
+
179
+ ### What we tried
180
+
181
+ - Tried X — found Y
182
+ - Tried A — ruled out B
183
+
184
+ ### What we found
185
+
186
+ - Observation 1
187
+ - Observation 2
188
+
189
+ ## Root cause
190
+
191
+ > [!WARNING]
192
+ > State the underlying cause clearly. If the cause is unknown, say so
193
+ > and list the most likely candidates.
194
+
195
+ _The underlying issue._
196
+
197
+ ## Fix
198
+
199
+ > [!TIP]
200
+ > Describe the proposed fix. Include a code diff or a sketch of the
201
+ > change. Note any follow-up work that's needed but out of scope here.
202
+
203
+ _The proposed fix._
204
+
205
+ ## Test plan
206
+
207
+ - [ ] Test that the bug is fixed (regression test on the failing case)
208
+ - [ ] Test that the fix doesn't break anything (existing tests still pass)
209
+ - [ ] Add a regression test to the suite
210
+ - [ ] Manual verification in staging
211
+
212
+ ## Rollback plan
213
+
214
+ _How do we revert if the fix makes things worse?_
215
+
216
+ ## References
217
+
218
+ - [PR #X](https://example.com/pr/X)
219
+ - [Slack thread](https://example.com/slack)
220
+ - [Related incidents](#)
221
+ `,
222
+ },
223
+ 'decision-record': {
224
+ description: 'Architecture Decision Record (ADR) — context, options, decision, consequences',
225
+ content: `# Decision: {{title}}
226
+
227
+ **Status:** \`[STATUS:draft]\` · **Author:** {{author}} · **Date:** {{created}}
228
+
229
+ > [!INFO]
230
+ > This is an **Architecture Decision Record (ADR)**. It captures a
231
+ > significant decision, the context that led to it, and the consequences
232
+ > that follow. Once accepted, ADRs are immutable — create a new ADR to
233
+ > supersede this one.
234
+
235
+ ## Context
236
+
237
+ _What is the situation? What forces are at play? What problem are we
238
+ trying to solve? What constraints do we have?_
239
+
240
+ ## Options considered
241
+
242
+ ### Option A: <name>
243
+
244
+ _Brief description._
245
+
246
+ - Pros: …
247
+ - Cons: …
248
+
249
+ ### Option B: <name>
250
+
251
+ _Brief description._
252
+
253
+ - Pros: …
254
+ - Cons: …
255
+
256
+ ### Option C: <name>
257
+
258
+ _Brief description._
259
+
260
+ - Pros: …
261
+ - Cons: …
262
+
263
+ ## Decision
264
+
265
+ > [!TIP]
266
+ > State the chosen option and the rationale. Quote relevant constraints
267
+ > from the Context section. Note any dissent.
268
+
269
+ We chose **Option X** because…
270
+
271
+ ## Consequences
272
+
273
+ ### Positive
274
+
275
+ - …
276
+
277
+ ### Negative
278
+
279
+ - …
280
+
281
+ ### Neutral
282
+
283
+ - …
284
+
285
+ ## Follow-ups
286
+
287
+ - [ ] Follow-up 1
288
+ - [ ] Follow-up 2
289
+ - [ ] Follow-up 3
290
+
291
+ ## References
292
+
293
+ - [Doc 1](https://example.com/doc-1)
294
+ - [Doc 2](https://example.com/doc-2)
295
+ - [Related ADRs](#)
296
+ `,
297
+ },
298
+ };
299
+
300
+ // ─── File loader ─────────────────────────────────────────────────────────────
301
+
302
+ /**
303
+ * Read a .mdx file from the library directory if it exists.
304
+ * Returns null if the file is absent or unreadable.
305
+ */
306
+ function readLibraryFile(slug) {
307
+ const path = join(LIBRARY_DIR, `${slug}.mdx`);
308
+ if (!existsSync(path)) return null;
309
+ try {
310
+ return readFileSync(path, 'utf-8');
311
+ } catch {
312
+ return null;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Discover user-added templates in the library directory that are not
318
+ * already in BUILT_IN_TEMPLATES. Any *.mdx file whose basename (without
319
+ * extension) isn't a reserved built-in name becomes a "custom" entry.
320
+ */
321
+ function discoverCustomTemplates() {
322
+ if (!existsSync(LIBRARY_DIR)) return [];
323
+ const reserved = new Set(Object.keys(BUILT_IN_TEMPLATES));
324
+ const out = [];
325
+ for (const name of readdirSync(LIBRARY_DIR)) {
326
+ if (!name.endsWith('.mdx')) continue;
327
+ const slug = name.slice(0, -'.mdx'.length);
328
+ if (reserved.has(slug)) continue;
329
+ const path = join(LIBRARY_DIR, name);
330
+ try {
331
+ const content = readFileSync(path, 'utf-8');
332
+ out.push({
333
+ name: slug,
334
+ description: 'Custom template',
335
+ content,
336
+ source: 'library',
337
+ });
338
+ } catch {
339
+ // skip unreadable
340
+ }
341
+ }
342
+ return out;
343
+ }
344
+
345
+ // ─── Public API ──────────────────────────────────────────────────────────────
346
+
347
+ /**
348
+ * Return the template record for a given name (case-insensitive).
349
+ * For the "blank" template, content is null — the caller should use
350
+ * plan.mdx.template as the source.
351
+ *
352
+ * @param {string} name
353
+ * @returns {{ name: string, description: string, content: string|null, source: 'built-in'|'library' } | null}
354
+ */
355
+ export function getTemplate(name) {
356
+ if (!name) return null;
357
+ const normalized = String(name).toLowerCase().trim();
358
+ if (!BUILT_IN_TEMPLATES[normalized]) return null;
359
+
360
+ // "blank" is special: caller should use the standard plan.mdx.template
361
+ if (normalized === 'blank') {
362
+ return {
363
+ name: 'blank',
364
+ description: BUILT_IN_TEMPLATES.blank.description,
365
+ content: null,
366
+ source: 'built-in',
367
+ };
368
+ }
369
+
370
+ // Try the on-disk library file first; fall back to the JS-embedded copy.
371
+ const fileContent = readLibraryFile(normalized);
372
+ if (fileContent != null) {
373
+ return {
374
+ name: normalized,
375
+ description: BUILT_IN_TEMPLATES[normalized].description,
376
+ content: fileContent,
377
+ source: 'library',
378
+ };
379
+ }
380
+
381
+ return {
382
+ name: normalized,
383
+ description: BUILT_IN_TEMPLATES[normalized].description,
384
+ content: BUILT_IN_TEMPLATES[normalized].content,
385
+ source: 'built-in',
386
+ };
387
+ }
388
+
389
+ /**
390
+ * @returns {string[]} Sorted list of built-in template names.
391
+ */
392
+ export function getTemplateNames() {
393
+ return Object.keys(BUILT_IN_TEMPLATES).sort();
394
+ }
395
+
396
+ /**
397
+ * List all templates, including user-added ones in the library directory.
398
+ * @returns {Array<{ name: string, description: string, source: string }>}
399
+ */
400
+ export function listTemplates() {
401
+ const builtIn = Object.keys(BUILT_IN_TEMPLATES).sort().map((name) => ({
402
+ name,
403
+ description: BUILT_IN_TEMPLATES[name].description,
404
+ source: name === 'blank' ? 'built-in' : (readLibraryFile(name) != null ? 'library' : 'built-in'),
405
+ }));
406
+ return [...builtIn, ...discoverCustomTemplates()];
407
+ }
408
+
409
+ /**
410
+ * Print a friendly CLI listing of all available templates.
411
+ */
412
+ export function printTemplates() {
413
+ const all = listTemplates();
414
+ console.log(' Built-in templates:');
415
+ const nameWidth = Math.max(...all.map((t) => t.name.length), 8);
416
+ for (const t of all) {
417
+ const tag = t.source === 'library' ? ' (override)' : '';
418
+ console.log(` ${t.name.padEnd(nameWidth)} ${t.description}${tag}`);
419
+ }
420
+ console.log();
421
+ console.log(' Use: bizar plan new <slug> --template <name>');
422
+ console.log(' Built-in: ' + Object.keys(BUILT_IN_TEMPLATES).join(', '));
423
+ }
424
+
425
+ // ─── Variable substitution ───────────────────────────────────────────────────
426
+
427
+ /**
428
+ * Substitute {{key}} placeholders in template content. Mirrors the
429
+ * behavior of the existing plan.mjs replaceTemplate — kept here so
430
+ * the templates module is self-contained.
431
+ *
432
+ * @param {string} content
433
+ * @param {Record<string,string>} vars
434
+ */
435
+ export function substitute(content, vars) {
436
+ let out = content;
437
+ for (const [k, v] of Object.entries(vars)) {
438
+ out = out.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), v);
439
+ }
440
+ return out;
441
+ }
442
+
443
+ /**
444
+ * Build the standard variable set used to render a template.
445
+ *
446
+ * @param {{ slug: string, title?: string }} opts
447
+ */
448
+ export function buildVars({ slug, title }) {
449
+ const now = new Date().toISOString();
450
+ return {
451
+ title: title || slug,
452
+ slug,
453
+ author: process.env.USER || 'unknown',
454
+ created: now,
455
+ lastEdited: now,
456
+ };
457
+ }
458
+
459
+ // ─── CLI helpers (for `plan template save/list/delete`) ──────────────────────
460
+ //
461
+ // These are minimal — the spec for v2 calls for full user-saved templates
462
+ // in ~/.config/bizar/plan-templates/. v2.0 ships a stub for
463
+ // library-directory operations; the user-templates dir is deferred to
464
+ // a follow-up.
465
+
466
+ const USER_TEMPLATES_DIR = join(
467
+ process.env.HOME || process.env.USERPROFILE || '~',
468
+ '.config',
469
+ 'bizar',
470
+ 'plan-templates',
471
+ );
472
+
473
+ /**
474
+ * Save the content of an existing plan as a library template.
475
+ * If a name is given, the file is written to templates/plan/library/.
476
+ * The plan is read from plans/<planSlug>/plan.mdx.
477
+ *
478
+ * @param {string} name template name (becomes the filename)
479
+ * @param {string} planSlug source plan to read content from
480
+ * @returns {string} absolute path to the saved file
481
+ */
482
+ export function saveTemplate(name, planSlug) {
483
+ if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
484
+ throw new Error(`Invalid template name "${name}". Use lowercase letters, digits, and hyphens.`);
485
+ }
486
+ if (!planSlug || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(planSlug)) {
487
+ throw new Error(`Invalid plan slug "${planSlug}".`);
488
+ }
489
+ const sourcePath = join(PROJECT_ROOT, 'plans', planSlug, 'plan.mdx');
490
+ if (!existsSync(sourcePath)) {
491
+ throw new Error(`Plan "${planSlug}" not found at ${sourcePath}`);
492
+ }
493
+ const content = readFileSync(sourcePath, 'utf-8');
494
+ mkdirSync(LIBRARY_DIR, { recursive: true });
495
+ const target = join(LIBRARY_DIR, `${name}.mdx`);
496
+ writeFileSync(target, content, 'utf-8');
497
+ return target;
498
+ }
499
+
500
+ /**
501
+ * Delete a user-added template from the library directory.
502
+ * Refuses to delete built-in templates.
503
+ *
504
+ * @param {string} name
505
+ */
506
+ export function deleteTemplate(name) {
507
+ const normalized = String(name || '').toLowerCase();
508
+ if (BUILT_IN_TEMPLATES[normalized]) {
509
+ throw new Error(`"${name}" is a built-in template and cannot be deleted.`);
510
+ }
511
+ const path = join(LIBRARY_DIR, `${normalized}.mdx`);
512
+ if (!existsSync(path)) {
513
+ throw new Error(`Template "${name}" not found in library.`);
514
+ }
515
+ unlinkSync(path);
516
+ return path;
517
+ }
518
+
519
+ // ─── Constants export (for tests) ────────────────────────────────────────────
520
+
521
+ export const BUILT_IN_TEMPLATE_NAMES = Object.keys(BUILT_IN_TEMPLATES);
522
+ export const LIBRARY_DIR_PATH = LIBRARY_DIR;
523
+ export const USER_TEMPLATES_DIR_PATH = USER_TEMPLATES_DIR;