diff --git a/src/lib/skills/actions.js b/src/lib/skills/actions.ts similarity index 98% rename from src/lib/skills/actions.js rename to src/lib/skills/actions.ts index d1316d6..c5633dc 100644 --- a/src/lib/skills/actions.js +++ b/src/lib/skills/actions.ts @@ -168,7 +168,11 @@ export function buildPositionPrefill(question, categories = []) { const english = extractEnglish(question); const certifications = extractCertifications(question); - const prefill = {}; + const prefill: { + role_category?: any; title?: any; location?: any; + pay_range_min?: any; pay_range_max?: any; min_experience_years?: any; + english_required?: any; certifications_required?: any; + } = {}; if (role) { /* A known category fills the category field; anything else is a title, so a one-off role is not silently filed under the wrong category. Either way @@ -378,7 +382,10 @@ export function buildSkillPrefill(question, categories = []) { const outline = template?.outline || []; const challengeType = template?.proof || null; - const prefill = {}; + const prefill: { + title?: any; category?: any; difficulty?: any; + outline?: any; challengeType?: any; + } = {}; if (title) prefill.title = title; if (category || template?.category) prefill.category = category || template.category; if (difficulty) prefill.difficulty = difficulty; diff --git a/src/lib/skills/catalog.js b/src/lib/skills/catalog.ts similarity index 99% rename from src/lib/skills/catalog.js rename to src/lib/skills/catalog.ts index 39667d2..db2df72 100644 --- a/src/lib/skills/catalog.js +++ b/src/lib/skills/catalog.ts @@ -295,7 +295,7 @@ export const TYPE_LABEL = { owliver: 'Owliver', board: 'Board', both: 'Owliver + /** The sections a Board skill declares, flattened with the page each sits on. */ function sectionsOf(skill) { - return Object.entries(skill.ui || {}).flatMap(([page, config]) => + return Object.entries(skill.ui || {}).flatMap(([page, config]) => (config.sections || []).map((section) => ({ id: section.id, title: section.title || sectionTypeLabel(section.type), diff --git a/src/lib/skills/customSkills.js b/src/lib/skills/customSkills.ts similarity index 100% rename from src/lib/skills/customSkills.js rename to src/lib/skills/customSkills.ts diff --git a/src/lib/skills/dataResolver.js b/src/lib/skills/dataResolver.ts similarity index 99% rename from src/lib/skills/dataResolver.js rename to src/lib/skills/dataResolver.ts index 8339b04..f839637 100644 --- a/src/lib/skills/dataResolver.js +++ b/src/lib/skills/dataResolver.ts @@ -563,7 +563,7 @@ const RESOLVERS = { /* Deduped: overlapping windows — `today` inside `last-7-days` — would otherwise count the same application twice. */ - const unique = [...new Map(pool.map((a) => [a.id, a])).values()]; + const unique: any[] = [...new Map(pool.map((a) => [a.id, a])).values()]; const scored = unique.filter((a) => a.ai_score > 0); const bands = [ @@ -797,11 +797,11 @@ const RESOLVERS = { ).values()] : activity; - const byType = events.reduce((acc, e) => { + const byType = events.reduce>((acc, e) => { acc[e.event_type] = (acc[e.event_type] || 0) + 1; return acc; }, {}); - const byAccount = events.reduce((acc, e) => { + const byAccount = events.reduce>((acc, e) => { acc[e.user_name || e.user_email] = (acc[e.user_name || e.user_email] || 0) + 1; return acc; }, {}); @@ -1189,7 +1189,7 @@ const RESOLVERS = { * is missing returns `unavailable`, which the renderer states rather than * filling in. */ -export function resolveSkillData(section, context = {}, now = new Date()) { +export function resolveSkillData(section, context: any = {}, now = new Date()) { const resolve = RESOLVERS[section?.source]; if (!resolve) return { unavailable: true, emptyNote: `No resolver for ${section?.source}.` }; diff --git a/src/lib/skills/flows/employeeRole.js b/src/lib/skills/flows/employeeRole.ts similarity index 99% rename from src/lib/skills/flows/employeeRole.js rename to src/lib/skills/flows/employeeRole.ts index c625964..a731771 100644 --- a/src/lib/skills/flows/employeeRole.js +++ b/src/lib/skills/flows/employeeRole.ts @@ -191,7 +191,7 @@ const fields = { * `@workers` is the worker profiles the panel has already loaded for this * caller — org-scoped by the API, and nothing here widens that view. */ -const resolve = (token, { roles = [], postings = null }, draft = {}) => { +const resolve = (token, { roles = [], postings = null }, draft: any = {}) => { switch (token) { case '@roles': return roles; case '@english': return ENGLISH_LEVELS.map((l) => l.label); @@ -224,7 +224,7 @@ const resolve = (token, { roles = [], postings = null }, draft = {}) => { * how a role gets filed against the wrong person. */ function extract(said, ctx, fieldNames) { - const patch = {}; + const patch: any = {}; const touched = new Set(); if (fieldNames.has('role_category')) { diff --git a/src/lib/skills/flows/index.js b/src/lib/skills/flows/index.ts similarity index 100% rename from src/lib/skills/flows/index.js rename to src/lib/skills/flows/index.ts diff --git a/src/lib/skills/flows/position.js b/src/lib/skills/flows/position.ts similarity index 99% rename from src/lib/skills/flows/position.js rename to src/lib/skills/flows/position.ts index 00c68f9..0f37a10 100644 --- a/src/lib/skills/flows/position.js +++ b/src/lib/skills/flows/position.ts @@ -143,7 +143,7 @@ const fields = { * provision a TENANT the operator cannot then see, because every read is * predicated on the session's own org_id. */ -const resolve = (token, { roles = [], companies = [], postings = null }, draft = {}) => { +const resolve = (token, { roles = [], companies = [], postings = null }, draft: any = {}) => { switch (token) { case '@roles': return roles; case '@companies': return companies; @@ -160,7 +160,7 @@ const resolve = (token, { roles = [], companies = [], postings = null }, draft = /** One sentence read against every field of a posting at once. */ function extract(said, { roles = [] }, fieldNames) { const { prefill } = buildPositionPrefill(said, roles); - const patch = {}; + const patch: any = {}; const touched = new Set(); if (fieldNames.has('role_category') && prefill.title) { diff --git a/src/lib/skills/owliverConfig.js b/src/lib/skills/owliverConfig.ts similarity index 99% rename from src/lib/skills/owliverConfig.js rename to src/lib/skills/owliverConfig.ts index 81dc9e7..838b970 100644 --- a/src/lib/skills/owliverConfig.js +++ b/src/lib/skills/owliverConfig.ts @@ -59,7 +59,7 @@ const shapeOf = (capability) => owliverCapabilityFor(capability)?.shape || null; * section wins, in declaration order. */ function inheritedSection(ui) { - for (const page of Object.values(ui || {})) { + for (const page of Object.values(ui || {})) { const section = (page.sections || [])[0]; if (section) return section; } diff --git a/src/lib/skills/owliverResolver.js b/src/lib/skills/owliverResolver.ts similarity index 99% rename from src/lib/skills/owliverResolver.js rename to src/lib/skills/owliverResolver.ts index 3d12365..45ce3ac 100644 --- a/src/lib/skills/owliverResolver.js +++ b/src/lib/skills/owliverResolver.ts @@ -66,7 +66,7 @@ const TOTAL = 4; * A suggestion naming a capability the skill does not offer is dropped rather * than shown and then refused. */ -export function owliverSuggestions(contextId, disabled = [], customSources = [], context = {}) { +export function owliverSuggestions(contextId, disabled = [], customSources = [], context: any = {}) { const chips = []; for (const skill of owliverSkillsForContext(contextId, disabled, customSources)) { @@ -263,7 +263,7 @@ export function matchOwliverSkill(question, skills = []) { * says which role they mean has said so, and answering about a different one * because a drawer happened to be open would be worse than asking. */ -export function resolveEntity(section, question, context = {}) { +export function resolveEntity(section, question, context: any = {}) { const need = section.context; if (!need) return { context, ok: true }; @@ -338,7 +338,7 @@ export const responseTitle = (skill, section) => * Returns `{ skill, capability, section, data, missing }`. `missing` names what * the caller must ask for; when it is null the reading is real and complete. */ -export function resolveOwliverResponse({ skill, capability, question, context = {}, now = new Date() }) { +export function resolveOwliverResponse({ skill, capability, question, context = {}, now = new Date() }: any) { const section = skill.owliver.responses[capability]; if (!section) return null; diff --git a/src/lib/skills/pageSkills.js b/src/lib/skills/pageSkills.ts similarity index 99% rename from src/lib/skills/pageSkills.js rename to src/lib/skills/pageSkills.ts index 669ab68..9ac16b7 100644 --- a/src/lib/skills/pageSkills.js +++ b/src/lib/skills/pageSkills.ts @@ -85,7 +85,7 @@ export function skillStatesForPage(pageId, courses = [], profile = null, options * Same definitions, same graph, same derivation as the page-scoped version * above: this is a second question asked of one dataset, not a second dataset. */ -export function workforceSkillStates(courses = [], profile = null, options = {}) { +export function workforceSkillStates(courses = [], profile = null, options: any = {}) { const { customSources = [], disabled = [] } = options; return allSkills(customSources) .filter((s) => s.kind === 'workforce' && s.status === 'active' && !disabled.includes(s.id)) diff --git a/src/lib/skills/positionFlow.js b/src/lib/skills/positionFlow.ts similarity index 100% rename from src/lib/skills/positionFlow.js rename to src/lib/skills/positionFlow.ts diff --git a/src/lib/skills/registry.js b/src/lib/skills/registry.ts similarity index 97% rename from src/lib/skills/registry.js rename to src/lib/skills/registry.ts index 188a956..d97a817 100644 --- a/src/lib/skills/registry.js +++ b/src/lib/skills/registry.ts @@ -57,7 +57,29 @@ export const normalizeDefinition = (raw) => String(raw ?? '') export const hasFrontmatter = (raw) => /^---[ \t]*\n[\s\S]*?\n---[ \t]*(?=\n|$)/ .test(normalizeDefinition(raw)); -export function parseFrontmatter(raw) { +/** + * A definition file split into its YAML head and its Markdown body. + * + * `data` is `any` because it is whatever the author wrote. It comes back from + * `parseYaml`, which reads an arbitrary mapping out of the frontmatter, and the + * keys it may carry are the whole of the skill format — `pages:`, `owliver:`, + * `actions:`, `ui:` and the rest, each optional and each shaped differently. + * Describing it properly means describing that format as a type, which is a + * much larger piece of work than a rename and belongs with the format, not + * here. + * + * Without this annotation the two `return` statements unify to `{}`: the + * no-frontmatter branch hands back a literal `{}`, and TypeScript takes the + * common shape of the two, which has no properties at all. That is how one + * empty object in an early return came to make twenty-five readings of `data` + * further down look like mistakes. + */ +export interface Frontmatter { + data: any; + body: string; +} + +export function parseFrontmatter(raw): Frontmatter { const text = normalizeDefinition(raw); const match = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*(?=\n|$)/.exec(text); if (!match) return { data: {}, body: text }; @@ -249,7 +271,7 @@ export const SKILL_FACETS = ['ui', 'owliver']; * and are managed in Skill Development, so they carry no facet and appear on * neither list. */ -export function skillFacets({ data = {}, kind, ui = {}, owliver, conversation = [] }) { +export function skillFacets({ data = {}, kind, ui = {}, owliver, conversation = [] }: any) { if (kind === 'workforce') return []; const extendsPage = Object.keys(ui).length > 0; @@ -643,7 +665,7 @@ export function skillDiagnostics(customSources = []) { export function unresolvableSections(skill) { const problems = []; - for (const [page, config] of Object.entries(skill?.ui || {})) { + for (const [page, config] of Object.entries(skill?.ui || {})) { for (const section of config.sections || []) { if (!section.context) continue; if (placementProvides(page, section.placement).includes(section.context)) continue; diff --git a/src/lib/skills/skillFields.js b/src/lib/skills/skillFields.ts similarity index 99% rename from src/lib/skills/skillFields.js rename to src/lib/skills/skillFields.ts index 175f6ef..bffac75 100644 --- a/src/lib/skills/skillFields.js +++ b/src/lib/skills/skillFields.ts @@ -107,7 +107,7 @@ export function boardFieldsFromSource(source) { can show. `pages` still carries every page the definition reaches, and `uiIsEditableFromFields` is what stops those fields writing over the ones this record cannot represent. */ - const section = Object.values(skill.ui || {}).flatMap((p) => p.sections || [])[0]; + const section = Object.values(skill.ui || {}).flatMap((p) => p.sections || [])[0]; return { id: skill.id, name: skill.name, @@ -564,7 +564,7 @@ function responseFor(fields, capability) { const source = response?.source || ''; if (!source) return null; - const entry = { source }; + const entry: any = { source }; if (response.periods?.length && sourceSupportsOption(source, 'periods')) { entry.periods = response.periods; } diff --git a/src/lib/skills/tools.js b/src/lib/skills/tools.ts similarity index 100% rename from src/lib/skills/tools.js rename to src/lib/skills/tools.ts diff --git a/src/lib/skills/usePageSkills.js b/src/lib/skills/usePageSkills.ts similarity index 100% rename from src/lib/skills/usePageSkills.js rename to src/lib/skills/usePageSkills.ts