mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
A summary that stops inside its description's first sentence prints that sentence whole
GET /v1/ai/memory/recall's comment has no full stop, so zipdoc cut its summary at the end of the first line: the CLI row and the API index read "Recall recent/relevant memories for context injection; with q it". The loader now prints the description's first sentence when the summary is a strict prefix of it, and early() names a line that ends on no stop while its paragraph goes on; the whole-document test reads every printed summary against its description. A lead name whose first word would be one letter (iCloud, eBay, gRPC, OAuth2Client) stays whole instead of reading "I cloud", so the rules name it. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -201,6 +201,19 @@ test('a line runs past a quotation its sentence goes on after', async ({ page })
|
||||
);
|
||||
});
|
||||
|
||||
// A summary cut where its comment's first line ended carries no "…": the memory
|
||||
// recall row read "… for context injection; with q it" on the CLI page and the
|
||||
// API index. Both print the description's first sentence whole.
|
||||
test('a row runs to the end of the sentence its summary stopped inside', async ({ page }) => {
|
||||
for (const path of ['/docs/cli/ai/', '/docs/openapi/ai/']) {
|
||||
await open(page, path, 'dark');
|
||||
const rows = (await page.locator('.prose td:last-child').allInnerTexts()).map((c) => c.trim());
|
||||
expect(rows.filter((c) => c.startsWith('Recall recent/relevant memories')), path).toEqual([
|
||||
expect.stringMatching(/; with q it ranks semantically, without q it returns the most recent\.?$/),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test('prose is spaced: a paragraph has a margin', async ({ page }) => {
|
||||
await open(page, '/docs/quickstart/', 'dark');
|
||||
const margin = await page
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { parse as parseYaml, stringify as yaml } from 'yaml';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { BRANDS, DOCUMENT, loadDocument, METHODS, unimplemented, unname } from './openapi-doc';
|
||||
import { genCliPages, heading, nounOf } from './gen-cli-pages';
|
||||
@@ -154,6 +154,28 @@ describe('a Go name is not prose', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// A comment with no full stop is summarised as its first LINE, so the row read
|
||||
// "… for context injection; with q it" — no "…", and no rule on the row alone
|
||||
// can tell it stopped. The loader prints the description's first sentence.
|
||||
it("prints a summary that stops inside its description's first sentence whole", () => {
|
||||
const line = 'Recall recent/relevant memories for context injection; with q it';
|
||||
const text = `${line}\nranks semantically, without q it returns the most recent`;
|
||||
const raw = parseYaml(fs.readFileSync(DOCUMENT, 'utf8'));
|
||||
Object.assign(raw.paths['/v1/ai/memory/recall'].get, { summary: line, description: text });
|
||||
Object.assign(raw.paths['/v1/ai/memory/list'].get, {
|
||||
summary: 'Lists memories.',
|
||||
description: 'Lists memories. Newest first.',
|
||||
});
|
||||
const file = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'summary-')), 'openapi.yaml');
|
||||
fs.writeFileSync(file, yaml(raw));
|
||||
const doc = loadDocument(file);
|
||||
const whole = `${line} ranks semantically, without q it returns the most recent`;
|
||||
expect(doc.byId.get('get_ai_memory_recall')?.summary).toBe(whole);
|
||||
expect(doc.byId.get('get_ai_memory_list')?.summary).toBe('Lists memories.');
|
||||
expect(early(line, text)).not.toBe('');
|
||||
expect(early(whole, text)).toBe('');
|
||||
}, 60_000);
|
||||
|
||||
it('strips one comment at every address that carries it', () => {
|
||||
const jwks = loadDocument(DOCUMENT).byId.get('get_licensing_jwks');
|
||||
expect(jwks?.summary.startsWith('Publishes the Ed25519 PUBLIC verification key')).toBe(true);
|
||||
@@ -185,6 +207,20 @@ describe('a Go name is not prose', () => {
|
||||
'JavaScript code the page runs.',
|
||||
);
|
||||
});
|
||||
|
||||
// Split, these read "I cloud linked." and "G RPC served." — English to every
|
||||
// rule. Whole, the rules name the identifier and the brand goes into WRITTEN.
|
||||
it('leaves a name whose first word would be one letter whole, for the rules to name', () => {
|
||||
for (const [said, left] of [
|
||||
['Is iCloud linked.', 'iCloud linked.'],
|
||||
['Is eBay linked.', 'eBay linked.'],
|
||||
['Is gRPC served.', 'gRPC served.'],
|
||||
['Is OAuth2Client the client.', 'OAuth2Client the client.'],
|
||||
]) {
|
||||
expect(unname(said, 'get_x')).toBe(left);
|
||||
expect(named(left, [])).not.toBe('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('the rules a line is read against', () => {
|
||||
@@ -256,6 +292,11 @@ describe('the rules a line is read against', () => {
|
||||
expect(early(line, text)).not.toBe('');
|
||||
expect(early(firstSentence(text), text)).toBe('');
|
||||
}
|
||||
expect(early('Recall memories; with q it', 'Recall memories; with q it\nranks them.')).not.toBe(
|
||||
'',
|
||||
);
|
||||
expect(early('Recall memories', 'Recall memories')).toBe('');
|
||||
expect(early('Recall memories.', 'Recall memories. Newest first.')).toBe('');
|
||||
expect(early('It said "stop."', 'It said "stop." Then it stopped.')).toBe('');
|
||||
expect(early('It said "stop."', 'It said "stop."\n\nthen more.')).toBe('');
|
||||
expect(early('Is it on?', 'Is it on? ask.')).toBe('');
|
||||
|
||||
@@ -37,16 +37,23 @@ export function cut(line: string): string {
|
||||
/**
|
||||
* Why `line`, printed as the first sentence of `text`, stops before that
|
||||
* sentence does, or '' when it does not. `cut` reads the line alone and cannot
|
||||
* see this: a quotation's own "?" or full stop reads as a sentence's end —
|
||||
* `Answers "what am I approving?"` — while the paragraph it came from goes on
|
||||
* in lower case ("for a pending device code.") or after a dash. Only the
|
||||
* source says the sentence went on, so this rule reads the line against it.
|
||||
* see this, so this rule reads the line against its source:
|
||||
*
|
||||
* mid-sentence the line ends on no stop while its paragraph goes on — a
|
||||
* summary cut at the end of a comment's first line, "Recall
|
||||
* … for context injection; with q it" of "… it ranks
|
||||
* semantically, …"
|
||||
* at a quotation a quotation's own "?" or full stop reads as a sentence's end —
|
||||
* `Answers "what am I approving?"` — while the paragraph goes
|
||||
* on in lower case ("for a pending device code.") or after a
|
||||
* dash
|
||||
*/
|
||||
export function early(line: string, text: string): string {
|
||||
const t = line.trim();
|
||||
const para = (text.trim().split(/\n\s*\n/)[0] ?? '').replace(/\s+/g, ' ').trim();
|
||||
if (!/[.?!]["'”’]+$/.test(t) || !para.startsWith(t)) return '';
|
||||
return /^\s*(?:[a-z]|[—–-])/.test(para.slice(t.length))
|
||||
if (!t || para.length <= t.length || !para.startsWith(t)) return '';
|
||||
if (!/[.?!]["'”’]*$/.test(t)) return 'stops mid-sentence where its paragraph goes on';
|
||||
return /[.?!]["'”’]+$/.test(t) && /^\s*(?:[a-z]|[—–-])/.test(para.slice(t.length))
|
||||
? 'stops at a quotation its sentence goes on after'
|
||||
: '';
|
||||
}
|
||||
|
||||
@@ -564,6 +564,9 @@ const capabilityOf = (op: any): string =>
|
||||
* `ClickHouse`, `YouTube` and an exported `DetachMethod` keep their casing — a
|
||||
* brand split into "Click house" reads wrong and no test can see it, where a Go
|
||||
* name left whole is caught by the reference tests and fixed at its comment.
|
||||
* So does a name whose first word would be one letter (`iCloud`, `eBay`, `gRPC`,
|
||||
* `OAuth2Client`): split, it read "I cloud", "G RPC", "O Auth2 client", which
|
||||
* no rule can tell from English; whole, it fails the reference tests loudly.
|
||||
*/
|
||||
const OPENS = new Set(
|
||||
(
|
||||
@@ -584,9 +587,10 @@ export function unname(prose: string, id: string): string {
|
||||
rest = prose.slice(m[1].length + 1 + (m[2] === 'is' || m[2] === 'are' ? m[2].length + 1 : 0));
|
||||
if (rest === prose || !rest) return prose;
|
||||
const lead = /^[A-Za-z]*[a-z0-9][A-Z][A-Za-z0-9]*(?= )/.exec(rest)?.[0] ?? '';
|
||||
if (BRANDS.has(lead)) return rest;
|
||||
if (/^[a-z]|\d/.test(lead)) rest = words(lead) + rest.slice(lead.length);
|
||||
return rest[0].toUpperCase() + rest.slice(1);
|
||||
if (!lead) return rest[0].toUpperCase() + rest.slice(1);
|
||||
const split = words(lead);
|
||||
if (BRANDS.has(lead) || !/^[a-z]|\d/.test(lead) || /^\S /.test(split)) return rest;
|
||||
return split[0].toUpperCase() + split.slice(1) + rest.slice(lead.length);
|
||||
}
|
||||
|
||||
/** A capitalised first word, followed by `verb`, that names a function. */
|
||||
@@ -620,14 +624,20 @@ const words = (name: string): string =>
|
||||
* flattened into one line, printed as its title, its CLI row and its index row.
|
||||
* A sentence does not cross a paragraph, so a summary that runs past the first
|
||||
* one is that paragraph's sentence instead.
|
||||
*
|
||||
* A comment with no full stop at all is cut where its first LINE ends: GET
|
||||
* /v1/ai/memory/recall was summarised "Recall recent/relevant memories for
|
||||
* context injection; with q it" while its description goes on "ranks
|
||||
* semantically, …". A summary that stops inside its description's first
|
||||
* sentence is that sentence, whole.
|
||||
*/
|
||||
const sentenceOf = (summary: string, description: string): string => {
|
||||
const flat = summary.replace(/\s+/g, ' ').trim();
|
||||
const whole = firstSentence(description);
|
||||
if (flat && whole.length > flat.length && whole.startsWith(flat)) return whole;
|
||||
const [lead, ...block] = description.split(/\n\s*\n/);
|
||||
const p = (lead ?? '').replace(/\s+/g, ' ').trim().replace(/:$/, '');
|
||||
return block.length && p && flat.length > p.length + 1 && flat.startsWith(p)
|
||||
? firstSentence(description)
|
||||
: flat;
|
||||
return block.length && p && flat.length > p.length + 1 && flat.startsWith(p) ? whole : flat;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -171,8 +171,11 @@ describe('readable — a page describes its operation in English', () => {
|
||||
// own — so the rule is held against ALL of the document's prose, not the
|
||||
// pages: a sentence that stops at a quotation's own "?" while the paragraph
|
||||
// goes on in lower case, or with a dash, stopped too soon —
|
||||
// `Answers "what am I approving?"` of "… for a pending device code." And a
|
||||
// summary does not run on past the paragraph its description opens with.
|
||||
// `Answers "what am I approving?"` of "… for a pending device code." A
|
||||
// summary does not run on past the paragraph its description opens with, and
|
||||
// the line printed from it — CLI row, index row, MCP legend — does not stop
|
||||
// inside that description's first sentence: "… with q it" of "… with q it
|
||||
// ranks semantically, …".
|
||||
it('ends each first sentence where its paragraph does not go on', () => {
|
||||
const stopped: string[] = [];
|
||||
let read = 0;
|
||||
@@ -194,13 +197,18 @@ describe('readable — a page describes its operation in English', () => {
|
||||
expect(stopped).toEqual([]);
|
||||
expect(read).toBeGreaterThan(10_000);
|
||||
const ran: string[] = [];
|
||||
const short: string[] = [];
|
||||
for (const op of doc.operations) {
|
||||
const [first = '', ...block] = op.description.split(/\n\s*\n/);
|
||||
const lead = flat(first).replace(/:$/, '');
|
||||
const past = op.summary.length > lead.length + 1 && op.summary.startsWith(lead);
|
||||
if (block.length && lead && past) ran.push(op.id);
|
||||
const line = firstSentence(op.summary || op.description);
|
||||
const why = early(line, op.description);
|
||||
if (why) short.push(`${op.id}: ${why}: ${line.slice(-50)}`);
|
||||
}
|
||||
expect(ran).toEqual([]);
|
||||
expect(short).toEqual([]);
|
||||
});
|
||||
|
||||
it('writes no page for an operation that answers 501 to every call', () => {
|
||||
|
||||
Reference in New Issue
Block a user