iikit-dashboard 1.0.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 ADDED
@@ -0,0 +1,77 @@
1
+ # IIKit Dashboard
2
+
3
+ **Watch your AI agent develop features in real time.**
4
+
5
+ A browser-based dashboard for [Intent Integrity Kit](https://github.com/intent-integrity-chain/kit) projects. Visualizes every phase of the IIKit workflow — from constitution principles through specification, planning, and implementation — with live updates as artifacts change on disk.
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ # Run in your IIKit project directory
11
+ npx iikit-dashboard
12
+
13
+ # Or specify a project path
14
+ npx iikit-dashboard /path/to/your/project
15
+ ```
16
+
17
+ The dashboard opens at `http://localhost:3000`.
18
+
19
+ ## Setup
20
+
21
+ ```bash
22
+ npm install
23
+ tessl install # installs tile dependencies (like npm install for tiles)
24
+ ```
25
+
26
+ ## Views
27
+
28
+ The pipeline bar at the top shows all nine IIKit workflow phases. Click any phase to see its visualization:
29
+
30
+ | Phase | View |
31
+ |-------|------|
32
+ | **Constitution** | Radar chart of governance principles with obligation levels (MUST / SHOULD / MAY) |
33
+ | **Spec** | Story map with swim lanes by priority + interactive requirements graph (US / FR / SC nodes and edges) |
34
+ | **Clarify** | Q&A trail from clarification sessions, with clickable spec-item references that navigate back to the Spec view |
35
+ | **Plan** | Tech stack badge wall, interactive file-structure tree (existing vs. planned files), rendered architecture diagram, and Tessl tile cards |
36
+ | **Checklist** | *Coming soon* |
37
+ | **Testify** | *Coming soon* |
38
+ | **Tasks** | *Coming soon* |
39
+ | **Analyze** | *Coming soon* |
40
+ | **Implement** | Kanban board with cards sliding Todo → In Progress → Done as the agent checks off tasks |
41
+
42
+ ## Features
43
+
44
+ - **Live updates** — all views refresh in real time via WebSocket as project files change
45
+ - **Pipeline navigation** — phase nodes show status (complete / in-progress / skipped / not started) with progress percentages
46
+ - **Feature selector** — dropdown to switch between features in `specs/`, sorted newest-first
47
+ - **Clarification traceability** — Q&A entries link back to the FR / US / SC spec items they clarify
48
+ - **Integrity badges** — shows whether test assertions have been tampered with
49
+ - **Three-state theme** — cycles System (OS preference) → Light → Dark
50
+ - **Zero build step** — single HTML file with inline CSS and JS
51
+
52
+ ## How It Works
53
+
54
+ The server reads directly from your project's `specs/` directory:
55
+
56
+ | File | Purpose |
57
+ |------|---------|
58
+ | `spec.md` | User stories, requirements, success criteria, and clarification Q&A |
59
+ | `plan.md` | Tech stack, file structure, and architecture diagram |
60
+ | `tasks.md` | Task checkboxes grouped by `[US1]`, `[US2]` tags |
61
+ | `CONSTITUTION.md` | Governance principles and obligation levels |
62
+ | `tessl.json` | Installed Tessl tiles for the dependency panel |
63
+
64
+ A file watcher (chokidar) detects changes and pushes updates to the browser via WebSocket with 300 ms debounce.
65
+
66
+ ## Integration with IIKit
67
+
68
+ When you run `/iikit-08-implement`, the implement skill automatically launches the dashboard in the background. No manual setup needed.
69
+
70
+ ## Requirements
71
+
72
+ - Node.js 18+
73
+ - An IIKit project with a `specs/` directory
74
+
75
+ ## License
76
+
77
+ MIT
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { createServer } = require('../src/server');
6
+
7
+ // Parse arguments
8
+ const args = process.argv.slice(2);
9
+ let projectPath = process.cwd();
10
+ let port = 3000;
11
+
12
+ for (let i = 0; i < args.length; i++) {
13
+ if (args[i] === '--path' && args[i + 1]) {
14
+ projectPath = path.resolve(args[i + 1]);
15
+ i++;
16
+ } else if (args[i] === '--port' && args[i + 1]) {
17
+ port = parseInt(args[i + 1], 10);
18
+ i++;
19
+ } else if (!args[i].startsWith('--')) {
20
+ // Positional argument = project path
21
+ projectPath = path.resolve(args[i]);
22
+ }
23
+ }
24
+
25
+ async function main() {
26
+ console.log(`\n IIKit Dashboard`);
27
+ console.log(` ===============\n`);
28
+ console.log(` Project: ${projectPath}`);
29
+
30
+ try {
31
+ const result = await createServer({ projectPath, port });
32
+ const url = `http://localhost:${result.port}`;
33
+ console.log(` Server: ${url}`);
34
+ console.log(`\n Open your browser to view the dashboard.\n`);
35
+
36
+ // Try to open browser (best effort, don't fail if it doesn't work)
37
+ try {
38
+ const { exec } = require('child_process');
39
+ const cmd = process.platform === 'darwin' ? 'open' :
40
+ process.platform === 'win32' ? 'start' : 'xdg-open';
41
+ exec(`${cmd} ${url}`);
42
+ } catch {
43
+ // Ignore browser open errors
44
+ }
45
+
46
+ // Handle graceful shutdown
47
+ process.on('SIGINT', () => {
48
+ console.log('\n Shutting down...');
49
+ result.server.close(() => {
50
+ if (result.watcher) result.watcher.close();
51
+ process.exit(0);
52
+ });
53
+ });
54
+
55
+ process.on('SIGTERM', () => {
56
+ result.server.close(() => {
57
+ if (result.watcher) result.watcher.close();
58
+ process.exit(0);
59
+ });
60
+ });
61
+
62
+ } catch (err) {
63
+ console.error(` Error: ${err.message}`);
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ main();
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "iikit-dashboard",
3
+ "version": "1.0.0",
4
+ "description": "IIKit Dashboard — real-time visualization for Intent Integrity Kit projects",
5
+ "main": "src/server.js",
6
+ "bin": {
7
+ "iikit-dashboard": "bin/iikit-dashboard.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/"
12
+ ],
13
+ "scripts": {
14
+ "start": "node bin/iikit-dashboard.js",
15
+ "test": "jest --forceExit"
16
+ },
17
+ "keywords": [
18
+ "iikit",
19
+ "intent-integrity",
20
+ "dashboard",
21
+ "specification",
22
+ "kanban",
23
+ "pipeline",
24
+ "tdd",
25
+ "live-updates"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/intent-integrity-chain/iikit-dashboard"
30
+ },
31
+ "author": "Baruch Sadogursky",
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=18.0.0"
35
+ },
36
+ "dependencies": {
37
+ "@anthropic-ai/sdk": "^0.74.0",
38
+ "chokidar": "^3.6.0",
39
+ "express": "^5.2.1",
40
+ "ws": "^8.19.0"
41
+ },
42
+ "devDependencies": {
43
+ "jest": "^30.2.0"
44
+ }
45
+ }
package/src/board.js ADDED
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Compute board state: assign stories to todo/in_progress/done columns
5
+ * based on their task completion status.
6
+ *
7
+ * Column assignment rules (from data-model.md):
8
+ * - todo: all tasks unchecked (or no tasks)
9
+ * - in_progress: at least 1 task checked but not all
10
+ * - done: all tasks checked
11
+ *
12
+ * @param {Array<{id: string, title: string, priority: string}>} stories
13
+ * @param {Array<{id: string, storyTag: string|null, description: string, checked: boolean}>} tasks
14
+ * @returns {{todo: Array, in_progress: Array, done: Array}}
15
+ */
16
+ function computeBoardState(stories, tasks) {
17
+ const board = { todo: [], in_progress: [], done: [] };
18
+
19
+ if (!stories || !Array.isArray(stories)) return board;
20
+ if (!tasks) tasks = [];
21
+
22
+ // Group tasks by storyTag
23
+ const tasksByStory = {};
24
+ const untaggedTasks = [];
25
+
26
+ for (const task of tasks) {
27
+ if (task.storyTag) {
28
+ if (!tasksByStory[task.storyTag]) {
29
+ tasksByStory[task.storyTag] = [];
30
+ }
31
+ tasksByStory[task.storyTag].push(task);
32
+ } else {
33
+ untaggedTasks.push(task);
34
+ }
35
+ }
36
+
37
+ // Assign each story to a column
38
+ for (const story of stories) {
39
+ const storyTasks = tasksByStory[story.id] || [];
40
+ const checkedCount = storyTasks.filter(t => t.checked).length;
41
+ const totalCount = storyTasks.length;
42
+
43
+ let column;
44
+ if (totalCount === 0 || checkedCount === 0) {
45
+ column = 'todo';
46
+ } else if (checkedCount === totalCount) {
47
+ column = 'done';
48
+ } else {
49
+ column = 'in_progress';
50
+ }
51
+
52
+ const card = {
53
+ id: story.id,
54
+ title: story.title,
55
+ priority: story.priority,
56
+ tasks: storyTasks,
57
+ progress: `${checkedCount}/${totalCount}`,
58
+ column
59
+ };
60
+
61
+ board[column].push(card);
62
+ }
63
+
64
+ // Handle untagged tasks — put them in an "Unassigned" card
65
+ if (untaggedTasks.length > 0) {
66
+ const checkedCount = untaggedTasks.filter(t => t.checked).length;
67
+ const totalCount = untaggedTasks.length;
68
+
69
+ let column;
70
+ if (checkedCount === 0) {
71
+ column = 'todo';
72
+ } else if (checkedCount === totalCount) {
73
+ column = 'done';
74
+ } else {
75
+ column = 'in_progress';
76
+ }
77
+
78
+ const card = {
79
+ id: 'Unassigned',
80
+ title: 'Unassigned Tasks',
81
+ priority: 'P3',
82
+ tasks: untaggedTasks,
83
+ progress: `${checkedCount}/${totalCount}`,
84
+ column
85
+ };
86
+
87
+ board[column].push(card);
88
+ }
89
+
90
+ return board;
91
+ }
92
+
93
+ module.exports = { computeBoardState };
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ /**
6
+ * Extract Given/When/Then lines from test-specs.md,
7
+ * normalize whitespace, sort, and compute SHA256 hash.
8
+ *
9
+ * @param {string} content - Raw content of test-specs.md
10
+ * @returns {string|null} SHA256 hex hash, or null if no assertions found
11
+ */
12
+ function computeAssertionHash(content) {
13
+ if (!content || typeof content !== 'string') return null;
14
+
15
+ const lines = content.split('\n');
16
+ const assertionLines = [];
17
+
18
+ for (const line of lines) {
19
+ const trimmed = line.trim();
20
+ if (
21
+ trimmed.startsWith('**Given**:') ||
22
+ trimmed.startsWith('**When**:') ||
23
+ trimmed.startsWith('**Then**:')
24
+ ) {
25
+ // Normalize whitespace: collapse multiple spaces to single space
26
+ const normalized = trimmed.replace(/\s+/g, ' ').trim();
27
+ assertionLines.push(normalized);
28
+ }
29
+ }
30
+
31
+ if (assertionLines.length === 0) return null;
32
+
33
+ // Sort for deterministic ordering
34
+ assertionLines.sort();
35
+
36
+ const joined = assertionLines.join('\n');
37
+ return crypto.createHash('sha256').update(joined, 'utf8').digest('hex');
38
+ }
39
+
40
+ /**
41
+ * Compare current assertion hash against stored hash.
42
+ *
43
+ * @param {string|null} currentHash - Hash computed from current test-specs.md
44
+ * @param {string|null} storedHash - Hash from context.json
45
+ * @returns {{status: string, currentHash: string|null, storedHash: string|null}}
46
+ */
47
+ function checkIntegrity(currentHash, storedHash) {
48
+ if (!currentHash || !storedHash) {
49
+ return {
50
+ status: 'missing',
51
+ currentHash: currentHash || null,
52
+ storedHash: storedHash || null
53
+ };
54
+ }
55
+
56
+ return {
57
+ status: currentHash === storedHash ? 'valid' : 'tampered',
58
+ currentHash,
59
+ storedHash
60
+ };
61
+ }
62
+
63
+ module.exports = { computeAssertionHash, checkIntegrity };