tenbo-dashboard 0.1.0 → 0.2.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
@@ -2,8 +2,6 @@
2
2
 
3
3
  Local architecture dashboard and CLI tools for [tenbo](https://github.com/poyi/tenbo) — an agentic project manager for AI-assisted coding.
4
4
 
5
- ![tenbo dashboard — kanban board view](https://raw.githubusercontent.com/poyi/tenbo/main/docs/images/dashboard-kanban.png)
6
-
7
5
  ## Quick start
8
6
 
9
7
  ```bash
@@ -11,19 +9,45 @@ Local architecture dashboard and CLI tools for [tenbo](https://github.com/poyi/t
11
9
  npx tenbo-dashboard
12
10
  ```
13
11
 
14
- Opens a local dashboard for browsing roadmaps, triaging items, and drag-reorder.
12
+ Opens a local dashboard at http://localhost:5174.
13
+
14
+ ## What it gives you
15
+
16
+ Three views over the same `.tenbo/` files Claude maintains. Edits in either surface show up in the other on next refresh.
17
+
18
+ ### Roadmap — what to work on next
19
+
20
+ ![tenbo dashboard — kanban view](https://raw.githubusercontent.com/poyi/tenbo/main/docs/images/dashboard-kanban.png)
21
+
22
+ Kanban across `now` / `next` / `later` / `done`, grouped by scope and layer. Drag to reprioritize, click to edit. The same roadmap Claude reads when you ask "what should I build next?".
23
+
24
+ ### Docs — what the project actually does
25
+
26
+ ![tenbo dashboard — docs view](https://raw.githubusercontent.com/poyi/tenbo/main/docs/images/dashboard-docs.png)
27
+
28
+ Project overview, principles, and glossary alongside per-layer narratives, intents (responsibilities + boundaries + invariants), and code maps (entry points + key files + extension recipes). Onboard new contributors — human or AI — in minutes instead of an hour of grep.
29
+
30
+ ### Health — where the codebase is quietly rotting
31
+
32
+ ![tenbo dashboard — health view](https://raw.githubusercontent.com/poyi/tenbo/main/docs/images/dashboard-health.png)
33
+
34
+ Surfaces the things that would otherwise become tech debt nobody mentions: oversized layers (hotspots), code-map references that no longer match real files (doc drift), unreferenced files, coupling violations, dead code. Each finding carries a severity, the file it points at, and a suggested fix.
15
35
 
16
36
  ## CLI tools
17
37
 
38
+ The same package ships commands the skill uses behind the scenes — also runnable directly:
39
+
18
40
  ```bash
19
- npx tenbo-dashboard validate # Run validation rules
41
+ npx tenbo-dashboard validate # Schema + consistency checks
42
+ npx tenbo-dashboard init-check # Strict completeness check after fresh init
43
+ npx tenbo-dashboard metrics --all # Recompute scope metrics + health findings
20
44
  npx tenbo-dashboard next-id <prefix> # Allocate next roadmap item ID
21
- npx tenbo-dashboard metrics --all # Compute scope metrics
45
+ npx tenbo-dashboard --version # Print the installed version
22
46
  ```
23
47
 
24
48
  ## What is tenbo?
25
49
 
26
- tenbo is a Claude Code skill that gives your AI coding assistant persistent project memory — architecture docs, roadmaps, and health signals that survive across sessions. The dashboard is an optional visual companion.
50
+ A Claude Code skill that gives your AI coding assistant persistent project memory — architecture docs, roadmaps, and health signals that survive across sessions. The dashboard is the optional visual companion; the skill is the always-on conversational brain.
27
51
 
28
52
  Install the skill: [github.com/poyi/tenbo](https://github.com/poyi/tenbo)
29
53
 
@@ -0,0 +1,8 @@
1
+ import { createServer } from 'vite';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+
5
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
6
+ const server = await createServer({ root, configFile: path.join(root, 'vite.config.ts') });
7
+ await server.listen();
8
+ server.printUrls();
@@ -1,22 +1,47 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn } from 'node:child_process';
4
+ import { existsSync, readFileSync } from 'node:fs';
4
5
  import { fileURLToPath } from 'node:url';
5
6
  import path from 'node:path';
6
7
 
7
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
9
  const root = path.resolve(__dirname, '..');
9
10
 
11
+ /**
12
+ * Find the tsx binary by walking up node_modules from this file's directory.
13
+ * Required because npm hoists shared dependencies to a parent node_modules,
14
+ * so `<package>/node_modules/.bin/tsx` may not exist for consumers.
15
+ */
16
+ function findTsx() {
17
+ let dir = __dirname;
18
+ for (let i = 0; i < 10; i++) {
19
+ const candidate = path.join(dir, 'node_modules', '.bin', 'tsx');
20
+ if (existsSync(candidate)) return candidate;
21
+ const parent = path.dirname(dir);
22
+ if (parent === dir) break;
23
+ dir = parent;
24
+ }
25
+ return 'tsx'; // last-resort fallback to PATH
26
+ }
27
+
10
28
  const [command, ...args] = process.argv.slice(2);
11
29
 
30
+ if (command === '--version' || command === '-v' || command === 'version') {
31
+ const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
32
+ console.log(pkg.version);
33
+ process.exit(0);
34
+ }
35
+
12
36
  const commands = {
13
37
  validate: 'scripts/validate-cli.ts',
14
38
  'next-id': 'scripts/next-id.ts',
15
39
  metrics: 'scripts/compute-metrics.ts',
40
+ 'init-check': 'scripts/init-check.ts',
16
41
  };
17
42
 
18
43
  function run(script, scriptArgs) {
19
- const tsx = path.join(root, 'node_modules', '.bin', 'tsx');
44
+ const tsx = findTsx();
20
45
  const child = spawn(tsx, [path.join(root, script), ...scriptArgs], {
21
46
  stdio: 'inherit',
22
47
  cwd: process.cwd(),
@@ -25,18 +50,8 @@ function run(script, scriptArgs) {
25
50
  }
26
51
 
27
52
  function startDashboard() {
28
- // Use Vite's JS API to start the dev server
29
- const viteScript = `
30
- import { createServer } from 'vite';
31
- import { fileURLToPath } from 'node:url';
32
- import path from 'node:path';
33
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
34
- const server = await createServer({ root, configFile: path.join(root, 'vite.config.ts') });
35
- await server.listen();
36
- server.printUrls();
37
- `;
38
- const tsx = path.join(root, 'node_modules', '.bin', 'tsx');
39
- const child = spawn(tsx, ['--eval', viteScript], {
53
+ const launcher = path.join(__dirname, 'launch-dashboard.mjs');
54
+ const child = spawn(process.execPath, [launcher], {
40
55
  stdio: 'inherit',
41
56
  cwd: process.cwd(),
42
57
  env: { ...process.env, TENBO_CWD: process.cwd() },
@@ -44,15 +59,17 @@ function startDashboard() {
44
59
  child.on('close', (code) => process.exit(code ?? 0));
45
60
  }
46
61
 
47
- if (!command || command === 'help' || command === '--help') {
62
+ if (command === 'help' || command === '--help') {
48
63
  console.log(`
49
64
  tenbo-dashboard — local architecture dashboard for .tenbo/ repos
50
65
 
51
66
  Usage:
52
67
  tenbo-dashboard Launch the dashboard (http://localhost:5174)
53
68
  tenbo-dashboard validate Run validation rules
69
+ tenbo-dashboard init-check Strict completeness check for fresh init (errors on missing skeletons, file_count:0, etc)
54
70
  tenbo-dashboard next-id <prefix> Allocate next roadmap item ID
55
71
  tenbo-dashboard metrics --all Compute scope metrics
72
+ tenbo-dashboard --version Print package version
56
73
  tenbo-dashboard help Show this help
57
74
  `);
58
75
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tenbo-dashboard",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Local-first architecture dashboard and CLI tools for .tenbo/ repos",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,20 +45,22 @@
45
45
  "@dnd-kit/core": "^6.3.1",
46
46
  "@dnd-kit/sortable": "^8.0.0",
47
47
  "@dnd-kit/utilities": "^3.2.2",
48
+ "@vitejs/plugin-react": "^4.3.1",
48
49
  "@xyflow/react": "^12.10.2",
49
50
  "chokidar": "^3.6.0",
50
51
  "dagre": "^0.8.5",
51
52
  "gray-matter": "^4.0.3",
53
+ "jscpd": "^4",
52
54
  "lucide-react": "^0.545.0",
53
55
  "react": "^18.3.1",
54
56
  "react-dom": "^18.3.1",
55
57
  "react-markdown": "^9.0.1",
56
58
  "remark-gfm": "^4.0.0",
57
- "yaml": "^2.5.1",
58
- "@vitejs/plugin-react": "^4.3.1",
59
+ "ts-morph": "^25",
59
60
  "tsx": "^4.19.0",
60
61
  "typescript": "^5.5.4",
61
- "vite": "^5.4.3"
62
+ "vite": "^5.4.3",
63
+ "yaml": "^2.5.1"
62
64
  },
63
65
  "devDependencies": {
64
66
  "@testing-library/jest-dom": "^6.9.1",
@@ -67,9 +69,7 @@
67
69
  "@types/node": "^22.5.4",
68
70
  "@types/react": "^18.3.5",
69
71
  "@types/react-dom": "^18.3.0",
70
- "jscpd": "^4",
71
72
  "jsdom": "^25.0.0",
72
- "ts-morph": "^25",
73
73
  "vitest": "^2.0.5"
74
74
  }
75
75
  }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * init-check.ts — strict variant of validate, used by the skill at the end of
3
+ * the Initialize Project Memory flow.
4
+ *
5
+ * Reuses the standard validator, then upgrades or adds checks that catch
6
+ * silent init failures (missing skeleton docs, glob mistakes producing
7
+ * `file_count: 0`, missing principles.md/glossary.md, missing metrics.json).
8
+ *
9
+ * Exit codes:
10
+ * 0 — every init artifact is present and structurally sound
11
+ * 1 — at least one init defect found
12
+ * 2 — could not locate repo root
13
+ */
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import { findRepoRoot } from '../src/api/lib/repoRoot';
17
+ import { readState } from '../src/api/lib/tenboFs';
18
+ import { validate } from '../src/api/lib/validator';
19
+
20
+ interface InitDefect {
21
+ message: string;
22
+ scope?: string;
23
+ layerId?: string;
24
+ }
25
+
26
+ export function runInitCheck(repoRoot: string): { defects: InitDefect[]; preExistingErrors: number } {
27
+ const defects: InitDefect[] = [];
28
+ const tenbo = path.join(repoRoot, '.tenbo');
29
+
30
+ // Repo-level files
31
+ if (!fs.existsSync(path.join(tenbo, 'workspace.yaml'))) {
32
+ defects.push({ message: 'missing .tenbo/workspace.yaml' });
33
+ }
34
+ if (!fs.existsSync(path.join(tenbo, 'principles.md'))) {
35
+ defects.push({ message: 'missing .tenbo/principles.md (init step 8 — write from template)' });
36
+ }
37
+ if (!fs.existsSync(path.join(tenbo, 'glossary.md'))) {
38
+ defects.push({ message: 'missing .tenbo/glossary.md (init step 8 — write from template)' });
39
+ }
40
+
41
+ const state = readState(repoRoot);
42
+
43
+ // Per-scope and per-layer
44
+ for (const scope of state.scopes) {
45
+ const scopeDir = path.join(tenbo, 'scopes', scope.id);
46
+ const metricsPath = path.join(scopeDir, 'metrics.json');
47
+
48
+ if (!fs.existsSync(metricsPath)) {
49
+ defects.push({
50
+ message: `scope "${scope.id}" missing metrics.json — run "npx tenbo-dashboard metrics --all"`,
51
+ scope: scope.id,
52
+ });
53
+ } else {
54
+ // Check for the silent glob bug: file_count: 0 in a scope that obviously has files.
55
+ try {
56
+ const metrics = JSON.parse(fs.readFileSync(metricsPath, 'utf8'));
57
+ const layers = metrics?.layers ?? {};
58
+ // Quick file existence check on the scope path
59
+ const scopeRoot = path.resolve(repoRoot, scope.path ?? '.');
60
+ const scopeHasFiles = fs.existsSync(scopeRoot) && fs.readdirSync(scopeRoot).length > 0;
61
+ for (const [layerId, m] of Object.entries(layers) as [string, { file_count?: number }][]) {
62
+ if (scopeHasFiles && (m.file_count ?? 0) === 0) {
63
+ defects.push({
64
+ message: `layer "${layerId}" has file_count: 0 despite scope "${scope.id}" containing files — likely a glob path mistake. Globs in architecture.yaml are RELATIVE to scope.path (not the repo root). See architecture.yaml.tmpl header comment.`,
65
+ scope: scope.id,
66
+ layerId,
67
+ });
68
+ }
69
+ }
70
+ } catch (e) {
71
+ defects.push({
72
+ message: `scope "${scope.id}" metrics.json is unreadable: ${(e as Error).message}`,
73
+ scope: scope.id,
74
+ });
75
+ }
76
+ }
77
+
78
+ for (const layer of scope.layers) {
79
+ const docs = state.layerDocs?.[`${scope.id}/${layer.id}`];
80
+ if (!docs?.hasIntent) {
81
+ defects.push({
82
+ message: `layer "${layer.id}" missing intent.md (init step 8 — write skeleton from template)`,
83
+ scope: scope.id,
84
+ layerId: layer.id,
85
+ });
86
+ }
87
+ if (!docs?.hasCodeMap) {
88
+ defects.push({
89
+ message: `layer "${layer.id}" missing code-map.md (init step 8 — write skeleton from template)`,
90
+ scope: scope.id,
91
+ layerId: layer.id,
92
+ });
93
+ }
94
+ }
95
+ }
96
+
97
+ // Reuse standard validator for everything else; treat its errors as defects.
98
+ const result = validate(state);
99
+ for (const e of result.errors) {
100
+ defects.push({ message: e.message, scope: e.scope, layerId: e.layerId });
101
+ }
102
+
103
+ return { defects, preExistingErrors: result.errors.length };
104
+ }
105
+
106
+ function isMain(): boolean {
107
+ try {
108
+ const invoked = process.argv[1] ? path.resolve(process.argv[1]) : '';
109
+ const here = path.resolve(new URL(import.meta.url).pathname);
110
+ return invoked === here;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ if (isMain()) {
117
+ const repoRoot = findRepoRoot(process.cwd());
118
+ if (!repoRoot) {
119
+ process.stderr.write('init-check: unable to find repo root (no .tenbo/ in any parent directory)\n');
120
+ process.exit(2);
121
+ }
122
+ const { defects } = runInitCheck(repoRoot);
123
+ if (defects.length === 0) {
124
+ process.stdout.write('init-check: all init artifacts present.\n');
125
+ process.exit(0);
126
+ }
127
+ process.stderr.write(`init-check FAILED — ${defects.length} init defect(s):\n`);
128
+ for (const d of defects) {
129
+ const loc = d.scope ? ` [${d.scope}${d.layerId ? `/${d.layerId}` : ''}]` : '';
130
+ process.stderr.write(` ❌${loc} ${d.message}\n`);
131
+ }
132
+ process.exit(1);
133
+ }
package/vite.config.ts CHANGED
@@ -5,5 +5,26 @@ import { tenboApiPlugin } from './src/api/plugin';
5
5
  export default defineConfig({
6
6
  plugins: [react(), tenboApiPlugin()],
7
7
  server: { port: 5174 },
8
+ // Force pre-bundling of the markdown chain. When the dashboard is installed as a
9
+ // consumer dependency, Vite's optimizer fails to walk into transitive CJS deps
10
+ // like `style-to-js`, and serving them raw via `/@fs/` produces a silent
11
+ // "module does not provide an export named 'default'" SyntaxError that aborts
12
+ // React mounting. Listing them here makes Vite pre-bundle them with proper
13
+ // ESM interop.
14
+ optimizeDeps: {
15
+ include: [
16
+ // React subpath — the optimizer auto-handles `react-dom` root but NOT
17
+ // `react-dom/client`, so the subpath gets served raw via `/@fs/` and
18
+ // breaks default-import interop in consumer installs.
19
+ 'react-dom/client',
20
+ // Markdown chain — transitive CJS deps (style-to-js, hast-util-to-jsx-runtime)
21
+ // also need explicit pre-bundling for consumer installs. Without this,
22
+ // they're served raw and produce silent SyntaxError aborting React mount.
23
+ 'react-markdown',
24
+ 'remark-gfm',
25
+ 'style-to-js',
26
+ 'hast-util-to-jsx-runtime',
27
+ ],
28
+ },
8
29
  test: { environment: 'jsdom', globals: true, setupFiles: ['./src/test-setup.ts'] },
9
30
  });