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 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. Visualizes every phase of the IIKit workflow — from constitution principles through specification, planning, and implementation with live updates as artifacts change on disk.
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
- ```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`.
9
+ The dashboard launches automatically early in the IIKit workflow — no manual setup needed.
18
10
 
19
- ## Setup
11
+ You can also start it standalone to browse historical data for any project that has feature specs:
20
12
 
21
13
  ```bash
22
- npm install
23
- tessl install # installs tile dependencies (like npm install for tiles)
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** | *Coming soon* |
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
- - An IIKit project with a `specs/` directory
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.0.0",
4
- "description": "IIKit Dashboard — real-time visualization for Intent Integrity Kit projects",
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"
@@ -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
- module.exports = { parseSpecStories, parseTasks, parseChecklists, parseConstitutionTDD, hasClarifications, parseConstitutionPrinciples, parseRequirements, parseSuccessCriteria, parseClarifications, parseStoryRequirementRefs, parseTechContext, parseFileStructure, parseAsciiDiagram, parseTesslJson, parseResearchDecisions };
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 };