@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.
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/_fixtureLoader.d.ts +9 -0
- package/dist/__tests__/_fixtureLoader.d.ts.map +1 -0
- package/dist/__tests__/_fixtureLoader.js +75 -0
- package/dist/domain/assertions.d.ts +61 -0
- package/dist/domain/assertions.d.ts.map +1 -0
- package/dist/domain/assertions.js +134 -0
- package/dist/domain/coverageRelevance.d.ts +73 -0
- package/dist/domain/coverageRelevance.d.ts.map +1 -0
- package/dist/domain/coverageRelevance.js +155 -0
- package/dist/domain/deduplication.d.ts +17 -0
- package/dist/domain/deduplication.d.ts.map +1 -0
- package/dist/domain/deduplication.js +95 -0
- package/dist/domain/figma/a11yBaseline.d.ts +17 -0
- package/dist/domain/figma/a11yBaseline.d.ts.map +1 -0
- package/dist/domain/figma/a11yBaseline.js +218 -0
- package/dist/domain/figma/cleanToScreenIr.d.ts +3 -0
- package/dist/domain/figma/cleanToScreenIr.d.ts.map +1 -0
- package/dist/domain/figma/cleanToScreenIr.js +62 -0
- package/dist/domain/figma/codeTargetAdapter.d.ts +30 -0
- package/dist/domain/figma/codeTargetAdapter.d.ts.map +1 -0
- package/dist/domain/figma/codeTargetAdapter.js +27 -0
- package/dist/domain/figma/color.d.ts +31 -0
- package/dist/domain/figma/color.d.ts.map +1 -0
- package/dist/domain/figma/color.js +99 -0
- package/dist/domain/figma/emissionPlan.d.ts +56 -0
- package/dist/domain/figma/emissionPlan.d.ts.map +1 -0
- package/dist/domain/figma/emissionPlan.js +87 -0
- package/dist/domain/figma/htmlCssAdapter.d.ts +13 -0
- package/dist/domain/figma/htmlCssAdapter.d.ts.map +1 -0
- package/dist/domain/figma/htmlCssAdapter.js +452 -0
- package/dist/domain/figma/index.d.ts +19 -0
- package/dist/domain/figma/index.d.ts.map +1 -0
- package/dist/domain/figma/index.js +31 -0
- package/dist/domain/figma/irTypes.d.ts +156 -0
- package/dist/domain/figma/irTypes.d.ts.map +1 -0
- package/dist/domain/figma/irTypes.js +8 -0
- package/dist/domain/figma/links.d.ts +6 -0
- package/dist/domain/figma/links.d.ts.map +1 -0
- package/dist/domain/figma/links.js +102 -0
- package/dist/domain/figma/navGraph.d.ts +74 -0
- package/dist/domain/figma/navGraph.d.ts.map +1 -0
- package/dist/domain/figma/navGraph.js +315 -0
- package/dist/domain/figma/normalize.d.ts +7 -0
- package/dist/domain/figma/normalize.d.ts.map +1 -0
- package/dist/domain/figma/normalize.js +252 -0
- package/dist/domain/figma/prune.d.ts +15 -0
- package/dist/domain/figma/prune.d.ts.map +1 -0
- package/dist/domain/figma/prune.js +65 -0
- package/dist/domain/figma/screenDetect.d.ts +8 -0
- package/dist/domain/figma/screenDetect.d.ts.map +1 -0
- package/dist/domain/figma/screenDetect.js +35 -0
- package/dist/domain/figma/screenIrTestBaseline.d.ts +52 -0
- package/dist/domain/figma/screenIrTestBaseline.d.ts.map +1 -0
- package/dist/domain/figma/screenIrTestBaseline.js +326 -0
- package/dist/domain/figma/semanticNaming.d.ts +24 -0
- package/dist/domain/figma/semanticNaming.d.ts.map +1 -0
- package/dist/domain/figma/semanticNaming.js +67 -0
- package/dist/domain/figma/sourceNode.d.ts +24 -0
- package/dist/domain/figma/sourceNode.d.ts.map +1 -0
- package/dist/domain/figma/sourceNode.js +26 -0
- package/dist/domain/figma/tokens.d.ts +11 -0
- package/dist/domain/figma/tokens.d.ts.map +1 -0
- package/dist/domain/figma/tokens.js +148 -0
- package/dist/domain/figma/visionAugmentation.d.ts +14 -0
- package/dist/domain/figma/visionAugmentation.d.ts.map +1 -0
- package/dist/domain/figma/visionAugmentation.js +48 -0
- package/dist/domain/intentDerivation.d.ts +21 -0
- package/dist/domain/intentDerivation.d.ts.map +1 -0
- package/dist/domain/intentDerivation.js +126 -0
- package/dist/domain/policyProfile.d.ts +37 -0
- package/dist/domain/policyProfile.d.ts.map +1 -0
- package/dist/domain/policyProfile.js +94 -0
- package/dist/domain/requirementExcerpt.d.ts +9 -0
- package/dist/domain/requirementExcerpt.d.ts.map +1 -0
- package/dist/domain/requirementExcerpt.js +39 -0
- package/dist/domain/staleness.d.ts +56 -0
- package/dist/domain/staleness.d.ts.map +1 -0
- package/dist/domain/staleness.js +313 -0
- package/dist/domain/testDesignModel.d.ts +38 -0
- package/dist/domain/testDesignModel.d.ts.map +1 -0
- package/dist/domain/testDesignModel.js +264 -0
- package/dist/domain/testQualityRubric.d.ts +20 -0
- package/dist/domain/testQualityRubric.d.ts.map +1 -0
- package/dist/domain/testQualityRubric.js +38 -0
- package/dist/domain/validation.d.ts +7 -0
- package/dist/domain/validation.d.ts.map +1 -0
- package/dist/domain/validation.js +145 -0
- package/dist/export/adapters/alm.d.ts +4 -0
- package/dist/export/adapters/alm.d.ts.map +1 -0
- package/dist/export/adapters/alm.js +75 -0
- package/dist/export/adapters/csv.d.ts +5 -0
- package/dist/export/adapters/csv.d.ts.map +1 -0
- package/dist/export/adapters/csv.js +55 -0
- package/dist/export/adapters/index.d.ts +13 -0
- package/dist/export/adapters/index.d.ts.map +1 -0
- package/dist/export/adapters/index.js +15 -0
- package/dist/export/adapters/jira.d.ts +5 -0
- package/dist/export/adapters/jira.d.ts.map +1 -0
- package/dist/export/adapters/jira.js +79 -0
- package/dist/export/adapters/json.d.ts +3 -0
- package/dist/export/adapters/json.d.ts.map +1 -0
- package/dist/export/adapters/json.js +54 -0
- package/dist/export/adapters/markdown.d.ts +3 -0
- package/dist/export/adapters/markdown.d.ts.map +1 -0
- package/dist/export/adapters/markdown.js +88 -0
- package/dist/export/adapters/plaintext.d.ts +3 -0
- package/dist/export/adapters/plaintext.d.ts.map +1 -0
- package/dist/export/adapters/plaintext.js +65 -0
- package/dist/export/adapters/polarion.d.ts +4 -0
- package/dist/export/adapters/polarion.d.ts.map +1 -0
- package/dist/export/adapters/polarion.js +67 -0
- package/dist/export/adapters/qtest.d.ts +4 -0
- package/dist/export/adapters/qtest.d.ts.map +1 -0
- package/dist/export/adapters/qtest.js +78 -0
- package/dist/export/adapters/qualityCenter.d.ts +3 -0
- package/dist/export/adapters/qualityCenter.d.ts.map +1 -0
- package/dist/export/adapters/qualityCenter.js +56 -0
- package/dist/export/adapters/spreadsheetSafeCsv.d.ts +36 -0
- package/dist/export/adapters/spreadsheetSafeCsv.d.ts.map +1 -0
- package/dist/export/adapters/spreadsheetSafeCsv.js +157 -0
- package/dist/export/adapters/traceability.d.ts +34 -0
- package/dist/export/adapters/traceability.d.ts.map +1 -0
- package/dist/export/adapters/traceability.js +142 -0
- package/dist/export/adapters/xray.d.ts +4 -0
- package/dist/export/adapters/xray.d.ts.map +1 -0
- package/dist/export/adapters/xray.js +72 -0
- package/dist/export/formats.d.ts +29 -0
- package/dist/export/formats.d.ts.map +1 -0
- package/dist/export/formats.js +34 -0
- package/dist/export/index.d.ts +4 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +10 -0
- package/dist/export/serialize.d.ts +17 -0
- package/dist/export/serialize.d.ts.map +1 -0
- package/dist/export/serialize.js +56 -0
- package/dist/export/textSafety.d.ts +15 -0
- package/dist/export/textSafety.d.ts.map +1 -0
- package/dist/export/textSafety.js +30 -0
- package/dist/generation/candidateBounds.d.ts +10 -0
- package/dist/generation/candidateBounds.d.ts.map +1 -0
- package/dist/generation/candidateBounds.js +14 -0
- package/dist/generation/index.d.ts +4 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +20 -0
- package/dist/generation/parseGeneratedCandidates.d.ts +27 -0
- package/dist/generation/parseGeneratedCandidates.d.ts.map +1 -0
- package/dist/generation/parseGeneratedCandidates.js +253 -0
- package/dist/generation/prompt.d.ts +16 -0
- package/dist/generation/prompt.d.ts.map +1 -0
- package/dist/generation/prompt.js +151 -0
- package/dist/generation/requirementsIngestion.d.ts +21 -0
- package/dist/generation/requirementsIngestion.d.ts.map +1 -0
- package/dist/generation/requirementsIngestion.js +70 -0
- package/dist/hardening/index.d.ts +6 -0
- package/dist/hardening/index.d.ts.map +1 -0
- package/dist/hardening/index.js +8 -0
- package/dist/hardening/oversizeGuards.d.ts +21 -0
- package/dist/hardening/oversizeGuards.d.ts.map +1 -0
- package/dist/hardening/oversizeGuards.js +35 -0
- package/dist/hardening/pathSafety.d.ts +19 -0
- package/dist/hardening/pathSafety.d.ts.map +1 -0
- package/dist/hardening/pathSafety.js +61 -0
- package/dist/hardening/promptInjectionScrub.d.ts +17 -0
- package/dist/hardening/promptInjectionScrub.d.ts.map +1 -0
- package/dist/hardening/promptInjectionScrub.js +72 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/ingestion/adfParser.d.ts +61 -0
- package/dist/ingestion/adfParser.d.ts.map +1 -0
- package/dist/ingestion/adfParser.js +262 -0
- package/dist/ingestion/index.d.ts +6 -0
- package/dist/ingestion/index.d.ts.map +1 -0
- package/dist/ingestion/index.js +10 -0
- package/dist/ingestion/sourceMixPlanning.d.ts +36 -0
- package/dist/ingestion/sourceMixPlanning.d.ts.map +1 -0
- package/dist/ingestion/sourceMixPlanning.js +65 -0
- package/dist/ingestion/sourceReconciliation.d.ts +39 -0
- package/dist/ingestion/sourceReconciliation.d.ts.map +1 -0
- package/dist/ingestion/sourceReconciliation.js +74 -0
- package/dist/ingestion/untrustedContentNormalisation.d.ts +23 -0
- package/dist/ingestion/untrustedContentNormalisation.d.ts.map +1 -0
- package/dist/ingestion/untrustedContentNormalisation.js +121 -0
- package/dist/ingestion/workspaceAdapter.d.ts +55 -0
- package/dist/ingestion/workspaceAdapter.d.ts.map +1 -0
- package/dist/ingestion/workspaceAdapter.js +113 -0
- package/dist/review/auditEvents.d.ts +61 -0
- package/dist/review/auditEvents.d.ts.map +1 -0
- package/dist/review/auditEvents.js +50 -0
- package/dist/review/fourEyes.d.ts +24 -0
- package/dist/review/fourEyes.d.ts.map +1 -0
- package/dist/review/fourEyes.js +45 -0
- package/dist/review/index.d.ts +5 -0
- package/dist/review/index.d.ts.map +1 -0
- package/dist/review/index.js +14 -0
- package/dist/review/lifecyclePolicy.d.ts +21 -0
- package/dist/review/lifecyclePolicy.d.ts.map +1 -0
- package/dist/review/lifecyclePolicy.js +38 -0
- package/dist/review/stateMachine.d.ts +28 -0
- package/dist/review/stateMachine.d.ts.map +1 -0
- package/dist/review/stateMachine.js +71 -0
- package/package.json +31 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Vision augmentation merge for the Figma-snapshot QI source (Epic #750, Issue #754).
|
|
2
|
+
//
|
|
3
|
+
// Capability-routed vision (a multimodal model reading the rendered screen, selected via #810) may
|
|
4
|
+
// add image-derived semantics the structural IR cannot reveal. This module enforces the load-bearing
|
|
5
|
+
// invariant STRUCTURALLY: vision augments, it never OVERRIDES the IR. The deterministic baseline
|
|
6
|
+
// text is preserved byte-for-byte and the vision hints are appended as a clearly-labelled, separate,
|
|
7
|
+
// additive section — there is no code path by which a vision hint can mutate or remove a baseline
|
|
8
|
+
// line. With no hints (no multimodal capability, or a garbage/thrown result the caller drops to an
|
|
9
|
+
// empty list), the output IS the baseline text unchanged.
|
|
10
|
+
const MAX_HINTS = 24;
|
|
11
|
+
const MAX_HINT_CHARS = 500;
|
|
12
|
+
const VISION_SECTION_HEADER = "Vision-derived semantic hints (additive — cross-check against the structural baseline above; " +
|
|
13
|
+
"never overrides it):";
|
|
14
|
+
// Drop empties and over-long entries, normalise whitespace, and de-duplicate while preserving order.
|
|
15
|
+
// A model that returns garbage (empty strings, an over-long blob) contributes nothing rather than
|
|
16
|
+
// corrupting the atom.
|
|
17
|
+
function sanitiseHints(hints) {
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const raw of hints) {
|
|
21
|
+
if (typeof raw !== "string")
|
|
22
|
+
continue;
|
|
23
|
+
const trimmed = raw.replace(/\s+/gu, " ").trim();
|
|
24
|
+
if (trimmed.length === 0 || trimmed.length > MAX_HINT_CHARS)
|
|
25
|
+
continue;
|
|
26
|
+
if (seen.has(trimmed))
|
|
27
|
+
continue;
|
|
28
|
+
seen.add(trimmed);
|
|
29
|
+
out.push(trimmed);
|
|
30
|
+
if (out.length >= MAX_HINTS)
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Merge vision hints into the deterministic baseline text WITHOUT overriding it. The baseline text is
|
|
37
|
+
* always the prefix of the result; hints (if any survive sanitisation) follow under a labelled
|
|
38
|
+
* section. When no hint survives, the result text is identical to `baselineText`, so the structural
|
|
39
|
+
* baseline always ships and the vision contribution is provably additive.
|
|
40
|
+
*/
|
|
41
|
+
export function mergeVisionHints(baselineText, hints) {
|
|
42
|
+
const clean = sanitiseHints(hints);
|
|
43
|
+
if (clean.length === 0) {
|
|
44
|
+
return { text: baselineText, augmentedCount: 0 };
|
|
45
|
+
}
|
|
46
|
+
const section = [VISION_SECTION_HEADER, ...clean.map((hint) => `- ${hint}`)].join("\n");
|
|
47
|
+
return { text: `${baselineText}\n\n${section}`, augmentedCount: clean.length };
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { QualityIntelligence } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import type { PolicyProfile } from "./policyProfile.js";
|
|
3
|
+
export interface IntentSummary {
|
|
4
|
+
/** Distinct themes (high-level subject buckets) the envelopes touch. */
|
|
5
|
+
readonly themes: readonly string[];
|
|
6
|
+
/** Candidate requirement phrases extracted from envelope display labels. */
|
|
7
|
+
readonly requirementCandidates: readonly string[];
|
|
8
|
+
/** Lower-cased risk-shaped keywords spotted across envelope display labels. */
|
|
9
|
+
readonly riskHints: readonly string[];
|
|
10
|
+
/** Derived priority bucket, biased by the policy profile. */
|
|
11
|
+
readonly priorityHint: QualityIntelligence.QualityIntelligencePriority | "unknown";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Derive a deterministic `IntentSummary` from the supplied envelopes. Returns
|
|
15
|
+
* an empty summary when the input list is empty.
|
|
16
|
+
*
|
|
17
|
+
* @param envelopes Source envelopes (browser-safe display labels only).
|
|
18
|
+
* @param profile Policy profile. Defaults to `regressionDefault`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const deriveIntent: (envelopes: readonly QualityIntelligence.QualityIntelligenceSourceEnvelope[], profile?: PolicyProfile) => IntentSummary;
|
|
21
|
+
//# sourceMappingURL=intentDerivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intentDerivation.d.ts","sourceRoot":"","sources":["../../src/domain/intentDerivation.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAGzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IAClD,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC,2BAA2B,GAAG,SAAS,CAAC;CACpF;AA+ED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,WAAW,SAAS,mBAAmB,CAAC,iCAAiC,EAAE,EAC3E,UAAS,aAAiC,KACzC,aA2CF,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Quality Intelligence intent derivation (Epic #270, Issue #272).
|
|
2
|
+
//
|
|
3
|
+
// Derives a structured `IntentSummary` from a list of source envelopes using
|
|
4
|
+
// deterministic lexical heuristics only — no embeddings, no model calls.
|
|
5
|
+
//
|
|
6
|
+
// Structurally inspired by
|
|
7
|
+
// Test Intelligence reference (TI) packages/core-engine/src/intent-derivation.ts
|
|
8
|
+
// (deriveBusinessTestIntentIr), but rewritten to consume the Keiko contracts
|
|
9
|
+
// surface. The TI reference performs the same role (turn structured input
|
|
10
|
+
// into a list of business intents) but reaches into UI-specific node trees;
|
|
11
|
+
// our Keiko port stays envelope-shaped and policy-driven.
|
|
12
|
+
import { isMeaningfulText, normaliseText } from "./assertions.js";
|
|
13
|
+
import { regressionDefault } from "./policyProfile.js";
|
|
14
|
+
const REQUIREMENT_VERB_PATTERNS = [
|
|
15
|
+
/\b(must|shall|should|cannot|may not|may|will)\b/iu,
|
|
16
|
+
/\b(verify|ensure|confirm|reject|prevent|allow|deny)\b/iu,
|
|
17
|
+
];
|
|
18
|
+
const THEME_SEPARATOR = /[\s–—:;|/,.()[\]{}]+/u;
|
|
19
|
+
const extractThemes = (label) => {
|
|
20
|
+
const normalised = normaliseText(label);
|
|
21
|
+
if (normalised.length === 0) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const tokens = normalised.split(THEME_SEPARATOR);
|
|
25
|
+
const themes = new Set();
|
|
26
|
+
for (const token of tokens) {
|
|
27
|
+
const lowered = token.toLowerCase();
|
|
28
|
+
if (lowered.length < 3) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
themes.add(lowered);
|
|
32
|
+
}
|
|
33
|
+
return Array.from(themes).sort();
|
|
34
|
+
};
|
|
35
|
+
const lookLikeRequirementPhrase = (text) => {
|
|
36
|
+
for (const pattern of REQUIREMENT_VERB_PATTERNS) {
|
|
37
|
+
if (pattern.test(text)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
const detectRiskHints = (text, profile) => {
|
|
44
|
+
const lowered = text.toLowerCase();
|
|
45
|
+
const hits = new Set();
|
|
46
|
+
for (const keywords of Object.values(profile.riskKeywords)) {
|
|
47
|
+
for (const keyword of keywords) {
|
|
48
|
+
if (lowered.includes(keyword)) {
|
|
49
|
+
hits.add(keyword);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return Array.from(hits).sort();
|
|
54
|
+
};
|
|
55
|
+
const derivePriority = (text, profile) => {
|
|
56
|
+
if (text.length === 0) {
|
|
57
|
+
return "unknown";
|
|
58
|
+
}
|
|
59
|
+
const lowered = text.toLowerCase();
|
|
60
|
+
const priorityBuckets = [
|
|
61
|
+
"P0",
|
|
62
|
+
"P1",
|
|
63
|
+
"P2",
|
|
64
|
+
"P3",
|
|
65
|
+
];
|
|
66
|
+
for (let bucketIndex = 0; bucketIndex < profile.priorityKeywords.length; bucketIndex += 1) {
|
|
67
|
+
const keywords = profile.priorityKeywords[bucketIndex] ?? [];
|
|
68
|
+
for (const keyword of keywords) {
|
|
69
|
+
if (lowered.includes(keyword)) {
|
|
70
|
+
const mapped = priorityBuckets[bucketIndex];
|
|
71
|
+
if (mapped !== undefined) {
|
|
72
|
+
return mapped;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return profile.defaultPriority;
|
|
78
|
+
};
|
|
79
|
+
const compareLowercase = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
80
|
+
/**
|
|
81
|
+
* Derive a deterministic `IntentSummary` from the supplied envelopes. Returns
|
|
82
|
+
* an empty summary when the input list is empty.
|
|
83
|
+
*
|
|
84
|
+
* @param envelopes Source envelopes (browser-safe display labels only).
|
|
85
|
+
* @param profile Policy profile. Defaults to `regressionDefault`.
|
|
86
|
+
*/
|
|
87
|
+
export const deriveIntent = (envelopes, profile = regressionDefault) => {
|
|
88
|
+
if (envelopes.length === 0) {
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
themes: Object.freeze([]),
|
|
91
|
+
requirementCandidates: Object.freeze([]),
|
|
92
|
+
riskHints: Object.freeze([]),
|
|
93
|
+
priorityHint: "unknown",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const themes = new Set();
|
|
97
|
+
const requirements = new Set();
|
|
98
|
+
const risks = new Set();
|
|
99
|
+
let aggregateLowered = "";
|
|
100
|
+
for (const envelope of envelopes) {
|
|
101
|
+
const normalised = normaliseText(envelope.displayLabel);
|
|
102
|
+
if (!isMeaningfulText(normalised)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
for (const theme of extractThemes(normalised)) {
|
|
106
|
+
themes.add(theme);
|
|
107
|
+
}
|
|
108
|
+
if (lookLikeRequirementPhrase(normalised)) {
|
|
109
|
+
requirements.add(normalised);
|
|
110
|
+
}
|
|
111
|
+
for (const hint of detectRiskHints(normalised, profile)) {
|
|
112
|
+
risks.add(hint);
|
|
113
|
+
}
|
|
114
|
+
aggregateLowered =
|
|
115
|
+
aggregateLowered.length === 0
|
|
116
|
+
? normalised.toLowerCase()
|
|
117
|
+
: `${aggregateLowered}\n${normalised.toLowerCase()}`;
|
|
118
|
+
}
|
|
119
|
+
const priorityHint = derivePriority(aggregateLowered, profile);
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
themes: Object.freeze(Array.from(themes).sort(compareLowercase)),
|
|
122
|
+
requirementCandidates: Object.freeze(Array.from(requirements).sort(compareLowercase)),
|
|
123
|
+
riskHints: Object.freeze(Array.from(risks).sort(compareLowercase)),
|
|
124
|
+
priorityHint,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { QualityIntelligence } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
export interface PolicyProfile {
|
|
3
|
+
/** Stable identifier, used by callers to attribute outputs. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** Display-only label. */
|
|
6
|
+
readonly displayLabel: string;
|
|
7
|
+
/**
|
|
8
|
+
* Lower-cased keywords that, when present in source envelopes or atoms,
|
|
9
|
+
* bias the derived intent's priority toward the head of this list.
|
|
10
|
+
* Ordering implies severity: index 0 is the highest-priority bucket.
|
|
11
|
+
*/
|
|
12
|
+
readonly priorityKeywords: readonly (readonly string[])[];
|
|
13
|
+
/**
|
|
14
|
+
* Lower-cased keywords that signal a risk class. The first matching class
|
|
15
|
+
* (in the order risk-classes are enumerated below) wins. Pure heuristic.
|
|
16
|
+
*/
|
|
17
|
+
readonly riskKeywords: Readonly<Record<QualityIntelligence.QualityIntelligenceRiskClass, readonly string[]>>;
|
|
18
|
+
/** Default risk class when no risk keyword matches. */
|
|
19
|
+
readonly defaultRiskClass: QualityIntelligence.QualityIntelligenceRiskClass;
|
|
20
|
+
/** Default priority when no priority keyword matches. */
|
|
21
|
+
readonly defaultPriority: QualityIntelligence.QualityIntelligencePriority;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Default profile for banking workflows. Heightens safety/compliance terms.
|
|
25
|
+
*/
|
|
26
|
+
export declare const bankingDefault: PolicyProfile;
|
|
27
|
+
/**
|
|
28
|
+
* Default profile for insurance workflows. Tracks policy/claim language.
|
|
29
|
+
*/
|
|
30
|
+
export declare const insuranceDefault: PolicyProfile;
|
|
31
|
+
/**
|
|
32
|
+
* Default profile for regression suites. Biases toward existing functional
|
|
33
|
+
* paths over novel discovery.
|
|
34
|
+
*/
|
|
35
|
+
export declare const regressionDefault: PolicyProfile;
|
|
36
|
+
export declare const ALL_POLICY_PROFILES: readonly PolicyProfile[];
|
|
37
|
+
//# sourceMappingURL=policyProfile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policyProfile.d.ts","sourceRoot":"","sources":["../../src/domain/policyProfile.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC;IAC1D;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAC7B,MAAM,CAAC,mBAAmB,CAAC,4BAA4B,EAAE,SAAS,MAAM,EAAE,CAAC,CAC5E,CAAC;IACF,uDAAuD;IACvD,QAAQ,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,4BAA4B,CAAC;IAC5E,yDAAyD;IACzD,QAAQ,CAAC,eAAe,EAAE,mBAAmB,CAAC,2BAA2B,CAAC;CAC3E;AAcD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,aAkB3B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAkB7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAkB9B,CAAC;AAEH,eAAO,MAAM,mBAAmB,EAAE,SAAS,aAAa,EAItD,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Quality Intelligence policy-profile constants (Epic #270, Issue #272).
|
|
2
|
+
//
|
|
3
|
+
// Pure data, deeply frozen. A policy profile carries the per-domain heuristic
|
|
4
|
+
// configuration that downstream callers (validators, test-design model,
|
|
5
|
+
// coverage relevance) blend with the source envelopes. v1 only encodes the
|
|
6
|
+
// fields the test-design model needs; richer governance policies land with
|
|
7
|
+
// #282.
|
|
8
|
+
//
|
|
9
|
+
// Inspired structurally by the per-domain heuristics under
|
|
10
|
+
// Test Intelligence reference (TI) packages/core-engine/src/intent-derivation.ts
|
|
11
|
+
// — porting the lexical hints, not any provider-specific tuning.
|
|
12
|
+
const freezeProfile = (profile) => {
|
|
13
|
+
for (const keywords of profile.priorityKeywords) {
|
|
14
|
+
Object.freeze(keywords);
|
|
15
|
+
}
|
|
16
|
+
Object.freeze(profile.priorityKeywords);
|
|
17
|
+
for (const value of Object.values(profile.riskKeywords)) {
|
|
18
|
+
Object.freeze(value);
|
|
19
|
+
}
|
|
20
|
+
Object.freeze(profile.riskKeywords);
|
|
21
|
+
return Object.freeze(profile);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Default profile for banking workflows. Heightens safety/compliance terms.
|
|
25
|
+
*/
|
|
26
|
+
export const bankingDefault = freezeProfile({
|
|
27
|
+
id: "banking-default",
|
|
28
|
+
displayLabel: "Banking — default",
|
|
29
|
+
priorityKeywords: [
|
|
30
|
+
["fraud", "aml", "kyc", "sanction"],
|
|
31
|
+
["payment", "transfer", "settlement", "interest"],
|
|
32
|
+
["statement", "balance", "ledger"],
|
|
33
|
+
["preference", "theme", "marketing"],
|
|
34
|
+
],
|
|
35
|
+
riskKeywords: {
|
|
36
|
+
safety: ["unauthorised", "lockout", "credential", "session"],
|
|
37
|
+
compliance: ["aml", "kyc", "gdpr", "regulator", "sanction", "audit"],
|
|
38
|
+
regression: ["regression", "smoke", "release"],
|
|
39
|
+
functional: ["enter", "submit", "confirm", "cancel"],
|
|
40
|
+
visual: ["layout", "spacing", "colour", "color", "icon"],
|
|
41
|
+
},
|
|
42
|
+
defaultRiskClass: "compliance",
|
|
43
|
+
defaultPriority: "P2",
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* Default profile for insurance workflows. Tracks policy/claim language.
|
|
47
|
+
*/
|
|
48
|
+
export const insuranceDefault = freezeProfile({
|
|
49
|
+
id: "insurance-default",
|
|
50
|
+
displayLabel: "Insurance — default",
|
|
51
|
+
priorityKeywords: [
|
|
52
|
+
["fraud", "denial", "fatality"],
|
|
53
|
+
["claim", "policy", "premium", "underwriting"],
|
|
54
|
+
["quote", "renewal"],
|
|
55
|
+
["preference", "marketing", "newsletter"],
|
|
56
|
+
],
|
|
57
|
+
riskKeywords: {
|
|
58
|
+
safety: ["fatal", "injury", "exposure"],
|
|
59
|
+
compliance: ["regulator", "gdpr", "consent", "broker"],
|
|
60
|
+
regression: ["regression", "smoke"],
|
|
61
|
+
functional: ["submit", "renew", "approve", "decline"],
|
|
62
|
+
visual: ["layout", "spacing", "icon", "logo"],
|
|
63
|
+
},
|
|
64
|
+
defaultRiskClass: "functional",
|
|
65
|
+
defaultPriority: "P2",
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Default profile for regression suites. Biases toward existing functional
|
|
69
|
+
* paths over novel discovery.
|
|
70
|
+
*/
|
|
71
|
+
export const regressionDefault = freezeProfile({
|
|
72
|
+
id: "regression-default",
|
|
73
|
+
displayLabel: "Regression — default",
|
|
74
|
+
priorityKeywords: [
|
|
75
|
+
["smoke", "critical-path", "release"],
|
|
76
|
+
["regression"],
|
|
77
|
+
["edge-case"],
|
|
78
|
+
["nice-to-have"],
|
|
79
|
+
],
|
|
80
|
+
riskKeywords: {
|
|
81
|
+
safety: ["crash", "data-loss"],
|
|
82
|
+
compliance: ["audit", "log"],
|
|
83
|
+
regression: ["regression", "smoke", "release", "stable"],
|
|
84
|
+
functional: ["form", "click", "submit"],
|
|
85
|
+
visual: ["layout", "colour", "color", "spacing"],
|
|
86
|
+
},
|
|
87
|
+
defaultRiskClass: "regression",
|
|
88
|
+
defaultPriority: "P2",
|
|
89
|
+
});
|
|
90
|
+
export const ALL_POLICY_PROFILES = Object.freeze([
|
|
91
|
+
bankingDefault,
|
|
92
|
+
insuranceDefault,
|
|
93
|
+
regressionDefault,
|
|
94
|
+
]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Maximum excerpt length in characters, including the trailing ellipsis. */
|
|
2
|
+
export declare const REQUIREMENT_EXCERPT_MAX_CHARS: 96;
|
|
3
|
+
/**
|
|
4
|
+
* Build a short, redacted, single-line excerpt of an atom's canonical text. Returns `undefined`
|
|
5
|
+
* for empty/whitespace-only input so callers can simply omit the optional field. Deterministic:
|
|
6
|
+
* same input always yields the same excerpt (no timestamps, no randomness).
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildRequirementExcerpt(canonicalText: string): string | undefined;
|
|
9
|
+
//# sourceMappingURL=requirementExcerpt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirementExcerpt.d.ts","sourceRoot":"","sources":["../../src/domain/requirementExcerpt.ts"],"names":[],"mappings":"AA0BA,6EAA6E;AAC7E,eAAO,MAAM,6BAA6B,EAAG,EAAW,CAAC;AAIzD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKjF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Human-readable requirement excerpt for coverage/traceability surfaces (Epic #734, Issue #790).
|
|
2
|
+
//
|
|
3
|
+
// Coverage matrix rows, gap findings and the traceability matrix historically carried atom ids
|
|
4
|
+
// only — auditors could not tell WHICH requirement `qi-atom-…` is without manually cross-
|
|
5
|
+
// referencing the source. This helper derives a short, redaction-safe excerpt from an atom's
|
|
6
|
+
// server-side canonical text so those surfaces become auditor-readable without ever persisting
|
|
7
|
+
// raw source content.
|
|
8
|
+
//
|
|
9
|
+
// Ordering is load-bearing: unsafe bidi/zero-width/control code points are stripped FIRST, then the
|
|
10
|
+
// FULL text is redacted, then whitespace-collapsed, then truncated.
|
|
11
|
+
// * Stripping before redaction is a redaction SAFEGUARD, not just cosmetics: a credential
|
|
12
|
+
// obfuscated by an interstitial zero-width character (e.g. an AWS key with a U+200B inside it)
|
|
13
|
+
// slips past redact() — whose patterns match contiguous characters — and would survive into the
|
|
14
|
+
// excerpt. Removing the spoofing code points first de-obfuscates the text so redact() sees, and
|
|
15
|
+
// scrubs, the real secret. It also keeps the audit-ready coverage/traceability surfaces free of
|
|
16
|
+
// invisible/bidi characters (symmetric with the candidate-text chokepoint normaliseCandidateText).
|
|
17
|
+
// * Truncating before redaction could split a secret across the cut so the remainder no longer
|
|
18
|
+
// matches any redaction pattern (a partial AWS key is still a leak); redacting first makes the
|
|
19
|
+
// truncation operate on already-safe text.
|
|
20
|
+
// Persistence redacts every string leaf again (defense in depth), so a stored excerpt is redacted at
|
|
21
|
+
// least twice; the format-char strip sits upstream of every persisted coverage/finding/export surface.
|
|
22
|
+
import { redact } from "@oscharko-dev/keiko-security";
|
|
23
|
+
import { stripUnsafeFormatChars } from "./assertions.js";
|
|
24
|
+
/** Maximum excerpt length in characters, including the trailing ellipsis. */
|
|
25
|
+
export const REQUIREMENT_EXCERPT_MAX_CHARS = 96;
|
|
26
|
+
const ELLIPSIS = "…";
|
|
27
|
+
/**
|
|
28
|
+
* Build a short, redacted, single-line excerpt of an atom's canonical text. Returns `undefined`
|
|
29
|
+
* for empty/whitespace-only input so callers can simply omit the optional field. Deterministic:
|
|
30
|
+
* same input always yields the same excerpt (no timestamps, no randomness).
|
|
31
|
+
*/
|
|
32
|
+
export function buildRequirementExcerpt(canonicalText) {
|
|
33
|
+
const collapsed = redact(stripUnsafeFormatChars(canonicalText)).replace(/\s+/gu, " ").trim();
|
|
34
|
+
if (collapsed.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (collapsed.length <= REQUIREMENT_EXCERPT_MAX_CHARS)
|
|
37
|
+
return collapsed;
|
|
38
|
+
return collapsed.slice(0, REQUIREMENT_EXCERPT_MAX_CHARS - ELLIPSIS.length).trimEnd() + ELLIPSIS;
|
|
39
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** A reason why a single candidate is stale. */
|
|
2
|
+
export interface StalenessReason {
|
|
3
|
+
readonly candidateId: string;
|
|
4
|
+
readonly reason: "source-changed" | "source-removed";
|
|
5
|
+
readonly envelopeId: string;
|
|
6
|
+
}
|
|
7
|
+
/** Partitioned staleness report for a full run. */
|
|
8
|
+
export interface StalenessResult {
|
|
9
|
+
readonly fresh: readonly string[];
|
|
10
|
+
readonly changedStale: readonly StalenessReason[];
|
|
11
|
+
readonly orphanedStale: readonly StalenessReason[];
|
|
12
|
+
}
|
|
13
|
+
export interface CompareStalenessArgs {
|
|
14
|
+
/** Fingerprints persisted with the original run (from manifest.sourceFingerprints). */
|
|
15
|
+
readonly oldFingerprints: readonly {
|
|
16
|
+
envelopeId: string;
|
|
17
|
+
integrityHashSha256Hex: string;
|
|
18
|
+
}[];
|
|
19
|
+
/** Optional atom-level fingerprints persisted with the original run (post-fix runs only). */
|
|
20
|
+
readonly oldAtomFingerprints?: readonly {
|
|
21
|
+
atomId: string;
|
|
22
|
+
envelopeId: string;
|
|
23
|
+
canonicalHashSha256Hex: string;
|
|
24
|
+
replacementGroupId?: string;
|
|
25
|
+
replacementOrdinal?: number;
|
|
26
|
+
}[];
|
|
27
|
+
/** Evidence refs from the manifest (atomId → envelopeId mapping). */
|
|
28
|
+
readonly evidenceRefs: readonly {
|
|
29
|
+
envelopeId: string;
|
|
30
|
+
atomId: string;
|
|
31
|
+
}[];
|
|
32
|
+
/** Candidates to classify. */
|
|
33
|
+
readonly candidates: readonly {
|
|
34
|
+
id: string;
|
|
35
|
+
derivedFromAtomIds: readonly string[];
|
|
36
|
+
}[];
|
|
37
|
+
/** Fingerprints from re-ingesting the current sources right now. */
|
|
38
|
+
readonly currentFingerprints: readonly {
|
|
39
|
+
envelopeId: string;
|
|
40
|
+
integrityHashSha256Hex: string;
|
|
41
|
+
}[];
|
|
42
|
+
/** Optional atom-level fingerprints from re-ingesting the current sources right now. */
|
|
43
|
+
readonly currentAtomFingerprints?: readonly {
|
|
44
|
+
atomId: string;
|
|
45
|
+
envelopeId: string;
|
|
46
|
+
canonicalHashSha256Hex: string;
|
|
47
|
+
replacementGroupId?: string;
|
|
48
|
+
replacementOrdinal?: number;
|
|
49
|
+
}[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Classify each candidate as fresh or stale based on whether their source envelopes have changed
|
|
53
|
+
* since the run was persisted. Pure, deterministic; candidate input order is preserved.
|
|
54
|
+
*/
|
|
55
|
+
export declare function compareStaleness(args: CompareStalenessArgs): StalenessResult;
|
|
56
|
+
//# sourceMappingURL=staleness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../../src/domain/staleness.ts"],"names":[],"mappings":"AASA,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAAC;IACrD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAC;IAClD,QAAQ,CAAC,aAAa,EAAE,SAAS,eAAe,EAAE,CAAC;CACpD;AAED,MAAM,WAAW,oBAAoB;IACnC,uFAAuF;IACvF,QAAQ,CAAC,eAAe,EAAE,SAAS;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,sBAAsB,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5F,6FAA6F;IAC7F,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS;QACtC,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,EAAE,CAAC;IACJ,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACzE,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IACtF,oEAAoE;IACpE,QAAQ,CAAC,mBAAmB,EAAE,SAAS;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,sBAAsB,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChG,wFAAwF;IACxF,QAAQ,CAAC,uBAAuB,CAAC,EAAE,SAAS;QAC1C,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,EAAE,CAAC;CACL;AAoaD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,eAAe,CAgC5E"}
|