@boyingliu01/xp-gate 0.6.0 → 0.7.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/bin/xp-gate.js CHANGED
@@ -22,7 +22,7 @@ const COMMANDS = {
22
22
  'install-skill': {
23
23
  description: 'Install a xp-gate skill from GitHub',
24
24
  fn: installSkill,
25
- usage: 'xp-gate install-skill <name>[@<version>] [--offline] [--verbose] [--force]'
25
+ usage: 'xp-gate install-skill <name>[@<version>] [--offline] [--verbose] [--force] [--platform opencode|qoder]'
26
26
  },
27
27
  'update-skill': {
28
28
  description: 'Update installed skill(s)',
@@ -103,7 +103,7 @@ function main() {
103
103
  const name = subargs[0];
104
104
  if (!name) {
105
105
  console.error('Error: Skill name required');
106
- console.error('Usage: xp-gate install-skill <name>[@<version>]');
106
+ console.error('Usage: xp-gate install-skill <name>[@<version>] [--platform opencode|qoder]');
107
107
  process.exit(1);
108
108
  return;
109
109
  }
@@ -285,13 +285,18 @@ function printStatsTable(stats) {
285
285
  }
286
286
 
287
287
  function parseOptions(args) {
288
- const options = { offline: false, verbose: false, force: false, all: false, check: false };
289
- for (const arg of args) {
288
+ const options = { offline: false, verbose: false, force: false, all: false, check: false, platform: 'opencode' };
289
+ for (let i = 0; i < args.length; i++) {
290
+ const arg = args[i];
290
291
  if (arg === '--offline') options.offline = true;
291
292
  if (arg === '--verbose') options.verbose = true;
292
293
  if (arg === '--force') options.force = true;
293
294
  if (arg === '--all') options.all = true;
294
295
  if (arg === '--check') options.check = true;
296
+ if (arg === '--platform' && i + 1 < args.length) {
297
+ options.platform = args[i + 1];
298
+ i++;
299
+ }
295
300
  }
296
301
  return options;
297
302
  }
@@ -206,4 +206,38 @@ describe('detect-deps', () => {
206
206
  expect(result.ok).toBe(false);
207
207
  expect(result.versionMismatch.found).toBe('0.0.1');
208
208
  });
209
+
210
+ // --- Qoder platform tests ---
211
+
212
+ it('returns ok:true immediately for qoder platform (no hard deps)', async () => {
213
+ // Qoder has no superpowers/gstack dependency — should pass even with empty dirs
214
+ const { checkDeps } = require('../detect-deps');
215
+ const result = await checkDeps('qoder');
216
+ expect(result.ok).toBe(true);
217
+ expect(result.platform).toBe('qoder');
218
+ });
219
+
220
+ it('qoder platform passes even when superpowers/gstack are completely absent', async () => {
221
+ // No skill directories at all — qoder should still pass
222
+ const { checkDeps } = require('../detect-deps');
223
+ const result = await checkDeps('qoder');
224
+ expect(result.ok).toBe(true);
225
+ expect(result.missing).toBeUndefined();
226
+ });
227
+
228
+ it('claude-code platform behaves like default opencode (requires deps)', async () => {
229
+ // claude-code platform profile requires the same deps as opencode
230
+ const { checkDeps } = require('../detect-deps');
231
+ const result = await checkDeps('claude-code');
232
+ expect(result.ok).toBe(false);
233
+ expect(result.missing).toBe('superpowers');
234
+ });
235
+
236
+ it('unknown platform falls back to opencode profile', async () => {
237
+ const { checkDeps } = require('../detect-deps');
238
+ const result = await checkDeps('unknown-platform');
239
+ // Falls back to opencode profile → requires deps → fails
240
+ expect(result.ok).toBe(false);
241
+ expect(result.missing).toBe('superpowers');
242
+ });
209
243
  });
@@ -323,4 +323,49 @@ describe('install-skill', () => {
323
323
  expect(console.warn).not.toHaveBeenCalled();
324
324
  expect(console.error).toHaveBeenCalledWith('Error: Failed to download test-spec');
325
325
  });
326
+
327
+ // --- Qoder platform tests ---
328
+
329
+ function qoderSkillsDir() {
330
+ return path.join(tmpHome, '.qoder', 'skills');
331
+ }
332
+
333
+ it('qoder platform skips dep check (installs without superpowers/gstack)', async () => {
334
+ // Do NOT call setupValidDeps() — no superpowers/gstack present
335
+ mockHttpsGet({ statusCode: 200, body: '# Sprint Flow Qoder' });
336
+
337
+ const { installSkill } = require('../install-skill');
338
+ const result = await installSkill('sprint-flow', { platform: 'qoder' });
339
+
340
+ expect(result).toBe(0);
341
+ expect(console.log).toHaveBeenCalledWith('✓ sprint-flow installed');
342
+ });
343
+
344
+ it('qoder platform installs to ~/.qoder/skills/ instead of ~/.config/opencode/skills/', async () => {
345
+ mockHttpsGet({ statusCode: 200, body: '# Qoder Skill' });
346
+
347
+ const { installSkill } = require('../install-skill');
348
+ const result = await installSkill('sprint-flow', { platform: 'qoder' });
349
+
350
+ expect(result).toBe(0);
351
+ // Verify installed in Qoder directory
352
+ const qoderFile = path.join(qoderSkillsDir(), 'sprint-flow', 'SKILL.md');
353
+ expect(fs.existsSync(qoderFile)).toBe(true);
354
+ expect(fs.readFileSync(qoderFile, 'utf8')).toContain('Qoder Skill');
355
+
356
+ // Verify NOT installed in opencode directory
357
+ const opencodeFile = path.join(skillsDir(), 'sprint-flow', 'SKILL.md');
358
+ expect(fs.existsSync(opencodeFile)).toBe(false);
359
+ });
360
+
361
+ it('default platform still requires deps and installs to opencode dir', async () => {
362
+ // Without setupValidDeps, default platform should fail on dep check
363
+ const { installSkill } = require('../install-skill');
364
+ const result = await installSkill('sprint-flow');
365
+
366
+ expect(result).toBe(1);
367
+ expect(console.error).toHaveBeenCalledWith(
368
+ expect.stringContaining('superpowers is required')
369
+ );
370
+ });
326
371
  });
@@ -18,6 +18,14 @@ const REQUIRED_DEPS = [
18
18
  { name: 'gstack', minVersion: '1.0.0' }
19
19
  ];
20
20
 
21
+ // Platform-specific dependency profiles
22
+ // Qoder has no external skill dependencies (superpowers/gstack not required)
23
+ const PLATFORM_PROFILES = {
24
+ opencode: { requiredDeps: REQUIRED_DEPS, skillsDirs: [SKILLS_DIR, OPENCODE_DIR] },
25
+ qoder: { requiredDeps: [], skillsDirs: [path.join(HOME, '.qoder', 'skills')] },
26
+ 'claude-code': { requiredDeps: REQUIRED_DEPS, skillsDirs: [SKILLS_DIR, OPENCODE_DIR] },
27
+ };
28
+
21
29
  /**
22
30
  * Check if bash is available on the system.
23
31
  * XP-Gate hooks are bash scripts — Windows users need Git Bash installed.
@@ -84,12 +92,17 @@ function checkBash() {
84
92
  }
85
93
  }
86
94
 
87
- async function checkDeps() {
88
- for (const dep of REQUIRED_DEPS) {
89
- const possiblePaths = [
90
- path.join(SKILLS_DIR, dep.name),
91
- path.join(OPENCODE_DIR, dep.name)
92
- ];
95
+ async function checkDeps(platform = 'opencode') {
96
+ const profile = PLATFORM_PROFILES[platform] || PLATFORM_PROFILES.opencode;
97
+ const { requiredDeps, skillsDirs } = profile;
98
+
99
+ // Qoder and other platforms with no hard dependencies pass immediately
100
+ if (requiredDeps.length === 0) {
101
+ return { ok: true, platform };
102
+ }
103
+
104
+ for (const dep of requiredDeps) {
105
+ const possiblePaths = skillsDirs.map(dir => path.join(dir, dep.name));
93
106
 
94
107
  let depDir = null;
95
108
  for (const p of possiblePaths) {
@@ -13,6 +13,13 @@ const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
13
13
 
14
14
  const CONFIG_DIR = path.join(HOME, '.config', 'xp-gate');
15
15
  const SKILLS_DIR = path.join(HOME, '.config', 'opencode', 'skills');
16
+ const QODER_SKILLS_DIR = path.join(HOME, '.qoder', 'skills');
17
+
18
+ // Platform-specific skill directories
19
+ const PLATFORM_SKILLS_DIRS = {
20
+ opencode: SKILLS_DIR,
21
+ qoder: QODER_SKILLS_DIR,
22
+ };
16
23
 
17
24
  const SKILLS_REGISTRY = {
18
25
  'sprint-flow': { repo: 'boyingliu01/xp-gate', path: 'skills/sprint-flow' },
@@ -22,20 +29,23 @@ const SKILLS_REGISTRY = {
22
29
  };
23
30
 
24
31
  async function installSkill(name, options = {}) {
25
- const { offline = false, verbose = false, force = false } = options;
26
-
27
- const depCheck = await checkDeps();
28
- if (!depCheck.ok) {
29
- if (depCheck.missing) {
30
- console.error(`Error: ${depCheck.missing} is required but not installed`);
31
- console.error('Please install superpowers and gstack first');
32
- console.error('See: https://github.com/boyingliu01/superpowers');
33
- return 1;
34
- }
35
- if (depCheck.versionMismatch) {
36
- console.error(`Error: ${depCheck.versionMismatch.name} version too old`);
37
- console.error(`Need: ${depCheck.versionMismatch.required}, Found: ${depCheck.versionMismatch.found}`);
38
- return 1;
32
+ const { offline = false, verbose = false, force = false, platform = 'opencode' } = options;
33
+
34
+ // Qoder platform has no superpowers/gstack dependency
35
+ if (platform !== 'qoder') {
36
+ const depCheck = await checkDeps();
37
+ if (!depCheck.ok) {
38
+ if (depCheck.missing) {
39
+ console.error(`Error: ${depCheck.missing} is required but not installed`);
40
+ console.error('Please install superpowers and gstack first');
41
+ console.error('See: https://github.com/boyingliu01/superpowers');
42
+ return 1;
43
+ }
44
+ if (depCheck.versionMismatch) {
45
+ console.error(`Error: ${depCheck.versionMismatch.name} version too old`);
46
+ console.error(`Need: ${depCheck.versionMismatch.required}, Found: ${depCheck.versionMismatch.found}`);
47
+ return 1;
48
+ }
39
49
  }
40
50
  }
41
51
 
@@ -46,7 +56,8 @@ async function installSkill(name, options = {}) {
46
56
  return 1;
47
57
  }
48
58
 
49
- const targetDir = path.join(SKILLS_DIR, name);
59
+ const targetSkillsDir = PLATFORM_SKILLS_DIRS[platform] || SKILLS_DIR;
60
+ const targetDir = path.join(targetSkillsDir, name);
50
61
  if (fs.existsSync(targetDir) && !force) {
51
62
  console.error(`Error: ${name} is already installed`);
52
63
  console.error('Use --force to overwrite');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/xp-gate",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "./bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 6 quality gates, Sprint Flow, and Delphi multi-expert review.",
6
6
  "author": {
@@ -0,0 +1,93 @@
1
+ # QODER PLUGIN KNOWLEDGE BASE
2
+
3
+ **Generated:** 2026-06-03
4
+ **Version:** v0.6.0
5
+
6
+ ## OVERVIEW
7
+ Cross-platform plugin for Qoder IDE — shared skills, Qoder-specific enhancements (genui widgets, Canvas, Memory, CodeReview subagent, browser-use MCP). No hooks.json (Qoder uses skill-embedded gates instead of file-system event hooks).
8
+
9
+ ## STRUCTURE
10
+ ```
11
+ plugins/qoder/
12
+ ├── README.md # Installation and usage guide
13
+ ├── AGENTS.md # This knowledge base
14
+ ├── skills/ # Auto-populated by build-plugin.sh (8 skills)
15
+ │ ├── sprint-flow/SKILL.md # 7-phase pipeline + Qoder Pre-Edit Gate + Agent mapping
16
+ │ ├── delphi-review/SKILL.md # Multi-expert consensus + Qoder multi-model adaptation
17
+ │ ├── test-specification-alignment/SKILL.md # 2-phase test-spec verification
18
+ │ ├── ralph-loop/SKILL.md # REQ-level iterative build + Memory integration
19
+ │ ├── test-driven-development/SKILL.md # TDD enforcement (zero modification)
20
+ │ ├── improve-codebase-architecture/SKILL.md # Architecture health + Canvas output
21
+ │ ├── to-issues/SKILL.md # Vertical slice issue splitting (zero modification)
22
+ │ └── admin-template-guidelines/SKILL.md # 6 maintainability rules (zero modification)
23
+ ├── widgets/ # genui show_widget templates
24
+ │ ├── quality-report.html # Quality gate report dashboard
25
+ │ └── sprint-dashboard.html # Sprint status board
26
+ └── references/
27
+ └── qoder-adaptation.md # Platform differences and adaptation rules
28
+ ```
29
+
30
+ ## WHERE TO LOOK
31
+ | Task | Location | Notes |
32
+ |------|----------|-------|
33
+ | Installation | README.md | User-level (~/.qoder/skills/) and project-level (.qoder/skills/) |
34
+ | Skill adaptations | skills/*/SKILL.md | Qoder-specific paragraphs marked with "Qoder" header |
35
+ | Pre-Edit Gate | skills/sprint-flow/SKILL.md | Replaces Claude Code's delphi-review-guard.sh hook |
36
+ | Multi-model review | skills/delphi-review/references/qoder-multi-model.md | Expert→model mapping via Qoder Agent subagents |
37
+ | Agent dispatch | skills/sprint-flow/SKILL.md | Phase→Qoder Agent mapping table |
38
+ | External skill fallback | skills/sprint-flow/references/qoder-adaptation.md | superpowers/gstack replacements |
39
+ | Quality widget | widgets/quality-report.html | genui show_widget template for Phase 3 results |
40
+ | Sprint widget | widgets/sprint-dashboard.html | genui show_widget template for sprint status |
41
+ | Memory integration | skills/ralph-loop/SKILL.md | UpdateMemory for permanent/contextual learnings |
42
+ | CodeReview | skills/sprint-flow/SKILL.md | Phase 2 step 4 + Phase 3 code walkthrough |
43
+ | Browser MCP | skills/sprint-flow/references/qoder-adaptation.md | browser-use replaces gstack/browse |
44
+
45
+ ## CONVENTIONS
46
+ - Plugin skills auto-populated by build-plugin.sh --platform qoder — never edit plugins/qoder/skills/ directly
47
+ - Qoder-specific skill modifications live in source skills/ directory (marked with "## Qoder" sections)
48
+ - No hooks.json — Qoder lacks file-system event hooks; gates embedded in skill instructions
49
+ - genui widgets use show_widget MCP tool with widget_path template mode
50
+ - Memory system replaces gstack/learn for learnings persistence
51
+ - CodeReview subagent replaces superpowers/requesting-code-review
52
+ - browser-use MCP replaces gstack/browse for Phase 3 browser testing
53
+ - External skills (superpowers/gstack) replaced by orchestrator inline execution or Qoder native capabilities
54
+
55
+ ## ANTI-PATTERNS (THIS PLUGIN)
56
+ - Do NOT edit plugins/qoder/skills/ directly — rebuild from source via build-plugin.sh
57
+ - Do NOT assume xp-gate CLI is installed — skills must degrade gracefully
58
+ - Do NOT create hooks.json for Qoder — platform does not support file-system event hooks
59
+ - Do NOT use curl for API calls — curl is in Qoder's command deny list; use PowerShell Invoke-RestMethod or Qoder's built-in multi-model capability
60
+ - Do NOT reference OpenCode task() API in Qoder skills — use Agent tool (Browser/CodeReview/plan-agent) instead
61
+
62
+ ## UNIQUE STYLES
63
+ - Qoder: SKILL.md + genui widgets + Canvas + Memory + CodeReview subagent + browser-use MCP
64
+ - Pre-Edit Gate: skill-embedded mandatory check replaces Claude Code's physical hook blocking
65
+ - Multi-model review: Qoder Agent subagent dispatch (plan-agent/CodeReview) replaces OpenCode task() API
66
+ - Graceful degradation: skills work even without xp-gate CLI installed
67
+ - Shared skill source: one SKILL.md → copied to Claude Code, OpenCode, and Qoder platforms
68
+
69
+ ## COMMANDS
70
+ ```bash
71
+ # Build Qoder plugin
72
+ npm run build:qoder-plugin # Build Qoder plugin with all 8 skills
73
+ bash scripts/build-plugin.sh --platform qoder # Same, via script
74
+
75
+ # Install skills
76
+ bash scripts/install-qoder-skills.sh --global # User-level: ~/.qoder/skills/
77
+ bash scripts/install-qoder-skills.sh --local # Project-level: .qoder/skills/
78
+
79
+ # Test plugin
80
+ bash scripts/test-plugins.sh # Includes Qoder platform tests
81
+
82
+ # CLI skill install
83
+ xp-gate install-skill sprint-flow --platform qoder --global
84
+ xp-gate install-skill delphi-review --platform qoder --local
85
+ ```
86
+
87
+ ## NOTES
88
+ - v0.6.0+: Qoder plugin support introduced
89
+ - Qoder plugin: 8 skills (all source skills, including admin-template-guidelines)
90
+ - Claude Code plugin: 7 skills (no admin-template-guidelines)
91
+ - OpenCode plugin: 7 skills + 3 custom tools (gate-check, gate-principles, gate-arch)
92
+ - build-plugin.sh validates 8 expected skills for Qoder platform
93
+ - test-plugins.sh: includes Qoder-specific test cases (widgets, no hooks.json)
@@ -0,0 +1,87 @@
1
+ # XP-Gate Qoder Plugin
2
+
3
+ Qoder IDE plugin exposing xp-gate quality gates, AI workflow skills, and visual dashboards.
4
+
5
+ ## Skills (8 AI Workflow Skills)
6
+
7
+ | Skill | Trigger | Description |
8
+ |-------|---------|-------------|
9
+ | sprint-flow | `/sprint-flow "需求"` | 7-phase pipeline: THINK→PLAN→BUILD→REVIEW→ACCEPT→FEEDBACK→SHIP |
10
+ | delphi-review | `/delphi-review` | Multi-expert anonymous consensus review (design + code-walkthrough) |
11
+ | test-specification-alignment | `/test-specification-alignment` | 2-phase test-spec verification |
12
+ | ralph-loop | Via sprint-flow Phase 2 | REQ-level iterative build (saves 40-67% tokens) |
13
+ | test-driven-development | `/test-driven-development` | TDD enforcement (RED→GREEN→REFACTOR) |
14
+ | improve-codebase-architecture | `/improve-codebase-architecture` | Architecture health checks |
15
+ | to-issues | `/to-issues` | Vertical slice issue splitting |
16
+ | admin-template-guidelines | Via sprint-flow BUILD | 6 maintainability rules for admin templates |
17
+
18
+ ## Qoder-Specific Enhancements
19
+
20
+ - **genui Widgets**: Quality report dashboard and Sprint status board (via `show_widget`)
21
+ - **CodeReview Subagent**: Deep integration for Phase 2 blind review and Phase 3 code walkthrough
22
+ - **browser-use MCP**: Phase 3 browser automation testing (replaces gstack/browse)
23
+ - **Memory System**: Learnings persistence via Qoder's built-in memory (replaces gstack/learn)
24
+ - **Canvas Output**: Architecture relationship diagrams (via `.canvas.tsx`)
25
+
26
+ ## Installation
27
+
28
+ ### Option 1: User-Level (Global, All Projects)
29
+
30
+ ```bash
31
+ # Via xp-gate CLI
32
+ xp-gate install-skill sprint-flow --platform qoder --global
33
+ xp-gate install-skill delphi-review --platform qoder --global
34
+
35
+ # Or via install script (all 8 skills)
36
+ bash scripts/install-qoder-skills.sh --global
37
+ # Installs to: ~/.qoder/skills/
38
+ ```
39
+
40
+ ### Option 2: Project-Level (Current Project Only)
41
+
42
+ ```bash
43
+ # Via install script
44
+ bash scripts/install-qoder-skills.sh --local
45
+ # Installs to: .qoder/skills/
46
+
47
+ # Or via build script
48
+ bash scripts/build-plugin.sh --platform qoder
49
+ ```
50
+
51
+ ### Option 3: Build from Source
52
+
53
+ ```bash
54
+ npm run build:qoder-plugin
55
+ # Produces plugins/qoder/ with all skills + widgets
56
+ ```
57
+
58
+ ## Requirements
59
+
60
+ - Qoder IDE
61
+ - xp-gate npm package installed globally (for git hooks: `npm install -g @boyingliu01/xp-gate && xp-gate init`)
62
+ - Node.js ≥ 18 (for principles checker)
63
+
64
+ ## Pre-Edit Gate (Replaces Claude Code Hooks)
65
+
66
+ Qoder does not have file-system event hooks like Claude Code's `PreToolUse`/`PostToolUse`. Instead, xp-gate skills embed **mandatory pre-edit checks** in their SKILL.md instructions:
67
+
68
+ - **Delphi Gate**: Before any code edit in Phase 2+, skill verifies `.sprint-state/delphi-reviewed.json` verdict is `APPROVED`
69
+ - **Principles Check**: After each REQ completion, skill runs principles checker via terminal
70
+
71
+ ## Graceful Degradation
72
+
73
+ If xp-gate CLI is unavailable, skills provide helpful install instructions instead of failing. Quality gates still run via git hooks (pre-commit) at commit time.
74
+
75
+ ## Comparison with Other Platforms
76
+
77
+ | Feature | Claude Code | OpenCode | Qoder |
78
+ |---------|:-----------:|:--------:|:-----:|
79
+ | AI Skills (8) | ✅ | ✅ | ✅ |
80
+ | Git Hooks | ❌ (separate npm) | ❌ (separate npm) | ❌ (separate npm) |
81
+ | Event Hooks | ✅ PreToolUse/PostToolUse | ✅ tool() | ❌ (skill-embedded) |
82
+ | Custom Tools | ❌ | ✅ 3 tools | ❌ (MCP instead) |
83
+ | Widget UI | ❌ | ❌ | ✅ genui |
84
+ | Canvas Output | ❌ | ❌ | ✅ .canvas.tsx |
85
+ | Memory System | ❌ | ❌ | ✅ UpdateMemory |
86
+ | CodeReview Agent | ❌ | ❌ | ✅ subagent |
87
+ | Browser MCP | ❌ | ❌ | ✅ browser-use |
@@ -0,0 +1,163 @@
1
+ <style>
2
+ :root {
3
+ --qr-bg: var(--vscode-editor-background, #1e1e1e);
4
+ --qr-fg: var(--vscode-editor-foreground, #d4d4d4);
5
+ --qr-border: var(--vscode-panel-border, #333);
6
+ --qr-pass: var(--vscode-testing-iconPassed, #73c991);
7
+ --qr-fail: var(--vscode-testing-iconFailed, #f14c4c);
8
+ --qr-skip: var(--vscode-descriptionForeground, #888);
9
+ --qr-accent: var(--vscode-focusBorder, #007acc);
10
+ --qr-card-bg: var(--vscode-editor-widget-background, #252526);
11
+ }
12
+ * { box-sizing: border-box; margin: 0; padding: 0; }
13
+ body { font-family: var(--vscode-font-family, 'Segoe UI', sans-serif); color: var(--qr-fg); background: var(--qr-bg); padding: 16px; }
14
+ .qr-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
15
+ .qr-header h2 { font-size: 16px; font-weight: 600; }
16
+ .qr-verdict { padding: 4px 12px; border-radius: 4px; font-size: 13px; font-weight: 600; }
17
+ .qr-verdict.pass { background: color-mix(in srgb, var(--qr-pass) 20%, transparent); color: var(--qr-pass); border: 1px solid var(--qr-pass); }
18
+ .qr-verdict.fail { background: color-mix(in srgb, var(--qr-fail) 20%, transparent); color: var(--qr-fail); border: 1px solid var(--qr-fail); }
19
+ .qr-meta { font-size: 12px; color: var(--qr-skip); margin-bottom: 16px; display: flex; gap: 16px; }
20
+ .qr-score-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; }
21
+ .qr-score-bar .bar-track { flex: 1; height: 8px; background: var(--qr-border); border-radius: 4px; overflow: hidden; }
22
+ .qr-score-bar .bar-fill { height: 100%; border-radius: 4px; transition: width 0.4s ease; }
23
+ .qr-score-bar .bar-fill.pass { background: var(--qr-pass); }
24
+ .qr-score-bar .bar-fill.fail { background: var(--qr-fail); }
25
+ .qr-score-bar .score-label { font-size: 14px; font-weight: 600; min-width: 48px; text-align: right; }
26
+ .qr-gates { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; margin-bottom: 20px; }
27
+ .qr-gate-card { background: var(--qr-card-bg); border: 1px solid var(--qr-border); border-radius: 6px; padding: 12px; display: flex; align-items: flex-start; gap: 10px; }
28
+ .qr-gate-icon { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; }
29
+ .qr-gate-icon.pass { background: color-mix(in srgb, var(--qr-pass) 20%, transparent); color: var(--qr-pass); }
30
+ .qr-gate-icon.fail { background: color-mix(in srgb, var(--qr-fail) 20%, transparent); color: var(--qr-fail); }
31
+ .qr-gate-icon.skip { background: color-mix(in srgb, var(--qr-skip) 20%, transparent); color: var(--qr-skip); }
32
+ .qr-gate-info { flex: 1; min-width: 0; }
33
+ .qr-gate-name { font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
34
+ .qr-gate-detail { font-size: 11px; color: var(--qr-skip); margin-top: 2px; }
35
+ .qr-section { margin-bottom: 16px; }
36
+ .qr-section h3 { font-size: 14px; font-weight: 600; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--qr-border); }
37
+ .qr-delphi-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 13px; }
38
+ .qr-delphi-row .expert-badge { padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: 600; background: var(--qr-card-bg); border: 1px solid var(--qr-border); }
39
+ .qr-delphi-row .verdict-tag { margin-left: auto; font-weight: 500; }
40
+ .qr-delphi-row .verdict-tag.approved { color: var(--qr-pass); }
41
+ .qr-delphi-row .verdict-tag.rejected { color: var(--qr-fail); }
42
+ .qr-metrics { display: flex; gap: 12px; flex-wrap: wrap; }
43
+ .qr-metric { background: var(--qr-card-bg); border: 1px solid var(--qr-border); border-radius: 6px; padding: 12px 16px; text-align: center; min-width: 120px; }
44
+ .qr-metric .value { font-size: 24px; font-weight: 700; }
45
+ .qr-metric .label { font-size: 11px; color: var(--qr-skip); margin-top: 4px; }
46
+ .qr-metric .value.pass { color: var(--qr-pass); }
47
+ .qr-metric .value.fail { color: var(--qr-fail); }
48
+ .qr-empty { text-align: center; padding: 32px; color: var(--qr-skip); font-size: 13px; }
49
+ </style>
50
+
51
+ <div id="qr-root"></div>
52
+
53
+ <script>
54
+ (function() {
55
+ const data = window.__WIDGET_DATA__ || {};
56
+ const root = document.getElementById('qr-root');
57
+
58
+ if (!data || (!data.overall && !data.gates)) {
59
+ root.innerHTML = '<div class="qr-empty">No quality report data available.<br>Run the quality gates pipeline to generate a report.</div>';
60
+ return;
61
+ }
62
+
63
+ const overall = data.overall || {};
64
+ const gates = data.gates || {};
65
+ const delphi = data.delphi || null;
66
+ const testAlign = data.testAlignment || null;
67
+ const isPass = overall.verdict === 'PASS';
68
+
69
+ let html = '';
70
+
71
+ // Header
72
+ html += '<div class="qr-header">';
73
+ html += '<h2>Quality Report' + (data.project ? ' — ' + esc(data.project) : '') + '</h2>';
74
+ html += '<span class="qr-verdict ' + (isPass ? 'pass' : 'fail') + '">' + esc(overall.verdict || 'N/A') + '</span>';
75
+ html += '</div>';
76
+
77
+ // Meta
78
+ if (data.generatedAt || data.commit) {
79
+ html += '<div class="qr-meta">';
80
+ if (data.generatedAt) html += '<span>Generated: ' + esc(formatDate(data.generatedAt)) + '</span>';
81
+ if (data.commit) html += '<span>Commit: ' + esc(data.commit.substring(0, 8)) + '</span>';
82
+ if (data.language) html += '<span>Language: ' + esc(data.language) + '</span>';
83
+ html += '</div>';
84
+ }
85
+
86
+ // Score bar
87
+ const passed = overall.gatesPassed || 0;
88
+ const total = overall.gatesTotal || 0;
89
+ const score = overall.score || 0;
90
+ const pct = total > 0 ? Math.round((passed / total) * 100) : 0;
91
+ html += '<div class="qr-score-bar">';
92
+ html += '<div class="bar-track"><div class="bar-fill ' + (isPass ? 'pass' : 'fail') + '" style="width:' + pct + '%"></div></div>';
93
+ html += '<span class="score-label">' + passed + '/' + total + '</span>';
94
+ html += '</div>';
95
+
96
+ // Gate cards
97
+ html += '<div class="qr-gates">';
98
+ const gateKeys = Object.keys(gates).sort();
99
+ for (const key of gateKeys) {
100
+ const g = gates[key];
101
+ const status = (g.status || 'SKIP').toUpperCase();
102
+ const icon = status === 'PASS' ? '✓' : status === 'FAIL' ? '✗' : '—';
103
+ const cls = status === 'PASS' ? 'pass' : status === 'FAIL' ? 'fail' : 'skip';
104
+ html += '<div class="qr-gate-card">';
105
+ html += '<div class="qr-gate-icon ' + cls + '">' + icon + '</div>';
106
+ html += '<div class="qr-gate-info">';
107
+ html += '<div class="qr-gate-name">' + esc(g.name || key) + '</div>';
108
+ let detail = '';
109
+ if (g.tool) detail += 'Tool: ' + esc(g.tool);
110
+ if (g.metric) detail += (detail ? ' · ' : '') + esc(g.metric);
111
+ if (g.threshold) detail += (detail ? ' · ' : '') + 'Threshold: ' + esc(g.threshold);
112
+ if (g.coverage) detail += (detail ? ' · ' : '') + 'Coverage: ' + esc(g.coverage);
113
+ if (g.warnings !== undefined) detail += (detail ? ' · ' : '') + 'Warnings: ' + g.warnings;
114
+ if (!detail) detail = status;
115
+ html += '<div class="qr-gate-detail">' + detail + '</div>';
116
+ html += '</div></div>';
117
+ }
118
+ html += '</div>';
119
+
120
+ // Delphi Review section
121
+ if (delphi) {
122
+ html += '<div class="qr-section"><h3>Delphi Review</h3>';
123
+ if (delphi.experts) {
124
+ for (const exp of delphi.experts) {
125
+ const vCls = (exp.verdict || '').toUpperCase() === 'APPROVED' ? 'approved' : 'rejected';
126
+ html += '<div class="qr-delphi-row">';
127
+ html += '<span class="expert-badge">' + esc(exp.id || '?') + '</span>';
128
+ html += '<span>' + esc(exp.role || '') + '</span>';
129
+ html += '<span class="verdict-tag ' + vCls + '">' + esc(exp.verdict || 'N/A') + '</span>';
130
+ html += '</div>';
131
+ }
132
+ }
133
+ if (delphi.consensus !== undefined) {
134
+ html += '<div class="qr-delphi-row"><span>Consensus</span><span class="verdict-tag ' + (delphi.consensus >= 0.95 ? 'approved' : 'rejected') + '">' + Math.round(delphi.consensus * 100) + '%</span></div>';
135
+ }
136
+ html += '</div>';
137
+ }
138
+
139
+ // Metrics section (test alignment + coverage)
140
+ const hasMetrics = testAlign || (overall.coverage_pct !== undefined);
141
+ if (hasMetrics) {
142
+ html += '<div class="qr-section"><h3>Metrics</h3><div class="qr-metrics">';
143
+ if (testAlign) {
144
+ const alignScore = testAlign.score || 0;
145
+ const alignCls = alignScore >= 80 ? 'pass' : 'fail';
146
+ html += '<div class="qr-metric"><div class="value ' + alignCls + '">' + alignScore + '%</div><div class="label">Test-Spec Alignment</div></div>';
147
+ }
148
+ if (overall.coverage_pct !== undefined) {
149
+ const covCls = overall.coverage_pct >= 80 ? 'pass' : 'fail';
150
+ html += '<div class="qr-metric"><div class="value ' + covCls + '">' + overall.coverage_pct + '%</div><div class="label">Coverage</div></div>';
151
+ }
152
+ if (overall.score !== undefined) {
153
+ html += '<div class="qr-metric"><div class="value">' + overall.score + '</div><div class="label">Overall Score</div></div>';
154
+ }
155
+ html += '</div></div>';
156
+ }
157
+
158
+ root.innerHTML = html;
159
+
160
+ function esc(s) { const d = document.createElement('div'); d.textContent = String(s); return d.innerHTML; }
161
+ function formatDate(iso) { try { return new Date(iso).toLocaleString(); } catch { return iso; } }
162
+ })();
163
+ </script>