swwa-mcp 0.1.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/assets/alt-text-ko.json +7 -0
  4. package/assets/axe-rule-map.json +196 -0
  5. package/assets/certification.json +47 -0
  6. package/assets/kwcag22-checklist.md +123 -0
  7. package/assets/kwcag22.json +1688 -0
  8. package/assets/link-text-ko.json +8 -0
  9. package/assets/mobile-app-2.0.md +38 -0
  10. package/assets/sources.json +68 -0
  11. package/assets/wcag22.json +780 -0
  12. package/bin/swwa-mcp.mjs +15 -0
  13. package/dist/color/contrast.js +175 -0
  14. package/dist/color/contrast.js.map +1 -0
  15. package/dist/data/axe-map.js +32 -0
  16. package/dist/data/axe-map.js.map +1 -0
  17. package/dist/data/certification.js +54 -0
  18. package/dist/data/certification.js.map +1 -0
  19. package/dist/data/kwcag22.js +91 -0
  20. package/dist/data/kwcag22.js.map +1 -0
  21. package/dist/data/loader.js +18 -0
  22. package/dist/data/loader.js.map +1 -0
  23. package/dist/data/sources.js +44 -0
  24. package/dist/data/sources.js.map +1 -0
  25. package/dist/data/wcag22.js +44 -0
  26. package/dist/data/wcag22.js.map +1 -0
  27. package/dist/data/wordlists.js +40 -0
  28. package/dist/data/wordlists.js.map +1 -0
  29. package/dist/engine/browser-detect.js +44 -0
  30. package/dist/engine/browser-detect.js.map +1 -0
  31. package/dist/engine/browser.js +114 -0
  32. package/dist/engine/browser.js.map +1 -0
  33. package/dist/engine/static-worker.js +18 -0
  34. package/dist/engine/static-worker.js.map +1 -0
  35. package/dist/engine/static.js +139 -0
  36. package/dist/engine/static.js.map +1 -0
  37. package/dist/engine/url-guard.js +37 -0
  38. package/dist/engine/url-guard.js.map +1 -0
  39. package/dist/index.js +23 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/messages.js +176 -0
  42. package/dist/messages.js.map +1 -0
  43. package/dist/normalize/axe.js +72 -0
  44. package/dist/normalize/axe.js.map +1 -0
  45. package/dist/normalize/checkpoints.js +47 -0
  46. package/dist/normalize/checkpoints.js.map +1 -0
  47. package/dist/normalize/finding.js +24 -0
  48. package/dist/normalize/finding.js.map +1 -0
  49. package/dist/normalize/locale.js +20 -0
  50. package/dist/normalize/locale.js.map +1 -0
  51. package/dist/prompts/audit-report.js +45 -0
  52. package/dist/prompts/audit-report.js.map +1 -0
  53. package/dist/prompts/review-markup.js +35 -0
  54. package/dist/prompts/review-markup.js.map +1 -0
  55. package/dist/report/cert.js +72 -0
  56. package/dist/report/cert.js.map +1 -0
  57. package/dist/report/format.js +36 -0
  58. package/dist/report/format.js.map +1 -0
  59. package/dist/report/summarize.js +61 -0
  60. package/dist/report/summarize.js.map +1 -0
  61. package/dist/report/types.js +2 -0
  62. package/dist/report/types.js.map +1 -0
  63. package/dist/resources/index.js +69 -0
  64. package/dist/resources/index.js.map +1 -0
  65. package/dist/rules/b/b-focus-order.js +33 -0
  66. package/dist/rules/b/b-focus-order.js.map +1 -0
  67. package/dist/rules/b/b-focus-visible.js +71 -0
  68. package/dist/rules/b/b-focus-visible.js.map +1 -0
  69. package/dist/rules/b/b-keyboard-reachable.js +82 -0
  70. package/dist/rules/b/b-keyboard-reachable.js.map +1 -0
  71. package/dist/rules/b/b-motion-runtime.js +50 -0
  72. package/dist/rules/b/b-motion-runtime.js.map +1 -0
  73. package/dist/rules/b/b-skip-link-works.js +58 -0
  74. package/dist/rules/b/b-skip-link-works.js.map +1 -0
  75. package/dist/rules/b/b-target-size-6mm.js +54 -0
  76. package/dist/rules/b/b-target-size-6mm.js.map +1 -0
  77. package/dist/rules/b/index.js +7 -0
  78. package/dist/rules/b/index.js.map +1 -0
  79. package/dist/rules/k/index.js +33 -0
  80. package/dist/rules/k/index.js.map +1 -0
  81. package/dist/rules/k/k-accesskey.js +20 -0
  82. package/dist/rules/k/k-accesskey.js.map +1 -0
  83. package/dist/rules/k/k-alt-meaningless.js +35 -0
  84. package/dist/rules/k/k-alt-meaningless.js.map +1 -0
  85. package/dist/rules/k/k-autocomplete-missing.js +36 -0
  86. package/dist/rules/k/k-autocomplete-missing.js.map +1 -0
  87. package/dist/rules/k/k-autoplay-media.js +30 -0
  88. package/dist/rules/k/k-autoplay-media.js.map +1 -0
  89. package/dist/rules/k/k-captcha-detect.js +24 -0
  90. package/dist/rules/k/k-captcha-detect.js.map +1 -0
  91. package/dist/rules/k/k-carousel-no-pause.js +33 -0
  92. package/dist/rules/k/k-carousel-no-pause.js.map +1 -0
  93. package/dist/rules/k/k-device-motion.js +22 -0
  94. package/dist/rules/k/k-device-motion.js.map +1 -0
  95. package/dist/rules/k/k-down-event-action.js +19 -0
  96. package/dist/rules/k/k-down-event-action.js.map +1 -0
  97. package/dist/rules/k/k-error-association.js +31 -0
  98. package/dist/rules/k/k-error-association.js.map +1 -0
  99. package/dist/rules/k/k-flash-animation.js +57 -0
  100. package/dist/rules/k/k-flash-animation.js.map +1 -0
  101. package/dist/rules/k/k-gesture-listener.js +30 -0
  102. package/dist/rules/k/k-gesture-listener.js.map +1 -0
  103. package/dist/rules/k/k-iframe-title.js +23 -0
  104. package/dist/rules/k/k-iframe-title.js.map +1 -0
  105. package/dist/rules/k/k-lang-ko-expected.js +29 -0
  106. package/dist/rules/k/k-lang-ko-expected.js.map +1 -0
  107. package/dist/rules/k/k-link-same-text-diff-href.js +41 -0
  108. package/dist/rules/k/k-link-same-text-diff-href.js.map +1 -0
  109. package/dist/rules/k/k-link-text-generic.js +29 -0
  110. package/dist/rules/k/k-link-text-generic.js.map +1 -0
  111. package/dist/rules/k/k-media-track.js +22 -0
  112. package/dist/rules/k/k-media-track.js.map +1 -0
  113. package/dist/rules/k/k-mouse-only-handler.js +27 -0
  114. package/dist/rules/k/k-mouse-only-handler.js.map +1 -0
  115. package/dist/rules/k/k-new-window-notice.js +27 -0
  116. package/dist/rules/k/k-new-window-notice.js.map +1 -0
  117. package/dist/rules/k/k-onload-popup.js +30 -0
  118. package/dist/rules/k/k-onload-popup.js.map +1 -0
  119. package/dist/rules/k/k-outline-none.js +55 -0
  120. package/dist/rules/k/k-outline-none.js.map +1 -0
  121. package/dist/rules/k/k-parse-errors.js +38 -0
  122. package/dist/rules/k/k-parse-errors.js.map +1 -0
  123. package/dist/rules/k/k-placeholder-only-label.js +32 -0
  124. package/dist/rules/k/k-placeholder-only-label.js.map +1 -0
  125. package/dist/rules/k/k-select-onchange.js +19 -0
  126. package/dist/rules/k/k-select-onchange.js.map +1 -0
  127. package/dist/rules/k/k-sensory-instruction.js +32 -0
  128. package/dist/rules/k/k-sensory-instruction.js.map +1 -0
  129. package/dist/rules/k/k-session-timeout-hint.js +29 -0
  130. package/dist/rules/k/k-session-timeout-hint.js.map +1 -0
  131. package/dist/rules/k/k-skip-link-first.js +23 -0
  132. package/dist/rules/k/k-skip-link-first.js.map +1 -0
  133. package/dist/rules/k/k-skip-target-exists.js +28 -0
  134. package/dist/rules/k/k-skip-target-exists.js.map +1 -0
  135. package/dist/rules/k/k-tabindex-positive.js +23 -0
  136. package/dist/rules/k/k-tabindex-positive.js.map +1 -0
  137. package/dist/rules/k/k-table-caption.js +26 -0
  138. package/dist/rules/k/k-table-caption.js.map +1 -0
  139. package/dist/rules/k/k-table-th-missing.js +28 -0
  140. package/dist/rules/k/k-table-th-missing.js.map +1 -0
  141. package/dist/rules/k/k-title-generic.js +35 -0
  142. package/dist/rules/k/k-title-generic.js.map +1 -0
  143. package/dist/rules/registry.js +62 -0
  144. package/dist/rules/registry.js.map +1 -0
  145. package/dist/rules/types.js +2 -0
  146. package/dist/rules/types.js.map +1 -0
  147. package/dist/rules/util/browser-dom.js +50 -0
  148. package/dist/rules/util/browser-dom.js.map +1 -0
  149. package/dist/rules/util/focusable.js +29 -0
  150. package/dist/rules/util/focusable.js.map +1 -0
  151. package/dist/rules/util/selector.js +19 -0
  152. package/dist/rules/util/selector.js.map +1 -0
  153. package/dist/rules/util/text.js +13 -0
  154. package/dist/rules/util/text.js.map +1 -0
  155. package/dist/schema-dialect.js +41 -0
  156. package/dist/schema-dialect.js.map +1 -0
  157. package/dist/server.js +36 -0
  158. package/dist/server.js.map +1 -0
  159. package/dist/tools/audit-url.js +109 -0
  160. package/dist/tools/audit-url.js.map +1 -0
  161. package/dist/tools/browser-status.js +29 -0
  162. package/dist/tools/browser-status.js.map +1 -0
  163. package/dist/tools/cert-readiness.js +76 -0
  164. package/dist/tools/cert-readiness.js.map +1 -0
  165. package/dist/tools/check-contrast.js +58 -0
  166. package/dist/tools/check-contrast.js.map +1 -0
  167. package/dist/tools/check-html.js +88 -0
  168. package/dist/tools/check-html.js.map +1 -0
  169. package/dist/tools/checklist.js +94 -0
  170. package/dist/tools/checklist.js.map +1 -0
  171. package/dist/tools/lookup.js +70 -0
  172. package/dist/tools/lookup.js.map +1 -0
  173. package/dist/tools/report-schema.js +49 -0
  174. package/dist/tools/report-schema.js.map +1 -0
  175. package/package.json +76 -0
@@ -0,0 +1,47 @@
1
+ /** 이 모드에서 실제로 실행될 수 있는(=판정에 기여할 수 있는) 규칙 수(02 §4 status 규칙 4). */
2
+ function activeRuleCount(cp, mode, data) {
3
+ if (mode === "static") {
4
+ const activeAxe = cp.axeRules.filter((id) => !data.axeRuleMap[id]?.staticDisabled).length;
5
+ return activeAxe + cp.kRules.length;
6
+ }
7
+ return cp.axeRules.length + cp.bRules.length;
8
+ }
9
+ /**
10
+ * Finding[]으로부터 33개 검사항목의 status를 계산한다(02-architecture §4 5규칙, 항상 단일
11
+ * 소스 순서로 33개를 반환한다).
12
+ */
13
+ export function computeCheckpoints(findings, mode, data) {
14
+ const byKwcag = new Map();
15
+ for (const f of findings) {
16
+ if (!f.kwcag)
17
+ continue;
18
+ const list = byKwcag.get(f.kwcag);
19
+ if (list)
20
+ list.push(f);
21
+ else
22
+ byKwcag.set(f.kwcag, [f]);
23
+ }
24
+ return data.kwcag22.checkpoints.map((cp) => {
25
+ const cpFindings = byKwcag.get(cp.id) ?? [];
26
+ const hasFail = cpFindings.some((f) => f.outcome === "fail");
27
+ const hasIncomplete = cpFindings.some((f) => f.outcome === "incomplete");
28
+ let status;
29
+ if (hasFail)
30
+ status = "fail";
31
+ else if (hasIncomplete)
32
+ status = "incomplete";
33
+ else if (activeRuleCount(cp, mode, data) > 0)
34
+ status = "pass";
35
+ else
36
+ status = cp.automation === "na" ? "na" : "manual";
37
+ return {
38
+ id: cp.id,
39
+ alias: cp.alias,
40
+ name: cp.name_ko,
41
+ automation: cp.automation,
42
+ status,
43
+ findings: cpFindings.length,
44
+ };
45
+ });
46
+ }
47
+ //# sourceMappingURL=checkpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoints.js","sourceRoot":"","sources":["../../src/normalize/checkpoints.ts"],"names":[],"mappings":"AAIA,kEAAkE;AAClE,SAAS,eAAe,CAAC,EAAc,EAAE,IAAgB,EAAE,IAAgB;IACzE,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC,MAAM,CAAC;QAC1F,OAAO,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;IACtC,CAAC;IACD,OAAO,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAmB,EAAE,IAAgB,EAAE,IAAgB;IACxF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,CAAC,KAAK;YAAE,SAAS;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;YAClB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,EAAoB,EAAE;QAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;QAC7D,MAAM,aAAa,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,YAAY,CAAC,CAAC;QAEzE,IAAI,MAAwB,CAAC;QAC7B,IAAI,OAAO;YAAE,MAAM,GAAG,MAAM,CAAC;aACxB,IAAI,aAAa;YAAE,MAAM,GAAG,YAAY,CAAC;aACzC,IAAI,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,GAAG,MAAM,CAAC;;YACzD,MAAM,GAAG,EAAE,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;QAEvD,OAAO;YACL,EAAE,EAAE,EAAE,CAAC,EAAE;YACT,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,IAAI,EAAE,EAAE,CAAC,OAAO;YAChB,UAAU,EAAE,EAAE,CAAC,UAAU;YACzB,MAAM;YACN,QAAQ,EAAE,UAAU,CAAC,MAAM;SAC5B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { cssPath } from "../rules/util/selector.js";
2
+ const HTML_SNIPPET_MAX = 300;
3
+ const ENGINE_MAP = {
4
+ k: "k-rule",
5
+ b: "b-rule",
6
+ };
7
+ /** k-/b- 규칙 결과를 Finding으로 정규화하는 유일한 통로다(03 §7). */
8
+ export function createFinding(rule, el, opts) {
9
+ return {
10
+ ruleId: rule.id,
11
+ engine: ENGINE_MAP[rule.engine],
12
+ kwcag: rule.kwcag,
13
+ wcag: rule.wcag,
14
+ impact: rule.impact,
15
+ outcome: opts.outcome,
16
+ confidence: opts.confidence ?? rule.confidence,
17
+ selector: opts.selectorOverride ?? (el ? cssPath(el) : ""),
18
+ html: opts.htmlOverride ?? (el ? el.outerHTML.slice(0, HTML_SNIPPET_MAX) : ""),
19
+ message: opts.message,
20
+ fix: opts.fix,
21
+ helpUrl: opts.helpUrl,
22
+ };
23
+ }
24
+ //# sourceMappingURL=finding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.js","sourceRoot":"","sources":["../../src/normalize/finding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAIpD,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,MAAM,UAAU,GAA8C;IAC5D,CAAC,EAAE,QAAQ;IACX,CAAC,EAAE,QAAQ;CACZ,CAAC;AAcF,mDAAmD;AACnD,MAAM,UAAU,aAAa,CAAC,IAAc,EAAE,EAAkB,EAAE,IAA0B;IAC1F,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;QAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU;QAC9C,QAAQ,EAAE,IAAI,CAAC,gBAAgB,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { createRequire } from "node:module";
2
+ import { readFileSync } from "node:fs";
3
+ let cached;
4
+ function loadKoLocale() {
5
+ if (cached)
6
+ return cached;
7
+ const require = createRequire(import.meta.url);
8
+ const path = require.resolve("axe-core/locales/ko.json");
9
+ cached = JSON.parse(readFileSync(path, "utf8"));
10
+ return cached;
11
+ }
12
+ /** axe 규칙의 help·description을 ko.json으로 치환한다. 로케일에 없는 규칙은 영어 원문 유지(ADR-07). */
13
+ export function localizeAxeMessage(ruleId, help, description) {
14
+ const entry = loadKoLocale().rules?.[ruleId];
15
+ return {
16
+ help: entry?.help ?? help,
17
+ description: entry?.description ?? description,
18
+ };
19
+ }
20
+ //# sourceMappingURL=locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.js","sourceRoot":"","sources":["../../src/normalize/locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAavC,IAAI,MAA6B,CAAC;AAElC,SAAS,YAAY;IACnB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACzD,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAc,CAAC;IAC7D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,IAAY,EAAE,WAAmB;IAClF,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO;QACL,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,IAAI;QACzB,WAAW,EAAE,KAAK,EAAE,WAAW,IAAI,WAAW;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * audit-report 프롬프트(FR-08, 02 §3.2). skills/a11y-audit/references/report-template.md 형식으로
4
+ * 요약 → 33항목 판정표 → 우선순위 조치 → 수동·사용자 심사 잔여 → 인증 준비도 → 면책 고지 순서의
5
+ * 보고서를 작성하도록 지시한다.
6
+ */
7
+ export function registerAuditReportPrompt(server) {
8
+ server.registerPrompt("audit-report", {
9
+ title: "감사 보고서 작성",
10
+ description: "검사 Report(JSON)로부터 KWCAG 2.2 감사 보고서를 작성하는 지시를 만든다. 33항목 판정표·우선순위 조치·인증 준비도·면책 고지를 포함한다.",
11
+ argsSchema: {
12
+ siteName: z.string().min(1).describe("대상 사이트/서비스 이름"),
13
+ reportJson: z.string().min(1).describe("check_html·audit_url의 Report JSON(1개 또는 배열)"),
14
+ audience: z.enum(["developer", "manager", "certification"]).optional().describe("독자(선택)"),
15
+ },
16
+ }, ({ siteName, reportJson, audience }) => {
17
+ const aud = audience ?? "developer";
18
+ const audienceNote = {
19
+ developer: "개발자 대상: 코드 수정안·선택자·before/after를 중심으로 작성하세요.",
20
+ manager: "관리자 대상: 우선순위·공수·리스크 요약을 중심으로, 코드는 최소화하세요.",
21
+ certification: "인증 담당 대상: 33항목 판정표·준수율·잔여 심사 항목을 중심으로 근거·출처를 명시하세요.",
22
+ };
23
+ const parts = [
24
+ `당신은 KWCAG 2.2 심사관입니다. 아래 검사 결과로 "${siteName}"의 웹 접근성 감사 보고서를 작성하세요.`,
25
+ "",
26
+ "## 작성 형식 (skills/a11y-audit/references/report-template.md)",
27
+ "1. **요약** — 대상·표본 수·종합 판정(실패/검토 필요/통과)과 한 문장 근거, 자동 검사 커버리지(추정치임을 명시)",
28
+ "2. **33개 검사항목 판정표** — 검사항목을 `6.4.1(2.4.1)` 형식으로, 자동화 등급·상태·비고",
29
+ "3. **우선순위 조치** — 자동·보조 등급 항목 중 준수율 낮은 순, 영향·수정안",
30
+ "4. **수동·사용자 심사 잔여** — 사람이 확인해야 하는 항목(검사항목 ID + 확인 질문), 장애 유형별 과업 예시",
31
+ "5. **인증 준비도** — 여러 페이지면 `estimate_cert_readiness`를 호출해 준수율·gaps·manualRemaining을 반영. 품질인증 기준 수치는 인증기관 공지로 재확인 필요임을 명시",
32
+ "6. **면책 고지** — 자동 판정은 전문가·사용자 심사를 대체하지 않는 참고 결과임을 명시",
33
+ "",
34
+ `## 독자`,
35
+ audienceNote[aud],
36
+ "",
37
+ "## 검사 결과(Report JSON)",
38
+ "```json",
39
+ reportJson,
40
+ "```",
41
+ ];
42
+ return { messages: [{ role: "user", content: { type: "text", text: parts.join("\n") } }] };
43
+ });
44
+ }
45
+ //# sourceMappingURL=audit-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-report.js","sourceRoot":"","sources":["../../src/prompts/audit-report.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,yFAAyF;QACtG,UAAU,EAAE;YACV,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;YACrD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,6CAA6C,CAAC;YACrF,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;SAC1F;KACF,EACD,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,QAAQ,IAAI,WAAW,CAAC;QACpC,MAAM,YAAY,GAA2B;YAC3C,SAAS,EAAE,8CAA8C;YACzD,OAAO,EAAE,2CAA2C;YACpD,aAAa,EAAE,qDAAqD;SACrE,CAAC;QACF,MAAM,KAAK,GAAG;YACZ,oCAAoC,QAAQ,yBAAyB;YACrE,EAAE;YACF,4DAA4D;YAC5D,uEAAuE;YACvE,+DAA+D;YAC/D,iDAAiD;YACjD,qEAAqE;YACrE,uHAAuH;YACvH,sDAAsD;YACtD,EAAE;YACF,OAAO;YACP,YAAY,CAAC,GAAG,CAAC;YACjB,EAAE;YACF,uBAAuB;YACvB,SAAS;YACT,UAAU;YACV,KAAK;SACN,CAAC;QACF,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC7F,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * review-markup 프롬프트(FR-08, 02 §3.2). check_html 호출 → 결과를 6.4.1(2.4.1) 형식으로 인용하며
4
+ * before/after 수정안, 자동/보조/수동 구분, 수동 확인 목록, "심사 전 참고 결과" 고지까지 지시한다.
5
+ */
6
+ export function registerReviewMarkupPrompt(server) {
7
+ server.registerPrompt("review-markup", {
8
+ title: "마크업 접근성 검토",
9
+ description: "웹 UI 코드를 KWCAG 2.2 기준으로 검토하는 지시를 만든다. check_html 호출과 검사항목 인용·수정안 제시를 포함한다.",
10
+ argsSchema: {
11
+ html: z.string().min(1).describe("검토할 HTML/코드"),
12
+ framework: z.enum(["html", "react", "vue", "svelte"]).optional().describe("프레임워크(선택)"),
13
+ focus: z.string().optional().describe("집중할 검사항목 ID 콤마 목록(선택, 예: 6.4.1,7.3.2)"),
14
+ },
15
+ }, ({ html, framework, focus }) => {
16
+ const parts = [
17
+ "당신은 KWCAG 2.2(한국형 웹 콘텐츠 접근성 지침) 심사관입니다. 아래 코드의 접근성을 검토하고 수정안을 제시하세요.",
18
+ "",
19
+ "## 검토 방법",
20
+ "1. `check_html` 도구를 호출해 정적 검사 결과(Report)를 받습니다.",
21
+ "2. 발견한 문제는 검사항목을 **공식 번호(별칭)** 형식 `6.4.1(2.4.1)`으로 인용해 지적합니다.",
22
+ "3. 문제마다 before/after 코드로 구체적인 수정안을 제시합니다" +
23
+ (framework && framework !== "html" ? ` (${framework} 관례에 맞게).` : "."),
24
+ "4. 각 항목을 **자동/보조/수동** 판정으로 구분해 설명합니다(자동 검사가 확정한 것과 사람이 확인해야 하는 것을 분리).",
25
+ "5. Report의 `manualChecklist`를 바탕으로 **수동 확인이 필요한 항목**을 목록으로 정리합니다.",
26
+ "6. 렌더링이 필요한 항목(명도 대비·초점 표시·타깃 크기)은 `audit_url` 브라우저 감사로 보완하도록 안내합니다.",
27
+ ];
28
+ if (focus && focus.trim().length > 0) {
29
+ parts.push("", `## 집중 검토 항목`, `다음 검사항목을 특히 자세히 다루세요: ${focus}`);
30
+ }
31
+ parts.push("", "## 고지", "자동 검사 결과는 전문가·사용자 심사 전 참고 결과이며, 인증 통과 여부를 판정하지 않습니다.", "", "## 검토할 코드", "```" + (framework ?? "html"), html, "```");
32
+ return { messages: [{ role: "user", content: { type: "text", text: parts.join("\n") } }] };
33
+ });
34
+ }
35
+ //# sourceMappingURL=review-markup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-markup.js","sourceRoot":"","sources":["../../src/prompts/review-markup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAiB;IAC1D,MAAM,CAAC,cAAc,CACnB,eAAe,EACf;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,4EAA4E;QACzF,UAAU,EAAE;YACV,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC;YAC/C,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;YACtF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;SAC/E;KACF,EACD,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG;YACZ,sEAAsE;YACtE,EAAE;YACF,UAAU;YACV,iDAAiD;YACjD,+DAA+D;YAC/D,0CAA0C;gBACxC,CAAC,SAAS,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC;YACvE,wEAAwE;YACxE,mEAAmE;YACnE,sEAAsE;SACvE,CAAC;QACF,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,EAAE,yBAAyB,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,KAAK,CAAC,IAAI,CACR,EAAE,EACF,OAAO,EACP,sDAAsD,EACtD,EAAE,EACF,WAAW,EACX,KAAK,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC,EAC7B,IAAI,EACJ,KAAK,CACN,CAAC;QACF,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC7F,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,72 @@
1
+ const round2 = (n) => Math.round(n * 100) / 100;
2
+ const pct = (n) => Math.round(n * 100);
3
+ function aggregateStatus(statuses, automation, failingPages, incompletePages) {
4
+ if (failingPages > 0)
5
+ return "fail";
6
+ if (incompletePages > 0)
7
+ return "incomplete";
8
+ if (statuses.some((s) => s === "pass"))
9
+ return "pass";
10
+ if (automation === "na")
11
+ return "na";
12
+ return "manual";
13
+ }
14
+ /**
15
+ * 여러 페이지 Report를 검사항목별 준수율로 집계해 인증 준비도를 추정한다(FR-07, 02 §3.1).
16
+ * 자동 검사만으로는 인증 통과를 판정할 수 없으며, estimatedExpertRate는 자동·보조 등급 항목만으로
17
+ * 계산한 **추정치**다(notices에 고정 고지).
18
+ */
19
+ export function estimateCertReadiness(reports, pageCount, data) {
20
+ const pages = reports.length;
21
+ const expertCriterion = data.certification.criteria.find((c) => c.kind === "expert");
22
+ const expertThreshold = expertCriterion?.value ?? 0.95;
23
+ const checkpoints = data.kwcag22.checkpoints.map((cp) => {
24
+ const statuses = reports.map((r) => r.checkpoints.find((c) => c.id === cp.id)?.status ?? "manual");
25
+ const failingPages = statuses.filter((s) => s === "fail").length;
26
+ const incompletePages = statuses.filter((s) => s === "incomplete").length;
27
+ const complianceRate = pages > 0 ? (pages - failingPages) / pages : 1;
28
+ return {
29
+ id: cp.id,
30
+ alias: cp.alias,
31
+ name: cp.name_ko,
32
+ automation: cp.automation,
33
+ pages,
34
+ failingPages,
35
+ complianceRate: round2(complianceRate),
36
+ status: aggregateStatus(statuses, cp.automation, failingPages, incompletePages),
37
+ };
38
+ });
39
+ const autoAssist = checkpoints.filter((c) => c.automation === "auto" || c.automation === "assist");
40
+ const autoCheckedCoverage = round2(autoAssist.length / checkpoints.length);
41
+ const estimatedExpertRate = autoAssist.length > 0
42
+ ? round2(autoAssist.reduce((sum, c) => sum + c.complianceRate, 0) / autoAssist.length)
43
+ : 1;
44
+ const gaps = autoAssist
45
+ .filter((c) => c.complianceRate < expertThreshold)
46
+ .sort((a, b) => b.failingPages - a.failingPages || a.complianceRate - b.complianceRate)
47
+ .map((c, i) => ({
48
+ id: c.id,
49
+ failingPages: c.failingPages,
50
+ priority: i + 1,
51
+ reason: `${c.automation === "auto" ? "자동" : "보조"} 등급 항목 준수율 ${pct(c.complianceRate)}% < ${pct(expertThreshold)}%`,
52
+ }));
53
+ const manualRemaining = checkpoints
54
+ .filter((c) => c.status === "manual")
55
+ .map((c) => ({ id: c.id, name: c.name }));
56
+ const criteriaText = data.certification.criteria
57
+ .map((c) => c.text)
58
+ .join(" · ");
59
+ const notices = [
60
+ `${criteriaText} 기준은 인증기관 공지로 재확인 필요(확인일: ${expertCriterion?.verifiedOn ?? "미확인"}).`,
61
+ "estimatedExpertRate는 자동·보조 등급 항목만으로 계산한 추정치입니다.",
62
+ "자동 검사 결과만으로 인증 통과를 판단할 수 없습니다. 수동·사용자 심사 항목이 남아 있습니다.",
63
+ ];
64
+ return {
65
+ checkpoints,
66
+ overall: { autoCheckedCoverage, estimatedExpertRate, pagesAudited: pageCount ?? pages },
67
+ gaps,
68
+ manualRemaining,
69
+ notices,
70
+ };
71
+ }
72
+ //# sourceMappingURL=cert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cert.js","sourceRoot":"","sources":["../../src/report/cert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ const VERDICT_KO = {
2
+ fail: "실패",
3
+ "needs-review": "검토 필요",
4
+ pass: "통과",
5
+ };
6
+ /** 도구 응답의 content(한국어 요약)를 만든다(03 §8: 판정 → 항목별 상태 → 상위 Finding → 수동 확인 → notices). */
7
+ export function formatReportText(report) {
8
+ const lines = [
9
+ `판정: ${VERDICT_KO[report.verdict]}`,
10
+ `요약: fail ${report.summary.fail} · incomplete ${report.summary.incomplete} · manual ${report.summary.manual} · pass ${report.summary.pass} · na ${report.summary.na}`,
11
+ ];
12
+ const attention = report.checkpoints.filter((cp) => cp.status === "fail" || cp.status === "incomplete");
13
+ if (attention.length > 0) {
14
+ lines.push("", "검사항목별 상태:");
15
+ for (const cp of attention)
16
+ lines.push(`- ${cp.id}(${cp.alias}) ${cp.name}: ${cp.status}`);
17
+ }
18
+ const top = report.findings.slice(0, 10);
19
+ if (top.length > 0) {
20
+ lines.push("", "상위 Finding:");
21
+ for (const f of top)
22
+ lines.push(`- [${f.impact}] ${f.ruleId} ${f.selector}: ${f.message}`);
23
+ }
24
+ if (report.manualChecklist.length > 0) {
25
+ lines.push("", "수동 확인이 필요한 항목:");
26
+ for (const m of report.manualChecklist)
27
+ lines.push(`- ${m.kwcag}(${m.alias}): ${m.question}`);
28
+ }
29
+ if (report.notices.length > 0) {
30
+ lines.push("", "안내:");
31
+ for (const n of report.notices)
32
+ lines.push(`- ${n}`);
33
+ }
34
+ return lines.join("\n");
35
+ }
36
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/report/format.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAsC;IACpD,IAAI,EAAE,IAAI;IACV,cAAc,EAAE,OAAO;IACvB,IAAI,EAAE,IAAI;CACX,CAAC;AAEF,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,KAAK,GAAa;QACtB,OAAO,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;QACnC,YAAY,MAAM,CAAC,OAAO,CAAC,IAAI,iBAAiB,MAAM,CAAC,OAAO,CAAC,UAAU,aAAa,MAAM,CAAC,OAAO,CAAC,MAAM,WAAW,MAAM,CAAC,OAAO,CAAC,IAAI,SAAS,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE;KACtK,CAAC;IAEF,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,KAAK,MAAM,IAAI,EAAE,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC;IACxG,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QAC5B,KAAK,MAAM,EAAE,IAAI,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,eAAe;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChG,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACtB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { computeCheckpoints } from "../normalize/checkpoints.js";
2
+ const IMPACT_ORDER = { critical: 0, serious: 1, moderate: 2, minor: 3 };
3
+ /** verdict: fail=자동 등급 규칙 fail≥1 · needs-review=보조 등급 fail 또는 incomplete만 · pass=둘 다 없음(02 §4). */
4
+ export function computeVerdict(checkpoints) {
5
+ const autoFail = checkpoints.some((cp) => cp.automation === "auto" && cp.status === "fail");
6
+ if (autoFail)
7
+ return "fail";
8
+ const needsReview = checkpoints.some((cp) => (cp.automation === "assist" && cp.status === "fail") || cp.status === "incomplete");
9
+ if (needsReview)
10
+ return "needs-review";
11
+ return "pass";
12
+ }
13
+ function summarizeFindings(checkpoints, findings, truncated) {
14
+ const tally = { fail: 0, incomplete: 0, manual: 0, pass: 0, na: 0 };
15
+ for (const cp of checkpoints)
16
+ tally[cp.status] += 1;
17
+ const byImpact = { critical: 0, serious: 0, moderate: 0, minor: 0 };
18
+ for (const f of findings) {
19
+ if (f.outcome === "fail")
20
+ byImpact[f.impact] += 1;
21
+ }
22
+ return { ...tally, byImpact, truncated };
23
+ }
24
+ function buildManualChecklist(checkpoints, data) {
25
+ return checkpoints
26
+ .filter((cp) => cp.status === "manual")
27
+ .map((cp) => {
28
+ const full = data.kwcag22.findById(cp.id);
29
+ return { kwcag: cp.id, alias: cp.alias, question: full?.testMethod_ko[0] ?? full?.summary_ko ?? cp.name };
30
+ });
31
+ }
32
+ function buildNotices(mode, truncated) {
33
+ const notices = [
34
+ "자동 검사는 33개 검사항목 중 일부만 판정합니다. 인증 통과 여부는 전문가·사용자 심사로 결정됩니다.",
35
+ ];
36
+ if (mode === "static") {
37
+ notices.push("정적 검사는 렌더링이 필요한 항목(명도 대비·초점 표시·타깃 크기 등)을 판정하지 못합니다. 브라우저 감사(audit_url)로 보완하세요.");
38
+ }
39
+ if (truncated) {
40
+ notices.push("Finding이 maxFindings를 초과해 일부가 생략되었습니다. impact·검사항목 순으로 상위 항목만 표시합니다.");
41
+ }
42
+ return notices;
43
+ }
44
+ /**
45
+ * Finding[] → Report의 나머지 필드(verdict·summary·checkpoints·manualChecklist·notices)를 만든다.
46
+ * 33항목 status는 항상 전체 findings 기준으로 계산하고(truncated 여부와 무관하게 정확해야 한다),
47
+ * 응답에 담을 findings만 impact·개수 순으로 절단한다.
48
+ */
49
+ export function summarize(findings, mode, data, maxFindings) {
50
+ const checkpoints = computeCheckpoints(findings, mode, data);
51
+ const verdict = computeVerdict(checkpoints);
52
+ const truncated = findings.length > maxFindings;
53
+ const limited = [...findings]
54
+ .sort((a, b) => IMPACT_ORDER[a.impact] - IMPACT_ORDER[b.impact])
55
+ .slice(0, maxFindings);
56
+ const summary = summarizeFindings(checkpoints, findings, truncated);
57
+ const manualChecklist = buildManualChecklist(checkpoints, data);
58
+ const notices = buildNotices(mode, truncated);
59
+ return { verdict, summary, checkpoints, findings: limited, manualChecklist, notices };
60
+ }
61
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/report/summarize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/report/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,69 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ function readAsset(relativePath) {
4
+ return readFileSync(new URL(relativePath, import.meta.url), "utf8");
5
+ }
6
+ function certificationMarkdown(data) {
7
+ const c = data.certification;
8
+ const criteriaLines = c.criteria
9
+ .map((item) => `- **${item.kind}**: ${item.text}${item.needsVerification ? " (인증기관 공지로 재확인 필요)" : ""}`)
10
+ .join("\n");
11
+ const agencyLines = c.agencies.map((a) => `- ${a.name}${a.url ? ` (${a.url})` : ""}`).join("\n");
12
+ return [
13
+ `# 웹접근성 품질인증 절차·기준`,
14
+ ``,
15
+ `## 법적 근거`,
16
+ ...c.legalBasis.map((l) => `- ${l.law} ${l.article}${l.needsVerification ? " (검증 필요)" : ""}`),
17
+ ``,
18
+ `## 기준`,
19
+ criteriaLines,
20
+ ``,
21
+ `## 유효기간`,
22
+ `- ${c.validity.text}`,
23
+ ``,
24
+ `## 절차`,
25
+ c.procedure.map((step, i) => `${i + 1}. ${step}`).join("\n"),
26
+ ``,
27
+ `## 인증기관`,
28
+ agencyLines,
29
+ ``,
30
+ `> 자동 검사 도구는 인증 심사를 대체하지 않습니다. 수치는 인증기관 공지로 최종 확인하세요.`,
31
+ ].join("\n");
32
+ }
33
+ /** 도구 7·프롬프트 2와 별개로 지식 리소스 6종을 등록한다(02-architecture §3.3). */
34
+ export function registerResources(server, data) {
35
+ server.registerResource("kwcag22", "swwa://kwcag22", { description: "KWCAG 2.2 검사항목 33개 체크리스트(단일 소스 md 번들)", mimeType: "text/markdown" }, () => ({
36
+ contents: [
37
+ { uri: "swwa://kwcag22", mimeType: "text/markdown", text: readAsset("../../assets/kwcag22-checklist.md") },
38
+ ],
39
+ }));
40
+ server.registerResource("kwcag22-item", new ResourceTemplate("swwa://kwcag22/{id}", { list: undefined }), { description: "검사항목 1건 JSON(공식 번호·별칭 모두 허용)", mimeType: "application/json" }, (uri, variables) => {
41
+ const id = Array.isArray(variables.id) ? variables.id[0] : variables.id;
42
+ const cp = id ? data.kwcag22.findById(id) : undefined;
43
+ const text = JSON.stringify(cp ?? { error: `"${id}" 검사항목을 찾지 못했습니다.` }, null, 2);
44
+ return { contents: [{ uri: uri.href, mimeType: "application/json", text }] };
45
+ });
46
+ server.registerResource("mapping-wcag22", "swwa://mapping/wcag22", { description: "WCAG 2.2 ↔ KWCAG 2.2 ↔ axe 규칙 매핑", mimeType: "application/json" }, () => ({
47
+ contents: [
48
+ {
49
+ uri: "swwa://mapping/wcag22",
50
+ mimeType: "application/json",
51
+ text: JSON.stringify({ wcag22: data.wcag22.criteria, axeRuleMap: data.axeRuleMap }, null, 2),
52
+ },
53
+ ],
54
+ }));
55
+ server.registerResource("certification", "swwa://certification", { description: "품질인증 절차·기준·기관(확인일·출처 포함)", mimeType: "text/markdown" }, () => ({
56
+ contents: [{ uri: "swwa://certification", mimeType: "text/markdown", text: certificationMarkdown(data) }],
57
+ }));
58
+ server.registerResource("mobile-app-2.0", "swwa://mobile-app-2.0", { description: "모바일 애플리케이션 콘텐츠 접근성 지침 2.0 요약", mimeType: "text/markdown" }, () => ({
59
+ contents: [
60
+ { uri: "swwa://mobile-app-2.0", mimeType: "text/markdown", text: readAsset("../../assets/mobile-app-2.0.md") },
61
+ ],
62
+ }));
63
+ server.registerResource("sources", "swwa://sources", { description: "출처·확인일·접근 상태·라이선스 메모", mimeType: "application/json" }, () => ({
64
+ contents: [
65
+ { uri: "swwa://sources", mimeType: "application/json", text: JSON.stringify(data.sources, null, 2) },
66
+ ],
67
+ }));
68
+ }
69
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAG3E,SAAS,SAAS,CAAC,YAAoB;IACrC,OAAO,YAAY,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAgB;IAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC;IAC7B,MAAM,aAAa,GAAG,CAAC,CAAC,QAAQ;SAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;SACtG,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,WAAW,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjG,OAAO;QACL,mBAAmB;QACnB,EAAE;QACF,UAAU;QACV,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7F,EAAE;QACF,OAAO;QACP,aAAa;QACb,EAAE;QACF,SAAS;QACT,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE;QACtB,EAAE;QACF,OAAO;QACP,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5D,EAAE;QACF,SAAS;QACT,WAAW;QACX,EAAE;QACF,sDAAsD;KACvD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,IAAgB;IACnE,MAAM,CAAC,gBAAgB,CACrB,SAAS,EACT,gBAAgB,EAChB,EAAE,WAAW,EAAE,uCAAuC,EAAE,QAAQ,EAAE,eAAe,EAAE,EACnF,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ,EAAE;YACR,EAAE,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,CAAC,mCAAmC,CAAC,EAAE;SAC3G;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,cAAc,EACd,IAAI,gBAAgB,CAAC,qBAAqB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAChE,EAAE,WAAW,EAAE,8BAA8B,EAAE,QAAQ,EAAE,kBAAkB,EAAE,EAC7E,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE;QACjB,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;QACxE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACjF,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC/E,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,gBAAgB,EAChB,uBAAuB,EACvB,EAAE,WAAW,EAAE,kCAAkC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,EACjF,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,uBAAuB;gBAC5B,QAAQ,EAAE,kBAAkB;gBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;aAC7F;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,eAAe,EACf,sBAAsB,EACtB,EAAE,WAAW,EAAE,0BAA0B,EAAE,QAAQ,EAAE,eAAe,EAAE,EACtE,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,sBAAsB,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC;KAC1G,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,gBAAgB,EAChB,uBAAuB,EACvB,EAAE,WAAW,EAAE,8BAA8B,EAAE,QAAQ,EAAE,eAAe,EAAE,EAC1E,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ,EAAE;YACR,EAAE,GAAG,EAAE,uBAAuB,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,CAAC,gCAAgC,CAAC,EAAE;SAC/G;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,SAAS,EACT,gBAAgB,EAChB,EAAE,WAAW,EAAE,sBAAsB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,EACrE,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ,EAAE;YACR,EAAE,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;SACrG;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { createFinding } from "../../normalize/finding.js";
2
+ import { tabTraversal } from "../util/browser-dom.js";
3
+ import { bFocusOrder } from "../../messages.js";
4
+ export const rule = {
5
+ id: "b-focus-order",
6
+ kwcag: "6.1.2",
7
+ wcag: ["2.4.3"],
8
+ engine: "b",
9
+ impact: "moderate",
10
+ confidence: "high",
11
+ tier: "B",
12
+ async run(ctx) {
13
+ const stops = await tabTraversal(ctx.page, 100);
14
+ const findings = [];
15
+ let maxSeenIndex = -1;
16
+ for (const stop of stops) {
17
+ if (stop.domIndex < maxSeenIndex) {
18
+ findings.push(createFinding(rule, null, {
19
+ message: bFocusOrder.message,
20
+ fix: bFocusOrder.fix,
21
+ outcome: "fail",
22
+ selectorOverride: stop.selector,
23
+ htmlOverride: stop.html,
24
+ }));
25
+ }
26
+ else {
27
+ maxSeenIndex = stop.domIndex;
28
+ }
29
+ }
30
+ return findings;
31
+ },
32
+ };
33
+ //# sourceMappingURL=b-focus-order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"b-focus-order.js","sourceRoot":"","sources":["../../../src/rules/b/b-focus-order.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhD,MAAM,CAAC,MAAM,IAAI,GAAgB;IAC/B,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,GAAG;IACT,KAAK,CAAC,GAAG,CAAC,GAAmB;QAC3B,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC;QACtB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,QAAQ,GAAG,YAAY,EAAE,CAAC;gBACjC,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE;oBACxB,OAAO,EAAE,WAAW,CAAC,OAAO;oBAC5B,GAAG,EAAE,WAAW,CAAC,GAAG;oBACpB,OAAO,EAAE,MAAM;oBACf,gBAAgB,EAAE,IAAI,CAAC,QAAQ;oBAC/B,YAAY,EAAE,IAAI,CAAC,IAAI;iBACxB,CAAC,CACH,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { createFinding } from "../../normalize/finding.js";
2
+ import { bFocusVisible } from "../../messages.js";
3
+ const MAX_STOPS = 50;
4
+ export const rule = {
5
+ id: "b-focus-visible",
6
+ kwcag: "6.1.2",
7
+ wcag: ["2.4.7"],
8
+ engine: "b",
9
+ impact: "serious",
10
+ confidence: "high",
11
+ tier: "B",
12
+ async run(ctx) {
13
+ const { page } = ctx;
14
+ const baseline = await page.evaluate(() => {
15
+ const map = {};
16
+ Array.from(document.querySelectorAll("*")).forEach((el, i) => {
17
+ el.setAttribute("data-swwa-order", String(i));
18
+ const cs = getComputedStyle(el);
19
+ map[i] = [cs.outlineStyle, cs.outlineWidth, cs.outlineColor, cs.boxShadow, cs.borderColor, cs.backgroundColor].join("|");
20
+ });
21
+ return map;
22
+ });
23
+ const findings = [];
24
+ const seen = new Set();
25
+ for (let i = 0; i < MAX_STOPS; i++) {
26
+ await page.keyboard.press("Tab");
27
+ const info = await page.evaluate(() => {
28
+ const active = document.activeElement;
29
+ if (!active || active === document.body)
30
+ return null;
31
+ const idx = Number(active.getAttribute("data-swwa-order") ?? "-1");
32
+ const cs = getComputedStyle(active);
33
+ const style = [cs.outlineStyle, cs.outlineWidth, cs.outlineColor, cs.boxShadow, cs.borderColor, cs.backgroundColor].join("|");
34
+ function cssPath(node) {
35
+ const parts = [];
36
+ let cur = node;
37
+ while (cur) {
38
+ if (cur.id) {
39
+ parts.unshift(`#${cur.id}`);
40
+ break;
41
+ }
42
+ parts.unshift(cur.tagName.toLowerCase());
43
+ cur = cur.parentElement;
44
+ }
45
+ return parts.join(" > ");
46
+ }
47
+ return { idx, style, selector: cssPath(active), html: active.outerHTML.slice(0, 300) };
48
+ });
49
+ if (!info || info.idx < 0)
50
+ break;
51
+ if (seen.has(info.idx))
52
+ break;
53
+ seen.add(info.idx);
54
+ const before = baseline[String(info.idx)];
55
+ if (before === info.style) {
56
+ findings.push(createFinding(rule, null, {
57
+ message: bFocusVisible.message,
58
+ fix: bFocusVisible.fix,
59
+ outcome: "fail",
60
+ selectorOverride: info.selector,
61
+ htmlOverride: info.html,
62
+ }));
63
+ }
64
+ }
65
+ await page.evaluate(() => {
66
+ document.querySelectorAll("[data-swwa-order]").forEach((el) => el.removeAttribute("data-swwa-order"));
67
+ });
68
+ return findings;
69
+ },
70
+ };
71
+ //# sourceMappingURL=b-focus-visible.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"b-focus-visible.js","sourceRoot":"","sources":["../../../src/rules/b/b-focus-visible.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIlD,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,MAAM,CAAC,MAAM,IAAI,GAAgB;IAC/B,EAAE,EAAE,iBAAiB;IACrB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,GAAG;IACT,KAAK,CAAC,GAAG,CAAC,GAAmB;QAC3B,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;QAErB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;YACxC,MAAM,GAAG,GAA2B,EAAE,CAAC;YACvC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;gBAC3D,EAAE,CAAC,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9C,MAAM,EAAE,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;gBAChC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3H,CAAC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAE/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC;gBACtC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,QAAQ,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;gBACrD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC,CAAC;gBACnE,MAAM,EAAE,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACpC,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC9H,SAAS,OAAO,CAAC,IAAa;oBAC5B,MAAM,KAAK,GAAa,EAAE,CAAC;oBAC3B,IAAI,GAAG,GAAmB,IAAI,CAAC;oBAC/B,OAAO,GAAG,EAAE,CAAC;wBACX,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;4BACX,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;4BAC5B,MAAM;wBACR,CAAC;wBACD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;wBACzC,GAAG,GAAG,GAAG,CAAC,aAAa,CAAC;oBAC1B,CAAC;oBACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC3B,CAAC;gBACD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;YACzF,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC;gBAAE,MAAM;YACjC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,MAAM;YAC9B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAEnB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1C,IAAI,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE;oBACxB,OAAO,EAAE,aAAa,CAAC,OAAO;oBAC9B,GAAG,EAAE,aAAa,CAAC,GAAG;oBACtB,OAAO,EAAE,MAAM;oBACf,gBAAgB,EAAE,IAAI,CAAC,QAAQ;oBAC/B,YAAY,EAAE,IAAI,CAAC,IAAI;iBACxB,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvB,QAAQ,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC;QACxG,CAAC,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}