@oscharko-dev/keiko-quality-intelligence 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/__tests__/_fixtureLoader.d.ts +9 -0
  3. package/dist/__tests__/_fixtureLoader.d.ts.map +1 -0
  4. package/dist/__tests__/_fixtureLoader.js +75 -0
  5. package/dist/domain/assertions.d.ts +61 -0
  6. package/dist/domain/assertions.d.ts.map +1 -0
  7. package/dist/domain/assertions.js +134 -0
  8. package/dist/domain/coverageRelevance.d.ts +73 -0
  9. package/dist/domain/coverageRelevance.d.ts.map +1 -0
  10. package/dist/domain/coverageRelevance.js +155 -0
  11. package/dist/domain/deduplication.d.ts +17 -0
  12. package/dist/domain/deduplication.d.ts.map +1 -0
  13. package/dist/domain/deduplication.js +95 -0
  14. package/dist/domain/figma/a11yBaseline.d.ts +17 -0
  15. package/dist/domain/figma/a11yBaseline.d.ts.map +1 -0
  16. package/dist/domain/figma/a11yBaseline.js +218 -0
  17. package/dist/domain/figma/cleanToScreenIr.d.ts +3 -0
  18. package/dist/domain/figma/cleanToScreenIr.d.ts.map +1 -0
  19. package/dist/domain/figma/cleanToScreenIr.js +62 -0
  20. package/dist/domain/figma/codeTargetAdapter.d.ts +30 -0
  21. package/dist/domain/figma/codeTargetAdapter.d.ts.map +1 -0
  22. package/dist/domain/figma/codeTargetAdapter.js +27 -0
  23. package/dist/domain/figma/color.d.ts +31 -0
  24. package/dist/domain/figma/color.d.ts.map +1 -0
  25. package/dist/domain/figma/color.js +99 -0
  26. package/dist/domain/figma/emissionPlan.d.ts +56 -0
  27. package/dist/domain/figma/emissionPlan.d.ts.map +1 -0
  28. package/dist/domain/figma/emissionPlan.js +87 -0
  29. package/dist/domain/figma/htmlCssAdapter.d.ts +13 -0
  30. package/dist/domain/figma/htmlCssAdapter.d.ts.map +1 -0
  31. package/dist/domain/figma/htmlCssAdapter.js +452 -0
  32. package/dist/domain/figma/index.d.ts +19 -0
  33. package/dist/domain/figma/index.d.ts.map +1 -0
  34. package/dist/domain/figma/index.js +31 -0
  35. package/dist/domain/figma/irTypes.d.ts +156 -0
  36. package/dist/domain/figma/irTypes.d.ts.map +1 -0
  37. package/dist/domain/figma/irTypes.js +8 -0
  38. package/dist/domain/figma/links.d.ts +6 -0
  39. package/dist/domain/figma/links.d.ts.map +1 -0
  40. package/dist/domain/figma/links.js +102 -0
  41. package/dist/domain/figma/navGraph.d.ts +74 -0
  42. package/dist/domain/figma/navGraph.d.ts.map +1 -0
  43. package/dist/domain/figma/navGraph.js +315 -0
  44. package/dist/domain/figma/normalize.d.ts +7 -0
  45. package/dist/domain/figma/normalize.d.ts.map +1 -0
  46. package/dist/domain/figma/normalize.js +252 -0
  47. package/dist/domain/figma/prune.d.ts +15 -0
  48. package/dist/domain/figma/prune.d.ts.map +1 -0
  49. package/dist/domain/figma/prune.js +65 -0
  50. package/dist/domain/figma/screenDetect.d.ts +8 -0
  51. package/dist/domain/figma/screenDetect.d.ts.map +1 -0
  52. package/dist/domain/figma/screenDetect.js +35 -0
  53. package/dist/domain/figma/screenIrTestBaseline.d.ts +52 -0
  54. package/dist/domain/figma/screenIrTestBaseline.d.ts.map +1 -0
  55. package/dist/domain/figma/screenIrTestBaseline.js +326 -0
  56. package/dist/domain/figma/semanticNaming.d.ts +24 -0
  57. package/dist/domain/figma/semanticNaming.d.ts.map +1 -0
  58. package/dist/domain/figma/semanticNaming.js +67 -0
  59. package/dist/domain/figma/sourceNode.d.ts +24 -0
  60. package/dist/domain/figma/sourceNode.d.ts.map +1 -0
  61. package/dist/domain/figma/sourceNode.js +26 -0
  62. package/dist/domain/figma/tokens.d.ts +11 -0
  63. package/dist/domain/figma/tokens.d.ts.map +1 -0
  64. package/dist/domain/figma/tokens.js +148 -0
  65. package/dist/domain/figma/visionAugmentation.d.ts +14 -0
  66. package/dist/domain/figma/visionAugmentation.d.ts.map +1 -0
  67. package/dist/domain/figma/visionAugmentation.js +48 -0
  68. package/dist/domain/intentDerivation.d.ts +21 -0
  69. package/dist/domain/intentDerivation.d.ts.map +1 -0
  70. package/dist/domain/intentDerivation.js +126 -0
  71. package/dist/domain/policyProfile.d.ts +37 -0
  72. package/dist/domain/policyProfile.d.ts.map +1 -0
  73. package/dist/domain/policyProfile.js +94 -0
  74. package/dist/domain/requirementExcerpt.d.ts +9 -0
  75. package/dist/domain/requirementExcerpt.d.ts.map +1 -0
  76. package/dist/domain/requirementExcerpt.js +39 -0
  77. package/dist/domain/staleness.d.ts +56 -0
  78. package/dist/domain/staleness.d.ts.map +1 -0
  79. package/dist/domain/staleness.js +313 -0
  80. package/dist/domain/testDesignModel.d.ts +38 -0
  81. package/dist/domain/testDesignModel.d.ts.map +1 -0
  82. package/dist/domain/testDesignModel.js +264 -0
  83. package/dist/domain/testQualityRubric.d.ts +20 -0
  84. package/dist/domain/testQualityRubric.d.ts.map +1 -0
  85. package/dist/domain/testQualityRubric.js +38 -0
  86. package/dist/domain/validation.d.ts +7 -0
  87. package/dist/domain/validation.d.ts.map +1 -0
  88. package/dist/domain/validation.js +145 -0
  89. package/dist/export/adapters/alm.d.ts +4 -0
  90. package/dist/export/adapters/alm.d.ts.map +1 -0
  91. package/dist/export/adapters/alm.js +75 -0
  92. package/dist/export/adapters/csv.d.ts +5 -0
  93. package/dist/export/adapters/csv.d.ts.map +1 -0
  94. package/dist/export/adapters/csv.js +55 -0
  95. package/dist/export/adapters/index.d.ts +13 -0
  96. package/dist/export/adapters/index.d.ts.map +1 -0
  97. package/dist/export/adapters/index.js +15 -0
  98. package/dist/export/adapters/jira.d.ts +5 -0
  99. package/dist/export/adapters/jira.d.ts.map +1 -0
  100. package/dist/export/adapters/jira.js +79 -0
  101. package/dist/export/adapters/json.d.ts +3 -0
  102. package/dist/export/adapters/json.d.ts.map +1 -0
  103. package/dist/export/adapters/json.js +54 -0
  104. package/dist/export/adapters/markdown.d.ts +3 -0
  105. package/dist/export/adapters/markdown.d.ts.map +1 -0
  106. package/dist/export/adapters/markdown.js +88 -0
  107. package/dist/export/adapters/plaintext.d.ts +3 -0
  108. package/dist/export/adapters/plaintext.d.ts.map +1 -0
  109. package/dist/export/adapters/plaintext.js +65 -0
  110. package/dist/export/adapters/polarion.d.ts +4 -0
  111. package/dist/export/adapters/polarion.d.ts.map +1 -0
  112. package/dist/export/adapters/polarion.js +67 -0
  113. package/dist/export/adapters/qtest.d.ts +4 -0
  114. package/dist/export/adapters/qtest.d.ts.map +1 -0
  115. package/dist/export/adapters/qtest.js +78 -0
  116. package/dist/export/adapters/qualityCenter.d.ts +3 -0
  117. package/dist/export/adapters/qualityCenter.d.ts.map +1 -0
  118. package/dist/export/adapters/qualityCenter.js +56 -0
  119. package/dist/export/adapters/spreadsheetSafeCsv.d.ts +36 -0
  120. package/dist/export/adapters/spreadsheetSafeCsv.d.ts.map +1 -0
  121. package/dist/export/adapters/spreadsheetSafeCsv.js +157 -0
  122. package/dist/export/adapters/traceability.d.ts +34 -0
  123. package/dist/export/adapters/traceability.d.ts.map +1 -0
  124. package/dist/export/adapters/traceability.js +142 -0
  125. package/dist/export/adapters/xray.d.ts +4 -0
  126. package/dist/export/adapters/xray.d.ts.map +1 -0
  127. package/dist/export/adapters/xray.js +72 -0
  128. package/dist/export/formats.d.ts +29 -0
  129. package/dist/export/formats.d.ts.map +1 -0
  130. package/dist/export/formats.js +34 -0
  131. package/dist/export/index.d.ts +4 -0
  132. package/dist/export/index.d.ts.map +1 -0
  133. package/dist/export/index.js +10 -0
  134. package/dist/export/serialize.d.ts +17 -0
  135. package/dist/export/serialize.d.ts.map +1 -0
  136. package/dist/export/serialize.js +56 -0
  137. package/dist/export/textSafety.d.ts +15 -0
  138. package/dist/export/textSafety.d.ts.map +1 -0
  139. package/dist/export/textSafety.js +30 -0
  140. package/dist/generation/candidateBounds.d.ts +10 -0
  141. package/dist/generation/candidateBounds.d.ts.map +1 -0
  142. package/dist/generation/candidateBounds.js +14 -0
  143. package/dist/generation/index.d.ts +4 -0
  144. package/dist/generation/index.d.ts.map +1 -0
  145. package/dist/generation/index.js +20 -0
  146. package/dist/generation/parseGeneratedCandidates.d.ts +27 -0
  147. package/dist/generation/parseGeneratedCandidates.d.ts.map +1 -0
  148. package/dist/generation/parseGeneratedCandidates.js +253 -0
  149. package/dist/generation/prompt.d.ts +16 -0
  150. package/dist/generation/prompt.d.ts.map +1 -0
  151. package/dist/generation/prompt.js +151 -0
  152. package/dist/generation/requirementsIngestion.d.ts +21 -0
  153. package/dist/generation/requirementsIngestion.d.ts.map +1 -0
  154. package/dist/generation/requirementsIngestion.js +70 -0
  155. package/dist/hardening/index.d.ts +6 -0
  156. package/dist/hardening/index.d.ts.map +1 -0
  157. package/dist/hardening/index.js +8 -0
  158. package/dist/hardening/oversizeGuards.d.ts +21 -0
  159. package/dist/hardening/oversizeGuards.d.ts.map +1 -0
  160. package/dist/hardening/oversizeGuards.js +35 -0
  161. package/dist/hardening/pathSafety.d.ts +19 -0
  162. package/dist/hardening/pathSafety.d.ts.map +1 -0
  163. package/dist/hardening/pathSafety.js +61 -0
  164. package/dist/hardening/promptInjectionScrub.d.ts +17 -0
  165. package/dist/hardening/promptInjectionScrub.d.ts.map +1 -0
  166. package/dist/hardening/promptInjectionScrub.js +72 -0
  167. package/dist/index.d.ts +22 -0
  168. package/dist/index.d.ts.map +1 -0
  169. package/dist/index.js +44 -0
  170. package/dist/ingestion/adfParser.d.ts +61 -0
  171. package/dist/ingestion/adfParser.d.ts.map +1 -0
  172. package/dist/ingestion/adfParser.js +262 -0
  173. package/dist/ingestion/index.d.ts +6 -0
  174. package/dist/ingestion/index.d.ts.map +1 -0
  175. package/dist/ingestion/index.js +10 -0
  176. package/dist/ingestion/sourceMixPlanning.d.ts +36 -0
  177. package/dist/ingestion/sourceMixPlanning.d.ts.map +1 -0
  178. package/dist/ingestion/sourceMixPlanning.js +65 -0
  179. package/dist/ingestion/sourceReconciliation.d.ts +39 -0
  180. package/dist/ingestion/sourceReconciliation.d.ts.map +1 -0
  181. package/dist/ingestion/sourceReconciliation.js +74 -0
  182. package/dist/ingestion/untrustedContentNormalisation.d.ts +23 -0
  183. package/dist/ingestion/untrustedContentNormalisation.d.ts.map +1 -0
  184. package/dist/ingestion/untrustedContentNormalisation.js +121 -0
  185. package/dist/ingestion/workspaceAdapter.d.ts +55 -0
  186. package/dist/ingestion/workspaceAdapter.d.ts.map +1 -0
  187. package/dist/ingestion/workspaceAdapter.js +113 -0
  188. package/dist/review/auditEvents.d.ts +61 -0
  189. package/dist/review/auditEvents.d.ts.map +1 -0
  190. package/dist/review/auditEvents.js +50 -0
  191. package/dist/review/fourEyes.d.ts +24 -0
  192. package/dist/review/fourEyes.d.ts.map +1 -0
  193. package/dist/review/fourEyes.js +45 -0
  194. package/dist/review/index.d.ts +5 -0
  195. package/dist/review/index.d.ts.map +1 -0
  196. package/dist/review/index.js +14 -0
  197. package/dist/review/lifecyclePolicy.d.ts +21 -0
  198. package/dist/review/lifecyclePolicy.d.ts.map +1 -0
  199. package/dist/review/lifecyclePolicy.js +38 -0
  200. package/dist/review/stateMachine.d.ts +28 -0
  201. package/dist/review/stateMachine.d.ts.map +1 -0
  202. package/dist/review/stateMachine.js +71 -0
  203. package/package.json +31 -0
@@ -0,0 +1,61 @@
1
+ // Quality Intelligence — path-safety predicates (Epic #270, Issue #284).
2
+ //
3
+ // Pure synchronous predicates that reject obviously-dangerous relative paths
4
+ // BEFORE they cross any IO seam (Workspace, Evidence, Connector). The package
5
+ // itself performs no IO; these helpers exist so adjacent leaves can reject
6
+ // adversarial inputs at the contract boundary with byte-stable, cross-platform
7
+ // semantics. No `node:path` import: rule is intentionally `path.sep`-agnostic
8
+ // so the same predicate accepts/rejects identical strings on POSIX and Windows.
9
+ //
10
+ // Structurally inspired by Test Intelligence reference (TI) path-guard rules,
11
+ // rewritten against the Keiko contracts surface.
12
+ /** Maximum permitted UTF-16 length of a safe relative path. */
13
+ export const MAX_SAFE_RELATIVE_PATH_LENGTH = 256;
14
+ const FORBIDDEN_DOTDOT_SEGMENT = /(^|[\\/])\.\.([\\/]|$)/u;
15
+ const containsControlOrNullByte = (value) => {
16
+ for (let index = 0; index < value.length; index += 1) {
17
+ const code = value.charCodeAt(index);
18
+ // C0 control range (includes NUL 0x00), DEL, and C1 control range.
19
+ if (code <= 0x1f)
20
+ return true;
21
+ if (code === 0x7f)
22
+ return true;
23
+ if (code >= 0x80 && code <= 0x9f)
24
+ return true;
25
+ }
26
+ return false;
27
+ };
28
+ /**
29
+ * Predicate: returns `true` when the supplied string is a syntactically safe
30
+ * relative path acceptable for use as a contract field (Quality Intelligence
31
+ * evidence reference, source-mix planning key, etc.).
32
+ *
33
+ * Rejects when ANY of the following hold:
34
+ * - empty string
35
+ * - contains a `..` path segment (POSIX or Windows separator)
36
+ * - contains a null byte or any C0/DEL control char
37
+ * - starts with `/` (POSIX absolute) or `\` (Windows root)
38
+ * - contains `:` (Windows drive letter, NTFS ADS, scheme prefix)
39
+ * - exceeds {@link MAX_SAFE_RELATIVE_PATH_LENGTH} UTF-16 code units
40
+ *
41
+ * Pure: no IO, no clock, no randomness, no `node:path`.
42
+ */
43
+ export const isSafeRelativePath = (candidate) => {
44
+ if (typeof candidate !== "string")
45
+ return false;
46
+ if (candidate.length === 0)
47
+ return false;
48
+ if (candidate.length > MAX_SAFE_RELATIVE_PATH_LENGTH)
49
+ return false;
50
+ if (candidate.startsWith("/"))
51
+ return false;
52
+ if (candidate.startsWith("\\"))
53
+ return false;
54
+ if (candidate.includes(":"))
55
+ return false;
56
+ if (containsControlOrNullByte(candidate))
57
+ return false;
58
+ if (FORBIDDEN_DOTDOT_SEGMENT.test(candidate))
59
+ return false;
60
+ return true;
61
+ };
@@ -0,0 +1,17 @@
1
+ export interface PromptInjectionScanResult {
2
+ readonly safe: boolean;
3
+ readonly injections: readonly string[];
4
+ }
5
+ /** Number of patterns in the injection corpus. Useful for tests/diagnostics. */
6
+ export declare const PROMPT_INJECTION_PATTERN_COUNT: number;
7
+ /**
8
+ * Scan an untrusted text payload for known prompt-injection vectors.
9
+ *
10
+ * Returns `{ safe: true, injections: [] }` when no pattern matches, or
11
+ * `{ safe: false, injections }` with the names of the matched patterns. The
12
+ * caller decides whether to redact, quarantine, or fail-closed.
13
+ *
14
+ * Pure: no IO, no clock, no randomness.
15
+ */
16
+ export declare const scanForPromptInjections: (input: string) => PromptInjectionScanResult;
17
+ //# sourceMappingURL=promptInjectionScrub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promptInjectionScrub.d.ts","sourceRoot":"","sources":["../../src/hardening/promptInjectionScrub.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AA2CD,gFAAgF;AAChF,eAAO,MAAM,8BAA8B,QAAmC,CAAC;AAE/E;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,GAAI,OAAO,MAAM,KAAG,yBAWvD,CAAC"}
@@ -0,0 +1,72 @@
1
+ // Quality Intelligence — prompt-injection scrubber (Epic #270, Issue #284).
2
+ //
3
+ // Pure detection of prompt-injection vectors that survive
4
+ // `normaliseUntrustedContent` (Issue #278). The scrubber returns the list of
5
+ // matched patterns instead of mutating the text, so callers may quarantine the
6
+ // snippet, redact it, or fail-closed. The injection patterns are layered as a
7
+ // second line of defence behind the Markdown escapes in #278 — they aim at
8
+ // natural-language imperatives that markdown escaping does NOT touch.
9
+ //
10
+ // Pure: no IO, no clock, no randomness. Matching is case-insensitive over the
11
+ // NFKC-normalised + control-stripped form of the input — callers SHOULD run
12
+ // `normaliseUntrustedContent` first so the patterns see the canonical form.
13
+ //
14
+ // Structurally inspired by Test Intelligence reference (TI) prompt-injection
15
+ // detectors, rewritten against the Keiko contracts surface.
16
+ const PROMPT_INJECTION_PATTERNS = [
17
+ {
18
+ name: "ignore-previous-instructions",
19
+ pattern: /ignore (?:all |the )?previous instructions?/iu,
20
+ },
21
+ {
22
+ name: "disregard-previous-instructions",
23
+ pattern: /disregard (?:all |the )?(?:previous|above) instructions?/iu,
24
+ },
25
+ { name: "override-system-prompt", pattern: /(?:override|bypass) (?:the )?system prompt/iu },
26
+ { name: "system-role-injection", pattern: /(^|[\n\r])\s*system\s*:/iu },
27
+ { name: "assistant-role-injection", pattern: /(^|[\n\r])\s*assistant\s*:/iu },
28
+ { name: "developer-role-injection", pattern: /(^|[\n\r])\s*developer\s*:/iu },
29
+ { name: "im-start-token", pattern: /<\|im_start\|>/iu },
30
+ { name: "im-end-token", pattern: /<\|im_end\|>/iu },
31
+ { name: "endoftext-token", pattern: /<\|endoftext\|>/iu },
32
+ { name: "markdown-system-heading", pattern: /(^|\n)#{1,6}\s*system\b/iu },
33
+ {
34
+ name: "you-are-now-jailbreak",
35
+ pattern: /you are (?:now )?(?:a |an )?(?:dan|jailbroken|unrestricted)/iu,
36
+ },
37
+ {
38
+ name: "act-as-different-model",
39
+ pattern: /act as (?:a |an )?(?:different|new) (?:ai|model|assistant)/iu,
40
+ },
41
+ {
42
+ name: "reveal-system-prompt",
43
+ pattern: /(?:reveal|print|show|output) (?:the |your )?system prompt/iu,
44
+ },
45
+ {
46
+ name: "exfiltrate-secrets",
47
+ pattern: /(?:reveal|print|show|leak|exfiltrate) (?:the |your |any )?(?:api[\s-]?key|secret|password|token)s?/iu,
48
+ },
49
+ ];
50
+ /** Number of patterns in the injection corpus. Useful for tests/diagnostics. */
51
+ export const PROMPT_INJECTION_PATTERN_COUNT = PROMPT_INJECTION_PATTERNS.length;
52
+ /**
53
+ * Scan an untrusted text payload for known prompt-injection vectors.
54
+ *
55
+ * Returns `{ safe: true, injections: [] }` when no pattern matches, or
56
+ * `{ safe: false, injections }` with the names of the matched patterns. The
57
+ * caller decides whether to redact, quarantine, or fail-closed.
58
+ *
59
+ * Pure: no IO, no clock, no randomness.
60
+ */
61
+ export const scanForPromptInjections = (input) => {
62
+ if (typeof input !== "string" || input.length === 0) {
63
+ return { safe: true, injections: [] };
64
+ }
65
+ const matched = [];
66
+ for (const { name, pattern } of PROMPT_INJECTION_PATTERNS) {
67
+ if (pattern.test(input)) {
68
+ matched.push(name);
69
+ }
70
+ }
71
+ return { safe: matched.length === 0, injections: matched };
72
+ };
@@ -0,0 +1,22 @@
1
+ export { compareStaleness } from "./domain/staleness.js";
2
+ export type { StalenessReason, StalenessResult } from "./domain/staleness.js";
3
+ export { deriveIntent } from "./domain/intentDerivation.js";
4
+ export type { IntentSummary } from "./domain/intentDerivation.js";
5
+ export { designTestCaseCandidates, DETERMINISTIC_BASELINE_PROVENANCE_TAG, } from "./domain/testDesignModel.js";
6
+ export type { DesignTestCaseCandidatesInput } from "./domain/testDesignModel.js";
7
+ export { buildAtomCoverageStatuses, buildCoverageMap, classifyAtomCoverage, COVERAGE_THRESHOLD_COVERED, COVERAGE_THRESHOLD_WEAKLY_COVERED, runCoveragePercentage, } from "./domain/coverageRelevance.js";
8
+ export type { AtomCoverageStatus, BuildCoverageMapInput, CoverageStatus, } from "./domain/coverageRelevance.js";
9
+ export { buildRequirementExcerpt, REQUIREMENT_EXCERPT_MAX_CHARS, } from "./domain/requirementExcerpt.js";
10
+ export { computeCandidateEquivalenceSignature, deduplicateCandidates, } from "./domain/deduplication.js";
11
+ export { validateCandidates } from "./domain/validation.js";
12
+ export { ALL_POLICY_PROFILES, bankingDefault, insuranceDefault, regressionDefault, } from "./domain/policyProfile.js";
13
+ export type { PolicyProfile } from "./domain/policyProfile.js";
14
+ export { canonicaliseFragmentList, isKnownPriority, isMeaningfulText, isUnsafeFormatCodePoint, normaliseCandidateText, normaliseText, stripUnsafeFormatChars, } from "./domain/assertions.js";
15
+ export { TEST_QUALITY_WEAK_THRESHOLD, scoreFromDimensions, verdictFromDimensions, verdictFromScore, } from "./domain/testQualityRubric.js";
16
+ export * as QualityIntelligenceIngestion from "./ingestion/index.js";
17
+ export * as QualityIntelligenceReview from "./review/index.js";
18
+ export * as QualityIntelligenceHardening from "./hardening/index.js";
19
+ export * as QualityIntelligenceExport from "./export/index.js";
20
+ export * as QualityIntelligenceGeneration from "./generation/index.js";
21
+ export * as QualityIntelligenceFigma from "./domain/figma/index.js";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,EACL,wBAAwB,EACxB,qCAAqC,GACtC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAEjF,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,EAC1B,iCAAiC,EACjC,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,GACf,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,oCAAoC,EACpC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,aAAa,EACb,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,KAAK,4BAA4B,MAAM,sBAAsB,CAAC;AAMrE,OAAO,KAAK,yBAAyB,MAAM,mBAAmB,CAAC;AAK/D,OAAO,KAAK,4BAA4B,MAAM,sBAAsB,CAAC;AAMrE,OAAO,KAAK,yBAAyB,MAAM,mBAAmB,CAAC;AAM/D,OAAO,KAAK,6BAA6B,MAAM,uBAAuB,CAAC;AAMvE,OAAO,KAAK,wBAAwB,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,44 @@
1
+ // Public barrel for @oscharko-dev/keiko-quality-intelligence (Epic #270, Issue #272).
2
+ //
3
+ // Pure-domain package: no IO, no provider SDK, no UI, no BFF, no scheduler, no event bus,
4
+ // no direct model calls. Consumes contracts via the @oscharko-dev/keiko-contracts barrel
5
+ // and redaction/hashing primitives via @oscharko-dev/keiko-security only.
6
+ // ─── Staleness / drift detection (Epic #735, Issue #742) ──────────────────────
7
+ export { compareStaleness } from "./domain/staleness.js";
8
+ export { deriveIntent } from "./domain/intentDerivation.js";
9
+ export { designTestCaseCandidates, DETERMINISTIC_BASELINE_PROVENANCE_TAG, } from "./domain/testDesignModel.js";
10
+ export { buildAtomCoverageStatuses, buildCoverageMap, classifyAtomCoverage, COVERAGE_THRESHOLD_COVERED, COVERAGE_THRESHOLD_WEAKLY_COVERED, runCoveragePercentage, } from "./domain/coverageRelevance.js";
11
+ export { buildRequirementExcerpt, REQUIREMENT_EXCERPT_MAX_CHARS, } from "./domain/requirementExcerpt.js";
12
+ export { computeCandidateEquivalenceSignature, deduplicateCandidates, } from "./domain/deduplication.js";
13
+ export { validateCandidates } from "./domain/validation.js";
14
+ export { ALL_POLICY_PROFILES, bankingDefault, insuranceDefault, regressionDefault, } from "./domain/policyProfile.js";
15
+ export { canonicaliseFragmentList, isKnownPriority, isMeaningfulText, isUnsafeFormatCodePoint, normaliseCandidateText, normaliseText, stripUnsafeFormatChars, } from "./domain/assertions.js";
16
+ export { TEST_QUALITY_WEAK_THRESHOLD, scoreFromDimensions, verdictFromDimensions, verdictFromScore, } from "./domain/testQualityRubric.js";
17
+ // ─── Ingestion sub-namespace (Issue #278) ──────────────────────────────────────
18
+ // Pure-domain ingestion modelling: ADF parsing, untrusted-content normalisation,
19
+ // source-mix planning, source reconciliation. No IO; consumes contract types only.
20
+ export * as QualityIntelligenceIngestion from "./ingestion/index.js";
21
+ // ─── Review sub-namespace (Issue #282) ─────────────────────────────────────────
22
+ // Pure-domain review governance: state machine, lifecycle policy, four-eyes
23
+ // pairing guard, and the producer half of the audit-event envelope. No IO, no
24
+ // persistence; consumes contract types only.
25
+ export * as QualityIntelligenceReview from "./review/index.js";
26
+ // ─── Hardening sub-namespace (Issue #284) ──────────────────────────────────────
27
+ // Pure adversarial-input predicates: path-safety, oversize guards, prompt-injection
28
+ // scrubber. No IO; layered defence beside `QualityIntelligenceIngestion`.
29
+ export * as QualityIntelligenceHardening from "./hardening/index.js";
30
+ // ─── Export sub-namespace (Issue #283) ─────────────────────────────────────────
31
+ // Pure-domain export adapters (CSV / JSON / TMS mappings) + bundle serialiser. No IO; the
32
+ // server tier owns persistence + connector authorisation. TMS-targeted bundles must attest
33
+ // redaction (enforced by the contract invariant).
34
+ export * as QualityIntelligenceExport from "./export/index.js";
35
+ // ─── Generation sub-namespace (Issue #272/#278/#279) ───────────────────────────
36
+ // Pure model-routed generation prep + recovery: trusted prompt assembly, requirements-text
37
+ // ingestion into content-bearing atoms, and deterministic model-output → candidate parsing.
38
+ // No IO, no model call; the server tier supplies the Keiko Model Gateway port.
39
+ export * as QualityIntelligenceGeneration from "./generation/index.js";
40
+ // ─── Figma Screen-IR sub-namespace (Epic #750, Issue #752) ─────────────────────
41
+ // Pure-domain Figma cleaner: raw scoped node tree → lean per-screen IR + deduped design tokens +
42
+ // raw inter-screen links + reduction report. No IO, no network, no model. Downstream stages
43
+ // (#753 evidence, #754 QI source, #811 nav graph, #812 a11y, #755 codegen) import from here.
44
+ export * as QualityIntelligenceFigma from "./domain/figma/index.js";
@@ -0,0 +1,61 @@
1
+ export type AdfParserErrorCode = "ROOT_NOT_OBJECT" | "ROOT_TYPE_MISMATCH" | "UNKNOWN_NODE_TYPE" | "NODE_NOT_OBJECT" | "MAX_DEPTH_EXCEEDED" | "MAX_NODES_EXCEEDED" | "CIRCULAR_REFERENCE" | "INVALID_HEADING_LEVEL";
2
+ export declare class AdfParserError extends Error {
3
+ readonly code: AdfParserErrorCode;
4
+ readonly path: string;
5
+ constructor(code: AdfParserErrorCode, path: string, message: string);
6
+ }
7
+ export interface AdfParserOptions {
8
+ readonly maxNodes?: number;
9
+ readonly maxDepth?: number;
10
+ readonly maxTextBytes?: number;
11
+ }
12
+ export interface IngestedTextRun {
13
+ readonly text: string;
14
+ readonly clamped: boolean;
15
+ readonly markdownInjectionEscapes: number;
16
+ }
17
+ export type IngestedBlock = {
18
+ readonly kind: "paragraph";
19
+ readonly runs: readonly IngestedTextRun[];
20
+ } | {
21
+ readonly kind: "heading";
22
+ readonly level: 1 | 2 | 3 | 4 | 5 | 6;
23
+ readonly runs: readonly IngestedTextRun[];
24
+ } | {
25
+ readonly kind: "bulletList";
26
+ readonly items: readonly IngestedBlock[];
27
+ } | {
28
+ readonly kind: "orderedList";
29
+ readonly items: readonly IngestedBlock[];
30
+ } | {
31
+ readonly kind: "listItem";
32
+ readonly content: readonly IngestedBlock[];
33
+ } | {
34
+ readonly kind: "codeBlock";
35
+ readonly language: string | null;
36
+ readonly text: string;
37
+ } | {
38
+ readonly kind: "linkRef";
39
+ readonly displayText: string;
40
+ readonly hrefLabel: string;
41
+ };
42
+ export interface IngestedDocument {
43
+ readonly version: 1;
44
+ readonly blocks: readonly IngestedBlock[];
45
+ readonly stats: {
46
+ readonly nodes: number;
47
+ readonly maxDepthReached: number;
48
+ readonly truncatedBlocks: number;
49
+ };
50
+ }
51
+ /**
52
+ * Parse a JSON-decoded ADF tree into an `IngestedDocument`. Throws `AdfParserError` for
53
+ * unknown node types, malformed structure, depth-bombs, or circular references. Pure.
54
+ */
55
+ export declare const parseAdfDocument: (raw: unknown, options?: AdfParserOptions) => IngestedDocument;
56
+ export declare const ADF_PARSER_DEFAULTS: {
57
+ readonly maxNodes: 5000;
58
+ readonly maxDepth: 32;
59
+ readonly maxTextBytes: number;
60
+ };
61
+ //# sourceMappingURL=adfParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adfParser.d.ts","sourceRoot":"","sources":["../../src/ingestion/adfParser.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,kBAAkB,GAC1B,iBAAiB,GACjB,oBAAoB,GACpB,mBAAmB,GACnB,iBAAiB,GACjB,oBAAoB,GACpB,oBAAoB,GACpB,oBAAoB,GACpB,uBAAuB,CAAC;AAE5B,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,kBAAkB,CAAC;IACzC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBACjB,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAMpE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CAC3C;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,GACzE;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvF;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;KAClC,CAAC;CACH;AAuRD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,OAAO,EACZ,UAAS,gBAAqB,KAC7B,gBA8BF,CAAC;AAEF,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC"}
@@ -0,0 +1,262 @@
1
+ // Quality Intelligence — ADF parser (Epic #270, Issue #278).
2
+ //
3
+ // Deterministic parser for the Atlassian Document Format (ADF) JSON tree. Used to turn
4
+ // structured Jira/Confluence-style content into a normalised internal `IngestedDocument`
5
+ // shape the QI ingestion pipeline can map onto evidence atoms.
6
+ //
7
+ // Pure: no IO, no network, no `node:fs`, no `XMLHttpRequest`, no clock reads. Throws a
8
+ // typed `AdfParserError` for malformed or unknown nodes. Bounded recursion + node count
9
+ // keep depth-bomb inputs from blowing the stack or running for unbounded time.
10
+ //
11
+ // Structurally inspired by Test Intelligence reference (TI) ADF handling, but the
12
+ // supported node set is a strict whitelist anchored on the Keiko contracts surface and
13
+ // the audit-ledger normalisation rules already encoded in
14
+ // @oscharko-dev/keiko-contracts/qualityIntelligence/ids.ts.
15
+ import { normaliseUntrustedContent } from "./untrustedContentNormalisation.js";
16
+ const DEFAULT_MAX_NODES = 5_000;
17
+ const DEFAULT_MAX_DEPTH = 32;
18
+ const DEFAULT_MAX_TEXT_BYTES = 64 * 1024;
19
+ export class AdfParserError extends Error {
20
+ code;
21
+ path;
22
+ constructor(code, path, message) {
23
+ super(`[${code}] ${path}: ${message}`);
24
+ this.name = "AdfParserError";
25
+ this.code = code;
26
+ this.path = path;
27
+ }
28
+ }
29
+ const KNOWN_NODE_TYPES = new Set([
30
+ "doc",
31
+ "paragraph",
32
+ "heading",
33
+ "text",
34
+ "bulletList",
35
+ "orderedList",
36
+ "listItem",
37
+ "codeBlock",
38
+ ]);
39
+ const isPlainObject = (value) => {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ };
42
+ const incrementNodes = (state, path) => {
43
+ state.nodeCount += 1;
44
+ if (state.nodeCount > state.maxNodes) {
45
+ throw new AdfParserError("MAX_NODES_EXCEEDED", path, `Exceeded maxNodes (${String(state.maxNodes)})`);
46
+ }
47
+ };
48
+ const checkDepth = (state, depth, path) => {
49
+ if (depth > state.maxDepth) {
50
+ throw new AdfParserError("MAX_DEPTH_EXCEEDED", path, `Exceeded maxDepth (${String(state.maxDepth)})`);
51
+ }
52
+ if (depth > state.maxDepthReached)
53
+ state.maxDepthReached = depth;
54
+ };
55
+ const markSeen = (state, node, path) => {
56
+ if (state.seen.has(node)) {
57
+ throw new AdfParserError("CIRCULAR_REFERENCE", path, "Node visited twice");
58
+ }
59
+ state.seen.add(node);
60
+ };
61
+ const assertObjectNode = (raw, path) => {
62
+ if (!isPlainObject(raw)) {
63
+ throw new AdfParserError("NODE_NOT_OBJECT", path, "Expected an object node");
64
+ }
65
+ return raw;
66
+ };
67
+ const getNodeType = (node, path) => {
68
+ const type = node.type;
69
+ if (typeof type !== "string") {
70
+ throw new AdfParserError("NODE_NOT_OBJECT", path, "Node `type` must be a string");
71
+ }
72
+ if (!KNOWN_NODE_TYPES.has(type)) {
73
+ throw new AdfParserError("UNKNOWN_NODE_TYPE", path, `Unknown node type "${type}"`);
74
+ }
75
+ return type;
76
+ };
77
+ const collectArrayContent = (node) => {
78
+ const content = node.content;
79
+ return Array.isArray(content) ? content : [];
80
+ };
81
+ const collectMarks = (node) => {
82
+ const marks = node.marks;
83
+ return Array.isArray(marks) ? marks : [];
84
+ };
85
+ const buildLinkRef = (rawText, marks, state) => {
86
+ for (const mark of marks) {
87
+ if (!isPlainObject(mark))
88
+ continue;
89
+ if (mark.type !== "link")
90
+ continue;
91
+ const attrs = mark.attrs;
92
+ const href = isPlainObject(attrs) && typeof attrs.href === "string" ? attrs.href : "(href)";
93
+ const hrefLabel = normaliseUntrustedContent(href, { maxBytes: state.maxTextBytes }).value;
94
+ const displayText = normaliseUntrustedContent(rawText, { maxBytes: state.maxTextBytes }).value;
95
+ return { kind: "linkRef", displayText, hrefLabel };
96
+ }
97
+ return null;
98
+ };
99
+ const parseTextNode = (node, path, state) => {
100
+ incrementNodes(state, path);
101
+ const rawText = typeof node.text === "string" ? node.text : "";
102
+ const marks = collectMarks(node);
103
+ const link = buildLinkRef(rawText, marks, state);
104
+ if (link !== null) {
105
+ return { run: null, link };
106
+ }
107
+ const normalised = normaliseUntrustedContent(rawText, { maxBytes: state.maxTextBytes });
108
+ return {
109
+ run: {
110
+ text: normalised.value,
111
+ clamped: normalised.clamped,
112
+ markdownInjectionEscapes: normalised.markdownInjectionEscapes,
113
+ },
114
+ link: null,
115
+ };
116
+ };
117
+ const parseInlineContent = (rawChildren, pathPrefix, depth, state) => {
118
+ checkDepth(state, depth, pathPrefix);
119
+ const runs = [];
120
+ const links = [];
121
+ rawChildren.forEach((child, index) => {
122
+ const path = `${pathPrefix}[${String(index)}]`;
123
+ const childNode = assertObjectNode(child, path);
124
+ markSeen(state, childNode, path);
125
+ const type = getNodeType(childNode, path);
126
+ if (type !== "text") {
127
+ throw new AdfParserError("UNKNOWN_NODE_TYPE", path, `Expected inline text, got "${type}"`);
128
+ }
129
+ const parsed = parseTextNode(childNode, path, state);
130
+ if (parsed.run !== null)
131
+ runs.push(parsed.run);
132
+ if (parsed.link !== null)
133
+ links.push(parsed.link);
134
+ });
135
+ return { runs, links };
136
+ };
137
+ const parseHeading = (node, path, depth, state) => {
138
+ const attrs = node.attrs;
139
+ const rawLevel = isPlainObject(attrs) ? attrs.level : undefined;
140
+ if (typeof rawLevel !== "number" || !Number.isInteger(rawLevel) || rawLevel < 1 || rawLevel > 6) {
141
+ throw new AdfParserError("INVALID_HEADING_LEVEL", path, "Heading level must be integer 1–6");
142
+ }
143
+ const inline = parseInlineContent(collectArrayContent(node), `${path}.content`, depth + 1, state);
144
+ return {
145
+ kind: "heading",
146
+ level: rawLevel,
147
+ runs: inline.runs,
148
+ };
149
+ };
150
+ const parseCodeBlock = (node, path, state) => {
151
+ const attrs = node.attrs;
152
+ const rawLanguage = isPlainObject(attrs) ? attrs.language : undefined;
153
+ const language = typeof rawLanguage === "string" && rawLanguage.length > 0
154
+ ? normaliseUntrustedContent(rawLanguage, { maxBytes: 256 }).value
155
+ : null;
156
+ const inline = collectArrayContent(node);
157
+ let combined = "";
158
+ inline.forEach((child, index) => {
159
+ const childPath = `${path}.content[${String(index)}]`;
160
+ const childNode = assertObjectNode(child, childPath);
161
+ markSeen(state, childNode, childPath);
162
+ incrementNodes(state, childPath);
163
+ if (childNode.type !== "text") {
164
+ throw new AdfParserError("UNKNOWN_NODE_TYPE", childPath, "codeBlock content must be text nodes");
165
+ }
166
+ const t = childNode.text;
167
+ if (typeof t === "string")
168
+ combined += t;
169
+ });
170
+ const normalised = normaliseUntrustedContent(combined, { maxBytes: state.maxTextBytes });
171
+ return { kind: "codeBlock", language, text: normalised.value };
172
+ };
173
+ const parseListItem = (node, path, depth, state) => {
174
+ const childPath = `${path}.content`;
175
+ const children = collectArrayContent(node);
176
+ const inner = [];
177
+ children.forEach((child, index) => {
178
+ const cp = `${childPath}[${String(index)}]`;
179
+ inner.push(parseBlock(child, cp, depth + 1, state));
180
+ });
181
+ return { kind: "listItem", content: inner };
182
+ };
183
+ const parseBulletOrOrderedList = (node, path, depth, state, kind) => {
184
+ const children = collectArrayContent(node);
185
+ const items = [];
186
+ children.forEach((child, index) => {
187
+ const cp = `${path}.content[${String(index)}]`;
188
+ const childNode = assertObjectNode(child, cp);
189
+ markSeen(state, childNode, cp);
190
+ incrementNodes(state, cp);
191
+ if (childNode.type !== "listItem") {
192
+ throw new AdfParserError("UNKNOWN_NODE_TYPE", cp, `${kind} children must be listItem nodes`);
193
+ }
194
+ items.push(parseListItem(childNode, cp, depth + 1, state));
195
+ });
196
+ return { kind, items };
197
+ };
198
+ const parseParagraph = (node, path, depth, state) => {
199
+ const inline = parseInlineContent(collectArrayContent(node), `${path}.content`, depth + 1, state);
200
+ return { kind: "paragraph", runs: inline.runs };
201
+ };
202
+ function parseBlock(raw, path, depth, state) {
203
+ const node = assertObjectNode(raw, path);
204
+ markSeen(state, node, path);
205
+ incrementNodes(state, path);
206
+ checkDepth(state, depth, path);
207
+ const type = getNodeType(node, path);
208
+ switch (type) {
209
+ case "paragraph":
210
+ return parseParagraph(node, path, depth, state);
211
+ case "heading":
212
+ return parseHeading(node, path, depth, state);
213
+ case "bulletList":
214
+ return parseBulletOrOrderedList(node, path, depth, state, "bulletList");
215
+ case "orderedList":
216
+ return parseBulletOrOrderedList(node, path, depth, state, "orderedList");
217
+ case "codeBlock":
218
+ return parseCodeBlock(node, path, state);
219
+ case "listItem":
220
+ return parseListItem(node, path, depth, state);
221
+ default:
222
+ throw new AdfParserError("UNKNOWN_NODE_TYPE", path, `Node type "${type}" is not a block-level node`);
223
+ }
224
+ }
225
+ /**
226
+ * Parse a JSON-decoded ADF tree into an `IngestedDocument`. Throws `AdfParserError` for
227
+ * unknown node types, malformed structure, depth-bombs, or circular references. Pure.
228
+ */
229
+ export const parseAdfDocument = (raw, options = {}) => {
230
+ if (!isPlainObject(raw)) {
231
+ throw new AdfParserError("ROOT_NOT_OBJECT", "$", "ADF root must be a JSON object");
232
+ }
233
+ if (raw.type !== "doc") {
234
+ throw new AdfParserError("ROOT_TYPE_MISMATCH", "$", 'ADF root type must be "doc"');
235
+ }
236
+ const state = {
237
+ nodeCount: 0,
238
+ maxDepthReached: 0,
239
+ maxNodes: options.maxNodes ?? DEFAULT_MAX_NODES,
240
+ maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
241
+ maxTextBytes: options.maxTextBytes ?? DEFAULT_MAX_TEXT_BYTES,
242
+ seen: new WeakSet(),
243
+ };
244
+ markSeen(state, raw, "$");
245
+ incrementNodes(state, "$");
246
+ const children = collectArrayContent(raw);
247
+ const blocks = children.map((child, index) => parseBlock(child, `$.content[${String(index)}]`, 1, state));
248
+ return {
249
+ version: 1,
250
+ blocks,
251
+ stats: {
252
+ nodes: state.nodeCount,
253
+ maxDepthReached: state.maxDepthReached,
254
+ truncatedBlocks: 0,
255
+ },
256
+ };
257
+ };
258
+ export const ADF_PARSER_DEFAULTS = {
259
+ maxNodes: DEFAULT_MAX_NODES,
260
+ maxDepth: DEFAULT_MAX_DEPTH,
261
+ maxTextBytes: DEFAULT_MAX_TEXT_BYTES,
262
+ };
@@ -0,0 +1,6 @@
1
+ export { normaliseUntrustedContent, UNTRUSTED_CONTENT_DEFAULT_MAX_BYTES, type NormaliseUntrustedContentOptions, type NormaliseUntrustedContentResult, } from "./untrustedContentNormalisation.js";
2
+ export { parseAdfDocument, AdfParserError, ADF_PARSER_DEFAULTS, type AdfParserErrorCode, type AdfParserOptions, type IngestedBlock, type IngestedDocument, type IngestedTextRun, } from "./adfParser.js";
3
+ export { planSourceMix, SOURCE_KIND_PRIORITY, type SourceMixPlan, type SourceMixPlanEntry, type SourceMixPlanOptions, } from "./sourceMixPlanning.js";
4
+ export { reconcileSourceGroups, type ProvenanceEntry, type ReconciledSourceSet, type SourceGroup, } from "./sourceReconciliation.js";
5
+ export { buildWorkspaceSourceEnvelopes, workspaceSourceMixPolicy, WorkspaceAdapterError, type BuildWorkspaceEnvelopesInput, type WorkspaceAdapterErrorCode, } from "./workspaceAdapter.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ingestion/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,yBAAyB,EACzB,mCAAmC,EACnC,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,GACrC,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,EACxB,qBAAqB,EACrB,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,GAC/B,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,10 @@
1
+ // Public barrel for the Quality Intelligence ingestion sub-namespace (Epic #270, Issue #278).
2
+ //
3
+ // Pure-domain helpers that prepare structured + free-text inputs for the QI pipeline.
4
+ // No IO. No network. No `node:fs`. All functions operate on contract types from
5
+ // @oscharko-dev/keiko-contracts.
6
+ export { normaliseUntrustedContent, UNTRUSTED_CONTENT_DEFAULT_MAX_BYTES, } from "./untrustedContentNormalisation.js";
7
+ export { parseAdfDocument, AdfParserError, ADF_PARSER_DEFAULTS, } from "./adfParser.js";
8
+ export { planSourceMix, SOURCE_KIND_PRIORITY, } from "./sourceMixPlanning.js";
9
+ export { reconcileSourceGroups, } from "./sourceReconciliation.js";
10
+ export { buildWorkspaceSourceEnvelopes, workspaceSourceMixPolicy, WorkspaceAdapterError, } from "./workspaceAdapter.js";
@@ -0,0 +1,36 @@
1
+ import type { QualityIntelligence } from "@oscharko-dev/keiko-contracts";
2
+ type Envelope = QualityIntelligence.QualityIntelligenceSourceEnvelope;
3
+ type EnvelopeId = QualityIntelligence.QualityIntelligenceSourceEnvelopeId;
4
+ type SourceKind = QualityIntelligence.QualityIntelligenceSourceKind;
5
+ /** Per-kind priority weight: lower number = higher priority (planned earlier). */
6
+ export declare const SOURCE_KIND_PRIORITY: Readonly<Record<SourceKind, number>>;
7
+ export interface SourceMixPlanOptions {
8
+ /**
9
+ * Envelope display-label byte budget. An envelope whose label exceeds this is flagged
10
+ * as oversize but NOT dropped — the consumer decides what to do. Defaults to 256.
11
+ */
12
+ readonly maxLabelBytes?: number;
13
+ /**
14
+ * Hard cap on the number of envelopes the planner will keep. Defaults to 256. Extras
15
+ * appear in `droppedForCap` in original priority order.
16
+ */
17
+ readonly maxEnvelopes?: number;
18
+ }
19
+ export interface SourceMixPlanEntry {
20
+ readonly envelopeId: EnvelopeId;
21
+ readonly kind: SourceKind;
22
+ readonly priority: number;
23
+ readonly oversize: boolean;
24
+ }
25
+ export interface SourceMixPlan {
26
+ readonly entries: readonly SourceMixPlanEntry[];
27
+ readonly droppedForDuplicate: readonly EnvelopeId[];
28
+ readonly droppedForCap: readonly EnvelopeId[];
29
+ readonly oversizeCount: number;
30
+ }
31
+ /**
32
+ * Build a deterministic plan from a list of envelopes. Stable, pure.
33
+ */
34
+ export declare const planSourceMix: (envelopes: readonly Envelope[], options?: SourceMixPlanOptions) => SourceMixPlan;
35
+ export {};
36
+ //# sourceMappingURL=sourceMixPlanning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourceMixPlanning.d.ts","sourceRoot":"","sources":["../../src/ingestion/sourceMixPlanning.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,KAAK,QAAQ,GAAG,mBAAmB,CAAC,iCAAiC,CAAC;AACtE,KAAK,UAAU,GAAG,mBAAmB,CAAC,mCAAmC,CAAC;AAC1E,KAAK,UAAU,GAAG,mBAAmB,CAAC,6BAA6B,CAAC;AAEpE,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAMrE,CAAC;AAIF,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAChD,QAAQ,CAAC,mBAAmB,EAAE,SAAS,UAAU,EAAE,CAAC;IACpD,QAAQ,CAAC,aAAa,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAaD;;GAEG;AACH,eAAO,MAAM,aAAa,GACxB,WAAW,SAAS,QAAQ,EAAE,EAC9B,UAAS,oBAAyB,KACjC,aAoCF,CAAC"}