@indigoai-us/hq-cli 5.263.0 → 5.264.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/CHANGELOG.md
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
import { type CompanyBriefResult } from "./company-brief.js";
|
|
11
11
|
import { type OntologyCompany } from "./company.js";
|
|
12
12
|
import { type MultiWriterStart } from "./multi-writer.js";
|
|
13
|
-
import { type EntityHint } from "./hints.js";
|
|
14
13
|
import { type SyncClientVersions } from "./sync-client.js";
|
|
15
14
|
export declare const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
16
15
|
/**
|
|
@@ -41,13 +40,6 @@ export declare const GROK_BACKEND_FLAG = "checkpoint.ontology.grok-backend";
|
|
|
41
40
|
* duplicate entities.
|
|
42
41
|
*/
|
|
43
42
|
export declare const MULTI_WRITER_FLAG = "checkpoint.ontology.multi-writer";
|
|
44
|
-
/**
|
|
45
|
-
* Each knowledge input lists the existing entities its text names
|
|
46
|
-
* (`known_entities`), and the model is told to use those names and to skip
|
|
47
|
-
* table rows, paths and slugs. CO-41: 11 of 93 creates in the CO-39 grok-4.6
|
|
48
|
-
* runs were model errors, 5 of them duplicates of an existing entity.
|
|
49
|
-
*/
|
|
50
|
-
export declare const ENTITY_HINTS_FLAG = "checkpoint.ontology.entity-hints";
|
|
51
43
|
/**
|
|
52
44
|
* A candidate that links to an existing entity adds no aliases to it; the plan
|
|
53
45
|
* carries `link_aliases: false` and apply honours it. CO-42: most aliases
|
|
@@ -138,8 +130,6 @@ export interface PlanInput {
|
|
|
138
130
|
missing_refs?: string[];
|
|
139
131
|
/** Run-directory-relative copy of `path` with annotator entity lists removed; same line numbers. */
|
|
140
132
|
read_path?: string;
|
|
141
|
-
/** Existing entities this input's text names; only when `checkpoint.ontology.entity-hints` is on. */
|
|
142
|
-
known_entities?: EntityHint[];
|
|
143
133
|
}
|
|
144
134
|
export interface OntologyPlan {
|
|
145
135
|
schema: typeof PLAN_SCHEMA;
|
|
@@ -208,7 +198,6 @@ export type PlanOutcome = {
|
|
|
208
198
|
companyBrief?: CompanyBriefResult;
|
|
209
199
|
};
|
|
210
200
|
export declare const READ_PATH_INSTRUCTION = "When an input has read_path, read that file (relative to the run directory) instead of path, with the same line range. It is the same text with an older annotator's entity lists and entity links removed.";
|
|
211
|
-
export declare const ENTITY_HINTS_INSTRUCTIONS: string[];
|
|
212
201
|
export declare const PERSON_NAME_INSTRUCTION = "Name a person by the full name the input or the company's entities give, not a first name alone.";
|
|
213
202
|
export declare const FIRST_NAME_PEOPLE_INSTRUCTION = "Name a person by the full name the input or the company's entities give. When neither gives more than a first name, name the person by that first name; do not leave them out.";
|
|
214
203
|
export declare function ledgerPath(hqRoot: string, company: OntologyCompany): string;
|
|
@@ -13,7 +13,6 @@ import * as path from "node:path";
|
|
|
13
13
|
import { writeCompanyBrief } from "./company-brief.js";
|
|
14
14
|
import { resolveOntologyCompany, safeWriteFile, stateRoot } from "./company.js";
|
|
15
15
|
import { startMultiWriterRun } from "./multi-writer.js";
|
|
16
|
-
import { entityHints, wordWeights } from "./hints.js";
|
|
17
16
|
import { checkSyncClient, readSyncClientVersions } from "./sync-client.js";
|
|
18
17
|
import { buildEntityIndex, listKnowledgeFiles, listSignalFiles, readSignal } from "./tree.js";
|
|
19
18
|
export const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
@@ -45,13 +44,6 @@ export const GROK_BACKEND_FLAG = "checkpoint.ontology.grok-backend";
|
|
|
45
44
|
* duplicate entities.
|
|
46
45
|
*/
|
|
47
46
|
export const MULTI_WRITER_FLAG = "checkpoint.ontology.multi-writer";
|
|
48
|
-
/**
|
|
49
|
-
* Each knowledge input lists the existing entities its text names
|
|
50
|
-
* (`known_entities`), and the model is told to use those names and to skip
|
|
51
|
-
* table rows, paths and slugs. CO-41: 11 of 93 creates in the CO-39 grok-4.6
|
|
52
|
-
* runs were model errors, 5 of them duplicates of an existing entity.
|
|
53
|
-
*/
|
|
54
|
-
export const ENTITY_HINTS_FLAG = "checkpoint.ontology.entity-hints";
|
|
55
47
|
/**
|
|
56
48
|
* A candidate that links to an existing entity adds no aliases to it; the plan
|
|
57
49
|
* carries `link_aliases: false` and apply honours it. CO-42: most aliases
|
|
@@ -105,10 +97,6 @@ export const BUDGET = {
|
|
|
105
97
|
chunkTokens: 12_000,
|
|
106
98
|
};
|
|
107
99
|
export const READ_PATH_INSTRUCTION = "When an input has read_path, read that file (relative to the run directory) instead of path, with the same line range. It is the same text with an older annotator's entity lists and entity links removed.";
|
|
108
|
-
export const ENTITY_HINTS_INSTRUCTIONS = [
|
|
109
|
-
"A knowledge input's known_entities lists entities that already exist in the company and whose names appear in its text. When the input refers to one of them, under that name or another, use that entity's name and type exactly and do not name a new entity for it.",
|
|
110
|
-
"Skip rows of a glossary or translation table, file and directory paths, and names built from tags or slugs.",
|
|
111
|
-
];
|
|
112
100
|
export const PERSON_NAME_INSTRUCTION = "Name a person by the full name the input or the company's entities give, not a first name alone.";
|
|
113
101
|
export const FIRST_NAME_PEOPLE_INSTRUCTION = "Name a person by the full name the input or the company's entities give. When neither gives more than a first name, name the person by that first name; do not leave them out.";
|
|
114
102
|
const CANDIDATE_INSTRUCTIONS = [
|
|
@@ -272,7 +260,6 @@ export async function planOntologyRun(options) {
|
|
|
272
260
|
const ignoreAnnotations = (await options.readFlag(IGNORE_ANNOTATIONS_FLAG, company)) && payload.backend === "grok";
|
|
273
261
|
recordBackendPreference(hqRoot, company, await options.readFlag(GROK_BACKEND_FLAG, company));
|
|
274
262
|
const multiWriterOn = await options.readFlag(MULTI_WRITER_FLAG, company);
|
|
275
|
-
const hintsOn = await options.readFlag(ENTITY_HINTS_FLAG, company);
|
|
276
263
|
const noLinkAliases = await options.readFlag(NO_LINK_ALIASES_FLAG, company);
|
|
277
264
|
const firstNamePeople = await options.readFlag(FIRST_NAME_PEOPLE_FLAG, company);
|
|
278
265
|
const skipAmbiguousConceptLinks = await options.readFlag(SKIP_AMBIGUOUS_CONCEPT_LINKS_FLAG, company);
|
|
@@ -289,15 +276,6 @@ export async function planOntologyRun(options) {
|
|
|
289
276
|
const pending = Object.entries(ledger.entries)
|
|
290
277
|
.filter(([, entry]) => entry.state === "pending" && entry.reason === "session-touched")
|
|
291
278
|
.sort(([aRel, a], [bRel, b]) => a.mtime_ms - b.mtime_ms || aRel.localeCompare(bRel));
|
|
292
|
-
// Built after merge-back, so merged entities are hinted too.
|
|
293
|
-
let hintIndex = null;
|
|
294
|
-
const hintsFor = (text) => {
|
|
295
|
-
if (!hintIndex) {
|
|
296
|
-
const index = buildEntityIndex(company.dir);
|
|
297
|
-
hintIndex = { index, weights: wordWeights(index) };
|
|
298
|
-
}
|
|
299
|
-
return entityHints(hintIndex.index, hintIndex.weights, text);
|
|
300
|
-
};
|
|
301
279
|
const inputs = [];
|
|
302
280
|
let tokens = 0;
|
|
303
281
|
let remaining = 0;
|
|
@@ -322,10 +300,6 @@ export async function planOntologyRun(options) {
|
|
|
322
300
|
const cost = estTokens(Buffer.byteLength(lines.slice(start - 1, end).join("\n"), "utf8"));
|
|
323
301
|
if (inputs.length >= BUDGET.maxInputs || tokens + cost > BUDGET.maxTokens)
|
|
324
302
|
break;
|
|
325
|
-
// The model reads the stripped copy when there is one; its line numbers match.
|
|
326
|
-
const knownEntities = hintsOn && kind === "knowledge"
|
|
327
|
-
? hintsFor((cleaned ?? text).split("\n").slice(start - 1, end).join("\n"))
|
|
328
|
-
: null;
|
|
329
303
|
inputs.push({
|
|
330
304
|
id: chunks.length > 1 ? `${rel}#${index}` : rel,
|
|
331
305
|
kind,
|
|
@@ -338,7 +312,6 @@ export async function planOntologyRun(options) {
|
|
|
338
312
|
chunk: chunks.length > 1 ? { index, count: chunks.length } : null,
|
|
339
313
|
...(kind === "signal" ? { missing_refs: missingRefs(company.dir, rel) } : {}),
|
|
340
314
|
...(readPath !== null ? { read_path: readPath } : {}),
|
|
341
|
-
...(knownEntities !== null ? { known_entities: knownEntities } : {}),
|
|
342
315
|
});
|
|
343
316
|
tokens += cost;
|
|
344
317
|
}
|
|
@@ -407,7 +380,6 @@ export async function planOntologyRun(options) {
|
|
|
407
380
|
...CANDIDATE_INSTRUCTIONS.slice(0, 3),
|
|
408
381
|
...(inputs.some((input) => input.read_path) ? [READ_PATH_INSTRUCTION] : []),
|
|
409
382
|
`${CANDIDATE_INSTRUCTIONS[3]} ${firstNamePeople ? FIRST_NAME_PEOPLE_INSTRUCTION : PERSON_NAME_INSTRUCTION}`,
|
|
410
|
-
...(inputs.some((input) => input.known_entities) ? ENTITY_HINTS_INSTRUCTIONS : []),
|
|
411
383
|
...CANDIDATE_INSTRUCTIONS.slice(4),
|
|
412
384
|
],
|
|
413
385
|
};
|
package/package.json
CHANGED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Existing entities an input names, for `checkpoint.ontology.entity-hints`.
|
|
3
|
-
*
|
|
4
|
-
* The model never sees the company's entities, so it can only link one when it
|
|
5
|
-
* happens to pick a name or alias the index holds. On the CO-39 fixtures it
|
|
6
|
-
* created `SpaceXAI` beside `xAI`, `Indigo desktop app` beside `indigo-nx`
|
|
7
|
-
* (alias `Indigo desktop repo`) and `hq-work-site` beside `Indigo Workspace`
|
|
8
|
-
* (workspace/checkpoint-ontology/analysis/co41-wrong-creates.json). Each
|
|
9
|
-
* input's plan entry lists the existing entities whose name the input text
|
|
10
|
-
* contains, so the model can use that name.
|
|
11
|
-
*/
|
|
12
|
-
import type { EntityIndex } from "./tree.js";
|
|
13
|
-
export declare const ENTITY_HINTS: {
|
|
14
|
-
/** Entities listed per input. */
|
|
15
|
-
readonly perInput: 25;
|
|
16
|
-
/** An alias match counts this fraction of a canonical-name match with the same words. */
|
|
17
|
-
readonly aliasWeight: 0.5;
|
|
18
|
-
/** Aliases shown per entity. */
|
|
19
|
-
readonly aliases: 3;
|
|
20
|
-
/** Longest name, in words, matched through the n-gram set. Longer names use a phrase search. */
|
|
21
|
-
readonly maxGram: 4;
|
|
22
|
-
};
|
|
23
|
-
export interface EntityHint {
|
|
24
|
-
type: string;
|
|
25
|
-
name: string;
|
|
26
|
-
aliases: string[];
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Inverse document frequency of each word over the entities' match names, so
|
|
30
|
-
* `Sentry` outranks `HQ Build` in a document that names both.
|
|
31
|
-
*/
|
|
32
|
-
export declare function wordWeights(index: EntityIndex): Map<string, number>;
|
|
33
|
-
/**
|
|
34
|
-
* Up to `ENTITY_HINTS.perInput` writable entities whose canonical name or
|
|
35
|
-
* qualifying alias appears in `text` as whole words within one sentence, table
|
|
36
|
-
* cell or markdown block, highest weight first (ties by path). A match scores the summed word weights, halved for an alias.
|
|
37
|
-
* Entities with the same type and canonical name are listed once. Only
|
|
38
|
-
* entities from `index`, which is the plan's own company.
|
|
39
|
-
*/
|
|
40
|
-
export declare function entityHints(index: EntityIndex, weights: Map<string, number>, text: string): EntityHint[];
|
|
41
|
-
//# sourceMappingURL=hints.d.ts.map
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Existing entities an input names, for `checkpoint.ontology.entity-hints`.
|
|
3
|
-
*
|
|
4
|
-
* The model never sees the company's entities, so it can only link one when it
|
|
5
|
-
* happens to pick a name or alias the index holds. On the CO-39 fixtures it
|
|
6
|
-
* created `SpaceXAI` beside `xAI`, `Indigo desktop app` beside `indigo-nx`
|
|
7
|
-
* (alias `Indigo desktop repo`) and `hq-work-site` beside `Indigo Workspace`
|
|
8
|
-
* (workspace/checkpoint-ontology/analysis/co41-wrong-creates.json). Each
|
|
9
|
-
* input's plan entry lists the existing entities whose name the input text
|
|
10
|
-
* contains, so the model can use that name.
|
|
11
|
-
*/
|
|
12
|
-
export const ENTITY_HINTS = {
|
|
13
|
-
/** Entities listed per input. */
|
|
14
|
-
perInput: 25,
|
|
15
|
-
/** An alias match counts this fraction of a canonical-name match with the same words. */
|
|
16
|
-
aliasWeight: 0.5,
|
|
17
|
-
/** Aliases shown per entity. */
|
|
18
|
-
aliases: 3,
|
|
19
|
-
/** Longest name, in words, matched through the n-gram set. Longer names use a phrase search. */
|
|
20
|
-
maxGram: 4,
|
|
21
|
-
};
|
|
22
|
-
const HINT_TYPES = new Set(["person", "project", "company", "concept"]);
|
|
23
|
-
/** Token that stands for a sentence, cell or block boundary. A name never matches across one it does not contain. */
|
|
24
|
-
const BOUNDARY = "\n";
|
|
25
|
-
const TOKEN = /[\p{L}\p{N}]+|[.!?;:](?=\s|$)|\|/gu;
|
|
26
|
-
/**
|
|
27
|
-
* A line that continues the previous one when the previous one is prose or a
|
|
28
|
-
* list item: hard-wrapped markdown keeps a name that wraps across the line
|
|
29
|
-
* break. Blank lines, headings, list items, table rows, quotes and fences start
|
|
30
|
-
* a new block.
|
|
31
|
-
*/
|
|
32
|
-
function continuesProse(line) {
|
|
33
|
-
return line.trim() !== "" && !/^\s*(#|[-*+]\s|\d+[.)]\s|\||>|```|~~~)/.test(line);
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Lowercased words, with `BOUNDARY` between sentences (`.`, `!`, `?`, `;` or
|
|
37
|
-
* `:` before whitespace), table cells and markdown blocks. Names and text are
|
|
38
|
-
* split the same way, so a name that holds a boundary, such as `Tip 001:
|
|
39
|
-
* checkpoint`, still matches its own text.
|
|
40
|
-
*/
|
|
41
|
-
function tokens(text) {
|
|
42
|
-
const out = [];
|
|
43
|
-
const push = (token) => {
|
|
44
|
-
if (token !== BOUNDARY || (out.length > 0 && out[out.length - 1] !== BOUNDARY))
|
|
45
|
-
out.push(token);
|
|
46
|
-
};
|
|
47
|
-
let prevProse = null;
|
|
48
|
-
for (const line of text.toLowerCase().split("\n")) {
|
|
49
|
-
const prose = continuesProse(line);
|
|
50
|
-
if (prevProse !== null && !(prose && prevProse))
|
|
51
|
-
push(BOUNDARY);
|
|
52
|
-
for (const match of line.matchAll(TOKEN))
|
|
53
|
-
push(/^[\p{L}\p{N}]/u.test(match[0]) ? match[0] : BOUNDARY);
|
|
54
|
-
prevProse = prose || /^\s*([-*+]|\d+[.)])\s/.test(line);
|
|
55
|
-
}
|
|
56
|
-
while (out[0] === BOUNDARY)
|
|
57
|
-
out.shift();
|
|
58
|
-
while (out[out.length - 1] === BOUNDARY)
|
|
59
|
-
out.pop();
|
|
60
|
-
return out;
|
|
61
|
-
}
|
|
62
|
-
function words(name) {
|
|
63
|
-
return name.filter((token) => token !== BOUNDARY);
|
|
64
|
-
}
|
|
65
|
-
function matchNames(entity) {
|
|
66
|
-
const out = [];
|
|
67
|
-
const canonical = tokens(entity.canonical_name);
|
|
68
|
-
if (words(canonical).length > 0)
|
|
69
|
-
out.push({ tokens: canonical, weight: 1 });
|
|
70
|
-
for (const alias of entity.aliases) {
|
|
71
|
-
const aliasTokens = tokens(alias);
|
|
72
|
-
const count = words(aliasTokens).length;
|
|
73
|
-
if (count >= 2 || (count === 1 && /[-_./0-9]/.test(alias))) {
|
|
74
|
-
out.push({ tokens: aliasTokens, weight: ENTITY_HINTS.aliasWeight, alias });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return out;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Inverse document frequency of each word over the entities' match names, so
|
|
81
|
-
* `Sentry` outranks `HQ Build` in a document that names both.
|
|
82
|
-
*/
|
|
83
|
-
export function wordWeights(index) {
|
|
84
|
-
const counts = new Map();
|
|
85
|
-
let total = 0;
|
|
86
|
-
for (const entity of index.byRel.values()) {
|
|
87
|
-
if (!HINT_TYPES.has(entity.type))
|
|
88
|
-
continue;
|
|
89
|
-
total += 1;
|
|
90
|
-
for (const word of new Set(matchNames(entity).flatMap((name) => words(name.tokens))))
|
|
91
|
-
counts.set(word, (counts.get(word) ?? 0) + 1);
|
|
92
|
-
}
|
|
93
|
-
const weights = new Map();
|
|
94
|
-
for (const [word, count] of counts)
|
|
95
|
-
weights.set(word, Math.log((total + 1) / count));
|
|
96
|
-
return weights;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Up to `ENTITY_HINTS.perInput` writable entities whose canonical name or
|
|
100
|
-
* qualifying alias appears in `text` as whole words within one sentence, table
|
|
101
|
-
* cell or markdown block, highest weight first (ties by path). A match scores the summed word weights, halved for an alias.
|
|
102
|
-
* Entities with the same type and canonical name are listed once. Only
|
|
103
|
-
* entities from `index`, which is the plan's own company.
|
|
104
|
-
*/
|
|
105
|
-
export function entityHints(index, weights, text) {
|
|
106
|
-
const textTokens = tokens(text);
|
|
107
|
-
const grams = new Set();
|
|
108
|
-
for (let i = 0; i < textTokens.length; i += 1) {
|
|
109
|
-
for (let n = 1; n <= ENTITY_HINTS.maxGram && i + n <= textTokens.length; n += 1) {
|
|
110
|
-
grams.add(textTokens.slice(i, i + n).join(" "));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const padded = ` ${textTokens.join(" ")} `;
|
|
114
|
-
const best = new Map();
|
|
115
|
-
for (const entity of index.byRel.values()) {
|
|
116
|
-
if (!HINT_TYPES.has(entity.type))
|
|
117
|
-
continue;
|
|
118
|
-
let score = 0;
|
|
119
|
-
const matched = [];
|
|
120
|
-
for (const name of matchNames(entity)) {
|
|
121
|
-
const phrase = name.tokens.join(" ");
|
|
122
|
-
const found = name.tokens.length <= ENTITY_HINTS.maxGram ? grams.has(phrase) : padded.includes(` ${phrase} `);
|
|
123
|
-
if (!found)
|
|
124
|
-
continue;
|
|
125
|
-
if (name.alias !== undefined)
|
|
126
|
-
matched.push(name.alias);
|
|
127
|
-
score = Math.max(score, name.weight * words(name.tokens).reduce((sum, word) => sum + (weights.get(word) ?? 0), 0));
|
|
128
|
-
}
|
|
129
|
-
if (score <= 0)
|
|
130
|
-
continue;
|
|
131
|
-
const key = `${entity.type}\0${entity.canonical_name.toLowerCase()}`;
|
|
132
|
-
const held = best.get(key);
|
|
133
|
-
if (!held || score > held.score || (score === held.score && entity.rel < held.entity.rel))
|
|
134
|
-
best.set(key, { entity, score, matched });
|
|
135
|
-
}
|
|
136
|
-
return [...best.values()]
|
|
137
|
-
.sort((a, b) => b.score - a.score || (a.entity.rel < b.entity.rel ? -1 : a.entity.rel > b.entity.rel ? 1 : 0))
|
|
138
|
-
.slice(0, ENTITY_HINTS.perInput)
|
|
139
|
-
.map(({ entity, matched }) => ({
|
|
140
|
-
type: entity.type,
|
|
141
|
-
name: entity.canonical_name,
|
|
142
|
-
// Aliases the text used come first, so the model sees the name it read.
|
|
143
|
-
aliases: [...matched, ...entity.aliases.filter((alias) => !matched.includes(alias))].slice(0, ENTITY_HINTS.aliases),
|
|
144
|
-
}));
|
|
145
|
-
}
|
|
146
|
-
//# sourceMappingURL=hints.js.map
|