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 +77 -0
- package/bin/iikit-dashboard.js +68 -0
- package/package.json +45 -0
- package/src/board.js +93 -0
- package/src/integrity.js +63 -0
- package/src/parser.js +768 -0
- package/src/pipeline.js +130 -0
- package/src/planview.js +195 -0
- package/src/public/index.html +3322 -0
- package/src/server.js +302 -0
- package/src/storymap.js +40 -0
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 };
|
package/src/integrity.js
ADDED
|
@@ -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 };
|