iikit-dashboard 1.0.0 → 1.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 +10 -19
- package/package.json +2 -2
- package/src/checklist.js +68 -0
- package/src/parser.js +170 -1
- package/src/public/index.html +988 -0
- package/src/server.js +46 -0
- package/src/testify.js +178 -0
package/README.md
CHANGED
|
@@ -2,27 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
**Watch your AI agent develop features in real time.**
|
|
4
4
|
|
|
5
|
-
A browser-based dashboard for [Intent Integrity Kit](https://github.com/intent-integrity-chain/kit) projects.
|
|
5
|
+
A browser-based dashboard for [Intent Integrity Kit (IIKit)](https://github.com/intent-integrity-chain/kit) projects. IIKit is a specification-driven development framework that guides AI agents through a structured workflow — from governance constitution through specification, clarification, planning, testing, and implementation. The dashboard visualizes every phase of that workflow with live updates as artifacts change on disk.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
|
-
|
|
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`.
|
|
9
|
+
The dashboard launches automatically early in the IIKit workflow — no manual setup needed.
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
You can also start it standalone to browse historical data for any project that has feature specs:
|
|
20
12
|
|
|
21
13
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
npx iikit-dashboard # current directory
|
|
15
|
+
npx iikit-dashboard /path/to/project # specific project
|
|
24
16
|
```
|
|
25
17
|
|
|
18
|
+
The dashboard opens at `http://localhost:3000`.
|
|
19
|
+
|
|
26
20
|
## Views
|
|
27
21
|
|
|
28
22
|
The pipeline bar at the top shows all nine IIKit workflow phases. Click any phase to see its visualization:
|
|
@@ -33,7 +27,7 @@ The pipeline bar at the top shows all nine IIKit workflow phases. Click any phas
|
|
|
33
27
|
| **Spec** | Story map with swim lanes by priority + interactive requirements graph (US / FR / SC nodes and edges) |
|
|
34
28
|
| **Clarify** | Q&A trail from clarification sessions, with clickable spec-item references that navigate back to the Spec view |
|
|
35
29
|
| **Plan** | Tech stack badge wall, interactive file-structure tree (existing vs. planned files), rendered architecture diagram, and Tessl tile cards |
|
|
36
|
-
| **Checklist** |
|
|
30
|
+
| **Checklist** | Progress rings per checklist file with color coding (red/yellow/green), gate traffic light (OPEN/BLOCKED), and accordion detail view with CHK IDs and tag badges |
|
|
37
31
|
| **Testify** | *Coming soon* |
|
|
38
32
|
| **Tasks** | *Coming soon* |
|
|
39
33
|
| **Analyze** | *Coming soon* |
|
|
@@ -58,19 +52,16 @@ The server reads directly from your project's `specs/` directory:
|
|
|
58
52
|
| `spec.md` | User stories, requirements, success criteria, and clarification Q&A |
|
|
59
53
|
| `plan.md` | Tech stack, file structure, and architecture diagram |
|
|
60
54
|
| `tasks.md` | Task checkboxes grouped by `[US1]`, `[US2]` tags |
|
|
55
|
+
| `checklists/*.md` | Checklist items with completion status, CHK IDs, and category groupings |
|
|
61
56
|
| `CONSTITUTION.md` | Governance principles and obligation levels |
|
|
62
57
|
| `tessl.json` | Installed Tessl tiles for the dependency panel |
|
|
63
58
|
|
|
64
59
|
A file watcher (chokidar) detects changes and pushes updates to the browser via WebSocket with 300 ms debounce.
|
|
65
60
|
|
|
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
61
|
## Requirements
|
|
71
62
|
|
|
72
63
|
- Node.js 18+
|
|
73
|
-
-
|
|
64
|
+
- A project with a `specs/` directory containing IIKit feature artifacts
|
|
74
65
|
|
|
75
66
|
## License
|
|
76
67
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iikit-dashboard",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Real-time dashboard for Intent Integrity Kit (IIKit) — visualizes every phase of specification-driven AI development",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"iikit-dashboard": "bin/iikit-dashboard.js"
|
package/src/checklist.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { parseChecklistsDetailed } = require('./parser');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Map a percentage (0-100) to a color bracket.
|
|
8
|
+
* @param {number} percentage
|
|
9
|
+
* @returns {string} "red" | "yellow" | "green"
|
|
10
|
+
*/
|
|
11
|
+
function percentageToColor(percentage) {
|
|
12
|
+
if (percentage <= 33) return 'red';
|
|
13
|
+
if (percentage <= 66) return 'yellow';
|
|
14
|
+
return 'green';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compute gate status from an array of file objects with percentage fields.
|
|
19
|
+
* Uses worst-case precedence: red if any at 0%, yellow if all 1-99%, green if all 100%.
|
|
20
|
+
*
|
|
21
|
+
* @param {Array<{percentage: number}>} files
|
|
22
|
+
* @returns {{status: string, level: string, label: string}}
|
|
23
|
+
*/
|
|
24
|
+
function computeGateStatus(files) {
|
|
25
|
+
if (files.length === 0) {
|
|
26
|
+
return { status: 'blocked', level: 'red', label: 'GATE: BLOCKED' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const anyAtZero = files.some(f => f.percentage === 0);
|
|
30
|
+
if (anyAtZero) {
|
|
31
|
+
return { status: 'blocked', level: 'red', label: 'GATE: BLOCKED' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const allComplete = files.every(f => f.percentage === 100);
|
|
35
|
+
if (allComplete) {
|
|
36
|
+
return { status: 'open', level: 'green', label: 'GATE: OPEN' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { status: 'blocked', level: 'yellow', label: 'GATE: BLOCKED' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Compute checklist view state for a feature.
|
|
44
|
+
* Returns per-file detail with items, percentage, color, and aggregate gate status.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} projectPath - Path to the project root
|
|
47
|
+
* @param {string} featureId - Feature directory name (e.g., "001-kanban-board")
|
|
48
|
+
* @returns {{files: Array, gate: {status: string, level: string, label: string}}}
|
|
49
|
+
*/
|
|
50
|
+
function computeChecklistViewState(projectPath, featureId) {
|
|
51
|
+
const checklistDir = path.join(projectPath, 'specs', featureId, 'checklists');
|
|
52
|
+
const parsed = parseChecklistsDetailed(checklistDir);
|
|
53
|
+
|
|
54
|
+
const files = parsed.map(file => {
|
|
55
|
+
const percentage = file.total > 0 ? Math.round((file.checked / file.total) * 100) : 0;
|
|
56
|
+
return {
|
|
57
|
+
...file,
|
|
58
|
+
percentage,
|
|
59
|
+
color: percentageToColor(percentage)
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const gate = computeGateStatus(files);
|
|
64
|
+
|
|
65
|
+
return { files, gate };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { computeChecklistViewState };
|
package/src/parser.js
CHANGED
|
@@ -115,6 +115,96 @@ function parseChecklists(checklistDir) {
|
|
|
115
115
|
return result;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Parse all checklist files in a directory and return detailed per-file data
|
|
120
|
+
* with individual items, categories, CHK IDs, and tags.
|
|
121
|
+
*
|
|
122
|
+
* Applies same requirements.md-only filter as parseChecklists:
|
|
123
|
+
* if requirements.md is the only file, returns empty array.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} checklistDir - Path to checklists/ directory
|
|
126
|
+
* @returns {Array<{name: string, filename: string, total: number, checked: number, items: Array}>}
|
|
127
|
+
*/
|
|
128
|
+
function parseChecklistsDetailed(checklistDir) {
|
|
129
|
+
if (!fs.existsSync(checklistDir)) return [];
|
|
130
|
+
|
|
131
|
+
const files = fs.readdirSync(checklistDir).filter(f => f.endsWith('.md'));
|
|
132
|
+
|
|
133
|
+
// Same filter as parseChecklists: skip if requirements.md is the only file
|
|
134
|
+
const hasDomainChecklists = files.some(f => f !== 'requirements.md');
|
|
135
|
+
if (!hasDomainChecklists) return [];
|
|
136
|
+
|
|
137
|
+
const result = [];
|
|
138
|
+
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
const content = fs.readFileSync(path.join(checklistDir, file), 'utf-8');
|
|
141
|
+
const lines = content.split('\n');
|
|
142
|
+
|
|
143
|
+
// Derive human-readable name from filename
|
|
144
|
+
const baseName = file.replace(/\.md$/, '');
|
|
145
|
+
const name = baseName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
146
|
+
|
|
147
|
+
const items = [];
|
|
148
|
+
let currentCategory = null;
|
|
149
|
+
let totalCount = 0;
|
|
150
|
+
let checkedCount = 0;
|
|
151
|
+
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
// Track category headings (## or ###)
|
|
154
|
+
const headingMatch = line.match(/^#{2,3}\s+(.+)/);
|
|
155
|
+
if (headingMatch) {
|
|
156
|
+
currentCategory = headingMatch[1].trim();
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Parse checkbox items
|
|
161
|
+
const checkboxMatch = line.match(/^- \[([ x])\]\s+(.*)/i);
|
|
162
|
+
if (!checkboxMatch) continue;
|
|
163
|
+
|
|
164
|
+
const isChecked = checkboxMatch[1].toLowerCase() === 'x';
|
|
165
|
+
let itemText = checkboxMatch[2].trim();
|
|
166
|
+
totalCount++;
|
|
167
|
+
if (isChecked) checkedCount++;
|
|
168
|
+
|
|
169
|
+
// Extract CHK-xxx ID
|
|
170
|
+
let chkId = null;
|
|
171
|
+
const chkMatch = itemText.match(/^(CHK-\d{3})\s+/);
|
|
172
|
+
if (chkMatch) {
|
|
173
|
+
chkId = chkMatch[1];
|
|
174
|
+
itemText = itemText.substring(chkMatch[0].length);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Extract trailing tags [tag1] [tag2] — but not the checkbox itself
|
|
178
|
+
const tags = [];
|
|
179
|
+
const tagRegex = /\[([^\]]+)\]\s*$/;
|
|
180
|
+
let tagMatch;
|
|
181
|
+
while ((tagMatch = itemText.match(tagRegex))) {
|
|
182
|
+
// Don't treat spec references like [Completeness, FR-004] as simple tags
|
|
183
|
+
tags.unshift(tagMatch[1]);
|
|
184
|
+
itemText = itemText.substring(0, tagMatch.index).trim();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
items.push({
|
|
188
|
+
text: itemText,
|
|
189
|
+
checked: isChecked,
|
|
190
|
+
chkId,
|
|
191
|
+
category: currentCategory,
|
|
192
|
+
tags
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
result.push({
|
|
197
|
+
name,
|
|
198
|
+
filename: file,
|
|
199
|
+
total: totalCount,
|
|
200
|
+
checked: checkedCount,
|
|
201
|
+
items
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
118
208
|
/**
|
|
119
209
|
* Parse CONSTITUTION.md to determine if TDD is required.
|
|
120
210
|
* Looks for strong TDD indicators combined with MUST/NON-NEGOTIABLE.
|
|
@@ -765,4 +855,83 @@ function parseResearchDecisions(content) {
|
|
|
765
855
|
return decisions;
|
|
766
856
|
}
|
|
767
857
|
|
|
768
|
-
|
|
858
|
+
/**
|
|
859
|
+
* Parse tests/test-specs.md to extract test specification entries.
|
|
860
|
+
* Pattern: ### TS-XXX: Title, then **Type**: value, **Priority**: value, **Traceability**: refs
|
|
861
|
+
*
|
|
862
|
+
* @param {string} content - Raw markdown content of test-specs.md
|
|
863
|
+
* @returns {Array<{id: string, title: string, type: string, priority: string, traceability: string[]}>}
|
|
864
|
+
*/
|
|
865
|
+
function parseTestSpecs(content) {
|
|
866
|
+
if (!content || typeof content !== 'string') return [];
|
|
867
|
+
|
|
868
|
+
const specs = [];
|
|
869
|
+
const headingRegex = /### TS-(\d+): (.+)/g;
|
|
870
|
+
const headingStarts = [];
|
|
871
|
+
let match;
|
|
872
|
+
|
|
873
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
874
|
+
headingStarts.push({
|
|
875
|
+
id: `TS-${match[1]}`,
|
|
876
|
+
title: match[2].trim(),
|
|
877
|
+
index: match.index
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
for (let i = 0; i < headingStarts.length; i++) {
|
|
882
|
+
const start = headingStarts[i].index;
|
|
883
|
+
const end = i + 1 < headingStarts.length ? headingStarts[i + 1].index : content.length;
|
|
884
|
+
const section = content.substring(start, end);
|
|
885
|
+
|
|
886
|
+
// Extract type
|
|
887
|
+
const typeMatch = section.match(/\*\*Type\*\*:\s*(acceptance|contract|validation)/);
|
|
888
|
+
const type = typeMatch ? typeMatch[1] : 'validation';
|
|
889
|
+
|
|
890
|
+
// Extract priority
|
|
891
|
+
const priorityMatch = section.match(/\*\*Priority\*\*:\s*(P\d+)/);
|
|
892
|
+
const priority = priorityMatch ? priorityMatch[1] : 'P3';
|
|
893
|
+
|
|
894
|
+
// Extract traceability — comma-separated IDs, filter to FR-/SC- only
|
|
895
|
+
let traceability = [];
|
|
896
|
+
const traceMatch = section.match(/\*\*Traceability\*\*:\s*(.+)/);
|
|
897
|
+
if (traceMatch) {
|
|
898
|
+
traceability = traceMatch[1]
|
|
899
|
+
.split(/,\s*/)
|
|
900
|
+
.map(s => s.trim())
|
|
901
|
+
.filter(s => /^(FR|SC)-\d+$/.test(s));
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
specs.push({
|
|
905
|
+
id: headingStarts[i].id,
|
|
906
|
+
title: headingStarts[i].title,
|
|
907
|
+
type,
|
|
908
|
+
priority,
|
|
909
|
+
traceability
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return specs;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Extract "must pass TS-xxx" references from already-parsed task descriptions.
|
|
918
|
+
*
|
|
919
|
+
* @param {Array<{id: string, description: string}>} tasks - Parsed tasks array
|
|
920
|
+
* @returns {Object<string, string[]>} Map of taskId to testSpecIds array
|
|
921
|
+
*/
|
|
922
|
+
function parseTaskTestRefs(tasks) {
|
|
923
|
+
if (!tasks || !Array.isArray(tasks)) return {};
|
|
924
|
+
|
|
925
|
+
const refs = {};
|
|
926
|
+
for (const task of tasks) {
|
|
927
|
+
const match = task.description ? task.description.match(/must pass ((?:TS-\d+(?:,\s*)?)+)/) : null;
|
|
928
|
+
if (match) {
|
|
929
|
+
refs[task.id] = match[1].split(/,\s*/).map(s => s.trim()).filter(Boolean);
|
|
930
|
+
} else {
|
|
931
|
+
refs[task.id] = [];
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return refs;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
module.exports = { parseSpecStories, parseTasks, parseChecklists, parseChecklistsDetailed, parseConstitutionTDD, hasClarifications, parseConstitutionPrinciples, parseRequirements, parseSuccessCriteria, parseClarifications, parseStoryRequirementRefs, parseTechContext, parseFileStructure, parseAsciiDiagram, parseTesslJson, parseResearchDecisions, parseTestSpecs, parseTaskTestRefs };
|