engineering-intelligence 1.2.0 → 1.4.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.
Files changed (31) hide show
  1. package/dist/adapters/index.js +4 -4
  2. package/dist/adapters/index.js.map +1 -1
  3. package/dist/cli/index.js +1 -1
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/templates.d.ts +1 -1
  6. package/dist/templates.d.ts.map +1 -1
  7. package/dist/templates.js +33 -0
  8. package/dist/templates.js.map +1 -1
  9. package/dist/visualizer/index.d.ts +1 -1
  10. package/dist/visualizer/index.d.ts.map +1 -1
  11. package/dist/visualizer/index.js +724 -57
  12. package/dist/visualizer/index.js.map +1 -1
  13. package/package.json +1 -1
  14. package/templates/canonical/rules/engineering-intelligence.md +5 -1
  15. package/templates/canonical/skills/aidlc-lifecycle-engine/SKILL.md +39 -0
  16. package/templates/canonical/skills/api-backward-compatibility-engine/SKILL.md +80 -0
  17. package/templates/canonical/skills/context-sync-engine/SKILL.md +15 -4
  18. package/templates/canonical/skills/database-migration-safety-engine/SKILL.md +79 -0
  19. package/templates/canonical/skills/engineering-intelligence-skill/SKILL.md +59 -2
  20. package/templates/canonical/skills/graph-engine/SKILL.md +22 -5
  21. package/templates/canonical/skills/impact-analysis-engine/SKILL.md +20 -4
  22. package/templates/canonical/skills/knowledge-base-validator/SKILL.md +19 -3
  23. package/templates/canonical/skills/memory-sync-engine/SKILL.md +16 -0
  24. package/templates/canonical/skills/operations-readiness-engine/SKILL.md +15 -4
  25. package/templates/canonical/skills/requirement-scoper/SKILL.md +9 -0
  26. package/templates/canonical/skills/security-audit-engine/SKILL.md +10 -3
  27. package/templates/canonical/skills/staleness-detector/SKILL.md +19 -0
  28. package/templates/canonical/skills/testing-intelligence-engine/SKILL.md +37 -1
  29. package/templates/canonical/skills/type-safety-engine/SKILL.md +81 -0
  30. package/templates/canonical/workflows/engineering-intelligence.md +6 -4
  31. package/templates/canonical/workflows/initialize-engineering-intelligence.md +3 -2
@@ -1,4 +1,6 @@
1
- import { SKILL_NAMES, AGENT_NAMES, WORKFLOW_NAMES } from "../templates.js";
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { readTemplate, SKILL_NAMES, AGENT_NAMES, WORKFLOW_NAMES } from "../templates.js";
2
4
  const SKILL_CATALOG = {
3
5
  "initialize-intelligence-skill": {
4
6
  name: "Initialize Intelligence",
@@ -302,7 +304,84 @@ const WORKFLOW_CATALOG = [
302
304
  ],
303
305
  },
304
306
  ];
305
- export function generateDashboardHTML() {
307
+ async function scanWorkspaceFiles(dir, baseDir) {
308
+ const results = {};
309
+ try {
310
+ const entries = await readdir(dir, { withFileTypes: true });
311
+ for (const entry of entries) {
312
+ const fullPath = path.join(dir, entry.name);
313
+ const relPath = path.relative(baseDir, fullPath);
314
+ if (entry.name === "node_modules" || entry.name === ".git") {
315
+ continue;
316
+ }
317
+ if (entry.isDirectory()) {
318
+ const subResults = await scanWorkspaceFiles(fullPath, baseDir);
319
+ Object.assign(results, subResults);
320
+ }
321
+ else {
322
+ const ext = path.extname(entry.name).toLowerCase();
323
+ if ([".md", ".json", ".txt", ".yaml", ".yml"].includes(ext)) {
324
+ try {
325
+ const content = await readFile(fullPath, "utf8");
326
+ results[relPath] = content;
327
+ }
328
+ catch {
329
+ // ignore
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+ catch {
336
+ // ignore
337
+ }
338
+ return results;
339
+ }
340
+ async function readWorkspaceIntelligence(projectRoot) {
341
+ const files = {};
342
+ for (const sub of ["knowledge-base", ".changes", ".engineering-intelligence"]) {
343
+ const dir = path.join(projectRoot, sub);
344
+ const scanned = await scanWorkspaceFiles(dir, projectRoot);
345
+ Object.assign(files, scanned);
346
+ }
347
+ return files;
348
+ }
349
+ export async function generateDashboardHTML(projectRoot) {
350
+ const vaultName = path.basename(projectRoot);
351
+ // Read all canonical template contents
352
+ const templates = {};
353
+ for (const name of SKILL_NAMES) {
354
+ try {
355
+ templates[`skills/${name}`] = await readTemplate("skills", name);
356
+ }
357
+ catch {
358
+ templates[`skills/${name}`] = "";
359
+ }
360
+ }
361
+ for (const name of WORKFLOW_NAMES) {
362
+ try {
363
+ templates[`workflows/${name}`] = await readTemplate("workflows", name);
364
+ }
365
+ catch {
366
+ templates[`workflows/${name}`] = "";
367
+ }
368
+ }
369
+ for (const name of AGENT_NAMES) {
370
+ try {
371
+ templates[`agents/${name}`] = await readTemplate("agents", name);
372
+ }
373
+ catch {
374
+ templates[`agents/${name}`] = "";
375
+ }
376
+ }
377
+ try {
378
+ templates["rules/engineering-intelligence"] = await readTemplate("rules", "engineering-intelligence");
379
+ }
380
+ catch {
381
+ templates["rules/engineering-intelligence"] = "";
382
+ }
383
+ // Read workspace intelligence files
384
+ const workspaceFiles = await readWorkspaceIntelligence(projectRoot);
306
385
  const skillCards = Object.entries(SKILL_CATALOG)
307
386
  .map(([id, info]) => {
308
387
  const color = CATEGORY_COLORS[info.category] ?? "#888";
@@ -323,6 +402,11 @@ export function generateDashboardHTML() {
323
402
  <div class="dep-row"><span class="dep-label">Depends on:</span>${deps}</div>
324
403
  <div class="dep-row"><span class="dep-label">Used by:</span>${consumers}</div>
325
404
  </div>
405
+ <div class="skill-actions">
406
+ <button class="btn btn-sm btn-view" onclick="viewTemplate('skills/${id}', '${info.name}')">View Template</button>
407
+ <button class="btn btn-sm btn-view-workspace" id="ws-btn-${id}" style="display:none;" onclick="viewSkillInWorkspace('${id}')">View Workspace</button>
408
+ <a class="btn btn-sm btn-obsidian" id="obsidian-btn-${id}" style="display:none;" target="_blank">Obsidian</a>
409
+ </div>
326
410
  </div>`;
327
411
  })
328
412
  .join("\n");
@@ -344,7 +428,14 @@ export function generateDashboardHTML() {
344
428
  : '<span class="wf-badge wf-readwrite">Read-Write</span>';
345
429
  return `<div class="wf-card">
346
430
  <div class="wf-header">
347
- ${typeBadge}
431
+ <div class="wf-header-top">
432
+ ${typeBadge}
433
+ <div class="wf-actions">
434
+ <button class="btn btn-sm btn-view" onclick="viewTemplate('workflows/${wf.name}', '${wf.name}')">View Template</button>
435
+ <button class="btn btn-sm btn-view-workspace" id="ws-btn-${wf.name}" style="display:none;" onclick="viewWorkflowInWorkspace('${wf.name}')">View Workspace</button>
436
+ <a class="btn btn-sm btn-obsidian" id="obsidian-btn-${wf.name}" style="display:none;" target="_blank">Obsidian</a>
437
+ </div>
438
+ </div>
348
439
  <h3>${wf.name}</h3>
349
440
  <p>${wf.description}</p>
350
441
  </div>
@@ -352,31 +443,22 @@ export function generateDashboardHTML() {
352
443
  </div>`;
353
444
  }).join("\n");
354
445
  const agentCards = [
355
- { name: "Engineering Orchestrator", role: "Classifies requests, routes work, coordinates agents", skills: "All skills", color: "#6366f1" },
356
- { name: "Change Agent", role: "Implements code changes, adds tests, collects evidence", skills: "engineering-intelligence-skill, testing-intelligence-engine", color: "#22c55e" },
357
- { name: "Quality Agent", role: "Validates correctness, runs tests, reviews architecture", skills: "engineering-change-review, testing-intelligence-engine", color: "#ef4444" },
358
- { name: "Knowledge Agent", role: "Maintains all intelligence artifacts", skills: "All sync engines, graph-engine, change-history-engine", color: "#06b6d4" },
359
- { name: "Product Analyst", role: "Scopes requirements, asks clarifying questions, generates prompts", skills: "requirement-scoper, deep-project-knowledge-extractor", color: "#a855f7" },
446
+ { name: "Engineering Orchestrator", role: "Classifies requests, routes work, coordinates agents", id: "engineering-orchestrator", skills: "All skills", color: "#6366f1" },
447
+ { name: "Change Agent", role: "Implements code changes, adds tests, collects evidence", id: "change-agent", skills: "engineering-intelligence-skill, testing-intelligence-engine", color: "#22c55e" },
448
+ { name: "Quality Agent", role: "Validates correctness, runs tests, reviews architecture", id: "quality-agent", skills: "engineering-change-review, testing-intelligence-engine", color: "#ef4444" },
449
+ { name: "Knowledge Agent", role: "Maintains all intelligence artifacts", id: "knowledge-agent", skills: "All sync engines, graph-engine, change-history-engine", color: "#06b6d4" },
450
+ { name: "Product Analyst", role: "Scopes requirements, asks clarifying questions, generates prompts", id: "product-analyst", skills: "requirement-scoper, deep-project-knowledge-extractor", color: "#a855f7" },
360
451
  ].map((agent) => `<div class="agent-card">
361
452
  <div class="agent-icon" style="background:${agent.color}">${agent.name.charAt(0)}</div>
362
453
  <div class="agent-body">
363
- <h3>${agent.name}</h3>
454
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.25rem;">
455
+ <h3>${agent.name}</h3>
456
+ <button class="btn btn-sm btn-view" onclick="viewTemplate('agents/${agent.id}', '${agent.name}')">View Instruction</button>
457
+ </div>
364
458
  <p>${agent.role}</p>
365
459
  <div class="agent-skills"><strong>Skills:</strong> ${agent.skills}</div>
366
460
  </div>
367
461
  </div>`).join("\n");
368
- const artifactTree = `
369
- <div class="tree">
370
- <div class="tree-node root">knowledge-base/<span class="tree-count">22 documents</span></div>
371
- <div class="tree-node root">.engineering-intelligence/
372
- <div class="tree-node">memory/<span class="tree-count">5 documents</span></div>
373
- <div class="tree-node">context/<span class="tree-count">6 maps</span></div>
374
- <div class="tree-node">events/<span class="tree-count">6 guides</span></div>
375
- <div class="tree-node">graph/<span class="tree-count">5 JSON + 1 map</span></div>
376
- <div class="tree-node">reports/<span class="tree-count">IMP-*/REV-*/DISCOVERY/FRESHNESS/GIT/DEBUG reports</span></div>
377
- </div>
378
- <div class="tree-node root">.changes/<span class="tree-count">CHG-* records</span></div>
379
- </div>`;
380
462
  const categoryFilters = Object.entries(CATEGORY_LABELS)
381
463
  .map(([id, label]) => `<button class="filter-btn active" data-filter="${id}" style="--btn-color:${CATEGORY_COLORS[id]}">${label}</button>`)
382
464
  .join("\n");
@@ -386,6 +468,8 @@ export function generateDashboardHTML() {
386
468
  <meta charset="UTF-8">
387
469
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
388
470
  <title>Engineering Intelligence — Dashboard</title>
471
+ <!-- Prism.js for code highlight -->
472
+ <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
389
473
  <style>
390
474
  :root {
391
475
  --bg: #0a0a0f;
@@ -395,6 +479,7 @@ export function generateDashboardHTML() {
395
479
  --text: #e4e4ef;
396
480
  --text-dim: #8888a0;
397
481
  --accent: #6366f1;
482
+ --accent-hover: #4f46e5;
398
483
  --radius: 12px;
399
484
  --glass: rgba(18, 18, 26, 0.8);
400
485
  }
@@ -425,6 +510,13 @@ body {
425
510
  margin-bottom: 0.5rem;
426
511
  }
427
512
  .header p { color: var(--text-dim); font-size: 1.1rem; }
513
+ .header .header-controls {
514
+ margin-top: 1.5rem;
515
+ display: flex;
516
+ justify-content: center;
517
+ gap: 0.75rem;
518
+ flex-wrap: wrap;
519
+ }
428
520
  .header .stats {
429
521
  display: flex;
430
522
  justify-content: center;
@@ -441,6 +533,47 @@ body {
441
533
  .stat-num { font-size: 2rem; font-weight: 700; color: var(--accent); }
442
534
  .stat-label { font-size: 0.85rem; color: var(--text-dim); }
443
535
 
536
+ /* Buttons */
537
+ .btn {
538
+ padding: 0.5rem 1rem;
539
+ border-radius: 6px;
540
+ border: 1px solid var(--border);
541
+ background: var(--surface);
542
+ color: var(--text);
543
+ font-size: 0.85rem;
544
+ cursor: pointer;
545
+ transition: all 0.2s;
546
+ text-decoration: none;
547
+ display: inline-flex;
548
+ align-items: center;
549
+ gap: 0.4rem;
550
+ }
551
+ .btn:hover {
552
+ background: var(--surface-hover);
553
+ border-color: var(--accent);
554
+ }
555
+ .btn-primary {
556
+ background: var(--accent);
557
+ border-color: var(--accent);
558
+ }
559
+ .btn-primary:hover {
560
+ background: var(--accent-hover);
561
+ border-color: var(--accent-hover);
562
+ }
563
+ .btn-sm {
564
+ padding: 0.25rem 0.6rem;
565
+ font-size: 0.75rem;
566
+ border-radius: 4px;
567
+ }
568
+ .btn-obsidian {
569
+ border-color: #a855f7;
570
+ color: #d8b4fe;
571
+ }
572
+ .btn-obsidian:hover {
573
+ background: rgba(168, 85, 247, 0.1);
574
+ border-color: #c084fc;
575
+ }
576
+
444
577
  /* Tabs */
445
578
  .tabs {
446
579
  display: flex;
@@ -490,6 +623,9 @@ body {
490
623
  border: 1px solid var(--border);
491
624
  border-radius: var(--radius);
492
625
  padding: 1.25rem;
626
+ display: flex;
627
+ flex-direction: column;
628
+ justify-content: space-between;
493
629
  transition: all 0.2s;
494
630
  }
495
631
  .skill-card:hover { background: var(--surface-hover); border-color: var(--accent); transform: translateY(-2px); }
@@ -509,11 +645,12 @@ body {
509
645
  .skill-desc { color: var(--text-dim); font-size: 0.9rem; margin-bottom: 0.75rem; }
510
646
  .skill-id { margin-bottom: 0.75rem; }
511
647
  .skill-id code { background: var(--bg); padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.8rem; color: var(--accent); }
512
- .skill-deps { font-size: 0.85rem; }
648
+ .skill-deps { font-size: 0.85rem; margin-bottom: 1rem; }
513
649
  .dep-row { margin-bottom: 0.4rem; display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
514
650
  .dep-label { color: var(--text-dim); font-weight: 500; margin-right: 0.3rem; }
515
651
  .dep-tag { background: var(--bg); padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; }
516
652
  .dep-none { color: var(--text-dim); font-size: 0.75rem; font-style: italic; }
653
+ .skill-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }
517
654
 
518
655
  /* Workflow cards */
519
656
  .wf-grid { display: flex; flex-direction: column; gap: 1.5rem; }
@@ -526,6 +663,7 @@ body {
526
663
  }
527
664
  .wf-card:hover { border-color: var(--accent); }
528
665
  .wf-header { margin-bottom: 1rem; }
666
+ .wf-header-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; }
529
667
  .wf-header h3 { font-size: 1.1rem; margin-bottom: 0.25rem; }
530
668
  .wf-header p { color: var(--text-dim); font-size: 0.9rem; }
531
669
  .wf-badge {
@@ -535,7 +673,6 @@ body {
535
673
  font-size: 0.7rem;
536
674
  font-weight: 600;
537
675
  text-transform: uppercase;
538
- margin-bottom: 0.5rem;
539
676
  }
540
677
  .wf-readonly { background: #1e3a5f; color: #60a5fa; }
541
678
  .wf-readwrite { background: #1a3d2a; color: #4ade80; }
@@ -571,11 +708,12 @@ body {
571
708
  .step-name { font-size: 0.85rem; font-weight: 600; }
572
709
  .step-skill { font-size: 0.75rem; }
573
710
  .step-arrow { color: var(--text-dim); font-size: 1.2rem; flex-shrink: 0; }
711
+ .wf-actions { display: flex; gap: 0.4rem; }
574
712
 
575
713
  /* Agent cards */
576
714
  .agent-grid {
577
715
  display: grid;
578
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
716
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
579
717
  gap: 1rem;
580
718
  }
581
719
  .agent-card {
@@ -600,45 +738,265 @@ body {
600
738
  color: white;
601
739
  flex-shrink: 0;
602
740
  }
603
- .agent-body h3 { font-size: 1rem; margin-bottom: 0.25rem; }
741
+ .agent-body { flex: 1; }
742
+ .agent-body h3 { font-size: 1rem; }
604
743
  .agent-body p { color: var(--text-dim); font-size: 0.85rem; margin-bottom: 0.5rem; }
605
744
  .agent-skills { font-size: 0.8rem; color: var(--text-dim); }
606
745
 
607
- /* Artifact tree */
608
- .tree { padding: 1rem; }
609
- .tree-node {
610
- padding: 0.5rem 0 0.5rem 1.5rem;
611
- border-left: 2px solid var(--border);
612
- position: relative;
746
+ /* Explorer Layout */
747
+ .explorer-layout {
748
+ display: grid;
749
+ grid-template-columns: 280px 1fr;
750
+ gap: 1.5rem;
751
+ background: var(--surface);
752
+ border: 1px solid var(--border);
753
+ border-radius: var(--radius);
754
+ min-height: 600px;
755
+ overflow: hidden;
756
+ }
757
+ .explorer-sidebar {
758
+ border-right: 1px solid var(--border);
759
+ padding: 1.25rem;
760
+ display: flex;
761
+ flex-direction: column;
762
+ gap: 1rem;
763
+ background: rgba(10, 10, 15, 0.3);
764
+ }
765
+ .explorer-sidebar h3 {
766
+ font-size: 0.95rem;
767
+ text-transform: uppercase;
768
+ letter-spacing: 0.05em;
769
+ color: var(--text-dim);
770
+ }
771
+ .file-list {
772
+ display: flex;
773
+ flex-direction: column;
774
+ gap: 1rem;
775
+ overflow-y: auto;
776
+ max-height: 550px;
777
+ }
778
+ .file-group-header {
779
+ font-size: 0.8rem;
780
+ font-weight: 700;
781
+ color: var(--accent);
782
+ margin-bottom: 0.4rem;
783
+ text-transform: capitalize;
784
+ }
785
+ .file-group-items {
786
+ display: flex;
787
+ flex-direction: column;
788
+ gap: 0.2rem;
789
+ padding-left: 0.5rem;
790
+ }
791
+ .file-item {
792
+ padding: 0.4rem 0.6rem;
793
+ border-radius: 6px;
613
794
  font-family: 'JetBrains Mono', 'Fira Code', monospace;
614
- font-size: 0.9rem;
615
- }
616
- .tree-node::before {
617
- content: '';
618
- position: absolute;
619
- left: -2px;
620
- top: 50%;
621
- width: 12px;
622
- height: 2px;
623
- background: var(--border);
624
- }
625
- .tree-node.root {
626
- border-left: none;
627
- padding-left: 0;
795
+ font-size: 0.8rem;
796
+ cursor: pointer;
797
+ color: var(--text-dim);
798
+ transition: all 0.15s;
799
+ display: flex;
800
+ align-items: center;
801
+ gap: 0.4rem;
802
+ }
803
+ .file-item::before {
804
+ content: '📄';
805
+ font-size: 0.75rem;
806
+ }
807
+ .file-item:hover {
808
+ background: rgba(255,255,255,0.05);
809
+ color: var(--text);
810
+ }
811
+ .file-item.active {
812
+ background: rgba(99, 102, 241, 0.15);
813
+ color: #818cf8;
628
814
  font-weight: 600;
815
+ }
816
+ .explorer-content {
817
+ display: flex;
818
+ flex-direction: column;
819
+ height: 650px;
820
+ }
821
+ .explorer-header {
822
+ padding: 1rem 1.5rem;
823
+ border-bottom: 1px solid var(--border);
824
+ display: flex;
825
+ justify-content: space-between;
826
+ align-items: center;
827
+ background: rgba(10, 10, 15, 0.2);
828
+ }
829
+ .explorer-path {
830
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
831
+ font-size: 0.85rem;
629
832
  color: var(--accent);
630
- margin-top: 0.75rem;
631
833
  }
632
- .tree-node.root::before { display: none; }
633
- .tree-count {
834
+ .explorer-body {
835
+ padding: 1.5rem;
836
+ overflow-y: auto;
837
+ flex: 1;
838
+ background: rgba(10,10,15,0.1);
839
+ }
840
+ .empty-state {
841
+ display: flex;
842
+ flex-direction: column;
843
+ align-items: center;
844
+ justify-content: center;
845
+ height: 100%;
634
846
  color: var(--text-dim);
635
- font-weight: 400;
636
- font-size: 0.8rem;
637
- margin-left: 0.5rem;
847
+ text-align: center;
848
+ padding: 2rem;
849
+ }
850
+ .empty-icon { font-size: 3rem; margin-bottom: 1rem; opacity: 0.5; }
851
+ .empty-state p { max-width: 400px; font-size: 0.9rem; }
852
+
853
+ /* Modal */
854
+ .modal {
855
+ display: none;
856
+ position: fixed;
857
+ z-index: 1000;
858
+ left: 0;
859
+ top: 0;
860
+ width: 100%;
861
+ height: 100%;
862
+ background: rgba(0, 0, 0, 0.75);
863
+ backdrop-filter: blur(4px);
864
+ align-items: center;
865
+ justify-content: center;
866
+ }
867
+ .modal.active { display: flex; }
868
+ .modal-content {
869
+ background: var(--surface);
870
+ border: 1px solid var(--border);
871
+ border-radius: var(--radius);
872
+ width: 90%;
873
+ max-width: 900px;
874
+ height: 80vh;
875
+ display: flex;
876
+ flex-direction: column;
877
+ box-shadow: 0 20px 25px -5px rgba(0,0,0,0.5);
878
+ animation: modalEnter 0.2s ease-out;
879
+ }
880
+ @keyframes modalEnter {
881
+ from { transform: scale(0.95); opacity: 0; }
882
+ to { transform: scale(1); opacity: 1; }
883
+ }
884
+ .modal-header {
885
+ padding: 1.25rem 1.5rem;
886
+ border-bottom: 1px solid var(--border);
887
+ display: flex;
888
+ justify-content: space-between;
889
+ align-items: center;
890
+ }
891
+ .modal-header h3 { font-size: 1.2rem; font-weight: 700; color: var(--accent); }
892
+ .modal-actions { display: flex; align-items: center; gap: 1rem; }
893
+ .modal-close {
894
+ background: transparent;
895
+ border: none;
896
+ color: var(--text-dim);
897
+ font-size: 1.8rem;
898
+ cursor: pointer;
899
+ transition: color 0.15s;
900
+ line-height: 1;
901
+ }
902
+ .modal-close:hover { color: var(--text); }
903
+ .modal-body {
904
+ padding: 1.5rem;
905
+ overflow-y: auto;
906
+ flex: 1;
907
+ }
908
+
909
+ /* Markdown Rendering Custom Styles */
910
+ .markdown-body {
911
+ font-size: 0.95rem;
912
+ color: var(--text);
913
+ }
914
+ .markdown-body h1, .markdown-body h2, .markdown-body h3 {
915
+ margin-top: 1.5rem;
916
+ margin-bottom: 0.8rem;
917
+ font-weight: 700;
918
+ border-bottom: 1px solid var(--border);
919
+ padding-bottom: 0.3rem;
920
+ color: #fff;
921
+ }
922
+ .markdown-body h1 { font-size: 1.6rem; }
923
+ .markdown-body h2 { font-size: 1.3rem; }
924
+ .markdown-body h3 { font-size: 1.1rem; }
925
+ .markdown-body p, .markdown-body ul, .markdown-body ol {
926
+ margin-bottom: 1rem;
927
+ color: #c9c9d6;
928
+ }
929
+ .markdown-body ul, .markdown-body ol {
930
+ padding-left: 1.5rem;
931
+ }
932
+ .markdown-body li { margin-bottom: 0.25rem; }
933
+ .markdown-body pre {
934
+ background: #1e1e2f !important;
935
+ border: 1px solid var(--border);
936
+ border-radius: 8px;
937
+ padding: 1rem;
938
+ margin-bottom: 1rem;
939
+ overflow-x: auto;
940
+ }
941
+ .markdown-body code {
942
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
943
+ font-size: 0.85rem;
944
+ background: rgba(255,255,255,0.06);
945
+ padding: 0.15rem 0.3rem;
946
+ border-radius: 4px;
947
+ color: #f472b6;
948
+ }
949
+ .markdown-body pre code {
950
+ background: transparent;
951
+ padding: 0;
952
+ color: inherit;
953
+ }
954
+ .markdown-body table {
955
+ width: 100%;
956
+ border-collapse: collapse;
957
+ margin-bottom: 1.5rem;
958
+ font-size: 0.85rem;
959
+ }
960
+ .markdown-body th, .markdown-body td {
961
+ border: 1px solid var(--border);
962
+ padding: 0.5rem 0.75rem;
963
+ text-align: left;
964
+ }
965
+ .markdown-body th {
966
+ background: rgba(255,255,255,0.03);
967
+ font-weight: 600;
968
+ }
969
+ .markdown-body blockquote {
970
+ border-left: 4px solid var(--accent);
971
+ padding-left: 1rem;
972
+ margin-bottom: 1rem;
973
+ color: var(--text-dim);
974
+ font-style: italic;
975
+ }
976
+ .markdown-body a {
977
+ color: #38bdf8;
978
+ text-decoration: none;
979
+ }
980
+ .markdown-body a:hover {
981
+ text-decoration: underline;
982
+ }
983
+
984
+ /* Mermaid Graph Rendering styling */
985
+ .mermaid {
986
+ background: rgba(255,255,255,0.02);
987
+ border: 1px solid var(--border);
988
+ border-radius: var(--radius);
989
+ padding: 1.5rem;
990
+ margin-bottom: 1.5rem;
991
+ display: flex;
992
+ justify-content: center;
638
993
  }
639
994
 
640
995
  /* Responsive */
641
- @media (max-width: 768px) {
996
+ @media (max-width: 900px) {
997
+ .explorer-layout { grid-template-columns: 1fr; }
998
+ .explorer-sidebar { border-right: none; border-bottom: 1px solid var(--border); height: 250px; }
999
+ .explorer-content { height: 450px; }
642
1000
  .header h1 { font-size: 1.8rem; }
643
1001
  .header .stats { flex-wrap: wrap; }
644
1002
  .skill-grid { grid-template-columns: 1fr; }
@@ -652,11 +1010,16 @@ body {
652
1010
  <div class="header">
653
1011
  <h1>Engineering Intelligence OS</h1>
654
1012
  <p>Graph-backed engineering intelligence toolkit</p>
1013
+ <div class="header-controls">
1014
+ <button class="btn btn-primary" onclick="viewTemplate('rules/engineering-intelligence', 'Engineering Intelligence Rules')">View Rules Template</button>
1015
+ <button class="btn btn-view-workspace" id="ws-rules-btn" style="display:none;" onclick="viewRulesInWorkspace()">View Workspace Rules</button>
1016
+ <a class="btn btn-obsidian" id="obsidian-rules-btn" style="display:none;" target="_blank">Open Rules in Obsidian</a>
1017
+ </div>
655
1018
  <div class="stats">
656
1019
  <div class="stat"><div class="stat-num">${SKILL_NAMES.length}</div><div class="stat-label">Skills</div></div>
657
1020
  <div class="stat"><div class="stat-num">${AGENT_NAMES.length}</div><div class="stat-label">Agents</div></div>
658
1021
  <div class="stat"><div class="stat-num">${WORKFLOW_NAMES.length}</div><div class="stat-label">Workflows</div></div>
659
- <div class="stat"><div class="stat-num">45+</div><div class="stat-label">Artifacts</div></div>
1022
+ <div class="stat"><div class="stat-num" id="workspace-files-count">0</div><div class="stat-label">Workspace Files</div></div>
660
1023
  </div>
661
1024
  </div>
662
1025
 
@@ -664,7 +1027,7 @@ body {
664
1027
  <button class="tab active" data-tab="skills">Skills</button>
665
1028
  <button class="tab" data-tab="workflows">Workflows</button>
666
1029
  <button class="tab" data-tab="agents">Agents</button>
667
- <button class="tab" data-tab="artifacts">Artifacts</button>
1030
+ <button class="tab" data-tab="artifacts">Workspace Explorer</button>
668
1031
  </div>
669
1032
 
670
1033
  <div class="tab-content active" id="skills">
@@ -681,21 +1044,89 @@ body {
681
1044
  </div>
682
1045
 
683
1046
  <div class="tab-content" id="artifacts">
684
- <h2 style="margin-bottom:1rem;">Generated Artifact Structure</h2>
685
- <p style="color:var(--text-dim);margin-bottom:1rem;">These artifacts are generated and maintained by the AI IDE agent after installation.</p>
686
- ${artifactTree}
1047
+ <div class="explorer-layout">
1048
+ <div class="explorer-sidebar">
1049
+ <h3>Intelligence Artifacts</h3>
1050
+ <div id="fileList" class="file-list"></div>
1051
+ </div>
1052
+ <div class="explorer-content">
1053
+ <div class="explorer-header">
1054
+ <span id="explorerFilePath" class="explorer-path">Select an artifact to view</span>
1055
+ <a id="explorerObsidianLink" href="#" class="btn btn-obsidian hidden" target="_blank">Open in Obsidian</a>
1056
+ </div>
1057
+ <div id="explorerFileContent" class="explorer-body">
1058
+ <div class="empty-state">
1059
+ <div class="empty-icon">📁</div>
1060
+ <p>Choose an intelligence or lifecycle artifact from the list to view its contents, Mermaid architecture map, or open it in Obsidian.</p>
1061
+ </div>
1062
+ </div>
1063
+ </div>
1064
+ </div>
1065
+ </div>
1066
+ </div>
1067
+
1068
+ <!-- Modal File Viewer -->
1069
+ <div id="fileModal" class="modal">
1070
+ <div class="modal-content">
1071
+ <div class="modal-header">
1072
+ <h3 id="modalTitle">File Viewer</h3>
1073
+ <div class="modal-actions">
1074
+ <a id="modalObsidianLink" href="#" class="btn btn-obsidian hidden" target="_blank">Open in Obsidian</a>
1075
+ <button class="modal-close" onclick="closeModal()">&times;</button>
1076
+ </div>
1077
+ </div>
1078
+ <div id="modalBody" class="modal-body markdown-body"></div>
687
1079
  </div>
688
1080
  </div>
689
1081
 
1082
+ <!-- Scripts -->
1083
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1084
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
1085
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-markdown.min.js"></script>
1086
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-json.min.js"></script>
1087
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
1088
+
690
1089
  <script>
1090
+ // Data payloads injected from server-side
1091
+ const TEMPLATES = ${JSON.stringify(templates)};
1092
+ const WORKSPACE_FILES = ${JSON.stringify(workspaceFiles)};
1093
+ const VAULT_NAME = ${JSON.stringify(vaultName)};
1094
+ const SKILL_CATALOG = ${JSON.stringify(SKILL_CATALOG)};
1095
+ const WORKFLOW_CATALOG = ${JSON.stringify(WORKFLOW_CATALOG)};
1096
+
1097
+ // Initialize Mermaid
1098
+ mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose' });
1099
+
1100
+ // Marked custom renderer to intercept Mermaid blocks
1101
+ const renderer = new marked.Renderer();
1102
+ renderer.code = function(code, lang) {
1103
+ if (lang === 'mermaid') {
1104
+ return \`<div class="mermaid">\${code}</div>\`;
1105
+ }
1106
+ return \`<pre><code class="language-\${lang}">\${code}</code></pre>\`;
1107
+ };
1108
+ marked.setOptions({ renderer });
1109
+
1110
+ // Tab Switching
691
1111
  document.querySelectorAll('.tab').forEach(tab => {
692
1112
  tab.addEventListener('click', () => {
693
1113
  document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
694
1114
  document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
695
1115
  tab.classList.add('active');
696
1116
  document.getElementById(tab.dataset.tab).classList.add('active');
1117
+
1118
+ // Auto-select first workspace file if explorer is opened and empty
1119
+ if (tab.dataset.tab === 'artifacts') {
1120
+ const activeFile = document.querySelector('.file-item.active');
1121
+ if (!activeFile) {
1122
+ const firstFile = document.querySelector('.file-item');
1123
+ if (firstFile) firstFile.click();
1124
+ }
1125
+ }
697
1126
  });
698
1127
  });
1128
+
1129
+ // Category filtering on skills
699
1130
  document.querySelectorAll('.filter-btn').forEach(btn => {
700
1131
  btn.addEventListener('click', () => {
701
1132
  btn.classList.toggle('active');
@@ -705,6 +1136,242 @@ document.querySelectorAll('.filter-btn').forEach(btn => {
705
1136
  });
706
1137
  });
707
1138
  });
1139
+
1140
+ // Modal Actions
1141
+ function closeModal() {
1142
+ document.getElementById('fileModal').classList.remove('active');
1143
+ document.getElementById('modalBody').innerHTML = '';
1144
+ }
1145
+
1146
+ function openModal(title, renderedContent, obsidianLink = null) {
1147
+ document.getElementById('modalTitle').innerText = title;
1148
+ document.getElementById('modalBody').innerHTML = renderedContent;
1149
+
1150
+ const obsLink = document.getElementById('modalObsidianLink');
1151
+ if (obsLink) {
1152
+ if (obsidianLink) {
1153
+ obsLink.href = obsidianLink;
1154
+ obsLink.classList.remove('hidden');
1155
+ } else {
1156
+ obsLink.classList.add('hidden');
1157
+ }
1158
+ }
1159
+
1160
+ document.getElementById('fileModal').classList.add('active');
1161
+
1162
+ // Highlight code blocks
1163
+ Prism.highlightAllUnder(document.getElementById('modalBody'));
1164
+
1165
+ // Render mermaid graphs
1166
+ if (window.mermaid) {
1167
+ try {
1168
+ mermaid.run({ nodes: document.getElementById('modalBody').querySelectorAll('.mermaid') });
1169
+ } catch (e) {
1170
+ console.error(e);
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ // Find files in workspace
1176
+ function findWorkspaceSkillPath(skillId) {
1177
+ for (const filePath of Object.keys(WORKSPACE_FILES)) {
1178
+ if (filePath.includes(\`/skills/\${skillId}/\`) || filePath.endsWith(\`/\${skillId}/SKILL.md\`)) {
1179
+ return filePath;
1180
+ }
1181
+ }
1182
+ return null;
1183
+ }
1184
+
1185
+ function findWorkspaceWorkflowPath(wfName) {
1186
+ for (const filePath of Object.keys(WORKSPACE_FILES)) {
1187
+ if (filePath.includes(\`/workflows/\${wfName}.md\`) || filePath.endsWith(\`/\${wfName}.md\`)) {
1188
+ return filePath;
1189
+ }
1190
+ }
1191
+ return null;
1192
+ }
1193
+
1194
+ function findWorkspaceRulesPath() {
1195
+ for (const filePath of Object.keys(WORKSPACE_FILES)) {
1196
+ if (filePath.includes("engineering-intelligence.md") && (filePath.includes("/rules/") || filePath.includes("/rules"))) {
1197
+ return filePath;
1198
+ }
1199
+ }
1200
+ return null;
1201
+ }
1202
+
1203
+ function getObsidianUrl(filePath) {
1204
+ return \`obsidian://open?vault=\${encodeURIComponent(VAULT_NAME)}&file=\${encodeURIComponent(filePath)}\`;
1205
+ }
1206
+
1207
+ // View Content Functions
1208
+ function viewTemplate(key, title) {
1209
+ const content = TEMPLATES[key] || "Template content empty or missing.";
1210
+ const rendered = marked.parse(content);
1211
+ openModal(title, rendered);
1212
+ }
1213
+
1214
+ function viewSkillInWorkspace(skillId) {
1215
+ const filePath = findWorkspaceSkillPath(skillId);
1216
+ if (filePath) viewWorkspaceFileInModal(filePath);
1217
+ }
1218
+
1219
+ function viewWorkflowInWorkspace(wfName) {
1220
+ const filePath = findWorkspaceWorkflowPath(wfName);
1221
+ if (filePath) viewWorkspaceFileInModal(filePath);
1222
+ }
1223
+
1224
+ function viewRulesInWorkspace() {
1225
+ const filePath = findWorkspaceRulesPath();
1226
+ if (filePath) viewWorkspaceFileInModal(filePath);
1227
+ }
1228
+
1229
+ function viewWorkspaceFileInModal(filePath) {
1230
+ const content = WORKSPACE_FILES[filePath] || "";
1231
+ const obsidianUrl = getObsidianUrl(filePath);
1232
+ let rendered = "";
1233
+ if (filePath.toLowerCase().endsWith(".json")) {
1234
+ rendered = \`<pre><code class="language-json">\${content}</code></pre>\`;
1235
+ } else {
1236
+ rendered = marked.parse(content);
1237
+ }
1238
+ openModal(filePath, rendered, obsidianUrl);
1239
+ }
1240
+
1241
+ // Sidebar Explorer Functions
1242
+ function viewWorkspaceFile(filePath) {
1243
+ const content = WORKSPACE_FILES[filePath] || "";
1244
+ const obsidianUrl = getObsidianUrl(filePath);
1245
+ const container = document.getElementById('explorerFileContent');
1246
+
1247
+ document.getElementById('explorerFilePath').innerText = filePath;
1248
+ const obsLink = document.getElementById('explorerObsidianLink');
1249
+ obsLink.href = obsidianUrl;
1250
+ obsLink.classList.remove('hidden');
1251
+
1252
+ let rendered = "";
1253
+ if (filePath.toLowerCase().endsWith(".json")) {
1254
+ rendered = \`<pre><code class="language-json">\${content}</code></pre>\`;
1255
+ } else {
1256
+ rendered = marked.parse(content);
1257
+ }
1258
+
1259
+ container.innerHTML = rendered;
1260
+
1261
+ // Highlight code
1262
+ Prism.highlightAllUnder(container);
1263
+
1264
+ // Render mermaid
1265
+ if (window.mermaid) {
1266
+ try {
1267
+ mermaid.run({ nodes: container.querySelectorAll('.mermaid') });
1268
+ } catch (e) {
1269
+ console.error(e);
1270
+ }
1271
+ }
1272
+ }
1273
+
1274
+ function renderFileList() {
1275
+ const fileListContainer = document.getElementById('fileList');
1276
+ fileListContainer.innerHTML = '';
1277
+
1278
+ const filesCount = Object.keys(WORKSPACE_FILES).length;
1279
+ document.getElementById('workspace-files-count').innerText = filesCount;
1280
+
1281
+ if (filesCount === 0) {
1282
+ fileListContainer.innerHTML = '<div style="color:var(--text-dim);font-size:0.8rem;text-align:center;padding:1rem;">No workspace files detected. Run /initialize-engineering-intelligence first.</div>';
1283
+ return;
1284
+ }
1285
+
1286
+ // Group files by top-level directories
1287
+ const groups = {};
1288
+ for (const filePath of Object.keys(WORKSPACE_FILES)) {
1289
+ const parts = filePath.split('/');
1290
+ const groupName = parts[0];
1291
+ if (!groups[groupName]) {
1292
+ groups[groupName] = [];
1293
+ }
1294
+ groups[groupName].push(filePath);
1295
+ }
1296
+
1297
+ // Sort groups and files, and append
1298
+ Object.keys(groups).sort().forEach(groupName => {
1299
+ const paths = groups[groupName];
1300
+
1301
+ const groupDiv = document.createElement('div');
1302
+ groupDiv.className = 'file-group';
1303
+
1304
+ const header = document.createElement('div');
1305
+ header.className = 'file-group-header';
1306
+ header.innerText = groupName === 'knowledge-base' ? 'Knowledge Base' : groupName === '.engineering-intelligence' ? 'Intelligence Graph & Core' : groupName;
1307
+ groupDiv.appendChild(header);
1308
+
1309
+ const itemsDiv = document.createElement('div');
1310
+ itemsDiv.className = 'file-group-items';
1311
+
1312
+ paths.sort().forEach(filePath => {
1313
+ const item = document.createElement('div');
1314
+ item.className = 'file-item';
1315
+ const basename = filePath.substring(filePath.lastIndexOf('/') + 1);
1316
+ item.innerHTML = \`<span class="file-name">\${basename}</span>\`;
1317
+ item.addEventListener('click', () => {
1318
+ document.querySelectorAll('.file-item').forEach(el => el.classList.remove('active'));
1319
+ item.classList.add('active');
1320
+ viewWorkspaceFile(filePath);
1321
+ });
1322
+ itemsDiv.appendChild(item);
1323
+ });
1324
+
1325
+ groupDiv.appendChild(itemsDiv);
1326
+ fileListContainer.appendChild(groupDiv);
1327
+ });
1328
+ }
1329
+
1330
+ // Initialize workspace components
1331
+ document.addEventListener("DOMContentLoaded", () => {
1332
+ // Populate workspace file tree
1333
+ renderFileList();
1334
+
1335
+ // Bind workspace file buttons on skills cards
1336
+ for (const skillId of Object.keys(SKILL_CATALOG || {})) {
1337
+ const wsPath = findWorkspaceSkillPath(skillId);
1338
+ if (wsPath) {
1339
+ const wsBtn = document.getElementById(\`ws-btn-\${skillId}\`);
1340
+ const obsBtn = document.getElementById(\`obsidian-btn-\${skillId}\`);
1341
+ if (wsBtn) wsBtn.style.display = 'inline-block';
1342
+ if (obsBtn) {
1343
+ obsBtn.style.display = 'inline-block';
1344
+ obsBtn.href = getObsidianUrl(wsPath);
1345
+ }
1346
+ }
1347
+ }
1348
+
1349
+ // Bind workspace file buttons on workflows cards
1350
+ for (const wf of WORKFLOW_CATALOG || []) {
1351
+ const wsPath = findWorkspaceWorkflowPath(wf.name);
1352
+ if (wsPath) {
1353
+ const wsBtn = document.getElementById(\`ws-btn-\${wf.name}\`);
1354
+ const obsBtn = document.getElementById(\`obsidian-btn-\${wf.name}\`);
1355
+ if (wsBtn) wsBtn.style.display = 'inline-block';
1356
+ if (obsBtn) {
1357
+ obsBtn.style.display = 'inline-block';
1358
+ obsBtn.href = getObsidianUrl(wsPath);
1359
+ }
1360
+ }
1361
+ }
1362
+
1363
+ // Bind workspace rules controls
1364
+ const rulesPath = findWorkspaceRulesPath();
1365
+ if (rulesPath) {
1366
+ const wsBtn = document.getElementById('ws-rules-btn');
1367
+ const obsBtn = document.getElementById('obsidian-rules-btn');
1368
+ if (wsBtn) wsBtn.style.display = 'inline-block';
1369
+ if (obsBtn) {
1370
+ obsBtn.style.display = 'inline-block';
1371
+ obsBtn.href = getObsidianUrl(rulesPath);
1372
+ }
1373
+ }
1374
+ });
708
1375
  </script>
709
1376
  </body>
710
1377
  </html>`;