@kontourai/flow-agents 0.3.0 → 1.0.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/.github/workflows/kit-gates-demo.yml +171 -0
- package/.github/workflows/release-please.yml +13 -1
- package/AGENTS.md +8 -1
- package/CHANGELOG.md +53 -0
- package/CONTEXT.md +1 -1
- package/README.md +13 -2
- package/build/src/cli/flow-kit.js +41 -2
- package/build/src/flow-kit/validate.js +98 -0
- package/build/src/tools/validate-source-tree.js +2 -1
- package/context/scripts/hooks/config-protection.js +217 -15
- package/docs/fixture-ownership.md +1 -0
- package/docs/index.md +9 -1
- package/docs/kit-authoring-guide.md +126 -0
- package/docs/knowledge-kit.md +69 -0
- package/docs/vision.md +22 -0
- package/evals/fixtures/kit-conformance-levels/k0-flows-only/flows/review.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/k0-flows-only/kit.json +13 -0
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/docs/README.md +3 -0
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/flows/build.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/kit.json +20 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/docs/README.md +3 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/eval-suites/contract-suite/suite.test.js +1 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/flows/synthesize.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/kit.json +27 -0
- package/evals/fixtures/kit-conformance-levels/third-party-extension/flows/review.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/third-party-extension/kit.json +19 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_hook_category_behaviors.sh +51 -0
- package/evals/integration/test_kit_conformance_levels.sh +209 -0
- package/evals/run.sh +2 -0
- package/evals/static/test_universal_bundles.sh +10 -0
- package/kits/catalog.json +6 -0
- package/kits/knowledge/adapters/default-store/index.js +95 -14
- package/kits/knowledge/adapters/flow-runner/entity-extractor.js +194 -0
- package/kits/knowledge/adapters/flow-runner/index.js +639 -0
- package/kits/knowledge/adapters/obsidian-store/README.md +141 -0
- package/kits/knowledge/adapters/obsidian-store/demo.js +181 -0
- package/kits/knowledge/adapters/obsidian-store/index.js +868 -0
- package/kits/knowledge/adapters/shared/codec.js +325 -0
- package/kits/knowledge/adapters/similarity-vector/index.js +284 -0
- package/kits/knowledge/docs/README.md +193 -0
- package/kits/knowledge/docs/store-contract.md +196 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +10 -5
- package/kits/knowledge/evals/entities/demo-acme.js +125 -0
- package/kits/knowledge/evals/entities/suite.test.js +722 -0
- package/kits/knowledge/evals/retirement/suite.test.js +1173 -0
- package/kits/knowledge/evals/similarity-vector/suite.test.js +685 -0
- package/kits/knowledge/evals/synthesis/suite.test.js +10 -3
- package/kits/knowledge/flows/retire.flow.json +77 -0
- package/kits/knowledge/kit.json +31 -1
- package/kits/release-evidence/fixtures/claims/README.md +14 -0
- package/kits/release-evidence/fixtures/claims/fail-rejected-release.trust.json +22 -0
- package/kits/release-evidence/fixtures/claims/pass-trusted-release.trust.json +22 -0
- package/kits/release-evidence/flows/release-evidence.flow.json +38 -0
- package/kits/release-evidence/kit.json +13 -0
- package/package.json +1 -1
- package/packaging/conformance/fixtures/config-protection--allow-no-verify-in-string.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-git-no-verify.json +23 -0
- package/scripts/hooks/config-protection.js +217 -15
- package/src/cli/flow-kit.ts +40 -2
- package/src/flow-kit/validate.ts +127 -0
- package/src/tools/validate-source-tree.ts +2 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Kit — Entity Extractor
|
|
3
|
+
*
|
|
4
|
+
* Pluggable interface for extracting person entities from raw/compiled records.
|
|
5
|
+
* Pattern mirrors SimilarityDetector (see flow-runner/index.js R3 pattern):
|
|
6
|
+
* an EntityExtractor is a function:
|
|
7
|
+
*
|
|
8
|
+
* async (record: Record) => PersonMention[]
|
|
9
|
+
*
|
|
10
|
+
* where PersonMention = { name: string, role?: string, org?: string }
|
|
11
|
+
*
|
|
12
|
+
* Default implementation: AttendeeLineExtractor
|
|
13
|
+
* - Parses "Attendees:" lines: entries separated by top-level commas (commas
|
|
14
|
+
* inside parentheticals are NOT treated as entry separators).
|
|
15
|
+
* - Each entry may carry an optional parenthetical role/org:
|
|
16
|
+
* "Dana Smith (Acme VP Eng), Lee Wong (Acme, procurement)."
|
|
17
|
+
* - Trailing sentence punctuation after the last ')' is stripped so that
|
|
18
|
+
* end-of-line entries like 'Lee Wong (Acme procurement).' parse correctly
|
|
19
|
+
* (fix for issue #48 — trailing period folded role into name).
|
|
20
|
+
* - Also extracts explicit [[wikilinks]] from the body (name = link target)
|
|
21
|
+
* - NO freeform NLP — conservative by design (R2)
|
|
22
|
+
*
|
|
23
|
+
* @module adapters/flow-runner/entity-extractor
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Attendee line parser
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
const ATTENDEES_LINE_RE = /^Attendees:\s*(.+)$/im;
|
|
31
|
+
const ENTRY_WITH_ROLE_RE = /^([^(]+?)\s*\(([^)]+)\)\s*$/;
|
|
32
|
+
const WIKILINK_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Split an attendee list on top-level commas (commas inside parentheticals
|
|
36
|
+
* are NOT treated as entry separators).
|
|
37
|
+
* "Dana Smith (Acme VP Eng), Lee Wong (Acme, procurement)." →
|
|
38
|
+
* ["Dana Smith (Acme VP Eng)", "Lee Wong (Acme, procurement)."]
|
|
39
|
+
*
|
|
40
|
+
* @param {string} text
|
|
41
|
+
* @returns {string[]}
|
|
42
|
+
*/
|
|
43
|
+
function splitAttendeeEntries(text) {
|
|
44
|
+
const entries = [];
|
|
45
|
+
let depth = 0;
|
|
46
|
+
let start = 0;
|
|
47
|
+
for (let i = 0; i < text.length; i++) {
|
|
48
|
+
if (text[i] === "(") depth++;
|
|
49
|
+
else if (text[i] === ")") depth--;
|
|
50
|
+
else if (text[i] === "," && depth === 0) {
|
|
51
|
+
const entry = text.slice(start, i).trim();
|
|
52
|
+
if (entry) entries.push(entry);
|
|
53
|
+
start = i + 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const last = text.slice(start).trim();
|
|
57
|
+
if (last) entries.push(last);
|
|
58
|
+
return entries;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse one attendee entry: "Dana Smith (Acme VP Eng)" or "Lee Wong"
|
|
63
|
+
* Returns { name, role?, org? }
|
|
64
|
+
*
|
|
65
|
+
* Strips trailing sentence punctuation only when it appears after a closing ')'
|
|
66
|
+
* to handle end-of-line cases like 'Lee Wong (Acme procurement).' without
|
|
67
|
+
* accidentally removing trailing periods that are part of abbreviated names
|
|
68
|
+
* like 'Dana S.'.
|
|
69
|
+
*/
|
|
70
|
+
function parseAttendeeEntry(entry) {
|
|
71
|
+
const trimmed = entry.trim();
|
|
72
|
+
// Only strip trailing punctuation when it appears after a closing ')'.
|
|
73
|
+
// This handles 'Lee Wong (Acme procurement).' (issue #48) while leaving
|
|
74
|
+
// 'Dana S.' intact so the abbreviated-name form is preserved.
|
|
75
|
+
const normalized = /\)\s*[.,;:!?]+\s*$/.test(trimmed)
|
|
76
|
+
? trimmed.replace(/[.,;:!?]+$/, "")
|
|
77
|
+
: trimmed;
|
|
78
|
+
const match = normalized.match(ENTRY_WITH_ROLE_RE);
|
|
79
|
+
if (!match) return { name: normalized };
|
|
80
|
+
|
|
81
|
+
const name = match[1].trim();
|
|
82
|
+
const roleOrgText = match[2].trim();
|
|
83
|
+
|
|
84
|
+
// Try to split "Org Role" or "Org Title Role" — heuristic:
|
|
85
|
+
// if the parenthetical contains multiple words, first token(s) = org,
|
|
86
|
+
// last token(s) = role. We just store the whole string as role; callers
|
|
87
|
+
// can parse further. For AC1 we need role text available.
|
|
88
|
+
return { name, role: roleOrgText };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Default entity extractor: parses Attendees: lines and explicit [[wikilinks]].
|
|
93
|
+
*
|
|
94
|
+
* EntityExtractor interface:
|
|
95
|
+
* async (record: Record) => PersonMention[]
|
|
96
|
+
*
|
|
97
|
+
* PersonMention: { name: string, role?: string, org?: string }
|
|
98
|
+
*
|
|
99
|
+
* @param {object} record
|
|
100
|
+
* @returns {Promise<Array<{name: string, role?: string, org?: string}>>}
|
|
101
|
+
*/
|
|
102
|
+
export async function defaultEntityExtractor(record) {
|
|
103
|
+
const body = record.body || "";
|
|
104
|
+
const mentions = new Map(); // name → mention (deduplicated)
|
|
105
|
+
|
|
106
|
+
// 1. Parse "Attendees:" line
|
|
107
|
+
const attendeesMatch = body.match(ATTENDEES_LINE_RE);
|
|
108
|
+
if (attendeesMatch) {
|
|
109
|
+
const entriesText = attendeesMatch[1];
|
|
110
|
+
const entries = splitAttendeeEntries(entriesText);
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
const mention = parseAttendeeEntry(entry);
|
|
113
|
+
if (mention.name && !mentions.has(mention.name)) {
|
|
114
|
+
mentions.set(mention.name, mention);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 2. Extract explicit [[wikilinks]] — target treated as the person name
|
|
120
|
+
for (const match of body.matchAll(WIKILINK_RE)) {
|
|
121
|
+
const name = match[1].trim();
|
|
122
|
+
if (!mentions.has(name)) {
|
|
123
|
+
mentions.set(name, { name });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return [...mentions.values()];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Name normalisation helpers
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Normalise a person name for comparison:
|
|
136
|
+
* lowercase, trim, collapse internal whitespace.
|
|
137
|
+
*/
|
|
138
|
+
export function normalizeName(name) {
|
|
139
|
+
return name.toLowerCase().trim().replace(/\s+/g, " ");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if two normalised names are an exact match.
|
|
144
|
+
*/
|
|
145
|
+
export function isExactMatch(a, b) {
|
|
146
|
+
return normalizeName(a) === normalizeName(b);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if `candidate` is a possible duplicate of `existing`:
|
|
151
|
+
* - Same-surname + same first initial, OR same-firstname + initial surname
|
|
152
|
+
* e.g. "Dana S." ~ "Dana Smith" (first matches, last is initial of last)
|
|
153
|
+
* "D. Smith" ~ "Dana Smith" (first is initial of first, last matches)
|
|
154
|
+
* Does NOT auto-merge — returns true only when ambiguous, not identical.
|
|
155
|
+
*/
|
|
156
|
+
export function isPossibleDuplicate(candidate, existing) {
|
|
157
|
+
const c = normalizeName(candidate).split(" ");
|
|
158
|
+
const e = normalizeName(existing).split(" ");
|
|
159
|
+
if (c.length < 1 || e.length < 1) return false;
|
|
160
|
+
|
|
161
|
+
// Exact match is not a "possible duplicate" — it's a real match
|
|
162
|
+
if (isExactMatch(candidate, existing)) return false;
|
|
163
|
+
|
|
164
|
+
if (c.length < 2 || e.length < 2) return false;
|
|
165
|
+
|
|
166
|
+
const cFirst = c[0];
|
|
167
|
+
const cLast = c[c.length - 1];
|
|
168
|
+
const eFirst = e[0];
|
|
169
|
+
const eLast = e[e.length - 1];
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* isInitialOf(abbr, full): true if abbr is a single-letter abbreviation of full.
|
|
173
|
+
* "s." → "smith" (s matches first char of smith)
|
|
174
|
+
* "d." → "dana"
|
|
175
|
+
*/
|
|
176
|
+
function isInitialOf(abbr, full) {
|
|
177
|
+
const a = abbr.replace(/\.$/, "");
|
|
178
|
+
return a.length === 1 && full.startsWith(a);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Case A: "Dana S." ~ "Dana Smith"
|
|
182
|
+
// first names match (or one is initial of other), last of candidate is initial of last of existing
|
|
183
|
+
const firstsMatch = cFirst === eFirst || isInitialOf(cFirst, eFirst) || isInitialOf(eFirst, cFirst);
|
|
184
|
+
const lastInitialA = isInitialOf(cLast, eLast) || isInitialOf(eLast, cLast);
|
|
185
|
+
|
|
186
|
+
// Case B: "D. Smith" ~ "Dana Smith"
|
|
187
|
+
// first of candidate is initial, last names match exactly
|
|
188
|
+
const firstInitialB = isInitialOf(cFirst, eFirst) || isInitialOf(eFirst, cFirst);
|
|
189
|
+
const lastsMatchB = cLast === eLast;
|
|
190
|
+
|
|
191
|
+
return (firstsMatch && lastInitialA) || (firstInitialB && lastsMatchB);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default defaultEntityExtractor;
|