update Markdown skills

This commit is contained in:
2026-08-19 17:36:27 +05:30
parent aa2fb41871
commit d3f7f439f6
30 changed files with 2341 additions and 221 deletions

View File

@@ -375,9 +375,432 @@ for (const n of [1, 4, 9]) {
);
}
/* ── 6. Authoring: what the editors save, and what they refuse ────────────── */
console.log('\n── Authoring ──');
const fields = await server.ssrLoadModule('/src/lib/skills/skillFields.js');
const templates = await server.ssrLoadModule('/src/lib/skills/customSkills.js');
const surfaces = await server.ssrLoadModule('/src/lib/skills/surfaces.js');
/* A template must produce something that works before it is edited. The Board
template used to default to a source its own default placement cannot read. */
record(
'the Board template validates as written',
reg.validateSkillSource(templates.uiSkillTemplate({
id: 'template-check', name: 'Template Check', pages: ['positions'],
})) === null,
reg.validateSkillSource(templates.uiSkillTemplate({
id: 'template-check', name: 'Template Check', pages: ['positions'],
})) || 'valid'
);
/* An Owliver block that can answer nothing is refused rather than saved and
then reported as a skill that does not work. */
record(
'an Owliver skill with no capabilities is refused',
Boolean(reg.validateSkillSource(templates.owliverSkillTemplate({
id: 'empty-owliver', name: 'Empty Owliver', pages: ['positions'],
}))),
reg.validateSkillSource(templates.owliverSkillTemplate({
id: 'empty-owliver', name: 'Empty Owliver', pages: ['positions'],
})) || 'ACCEPTED — should have been refused'
);
/* The section-can-never-resolve check, in both directions. */
const boardOn = (page, placement) => `---
id: context-check
name: Context Check
description: Reads position activity.
pages:
- ${page}
status: active
ui:
type: flow
placement: ${placement}
source: position.activity
---
# Context Check
`;
record(
'a position source on a page with no position is refused',
Boolean(reg.validateSkillSource(boardOn('analytics', 'after-header'))),
reg.validateSkillSource(boardOn('analytics', 'after-header')) || 'ACCEPTED — should have been refused'
);
record(
'the same source is refused above the Positions grid',
Boolean(reg.validateSkillSource(boardOn('positions', 'after-position-list'))),
reg.validateSkillSource(boardOn('positions', 'after-position-list')) || 'ACCEPTED — should have been refused'
);
record(
'and accepted inside a position card',
reg.validateSkillSource(boardOn('positions', 'after-position-card')) === null,
reg.validateSkillSource(boardOn('positions', 'after-position-card')) || 'accepted'
);
/* Owliver responses are deliberately NOT subject to that rule: an unmet need is
a question back, not a dead card. `hiring-activity-assistant` depends on it. */
record(
'a shipped Owliver skill reading one position still validates',
reg.validateSkillSource(
reg.SKILLS.find((s) => s.id === 'hiring-activity-assistant').markdown
) === null
);
record(
'every shipped definition still validates',
reg.SKILLS.every((s) => reg.validateSkillSource(s.markdown) === null),
reg.SKILLS.filter((s) => reg.validateSkillSource(s.markdown)).map((s) => s.id).join(', ') || 'all valid'
);
/* Reading a definition into the editor fields — the upload path. */
for (const skill of reg.SKILLS.filter((s) => s.kind === 'assistant')) {
const read = fields.owliverFieldsFromSource(skill.markdown);
record(
`\`${skill.id}\` reads back into the Owliver fields`,
read.id === skill.id && read.name === skill.name
&& read.pages.join(',') === skill.pages.join(','),
`${read.name} / ${read.pages.join(', ')}`
);
}
record(
'an unparseable file leaves the fields empty rather than throwing',
fields.owliverFieldsFromSource('not a definition').id === ''
);
/* Writing a field back — the half that used to do nothing. */
const original = reg.SKILLS.find((s) => s.id === 'hiring-activity-assistant').markdown;
const renamed = fields.patchFrontmatter(original, { name: 'Renamed Assistant' });
record(
'patching a field changes what the registry reads',
reg.parseSkill(renamed, { custom: true }).name === 'Renamed Assistant',
reg.parseSkill(renamed, { custom: true }).name
);
record(
'patching a field leaves the body untouched',
reg.parseSkill(renamed, { custom: true }).body === reg.parseSkill(original, { custom: true }).body
);
record(
'patching a field preserves frontmatter comments',
renamed.includes('# One suggestion per capability.')
);
record(
'patching a field changes nothing else',
original.split('\n').filter((l) => !l.startsWith('name:')).join('\n')
=== renamed.split('\n').filter((l) => !l.startsWith('name:')).join('\n')
);
record(
'a patched definition still validates',
reg.validateSkillSource(renamed) === null,
reg.validateSkillSource(renamed) || 'valid'
);
const repaged = fields.patchFrontmatter(original, { pages: ['analytics', 'activity'] });
record(
'patching a list replaces the whole block',
reg.parseSkill(repaged, { custom: true }).pages.join(',') === 'analytics,activity',
reg.parseSkill(repaged, { custom: true }).pages.join(',')
);
const restatused = fields.patchFrontmatter(original, { status: 'inactive' });
record(
'patching a key the file never declared adds it',
reg.parseSkill(restatused, { custom: true }).status === 'inactive'
);
/* Both keys, because that is what the editor's own field handler writes: the
registry reads capabilities as the union of the declared list and the keys of
`responses:`, so patching one without the other changes nothing. */
const recapped = fields.patchFrontmatter(original, {
'owliver.capabilities': ['summary'],
'owliver.responses': { summary: { source: 'position.activity', periods: ['today'] } },
});
record(
'patching a nested block rewrites only that block',
reg.parseSkill(recapped, { custom: true }).owliver.capabilities.join(',') === 'summary',
reg.parseSkill(recapped, { custom: true }).owliver.capabilities.join(',')
);
/* Asserted on the text, not on the parse: with `flow` no longer offered, the
registry correctly drops the suggestion that names it — which is the rule
working, not the patch reaching a sibling key it should not have. */
record(
'and leaves its siblings in the same block alone',
recapped.includes('Summarize hiring activity for this position')
&& recapped.includes('Show hiring activity as a flow')
&& recapped.includes(' enabled: true')
);
/* The two `ui:` shapes, told apart — the per-page form must not be overwritten
from four single-valued fields. */
record(
'the shorthand `ui:` form is recognised',
fields.uiShape(boardOn('positions', 'after-position-card')) === 'shorthand'
);
record(
'a definition with no `ui:` block reports none',
fields.uiShape(original) === 'none'
);
/* The picker and the validator read the same table. */
record(
'a position card supplies a position',
surfaces.contextSuppliedBy(['positions'], 'after-position-card').includes('positionId')
);
record(
'the Positions list supplies nothing',
surfaces.contextSuppliedBy(['positions'], 'after-position-list').length === 0
);
record(
'Analytics supplies nothing',
surfaces.contextSuppliedBy(['analytics'], 'after-header').length === 0
);
record(
'every surface declares what its placements provide',
surfaces.SKILL_SURFACES.every((s) => s.provides && typeof s.provides === 'object'),
surfaces.SKILL_SURFACES.filter((s) => !s.provides).map((s) => s.id).join(', ') || 'all declared'
);
/* ── 7. Upload hydration: a file, into the fields ─────────────────────────── */
console.log('\n── Upload hydration ──');
/**
* The shapes people actually upload.
*
* Every case here is a definition that arrived from outside the editors, which
* is the only way most definitions arrive. What is being asserted is not that
* the parser is lenient — it is that the three identity fields, the pages and
* the section a reader can see in the file are the ones the form shows.
*/
const COMPLETE = `---
id: hiring-activity-assistant
name: Hiring Activity Assistant
description: Answer questions about recent hiring activity on a position.
type: board
ui:
- page: Positions
placement: grid-card
source: position.activity
---
# Hiring Activity Assistant
`;
const NO_ID = `---
name: Hiring Activity Assistant
description: Answer questions about recent hiring activity.
pages:
- positions
---
# Hiring Activity Assistant
`;
const MULTI_PAGE = `---
id: hiring-activity
name: Hiring Activity
description: Hiring activity across the workspace.
ui:
- page: Positions
placement: grid-card
source: candidates.activity
- page: Analytics
placement: panel
source: hires.performance
---
# Hiring Activity
`;
/* Case 1 — a complete definition hydrates every field it declares. */
{
const f = fields.boardFieldsFromSource(COMPLETE);
record('upload: name hydrates', f.name === 'Hiring Activity Assistant', f.name || 'EMPTY');
record('upload: id hydrates', f.id === 'hiring-activity-assistant', f.id || 'EMPTY');
record(
'upload: description hydrates',
f.description === 'Answer questions about recent hiring activity on a position.',
f.description || 'EMPTY'
);
record('upload: pages come from the `ui:` entries', f.pages.join(',') === 'positions', f.pages.join(',') || 'EMPTY');
record('upload: `grid-card` resolves to a real placement', f.placement === 'after-position-card', f.placement || 'EMPTY');
record('upload: source hydrates', f.source === 'position.activity', f.source || 'EMPTY');
record('upload: an undeclared type is inferred from the source', f.type === 'flow', f.type || 'EMPTY');
record('upload: the definition validates as written', reg.validateSkillSource(COMPLETE) === null,
reg.validateSkillSource(COMPLETE) || 'valid');
record('upload: it is classified as a Board skill',
fields.facetsFromSource(COMPLETE).join(',') === 'ui', fields.facetsFromSource(COMPLETE).join(','));
/* The same file read by the other editor's reader — one pipeline, two views. */
const o = fields.owliverFieldsFromSource(COMPLETE);
record('upload: the Owliver reader hydrates the same identity',
o.id === f.id && o.name === f.name && o.description === f.description);
}
/* Case 2 — no `id:`, so it is slugged from the name and never invented. */
{
const f = fields.boardFieldsFromSource(NO_ID);
record('upload: a missing id slugs the name', f.id === 'hiring-activity-assistant', f.id || 'EMPTY');
record('upload: a missing id does not become the placeholder path', f.id !== 'custom');
record('upload: an explicit id is never replaced by a generated one',
fields.boardFieldsFromSource(COMPLETE).id === 'hiring-activity-assistant');
record('upload: a file with no frontmatter hydrates nothing',
fields.boardFieldsFromSource('# Just a heading\n').id === '');
}
/* Case 3 — several pages, and every entry survives a field edit. */
{
const f = fields.boardFieldsFromSource(MULTI_PAGE);
record('upload: every page in the list is reported', f.pages.join(',') === 'positions,analytics', f.pages.join(','));
const parsed = reg.parseSkill(MULTI_PAGE, { custom: true });
record('upload: every entry becomes a section',
parsed.ui.positions.sections.length === 1 && parsed.ui.analytics.sections.length === 1);
record('upload: each entry keeps its own source',
parsed.ui.positions.sections[0].source === 'candidates.activity'
&& parsed.ui.analytics.sections[0].source === 'hires.performance');
record('upload: a multi-page definition validates', reg.validateSkillSource(MULTI_PAGE) === null,
reg.validateSkillSource(MULTI_PAGE) || 'valid');
/* The fields must not be able to flatten it. */
record('upload: the section fields are read-only against a list', !fields.uiIsEditableFromFields(MULTI_PAGE));
record('upload: a single-section definition stays field-editable',
fields.uiIsEditableFromFields(templates.uiSkillTemplate({ id: 'x', name: 'X', pages: ['positions'] })));
/* Renaming is identity, not structure: it must still work, and must not
touch either entry. */
const renamedMulti = fields.patchFrontmatter(MULTI_PAGE, { name: 'Renamed Multi' });
const after = reg.parseSkill(renamedMulti, { custom: true });
record('upload: renaming a multi-page definition keeps both entries',
after.ui.positions?.sections.length === 1 && after.ui.analytics?.sections.length === 1,
Object.keys(after.ui).join(','));
record('upload: ...and actually renames it', after.name === 'Renamed Multi', after.name);
record('upload: ...and leaves the `ui:` text byte-identical',
renamedMulti.slice(renamedMulti.indexOf('ui:')) === MULTI_PAGE.slice(MULTI_PAGE.indexOf('ui:')));
}
/* Files as they actually arrive: from Windows, from a download, from paste. */
{
const dirty = {
'a byte-order mark': `${COMPLETE}`,
'CRLF line endings': COMPLETE.replace(/\n/g, '\r\n'),
'a BOM and CRLF': `${COMPLETE.replace(/\n/g, '\r\n')}`,
'a blank line above the fence': `\n\n${COMPLETE}`,
'trailing spaces on the fence': COMPLETE.replace(/^---$/gm, '--- '),
};
for (const [what, md] of Object.entries(dirty)) {
const f = fields.boardFieldsFromSource(md);
record(
`upload: a file with ${what} still hydrates`,
f.name === 'Hiring Activity Assistant' && f.id === 'hiring-activity-assistant'
&& f.pages.join(',') === 'positions',
`${f.name || 'EMPTY'} / ${f.id || 'EMPTY'} / ${f.pages.join(',') || 'EMPTY'}`
);
record(
`upload: ...and the registry reads it the same way`,
reg.parseSkill(md, { custom: true }).name === 'Hiring Activity Assistant',
reg.parseSkill(md, { custom: true }).name
);
record(`upload: ...and it validates`, reg.validateSkillSource(md) === null,
reg.validateSkillSource(md) || 'valid');
}
/* Normalising on the way in is what keeps `patchFrontmatter` safe: an
unrecognised fence would have it write a second one above the first. */
const patchedDirty = fields.patchFrontmatter(fields.normalizeUpload(`${COMPLETE}`), { name: 'Clean' });
record('upload: patching a normalised file writes one frontmatter block',
(patchedDirty.match(/^---$/gm) || []).length === 2,
`${(patchedDirty.match(/^---$/gm) || []).length} fences`);
record('upload: ...and it still parses', reg.parseSkill(patchedDirty, { custom: true }).name === 'Clean');
/* A file with nothing to read must be refused, not read as a blank skill. */
record('upload: a file with no fence is not readable', !fields.isReadableDefinition('# Just prose\n'));
record('upload: a real definition is readable', fields.isReadableDefinition(COMPLETE));
}
/* Page names and placements as they are written in the product, not as the
vocabulary spells them internally. */
record('upload: `Positions` resolves to the positions surface', surfaces.canonicalPage('Positions') === 'positions');
record('upload: `Talent Pool` resolves to the talent-pool surface', surfaces.canonicalPage('Talent Pool') === 'talent-pool');
record('upload: `panel` on Analytics resolves to a real placement',
surfaces.placementFor('analytics', 'panel') === 'after-header');
record('upload: an alias never resolves onto a surface that lacks it',
surfaces.placementFor('analytics', 'grid-card') === null);
record('upload: a canonical placement still resolves to itself',
surfaces.placementFor('positions', 'after-position-card') === 'after-position-card');
/* Case 4 — an existing definition reopened for editing. */
for (const skill of reg.SKILLS) {
const f = skill.facets?.includes('ui')
? fields.boardFieldsFromSource(skill.markdown)
: fields.owliverFieldsFromSource(skill.markdown);
record(
`reopening \`${skill.id}\` loads its identity unchanged`,
f.id === skill.id && f.name === skill.name && f.description === skill.description
&& f.pages.join(',') === skill.pages.join(','),
`${f.id} / ${f.name} / ${f.pages.join(', ')}`
);
}
/* Case 5 — a manual edit is not reverted by later synchronisation. */
{
/* The editor's own handler, in miniature: hydrate from the file, edit one
field, then edit an unrelated one. The first edit must survive the second. */
let draft = fields.owliverFieldsFromSource(COMPLETE);
let src = COMPLETE;
const edit = (patch) => {
draft = { ...draft, ...patch };
src = fields.patchFrontmatter(src, {
id: draft.id || undefined,
name: draft.name || undefined,
description: draft.description || undefined,
pages: draft.pages?.length && !fields.pagesAreDerived(src) ? draft.pages : undefined,
});
};
edit({ name: 'My Own Name' });
record('edit: a manual name reaches the artefact',
reg.parseSkill(src, { custom: true }).name === 'My Own Name');
edit({ description: 'My own description.' });
record('edit: a later edit does not revert the earlier one',
reg.parseSkill(src, { custom: true }).name === 'My Own Name',
reg.parseSkill(src, { custom: true }).name);
record('edit: ...and applies itself',
reg.parseSkill(src, { custom: true }).description === 'My own description.');
record('edit: ...and the `ui:` block is untouched throughout',
src.slice(src.indexOf('ui:')) === COMPLETE.slice(COMPLETE.indexOf('ui:')));
/* A second upload replaces the draft outright — it is a new source. */
const rehydrated = fields.owliverFieldsFromSource(MULTI_PAGE);
record('edit: a derived `pages:` is never written back',
!src.includes('\npages:'), src.includes('\npages:') ? 'pages: was inserted' : 'not written');
record('edit: a declared `pages:` still patches normally',
reg.parseSkill(
fields.patchFrontmatter(NO_ID, { pages: ['analytics'] }), { custom: true }
).pages.join(',') === 'analytics');
record('edit: an inherited trigger is not written into the file',
!src.includes('triggers:'), src.includes('triggers:') ? 'triggers: was materialised' : 'not written');
record('edit: a declared trigger is still read into the fields',
fields.owliverFieldsFromSource(
reg.SKILLS.find((x) => x.id === 'hiring-activity-assistant').markdown
).triggers.includes('hiring activity'));
record('edit: a definition with no triggers reads none',
fields.owliverFieldsFromSource(COMPLETE).triggers.length === 0,
JSON.stringify(fields.owliverFieldsFromSource(COMPLETE).triggers));
record('edit: a second upload hydrates from the new file',
rehydrated.id === 'hiring-activity' && rehydrated.name === 'Hiring Activity',
`${rehydrated.id} / ${rehydrated.name}`);
}
await server.close();
/* ── 6. Production bundle ─────────────────────────────────────────────────── */
/* ── 8. Production bundle ─────────────────────────────────────────────────── */
if (process.argv.includes('--dist')) {
console.log('\n── Production bundle ──');
const dir = join(ROOT, 'dist/assets');