@vibedx/vibekit 0.9.0 → 0.11.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/README.md CHANGED
@@ -54,6 +54,22 @@ The skill teaches agents the ticket-driven workflow — they'll create focused t
54
54
 
55
55
  **Coordinating multiple agents?** See **[docs/agent-workflow.md](./docs/agent-workflow.md)** for a framework-agnostic pattern for running multi-agent teams on a shared repo — assignees, polling loops, escalation, and loop prevention.
56
56
 
57
+ ### 🧩 Claude Code Plugin
58
+
59
+ Prefer Claude Code? Install the vibekit plugin to get the full ticket-driven workflow, agents, and hooks built in:
60
+
61
+ ```
62
+ /plugin install vibekit
63
+ ```
64
+
65
+ The plugin bundles:
66
+
67
+ - **Skills** — `vibekit-workflow` (full create → start → implement → close flow) and `ticket-writer` (well-structured tickets with acceptance criteria)
68
+ - **Agents** — `ticket-worker` (reads a ticket, implements it, closes it) and `reviewer` (checks completed work against acceptance criteria)
69
+ - **Hooks** — `SessionStart` detects your `.vibe/` directory and surfaces open ticket counts
70
+
71
+ See **[vibekit-plugin/](./vibekit-plugin/)** for details. The CLI is optional but recommended (`npm install -g @vibedx/vibekit`).
72
+
57
73
  ## 🤔 Why VibeKit?
58
74
 
59
75
  - **🎯 Vibe code with manageable smaller tasks** - Break down complex features into focused tickets
@@ -139,6 +155,27 @@ vibe plan to-ticket .vibe/plans/my-feature.md --auto
139
155
  vibe plan to-ticket .vibe/plans/my-feature.md --dry-run
140
156
  ```
141
157
 
158
+ ### 📚 Docs
159
+ ```bash
160
+ # Create a doc from a template (types: guide, design-doc, code-doc, faq)
161
+ vibe docs add "Getting Started"
162
+ vibe docs add "Docs Architecture" --type design-doc
163
+
164
+ # Also open it in $EDITOR after creating
165
+ vibe docs add "API Reference" --type code-doc --edit
166
+
167
+ # List docs, with optional filters
168
+ vibe docs list
169
+ vibe docs list --type design-doc --status draft --tag api
170
+
171
+ # Print a doc to stdout
172
+ vibe docs show DOC-001
173
+
174
+ # AI refinement pass — expands sections and fills placeholders
175
+ vibe docs refine DOC-001
176
+ ```
177
+ Docs live in `.vibe/docs/` with their own `DOC-NNN` sequence (independent from tickets). Templates are scaffolded into `.vibe/.templates/docs/` on `vibe init`.
178
+
142
179
  ### 🔀 Pull Requests
143
180
  ```bash
144
181
  # Open a GitHub PR from the current ticket branch (title/body from the ticket)
package/assets/config.yml CHANGED
@@ -21,6 +21,16 @@ tickets:
21
21
  - high
22
22
  - critical
23
23
 
24
+ docs:
25
+ path: ".vibe/docs"
26
+ id_format: "DOC-{number}"
27
+ default_template: ".vibe/.templates/docs/default.md"
28
+ default_type: guide
29
+ status_options:
30
+ - draft
31
+ - review
32
+ - published
33
+
24
34
  team:
25
35
  path: .vibe/team.yml
26
36
 
@@ -0,0 +1,39 @@
1
+ ---
2
+ id: {id}
3
+ title: {title}
4
+ slug: {slug}
5
+ type: code-doc
6
+ status: draft
7
+ tags: []
8
+ author: ""
9
+ created_at: {date}
10
+ updated_at: {date}
11
+ ---
12
+
13
+ ## Summary
14
+
15
+ <!-- What does this module/component/service do, in one or two sentences? -->
16
+
17
+ ## Architecture
18
+
19
+ <!-- How it's structured: key files, modules, and how they fit together. -->
20
+
21
+ ## Public API
22
+
23
+ <!-- Functions, classes, or endpoints exposed. Include signatures and `code` examples. -->
24
+
25
+ ## Usage
26
+
27
+ <!-- How to use it, with examples. -->
28
+
29
+ ## Internals
30
+
31
+ <!-- Notable implementation details, invariants, and gotchas. -->
32
+
33
+ ## Testing
34
+
35
+ <!-- How this code is tested and how to run those tests. -->
36
+
37
+ ## AI Prompt
38
+
39
+ <!-- Instructions for `vibe docs refine` to document undocumented parts of the code. -->
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: {id}
3
+ title: {title}
4
+ slug: {slug}
5
+ type: guide
6
+ status: draft
7
+ tags: []
8
+ author: ""
9
+ created_at: {date}
10
+ updated_at: {date}
11
+ ---
12
+
13
+ ## Overview
14
+
15
+ <!-- What is this guide about? Who is it for and what will they be able to do after reading it? -->
16
+
17
+ ## Prerequisites
18
+
19
+ <!-- Anything the reader needs to know or have set up first. -->
20
+
21
+ ## Steps
22
+
23
+ <!-- Walk through the process step by step. Use code blocks for commands and file paths. -->
24
+
25
+ ## Examples
26
+
27
+ <!-- Concrete examples that show the guide in action. -->
28
+
29
+ ## References
30
+
31
+ <!-- Links to related docs, tickets, or external resources. -->
32
+
33
+ ## AI Prompt
34
+
35
+ <!-- Instructions for `vibe docs refine` to expand or improve this guide. -->
@@ -0,0 +1,43 @@
1
+ ---
2
+ id: {id}
3
+ title: {title}
4
+ slug: {slug}
5
+ type: design-doc
6
+ status: draft
7
+ tags: []
8
+ author: ""
9
+ created_at: {date}
10
+ updated_at: {date}
11
+ ---
12
+
13
+ ## Context & Problem
14
+
15
+ <!-- What problem are we solving? Why now? What's the current state? -->
16
+
17
+ ## Goals
18
+
19
+ <!-- What must this design achieve? -->
20
+
21
+ ## Non-Goals
22
+
23
+ <!-- Explicitly out of scope. -->
24
+
25
+ ## Proposed Design
26
+
27
+ <!-- The core of the doc: architecture, data flow, key decisions. -->
28
+
29
+ ## Alternatives Considered
30
+
31
+ <!-- Other approaches and why they were not chosen. -->
32
+
33
+ ## Risks & Trade-offs
34
+
35
+ <!-- What could go wrong, and what are we trading away? -->
36
+
37
+ ## Rollout Plan
38
+
39
+ <!-- How this ships: phases, migrations, feature flags. -->
40
+
41
+ ## AI Prompt
42
+
43
+ <!-- Instructions for `vibe docs refine` to pressure-test or expand this design. -->
@@ -0,0 +1,33 @@
1
+ ---
2
+ id: {id}
3
+ title: {title}
4
+ slug: {slug}
5
+ type: faq
6
+ status: draft
7
+ tags: []
8
+ author: ""
9
+ created_at: {date}
10
+ updated_at: {date}
11
+ ---
12
+
13
+ ## Overview
14
+
15
+ <!-- What topic does this FAQ cover? -->
16
+
17
+ ## Questions
18
+
19
+ ### Q: <question here>
20
+
21
+ <!-- Answer. -->
22
+
23
+ ### Q: <question here>
24
+
25
+ <!-- Answer. -->
26
+
27
+ ## Still stuck?
28
+
29
+ <!-- Where to go for more help: docs, channels, contacts. -->
30
+
31
+ ## AI Prompt
32
+
33
+ <!-- Instructions for `vibe docs refine` to add or improve answers. -->
package/index.js CHANGED
@@ -24,7 +24,7 @@ const __dirname = dirname(__filename);
24
24
  const AVAILABLE_COMMANDS = [
25
25
  'init', 'new', 'close', 'list', 'get-started',
26
26
  'start', 'link', 'unlink', 'refine', 'lint', 'review', 'team', 'skills',
27
- 'status', 'stats', 'plan', 'pr', 'swarm'
27
+ 'status', 'stats', 'plan', 'pr', 'swarm', 'docs'
28
28
  ];
29
29
 
30
30
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedx/vibekit",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "A powerful CLI tool for managing development tickets and project workflows",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -8,7 +8,8 @@
8
8
  "index.js",
9
9
  "src/",
10
10
  "assets/",
11
- "skills/"
11
+ "skills/",
12
+ "vibekit-plugin/"
12
13
  ],
13
14
  "bin": {
14
15
  "vibe": "index.js"
@@ -0,0 +1,162 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname } from 'path';
6
+ import { getProjectRoot, getConfig, createSlug } from '../../utils/index.js';
7
+ import { getDocsDir, getNextDocId } from '../../utils/doc.js';
8
+ import { logger } from '../../utils/cli.js';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ const DEFAULT_TYPE = 'guide';
14
+
15
+ // Doc type -> template filename (bundled and project-local share names, guide -> default)
16
+ const TYPE_TEMPLATES = {
17
+ guide: 'default.md',
18
+ 'design-doc': 'design-doc.md',
19
+ 'code-doc': 'code-doc.md',
20
+ faq: 'faq.md'
21
+ };
22
+
23
+ /**
24
+ * Parse `vibe docs add` arguments.
25
+ * @param {string[]} args
26
+ * @returns {{title: string, type: string, edit: boolean}}
27
+ */
28
+ function parseArguments(args) {
29
+ const titleParts = [];
30
+ let type = null;
31
+ let edit = false;
32
+
33
+ for (let i = 0; i < args.length; i++) {
34
+ const arg = args[i];
35
+ if (arg === '--type' && i + 1 < args.length) {
36
+ type = args[i + 1];
37
+ i++;
38
+ } else if (arg === '--edit' || arg === '-e') {
39
+ edit = true;
40
+ } else if (!arg.startsWith('--')) {
41
+ titleParts.push(arg);
42
+ }
43
+ }
44
+
45
+ const title = titleParts.join(' ').trim();
46
+ if (!title) {
47
+ throw new Error('Please provide a title. Usage: vibe docs add "title" [--type <type>]');
48
+ }
49
+
50
+ return { title, type, edit };
51
+ }
52
+
53
+ /**
54
+ * Resolve the type, falling back to the default if unrecognised.
55
+ * @param {string|null} type
56
+ * @param {Object} config
57
+ * @returns {string}
58
+ */
59
+ function resolveType(type, config) {
60
+ const defaultType = config.docs?.default_type || DEFAULT_TYPE;
61
+ if (!type) {
62
+ return defaultType;
63
+ }
64
+ if (!TYPE_TEMPLATES[type]) {
65
+ logger.warning(`Unknown doc type '${type}'. Falling back to '${defaultType}'.`);
66
+ return defaultType;
67
+ }
68
+ return type;
69
+ }
70
+
71
+ /**
72
+ * Load the template for a type: project-local first, then bundled asset.
73
+ * @param {string} type
74
+ * @param {string} root
75
+ * @returns {string}
76
+ */
77
+ function loadTemplate(type, root) {
78
+ const templateFile = TYPE_TEMPLATES[type] || TYPE_TEMPLATES[DEFAULT_TYPE];
79
+ const projectTemplate = path.join(root, '.vibe', '.templates', 'docs', templateFile);
80
+ const bundledTemplate = path.join(__dirname, '../../../assets', 'docs', templateFile);
81
+
82
+ if (fs.existsSync(projectTemplate)) {
83
+ return fs.readFileSync(projectTemplate, 'utf-8');
84
+ }
85
+ if (fs.existsSync(bundledTemplate)) {
86
+ return fs.readFileSync(bundledTemplate, 'utf-8');
87
+ }
88
+ throw new Error(`No template found for type '${type}'. Run "vibe init" to scaffold doc templates.`);
89
+ }
90
+
91
+ /**
92
+ * Fill template placeholders.
93
+ * @param {string} template
94
+ * @param {{id: string, title: string, slug: string, type: string, timestamp: string}} data
95
+ * @returns {string}
96
+ */
97
+ function fillTemplate(template, { id, title, slug, type, timestamp }) {
98
+ return template
99
+ .replace(/{id}/g, id)
100
+ .replace(/{title}/g, title)
101
+ .replace(/{slug}/g, slug)
102
+ .replace(/{date}/g, timestamp)
103
+ .replace(/^type: .*$/m, `type: ${type}`);
104
+ }
105
+
106
+ /**
107
+ * Open a file in $EDITOR.
108
+ * @param {string} filePath
109
+ */
110
+ function openInEditor(filePath) {
111
+ const editor = process.env.EDITOR || process.env.VISUAL;
112
+ if (!editor) {
113
+ logger.warning('No $EDITOR set — skipping auto-open.');
114
+ return;
115
+ }
116
+ const child = spawn(editor, [filePath], { stdio: 'inherit' });
117
+ child.on('error', (err) => {
118
+ logger.warning(`Could not open editor: ${err.message}`);
119
+ });
120
+ }
121
+
122
+ /**
123
+ * `vibe docs add` — create a new doc from a template.
124
+ * @param {string[]} args
125
+ */
126
+ async function addDoc(args) {
127
+ try {
128
+ const { title, type: rawType, edit } = parseArguments(args);
129
+ const root = getProjectRoot();
130
+ const config = getConfig();
131
+
132
+ const type = resolveType(rawType, config);
133
+ const template = loadTemplate(type, root);
134
+
135
+ const id = getNextDocId();
136
+ const slug = createSlug(title, config);
137
+ const filename = `${id}-${slug}.md`;
138
+ const timestamp = new Date().toISOString().split('T')[0];
139
+
140
+ const docsDir = getDocsDir();
141
+ if (!fs.existsSync(docsDir)) {
142
+ fs.mkdirSync(docsDir, { recursive: true });
143
+ }
144
+
145
+ const outputPath = path.join(docsDir, filename);
146
+ const content = fillTemplate(template, { id, title, slug, type, timestamp });
147
+
148
+ fs.writeFileSync(outputPath, content, 'utf-8');
149
+
150
+ const relativePath = path.relative(process.cwd(), outputPath);
151
+ logger.success(`Created ${id} (${type}): ${relativePath}`);
152
+
153
+ if (edit) {
154
+ openInEditor(outputPath);
155
+ }
156
+ } catch (error) {
157
+ logger.error(error.message);
158
+ process.exit(1);
159
+ }
160
+ }
161
+
162
+ export default addDoc;
@@ -0,0 +1,47 @@
1
+ import { logger } from '../../utils/cli.js';
2
+ import addDoc from './add.js';
3
+ import listDocs from './list.js';
4
+ import refineDoc from './refine.js';
5
+ import showDoc from './show.js';
6
+
7
+ const SUBCOMMANDS = {
8
+ add: addDoc,
9
+ list: listDocs,
10
+ refine: refineDoc,
11
+ show: showDoc
12
+ };
13
+
14
+ function showHelp() {
15
+ console.log(`\n📚 vibe docs — project documentation\n`);
16
+ console.log('Usage: vibe docs <subcommand> [options]\n');
17
+ console.log('Subcommands:');
18
+ console.log(' add "title" [--type guide|design-doc|code-doc|faq] [--edit] Create a new doc');
19
+ console.log(' list [--type <type>] [--status <status>] [--tag <tag>] List docs');
20
+ console.log(' show <id> Print a doc to stdout');
21
+ console.log(' refine <id> AI refinement pass\n');
22
+ }
23
+
24
+ /**
25
+ * `vibe docs` entry point — routes to subcommands.
26
+ * @param {string[]} args - Command arguments
27
+ */
28
+ async function docsCommand(args = []) {
29
+ const [subcommand, ...rest] = args;
30
+
31
+ if (!subcommand || subcommand === '--help' || subcommand === '-h') {
32
+ showHelp();
33
+ return;
34
+ }
35
+
36
+ const handler = SUBCOMMANDS[subcommand];
37
+
38
+ if (!handler) {
39
+ logger.error(`Unknown docs subcommand: ${subcommand}`);
40
+ showHelp();
41
+ process.exit(1);
42
+ }
43
+
44
+ await handler(rest);
45
+ }
46
+
47
+ export default docsCommand;
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import yaml from 'js-yaml';
5
+ import {
6
+ createTempDir,
7
+ cleanupTempDir,
8
+ mockConsole,
9
+ mockProcessCwd,
10
+ mockProcessExit,
11
+ createMockVibeProject
12
+ } from '../../utils/test-helpers.js';
13
+ import addDoc from './add.js';
14
+ import listDocs from './list.js';
15
+ import showDoc from './show.js';
16
+
17
+ function readFrontmatter(filePath) {
18
+ const content = fs.readFileSync(filePath, 'utf-8');
19
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
20
+ return yaml.load(match[1]);
21
+ }
22
+
23
+ describe('docs command', () => {
24
+ let tempDir;
25
+ let consoleMock;
26
+ let restoreCwd;
27
+ let exitMock;
28
+
29
+ beforeEach(() => {
30
+ tempDir = createTempDir('docs-test');
31
+ consoleMock = mockConsole();
32
+ restoreCwd = mockProcessCwd(tempDir);
33
+ exitMock = mockProcessExit();
34
+ createMockVibeProject(tempDir);
35
+ });
36
+
37
+ afterEach(() => {
38
+ consoleMock.restore();
39
+ restoreCwd();
40
+ exitMock.restore();
41
+ cleanupTempDir(tempDir);
42
+ });
43
+
44
+ describe('add', () => {
45
+ it('creates a DOC-001 file with correct frontmatter', async () => {
46
+ await addDoc(['My First Guide']);
47
+
48
+ const docsDir = path.join(tempDir, '.vibe', 'docs');
49
+ const files = fs.readdirSync(docsDir);
50
+ expect(files).toHaveLength(1);
51
+ expect(files[0]).toMatch(/^DOC-001-my-first-guide\.md$/);
52
+
53
+ const fm = readFrontmatter(path.join(docsDir, files[0]));
54
+ expect(fm.id).toBe('DOC-001');
55
+ expect(fm.title).toBe('My First Guide');
56
+ expect(fm.type).toBe('guide');
57
+ expect(fm.status).toBe('draft');
58
+ });
59
+
60
+ it('assigns sequential IDs', async () => {
61
+ await addDoc(['First']);
62
+ await addDoc(['Second']);
63
+
64
+ const docsDir = path.join(tempDir, '.vibe', 'docs');
65
+ const ids = fs.readdirSync(docsDir).map(f => readFrontmatter(path.join(docsDir, f)).id).sort();
66
+ expect(ids).toEqual(['DOC-001', 'DOC-002']);
67
+ });
68
+
69
+ it('selects the template for --type', async () => {
70
+ await addDoc(['A Design', '--type', 'design-doc']);
71
+
72
+ const docsDir = path.join(tempDir, '.vibe', 'docs');
73
+ const file = fs.readdirSync(docsDir)[0];
74
+ const content = fs.readFileSync(path.join(docsDir, file), 'utf-8');
75
+ expect(readFrontmatter(path.join(docsDir, file)).type).toBe('design-doc');
76
+ expect(content).toContain('## Proposed Design');
77
+ });
78
+
79
+ it('falls back to guide for an unknown type', async () => {
80
+ await addDoc(['Weird', '--type', 'bogus']);
81
+
82
+ const docsDir = path.join(tempDir, '.vibe', 'docs');
83
+ const file = fs.readdirSync(docsDir)[0];
84
+ expect(readFrontmatter(path.join(docsDir, file)).type).toBe('guide');
85
+ });
86
+
87
+ it('exits with an error when no title is given', async () => {
88
+ await expect(addDoc([])).rejects.toThrow(/process.exit/);
89
+ expect(exitMock.exitCalls).toContain(1);
90
+ });
91
+ });
92
+
93
+ describe('list', () => {
94
+ beforeEach(async () => {
95
+ await addDoc(['Alpha Guide']);
96
+ await addDoc(['Beta Design', '--type', 'design-doc']);
97
+ });
98
+
99
+ it('lists all docs', () => {
100
+ listDocs([]);
101
+ const output = consoleMock.logs.log.join('\n');
102
+ expect(output).toContain('DOC-001');
103
+ expect(output).toContain('DOC-002');
104
+ expect(output).toContain('Found 2 doc(s)');
105
+ });
106
+
107
+ it('filters by type', () => {
108
+ listDocs(['--type', 'design-doc']);
109
+ const output = consoleMock.logs.log.join('\n');
110
+ expect(output).toContain('Beta Design');
111
+ expect(output).toContain('Found 1 doc(s) (type: design-doc)');
112
+ expect(output).not.toContain('Alpha Guide');
113
+ });
114
+ });
115
+
116
+ describe('show', () => {
117
+ it('prints a doc by id', async () => {
118
+ await addDoc(['Show Me']);
119
+ showDoc(['1']);
120
+ const output = consoleMock.logs.log.join('\n');
121
+ expect(output).toContain('id: DOC-001');
122
+ expect(output).toContain('title: Show Me');
123
+ });
124
+
125
+ it('errors on a missing doc', () => {
126
+ expect(() => showDoc(['999'])).toThrow(/process.exit/);
127
+ expect(exitMock.exitCalls).toContain(1);
128
+ });
129
+ });
130
+ });