dude-claude-plugin 2026.2.14 → 2026.2.19

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dude",
3
- "version": "2026.2.14",
3
+ "version": "2026.2.19",
4
4
  "description": "Ultra-minimal RAG and cross-project memory for Claude CLI",
5
5
  "author": {
6
6
  "name": "Fingerskier"
package/README.md CHANGED
@@ -20,7 +20,7 @@ claude plugin install dude-claude-plugin@fingerskier-plugins
20
20
 
21
21
  ### MCP server only (via npx)
22
22
 
23
- If you just want the 6 MCP tools without auto-hooks:
23
+ If you just want the 7 MCP tools without auto-hooks:
24
24
 
25
25
  ```bash
26
26
  claude mcp add dude -- npx dude-claude-plugin mcp
@@ -37,12 +37,13 @@ claude mcp add dude -- dude-claude mcp
37
37
 
38
38
  | Component | Description |
39
39
  |-----------|-------------|
40
- | **MCP server** | 6 tools: `search`, `upsert_record`, `get_record`, `list_records`, `delete_record`, `list_projects` |
40
+ | **MCP server** | 7 tools: `search`, `upsert_record`, `get_record`, `list_records`, `delete_record`, `list_projects`, `sync_status` |
41
41
  | **Auto-retrieve hook** | On each prompt, searches memory for relevant context and injects it |
42
42
  | **Auto-persist hook** | After each response, classifies the work and saves issues/specs |
43
43
  | **Web UI** | Local dashboard at `http://127.0.0.1:3456` for manual CRUD |
44
- | **Storage** | SQLite + sqlite-vec at `~/.dude-claude/dude.db` |
44
+ | **Storage** | libsql (SQLite-compatible) at `~/.dude-claude/dude-libsql.db` |
45
45
  | **Embeddings** | Local all-MiniLM-L6-v2 via @huggingface/transformers (no API keys) |
46
+ | **Cloud sync** | Optional Turso cloud sync via environment variables |
46
47
 
47
48
  ## How it works
48
49
 
@@ -62,7 +63,17 @@ npx dude-claude-plugin serve
62
63
  dude-claude serve
63
64
  ```
64
65
 
65
- Opens a local dashboard on port 3456 for browsing and editing projects, issues, and specifications.
66
+ Opens a local dashboard at `http://127.0.0.1:3456` (auto-opens in your browser). The UI has two panels: a sidebar listing records with filters, and a main panel for viewing and editing.
67
+
68
+ **What you can do:**
69
+
70
+ - **Project selector** — filter records by project or view all projects at once
71
+ - **Semantic search** — type in the search bar to find records by meaning, not just keywords (results ranked by similarity)
72
+ - **Filter by kind** — narrow to issues, specs, arch decisions, updates, or tests
73
+ - **Filter by status** — open, resolved, archived, active, or inactive
74
+ - **Create / edit / delete** — click "+ New" to add a record, click any record to edit it, or delete from the detail view
75
+
76
+ Set `DUDE_PORT` to change the default port (see [Configuration](#configuration)).
66
77
 
67
78
  ## Configuration
68
79
 
@@ -70,6 +81,19 @@ Opens a local dashboard on port 3456 for browsing and editing projects, issues,
70
81
  |---|---|---|
71
82
  | `DUDE_PORT` | `3456` | Web UI port |
72
83
  | `DUDE_CONTEXT_LIMIT` | `5` | Max records injected per prompt |
84
+ | `DUDE_RECENCY_HOURS` | `1` | Lookback window for recent records |
85
+
86
+ ### Cloud Sync (optional)
87
+
88
+ The plugin works fully offline by default. To enable cloud sync with [Turso](https://turso.tech), set these environment variables:
89
+
90
+ | Env variable | Description |
91
+ |---|---|
92
+ | `DUDE_TURSO_URL` | Turso database URL (e.g. `libsql://your-db.turso.io`) |
93
+ | `DUDE_TURSO_TOKEN` | Turso auth token |
94
+ | `DUDE_SYNC_INTERVAL` | Sync interval in ms (default: `60000`) |
95
+
96
+ When configured, `@libsql/client` maintains a local embedded replica that auto-syncs with Turso. You can also trigger a manual sync via the `sync_status` MCP tool or `POST /api/sync`.
73
97
 
74
98
  ## Requirements
75
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dude-claude-plugin",
3
- "version": "2026.2.14",
3
+ "version": "2026.2.19",
4
4
  "description": "Ultra-minimal RAG and cross-project memory for Claude CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Export the local libsql database as SQL INSERT statements.
4
+ *
5
+ * Usage:
6
+ * node scripts/dump-local.js > dump.sql
7
+ * turso db shell <dbname> < dump.sql
8
+ *
9
+ * Reads from ~/.dude-claude/dude-libsql.db (or DUDE_DB_PATH env override).
10
+ * Outputs projects first, then records (with vector embeddings), to stdout.
11
+ */
12
+
13
+ import { createClient } from '@libsql/client';
14
+ import { join } from 'node:path';
15
+ import { homedir } from 'node:os';
16
+
17
+ const DATA_DIR = join(homedir(), '.dude-claude');
18
+ const DB_PATH = process.env.DUDE_DB_PATH || join(DATA_DIR, 'dude-libsql.db');
19
+
20
+ function esc(str) {
21
+ if (str == null) return 'NULL';
22
+ return "'" + String(str).replace(/'/g, "''") + "'";
23
+ }
24
+
25
+ function parseEmbedding(blob) {
26
+ if (!blob) return null;
27
+ if (blob instanceof Float32Array) return blob;
28
+ if (blob instanceof ArrayBuffer) return new Float32Array(blob);
29
+ if (ArrayBuffer.isView(blob)) {
30
+ return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
31
+ }
32
+ if (typeof blob === 'string') return new Float32Array(JSON.parse(blob));
33
+ return null;
34
+ }
35
+
36
+ async function main() {
37
+ const db = createClient({ url: `file:${DB_PATH}` });
38
+
39
+ // -- Projects --
40
+ const projects = await db.execute('SELECT * FROM project ORDER BY id');
41
+ for (const p of projects.rows) {
42
+ console.log(
43
+ `INSERT INTO project (id, name, created_at, updated_at) VALUES (${p.id}, ${esc(p.name)}, ${esc(p.created_at)}, ${esc(p.updated_at)});`
44
+ );
45
+ }
46
+
47
+ // -- Records (with embeddings) --
48
+ const records = await db.execute('SELECT * FROM record ORDER BY id');
49
+ for (const r of records.rows) {
50
+ const emb = parseEmbedding(r.embedding);
51
+ const embExpr = emb
52
+ ? `vector('${JSON.stringify(Array.from(emb))}')`
53
+ : 'NULL';
54
+
55
+ console.log(
56
+ `INSERT INTO record (id, project_id, kind, title, body, status, embedding, created_at, updated_at) VALUES (${r.id}, ${r.project_id}, ${esc(r.kind)}, ${esc(r.title)}, ${esc(r.body)}, ${esc(r.status)}, ${embExpr}, ${esc(r.created_at)}, ${esc(r.updated_at)});`
57
+ );
58
+ }
59
+
60
+ db.close();
61
+ console.error(`[dump] Exported ${projects.rows.length} projects, ${records.rows.length} records`);
62
+ }
63
+
64
+ main().catch(err => {
65
+ console.error('dump-local failed:', err);
66
+ process.exit(1);
67
+ });
package/src/db-adapter.js CHANGED
@@ -77,6 +77,22 @@ export class DbAdapter {
77
77
  throw new Error('Not implemented');
78
78
  }
79
79
 
80
+ /**
81
+ * Get cloud sync status.
82
+ * @returns {Promise<{ enabled: boolean, syncUrl?: string, syncInterval?: number }>}
83
+ */
84
+ async syncStatus() {
85
+ return { enabled: false };
86
+ }
87
+
88
+ /**
89
+ * Trigger a manual cloud sync (no-op if sync is not configured).
90
+ * @returns {Promise<{ synced: boolean, message: string }>}
91
+ */
92
+ async sync() {
93
+ return { synced: false, message: 'Cloud sync not configured' };
94
+ }
95
+
80
96
  /**
81
97
  * Close the database connection.
82
98
  */
package/src/db-libsql.js CHANGED
@@ -21,6 +21,7 @@ export class LibsqlAdapter extends DbAdapter {
21
21
  this.config = config;
22
22
  this.db = null;
23
23
  this.currentProject = null;
24
+ this._syncError = null;
24
25
  }
25
26
 
26
27
  // ---------------------------------------------------------------------------
@@ -30,12 +31,33 @@ export class LibsqlAdapter extends DbAdapter {
30
31
  async init() {
31
32
  if (this.db) return;
32
33
  this._ensureDataDir();
33
- this.db = this._createClient();
34
+ this._syncError = null;
35
+
36
+ if (this._hasSyncConfig()) {
37
+ try {
38
+ this.db = this._createClient(); // tries sync-enabled client
39
+ } catch (err) {
40
+ console.error(`[dude] Cloud sync connection failed: ${err.message}`);
41
+ console.error('[dude] Falling back to local-only mode.');
42
+ this._syncError = err.message;
43
+ this.db = this._createLocalOnlyClient();
44
+ }
45
+ } else {
46
+ this.db = this._createLocalOnlyClient();
47
+ }
48
+
34
49
  await this._runSchema();
35
50
  const projectName = this._detectProject();
36
51
  this.currentProject = await this._upsertProject(projectName);
37
52
  await this._migrateProjectNames(projectName);
38
- console.error(`[dude] LibSQL DB ready — project "${this.currentProject.name}" (id=${this.currentProject.id})`);
53
+
54
+ const syncInfo = await this.syncStatus();
55
+ const syncMsg = syncInfo.enabled
56
+ ? ` | cloud sync → ${syncInfo.syncUrl}`
57
+ : syncInfo.error
58
+ ? ' | cloud sync FAILED — local-only mode'
59
+ : '';
60
+ console.error(`[dude] LibSQL DB ready — project "${this.currentProject.name}" (id=${this.currentProject.id})${syncMsg}`);
39
61
  }
40
62
 
41
63
  _ensureDataDir() {
@@ -44,6 +66,15 @@ export class LibsqlAdapter extends DbAdapter {
44
66
  }
45
67
  }
46
68
 
69
+ _hasSyncConfig() {
70
+ return !!(this.config.syncUrl || process.env.DUDE_TURSO_URL);
71
+ }
72
+
73
+ _createLocalOnlyClient() {
74
+ const url = this.config.url || `file:${this.config.dbPath || DB_PATH}`;
75
+ return createClient({ url });
76
+ }
77
+
47
78
  _createClient() {
48
79
  const url = this.config.url
49
80
  || `file:${this.config.dbPath || DB_PATH}`;
@@ -53,8 +84,8 @@ export class LibsqlAdapter extends DbAdapter {
53
84
  if (this.config.syncUrl || process.env.DUDE_TURSO_URL) {
54
85
  opts.syncUrl = this.config.syncUrl || process.env.DUDE_TURSO_URL;
55
86
  opts.authToken = this.config.authToken || process.env.DUDE_TURSO_TOKEN;
56
- const interval = this.config.syncInterval || process.env.DUDE_SYNC_INTERVAL;
57
- if (interval) opts.syncInterval = parseInt(interval);
87
+ const interval = this.config.syncInterval || process.env.DUDE_SYNC_INTERVAL || 60000;
88
+ opts.syncInterval = parseInt(interval);
58
89
  }
59
90
 
60
91
  return createClient(opts);
@@ -74,10 +105,10 @@ export class LibsqlAdapter extends DbAdapter {
74
105
  sql: `CREATE TABLE IF NOT EXISTS record (
75
106
  id INTEGER PRIMARY KEY AUTOINCREMENT,
76
107
  project_id INTEGER NOT NULL REFERENCES project(id) ON DELETE CASCADE,
77
- kind TEXT NOT NULL CHECK (kind IN ('issue','spec','arch','update')),
108
+ kind TEXT NOT NULL,
78
109
  title TEXT NOT NULL,
79
110
  body TEXT NOT NULL DEFAULT '',
80
- status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','resolved','archived')),
111
+ status TEXT NOT NULL DEFAULT 'open',
81
112
  embedding F32_BLOB(384),
82
113
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
83
114
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
@@ -92,6 +123,50 @@ export class LibsqlAdapter extends DbAdapter {
92
123
  ON record(libsql_vector_idx(embedding, 'metric=cosine'))`,
93
124
  },
94
125
  ], 'write');
126
+
127
+ // Migrate existing databases that still have CHECK constraints
128
+ await this._migrateDropChecks();
129
+ }
130
+
131
+ /**
132
+ * Detect whether the record table was created with CHECK constraints
133
+ * (from a previous schema version) and recreate it without them.
134
+ * This is needed because SQLite/libsql doesn't support ALTER TABLE
135
+ * to drop constraints — the table must be recreated.
136
+ */
137
+ async _migrateDropChecks() {
138
+ const result = await this.db.execute(
139
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name='record'",
140
+ );
141
+ const ddl = result.rows[0]?.sql || '';
142
+ if (!ddl.includes('CHECK')) return; // already migrated or fresh DB
143
+
144
+ await this.db.executeMultiple(`
145
+ CREATE TABLE record_new (
146
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
147
+ project_id INTEGER NOT NULL REFERENCES project(id) ON DELETE CASCADE,
148
+ kind TEXT NOT NULL,
149
+ title TEXT NOT NULL,
150
+ body TEXT NOT NULL DEFAULT '',
151
+ status TEXT NOT NULL DEFAULT 'open',
152
+ embedding F32_BLOB(384),
153
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
154
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
155
+ );
156
+
157
+ INSERT INTO record_new SELECT * FROM record;
158
+
159
+ DROP TABLE record;
160
+
161
+ ALTER TABLE record_new RENAME TO record;
162
+
163
+ CREATE INDEX IF NOT EXISTS idx_record_project_kind
164
+ ON record(project_id, kind);
165
+
166
+ CREATE INDEX IF NOT EXISTS idx_record_embedding
167
+ ON record(libsql_vector_idx(embedding, 'metric=cosine'));
168
+ `);
169
+ console.error('[dude] Migrated record table: removed CHECK constraints');
95
170
  }
96
171
 
97
172
  _detectProject() {
@@ -369,6 +444,48 @@ export class LibsqlAdapter extends DbAdapter {
369
444
  return this.get(Number(result.lastInsertRowid));
370
445
  }
371
446
 
447
+ async syncStatus() {
448
+ const syncUrl = this.config.syncUrl || process.env.DUDE_TURSO_URL || null;
449
+ const syncInterval = this.config.syncInterval
450
+ || process.env.DUDE_SYNC_INTERVAL
451
+ || (syncUrl ? 60000 : null);
452
+ return {
453
+ enabled: !!syncUrl && !this._syncError,
454
+ ...(syncUrl ? { syncUrl } : {}),
455
+ ...(syncInterval ? { syncInterval: parseInt(syncInterval) } : {}),
456
+ ...(this._syncError ? { error: this._syncError, degraded: true } : {}),
457
+ };
458
+ }
459
+
460
+ async sync() {
461
+ if (!this._hasSyncConfig()) {
462
+ return { synced: false, message: 'Cloud sync not configured. Set DUDE_TURSO_URL and DUDE_TURSO_TOKEN to enable.' };
463
+ }
464
+
465
+ // If degraded, attempt to reconnect with sync-enabled client
466
+ if (this._syncError) {
467
+ try {
468
+ const newClient = this._createClient();
469
+ // Run a test query to verify the connection works
470
+ await newClient.execute('SELECT 1');
471
+ this.db.close();
472
+ this.db = newClient;
473
+ this._syncError = null;
474
+ console.error('[dude] Cloud sync reconnected successfully.');
475
+ return { synced: true, message: `Reconnected and synced with ${this.config.syncUrl || process.env.DUDE_TURSO_URL}` };
476
+ } catch (err) {
477
+ return { synced: false, message: `Reconnection failed: ${err.message}` };
478
+ }
479
+ }
480
+
481
+ try {
482
+ await this.db.sync();
483
+ return { synced: true, message: `Synced with ${this.config.syncUrl || process.env.DUDE_TURSO_URL}` };
484
+ } catch (err) {
485
+ return { synced: false, message: `Sync failed: ${err.message}` };
486
+ }
487
+ }
488
+
372
489
  async close() {
373
490
  if (this.db) {
374
491
  this.db.close();
@@ -0,0 +1,28 @@
1
+ export const version = 3;
2
+
3
+ export function up(db) {
4
+ // Remove CHECK constraints from the record table.
5
+ // Constraints are now enforced at the business logic layer (Zod validation).
6
+ // This also enables new kinds (e.g. 'test') and statuses (e.g. 'active', 'inactive')
7
+ // without requiring further schema migrations.
8
+ db.exec(`
9
+ CREATE TABLE record_new (
10
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
11
+ project_id INTEGER NOT NULL REFERENCES project(id) ON DELETE CASCADE,
12
+ kind TEXT NOT NULL,
13
+ title TEXT NOT NULL,
14
+ body TEXT NOT NULL DEFAULT '',
15
+ status TEXT NOT NULL DEFAULT 'open',
16
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
17
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
18
+ );
19
+
20
+ INSERT INTO record_new SELECT * FROM record;
21
+
22
+ DROP TABLE record;
23
+
24
+ ALTER TABLE record_new RENAME TO record;
25
+
26
+ CREATE INDEX idx_record_project_kind ON record(project_id, kind);
27
+ `);
28
+ }
package/src/server.js CHANGED
@@ -15,10 +15,10 @@ export async function startServer() {
15
15
  // ---- search ----
16
16
  server.tool(
17
17
  'search',
18
- 'Semantic search across records (issues, specs, arch decisions & updates). Returns cross-project results ranked by similarity.',
18
+ 'Semantic search across records (issues, specs, arch decisions, updates & tests). Returns cross-project results ranked by similarity.',
19
19
  {
20
20
  query: z.string().describe('Natural language search query'),
21
- kind: z.enum(['issue', 'spec', 'arch', 'update', 'all']).optional().describe('Filter by record kind'),
21
+ kind: z.enum(['issue', 'spec', 'arch', 'update', 'test', 'all']).optional().describe('Filter by record kind'),
22
22
  project: z.string().optional().describe('Project name to boost; "*" for equal weight'),
23
23
  limit: z.number().int().positive().optional().describe('Max results (default 5)'),
24
24
  },
@@ -42,10 +42,10 @@ export async function startServer() {
42
42
  'Create or update a record. If id is provided, updates that record. Otherwise inserts with dedup.',
43
43
  {
44
44
  id: z.number().int().optional().describe('Record ID to update (omit for new)'),
45
- kind: z.enum(['issue', 'spec', 'arch', 'update']).describe('Record kind: issue (bug), spec (plan), arch (architecture decision), update (feature change)'),
45
+ kind: z.enum(['issue', 'spec', 'arch', 'update', 'test']).describe('Record kind: issue (bug), spec (plan), arch (architecture decision), update (feature change), test (verification procedure)'),
46
46
  title: z.string().describe('Short summary'),
47
47
  body: z.string().optional().describe('Full description'),
48
- status: z.enum(['open', 'resolved', 'archived']).optional().describe('Defaults to open'),
48
+ status: z.enum(['open', 'resolved', 'archived', 'active', 'inactive']).optional().describe('Defaults to open. For tests: active = should be performed, inactive = disabled'),
49
49
  },
50
50
  async ({ id, kind, title, body, status }) => {
51
51
  try {
@@ -96,8 +96,8 @@ export async function startServer() {
96
96
  'list_records',
97
97
  'List records with optional filters.',
98
98
  {
99
- kind: z.enum(['issue', 'spec', 'arch', 'update', 'all']).optional().describe('Filter by kind'),
100
- status: z.enum(['open', 'resolved', 'archived', 'all']).optional().describe('Filter by status'),
99
+ kind: z.enum(['issue', 'spec', 'arch', 'update', 'test', 'all']).optional().describe('Filter by kind'),
100
+ status: z.enum(['open', 'resolved', 'archived', 'active', 'inactive', 'all']).optional().describe('Filter by status'),
101
101
  project: z.string().optional().describe('Project name, or "*" for all'),
102
102
  },
103
103
  async ({ kind, status, project }) => {
@@ -151,6 +151,31 @@ export async function startServer() {
151
151
  },
152
152
  );
153
153
 
154
+ // ---- sync_status ----
155
+ server.tool(
156
+ 'sync_status',
157
+ 'Check cloud sync status and optionally trigger a manual sync.',
158
+ {
159
+ trigger_sync: z.boolean().optional().describe('If true, trigger an immediate sync'),
160
+ },
161
+ async ({ trigger_sync }) => {
162
+ try {
163
+ const status = await db.syncStatus();
164
+ let result = { ...status };
165
+ if (trigger_sync && status.enabled) {
166
+ const syncResult = await db.sync();
167
+ result.sync = syncResult;
168
+ }
169
+ return {
170
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
171
+ };
172
+ } catch (err) {
173
+ console.error('[dude] sync_status failed:', err);
174
+ return { content: [{ type: 'text', text: `Error in sync_status: ${err.message}` }], isError: true };
175
+ }
176
+ },
177
+ );
178
+
154
179
  // Start transport
155
180
  const transport = new StdioServerTransport();
156
181
  await server.connect(transport);
package/src/web.js CHANGED
@@ -61,6 +61,16 @@ async function handleRequest(db, req, res) {
61
61
 
62
62
  // --- API routes ---
63
63
 
64
+ // GET /api/sync-status
65
+ if (method === 'GET' && path === '/api/sync-status') {
66
+ return json(res, await db.syncStatus());
67
+ }
68
+
69
+ // POST /api/sync
70
+ if (method === 'POST' && path === '/api/sync') {
71
+ return json(res, await db.sync());
72
+ }
73
+
64
74
  // GET /api/projects
65
75
  if (method === 'GET' && path === '/api/projects') {
66
76
  return json(res, await db.listProjects());
package/web/index.html CHANGED
@@ -56,9 +56,12 @@
56
56
  .badge-spec { background: #e3f2fd; color: #1565c0; }
57
57
  .badge-arch { background: #fff3e0; color: #e65100; }
58
58
  .badge-update { background: #e8f5e9; color: #1b5e20; }
59
+ .badge-test { background: #e0f7fa; color: #00695c; }
59
60
  .badge-open { background: #e8f5e9; color: #2e7d32; }
60
61
  .badge-resolved { background: #f3e5f5; color: #6a1b9a; }
61
62
  .badge-archived { background: #eceff1; color: #546e7a; }
63
+ .badge-active { background: #e8f5e9; color: #2e7d32; }
64
+ .badge-inactive { background: #fce4e4; color: #c62828; }
62
65
  .main-panel {
63
66
  flex: 1; padding: 24px; overflow-y: auto;
64
67
  }
@@ -114,12 +117,15 @@
114
117
  <option value="spec">Specs</option>
115
118
  <option value="arch">Arch</option>
116
119
  <option value="update">Updates</option>
120
+ <option value="test">Tests</option>
117
121
  </select>
118
122
  <select id="statusFilter">
119
123
  <option value="">All statuses</option>
120
124
  <option value="open">Open</option>
121
125
  <option value="resolved">Resolved</option>
122
126
  <option value="archived">Archived</option>
127
+ <option value="active">Active</option>
128
+ <option value="inactive">Inactive</option>
123
129
  </select>
124
130
  <button id="newBtn">+ New</button>
125
131
  </div>
@@ -214,6 +220,7 @@
214
220
  <option value="spec" ${record.kind === 'spec' ? 'selected' : ''}>Spec</option>
215
221
  <option value="arch" ${record.kind === 'arch' ? 'selected' : ''}>Arch</option>
216
222
  <option value="update" ${record.kind === 'update' ? 'selected' : ''}>Update</option>
223
+ <option value="test" ${record.kind === 'test' ? 'selected' : ''}>Test</option>
217
224
  </select>
218
225
  </div>
219
226
  <div class="form-group">
@@ -230,6 +237,8 @@
230
237
  <option value="open" ${record.status === 'open' ? 'selected' : ''}>Open</option>
231
238
  <option value="resolved" ${record.status === 'resolved' ? 'selected' : ''}>Resolved</option>
232
239
  <option value="archived" ${record.status === 'archived' ? 'selected' : ''}>Archived</option>
240
+ <option value="active" ${record.status === 'active' ? 'selected' : ''}>Active</option>
241
+ <option value="inactive" ${record.status === 'inactive' ? 'selected' : ''}>Inactive</option>
233
242
  </select>
234
243
  </div>
235
244
  <div class="btn-row">
@@ -254,6 +263,7 @@
254
263
  <option value="spec">Spec</option>
255
264
  <option value="arch">Arch</option>
256
265
  <option value="update">Update</option>
266
+ <option value="test">Test</option>
257
267
  </select>
258
268
  </div>
259
269
  <div class="form-group">
@@ -270,6 +280,8 @@
270
280
  <option value="open">Open</option>
271
281
  <option value="resolved">Resolved</option>
272
282
  <option value="archived">Archived</option>
283
+ <option value="active">Active</option>
284
+ <option value="inactive">Inactive</option>
273
285
  </select>
274
286
  </div>
275
287
  <div class="btn-row">