@indigoai-us/hq-cli 5.193.1 → 5.194.1

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
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.194.1] — 2026-09-25
6
+
7
+ ### Fixed
8
+
9
+ - `hq core ontology apply` no longer creates a new entity that is probably an
10
+ existing one. A candidate whose alias is already another entity's name or
11
+ alias, or a person whose name contains every word of an existing person's
12
+ full name (for example "Chaudhry Hassaan Saleem" and "Hassaan Saleem"), is
13
+ held back and reported as a suspect duplicate.
14
+
15
+ ## [5.194.0] — 2026-09-25
16
+
17
+ ### Added
18
+
19
+ - `hq core checkpoint` can now maintain the company ontology, the job the
20
+ cloud ontology gardener did. It is off unless the hq-flags flag
21
+ `checkpoint.ontology` is on for the checkpoint's company
22
+ (`HQ_FLAG_CHECKPOINT_ONTOLOGY` is the local override). When a checkpoint
23
+ names a company, the background sibling runs `hq core ontology plan`, which
24
+ checks the flag and lists the company knowledge and signal files that
25
+ changed since the last run, within a fixed input and token budget. The
26
+ sibling writes entity candidates for those files, and
27
+ `hq core ontology apply` validates them and writes them. It updates an
28
+ existing entity rather than creating a duplicate, and keeps entity ids in
29
+ the cloud's format. It writes only to the checkpoint's own company and never
30
+ follows a symlink. It adds a back-reference to each signal that names an
31
+ entity, skipping signals the gardener already linked, and a rerun changes
32
+ nothing. Files under `@{key}` audience folders are not read.
33
+
5
34
  ## [5.193.1] — 2026-09-25
6
35
 
7
36
  ### Fixed
@@ -5101,6 +5101,45 @@ export declare const COMMAND_CATALOG: readonly [{
5101
5101
  readonly description: "print the planned checkpoint without writing";
5102
5102
  }];
5103
5103
  readonly subcommands: readonly [];
5104
+ }, {
5105
+ readonly name: "ontology";
5106
+ readonly description: "Checkpoint ontology steps run by the checkpoint sibling (gated by checkpoint.ontology)";
5107
+ readonly aliases: readonly [];
5108
+ readonly hidden: false;
5109
+ readonly usage: "[options] [command]";
5110
+ readonly arguments: readonly [];
5111
+ readonly options: readonly [];
5112
+ readonly subcommands: readonly [{
5113
+ readonly name: "plan";
5114
+ readonly description: "Check the flag and list the changed company files for this checkpoint run";
5115
+ readonly aliases: readonly [];
5116
+ readonly hidden: false;
5117
+ readonly usage: "[options]";
5118
+ readonly arguments: readonly [];
5119
+ readonly options: readonly [{
5120
+ readonly flags: "--run-dir <path>";
5121
+ readonly description: "checkpoint sibling run directory holding payload.json";
5122
+ }, {
5123
+ readonly flags: "--hq-root <path>";
5124
+ readonly description: "HQ installation to operate on";
5125
+ }];
5126
+ readonly subcommands: readonly [];
5127
+ }, {
5128
+ readonly name: "apply";
5129
+ readonly description: "Validate the run's entity candidates and write them to the payload company";
5130
+ readonly aliases: readonly [];
5131
+ readonly hidden: false;
5132
+ readonly usage: "[options]";
5133
+ readonly arguments: readonly [];
5134
+ readonly options: readonly [{
5135
+ readonly flags: "--run-dir <path>";
5136
+ readonly description: "checkpoint sibling run directory holding the plan and candidates";
5137
+ }, {
5138
+ readonly flags: "--hq-root <path>";
5139
+ readonly description: "HQ installation to operate on";
5140
+ }];
5141
+ readonly subcommands: readonly [];
5142
+ }];
5104
5143
  }, {
5105
5144
  readonly name: "generate-workers-registry";
5106
5145
  readonly description: "Regenerate the derived workers registry";
@@ -6598,6 +6598,55 @@ export const COMMAND_CATALOG = [
6598
6598
  ],
6599
6599
  "subcommands": []
6600
6600
  },
6601
+ {
6602
+ "name": "ontology",
6603
+ "description": "Checkpoint ontology steps run by the checkpoint sibling (gated by checkpoint.ontology)",
6604
+ "aliases": [],
6605
+ "hidden": false,
6606
+ "usage": "[options] [command]",
6607
+ "arguments": [],
6608
+ "options": [],
6609
+ "subcommands": [
6610
+ {
6611
+ "name": "plan",
6612
+ "description": "Check the flag and list the changed company files for this checkpoint run",
6613
+ "aliases": [],
6614
+ "hidden": false,
6615
+ "usage": "[options]",
6616
+ "arguments": [],
6617
+ "options": [
6618
+ {
6619
+ "flags": "--run-dir <path>",
6620
+ "description": "checkpoint sibling run directory holding payload.json"
6621
+ },
6622
+ {
6623
+ "flags": "--hq-root <path>",
6624
+ "description": "HQ installation to operate on"
6625
+ }
6626
+ ],
6627
+ "subcommands": []
6628
+ },
6629
+ {
6630
+ "name": "apply",
6631
+ "description": "Validate the run's entity candidates and write them to the payload company",
6632
+ "aliases": [],
6633
+ "hidden": false,
6634
+ "usage": "[options]",
6635
+ "arguments": [],
6636
+ "options": [
6637
+ {
6638
+ "flags": "--run-dir <path>",
6639
+ "description": "checkpoint sibling run directory holding the plan and candidates"
6640
+ },
6641
+ {
6642
+ "flags": "--hq-root <path>",
6643
+ "description": "HQ installation to operate on"
6644
+ }
6645
+ ],
6646
+ "subcommands": []
6647
+ }
6648
+ ]
6649
+ },
6601
6650
  {
6602
6651
  "name": "generate-workers-registry",
6603
6652
  "description": "Regenerate the derived workers registry",
@@ -40,8 +40,19 @@ export declare const CHECKPOINT_IDLE_REPLY_REMINDER: string;
40
40
  * Kept in TypeScript rather than in a bundled asset: it is an instruction to
41
41
  * a locally-installed agent, not a scaffold script that should be packaged.
42
42
  */
43
- export declare const SIBLING_PROMPT_TEMPLATE = "You are the HQ checkpoint sibling \u2014 a background maintenance agent for this\nHQ install. Your parent session's state is in <payloadPath>. Work\nquietly and do not ask questions; if something is ambiguous, record it in the\nreport instead of guessing.\n\n1. Read the payload. If it lists a transcript path that exists, read its tail (~400 lines)\n both for session context and to extract additional reusable learnings/insights\n the parent did not pass explicitly. Never quote secrets\n or tokens from the transcript. If .claude/skills/checkpoint/SKILL.md exists\n under this HQ root, read it and follow it wherever it goes beyond these instructions;\n the write bounds below always win over the skill text.\n2. Upgrade the thread file named in the payload IN PLACE: verify/repair its\n JSON; fill git.remote_url, git.initial_commit, git.commits_made, and\n git.knowledge_repos by scanning core/knowledge/public/*,\n core/knowledge/private/*, personal/knowledge/*, and companies/*/knowledge\n for symlinks or directories containing .git, recording dirty repositories\n as {\"<name>\": {\"commit\": \"<short>\", \"dirty\": true}}. Fill worker,\n next_steps, and insights; set type to \"checkpoint\"; then rename the file to\n drop -auto- from its filename. Use the renamed path in every reference you\n write afterwards.\n3. For every explicit or transcript-derived learning that is a reusable rule,\n FIRST search the existing policies for one the learning refines, contradicts\n or duplicates. Then take exactly one of these actions and name it in the\n report \u2014 the policy set is curated, not append-only:\n - AMEND an existing policy in place when the learning sharpens it, narrows\n its scope, or adds a case, and the rule as written is still correct.\n - SUPERSEDE it when the learning contradicts it: rewrite the rule to what is\n now true and record inside the file what changed and why.\n - MERGE near-duplicates into the single best-named file, then delete the\n files you merged away.\n - CREATE a new policy only when no existing policy covers the rule.\n Follow core/knowledge/public/hq-core/policies-spec.md. Write under\n personal/policies/ or, only when the payload names a company and the rule is\n company-specific, companies/<company>/policies/.\n DELETION BOUNDS: delete a policy only as the MERGE or SUPERSEDE step above,\n only inside those two directories, and never one whose body marks it HARD \u2014\n if a hard policy now looks wrong, leave it untouched and flag it in the\n report for a human to decide. Never delete a file you have not read.\n Apply the same curation to durable facts (not rules) under\n personal/knowledge/ or companies/<company>/knowledge/: correct a stale fact\n in place rather than appending a second, contradictory copy of it.\n Store up to two explicit or transcript-derived insights per\n core/knowledge/public/hq-core/insights-spec.md when present, otherwise\n workspace/insights/.\n4. Close an active session journal fail-soft with\n bash .claude/skills/_shared/journal.sh close \"<project_dir>\" \"<one-line synthesis>\".\n Write a legacy checkpoint JSON under workspace/checkpoints/<id>.json with\n id, created_at, summary, files, and next_steps for backward compatibility.\n5. Update workspace/threads/recent.md and regenerate\n workspace/threads/INDEX.md. For each company whose knowledge path appears\n in files_touched, regenerate companies/<company>/knowledge/INDEX.md under\n core/knowledge/public/hq-core/index-md-spec.md. Mechanical index generation\n is allowed for those companies, but knowledge/policy content writes remain\n restricted to the payload's named company.\n6. Run .claude/skills/document-release/SKILL.md best-effort when it exists;\n skip silently on any failure. Hook or automation improvements go ONLY under\n personal/hooks/ as proposals.\n7. WRITE BOUNDS: you may write only under personal/, workspace/, and companies/<company>/ as constrained above. You must NEVER write into .claude/, core/, .agents/, .codex/, repos/, or anywhere outside the HQ root.\n8. Write <runDir>/report.md \u2014 full prose: what you read, what you changed\n (paths), and what you skipped and why. List every policy or knowledge file\n you amended, superseded, merged or deleted with the reason, so a human can\n audit and reverse it; a deletion you do not name in the report is a defect.\n Then drain the queue: while\n workspace/checkpoints/sibling/pending.jsonl exists and is non-empty, claim\n it atomically by renaming it aside \u2014\n mv workspace/checkpoints/sibling/pending.jsonl <runDir>/pending-claimed-N.jsonl\n (N counting up from 2) \u2014 and process the claimed payloads with this same\n flow. Repeat until a claim finds nothing left, then update the report.\n NEVER read the queue and truncate it in place: a payload appended between\n your read and the truncate is lost, and its checkpoint is never enriched.\n";
44
- export declare function renderSiblingPrompt(runDir: string, payloadPath: string): string;
43
+ export declare const SIBLING_PROMPT_TEMPLATE = "You are the HQ checkpoint sibling \u2014 a background maintenance agent for this\nHQ install. Your parent session's state is in <payloadPath>. Work\nquietly and do not ask questions; if something is ambiguous, record it in the\nreport instead of guessing.\n\n1. Read the payload. If it lists a transcript path that exists, read its tail (~400 lines)\n both for session context and to extract additional reusable learnings/insights\n the parent did not pass explicitly. Never quote secrets\n or tokens from the transcript. If .claude/skills/checkpoint/SKILL.md exists\n under this HQ root, read it and follow it wherever it goes beyond these instructions;\n the write bounds below always win over the skill text.\n2. Upgrade the thread file named in the payload IN PLACE: verify/repair its\n JSON; fill git.remote_url, git.initial_commit, git.commits_made, and\n git.knowledge_repos by scanning core/knowledge/public/*,\n core/knowledge/private/*, personal/knowledge/*, and companies/*/knowledge\n for symlinks or directories containing .git, recording dirty repositories\n as {\"<name>\": {\"commit\": \"<short>\", \"dirty\": true}}. Fill worker,\n next_steps, and insights; set type to \"checkpoint\"; then rename the file to\n drop -auto- from its filename. Use the renamed path in every reference you\n write afterwards.\n3. For every explicit or transcript-derived learning that is a reusable rule,\n FIRST search the existing policies for one the learning refines, contradicts\n or duplicates. Then take exactly one of these actions and name it in the\n report \u2014 the policy set is curated, not append-only:\n - AMEND an existing policy in place when the learning sharpens it, narrows\n its scope, or adds a case, and the rule as written is still correct.\n - SUPERSEDE it when the learning contradicts it: rewrite the rule to what is\n now true and record inside the file what changed and why.\n - MERGE near-duplicates into the single best-named file, then delete the\n files you merged away.\n - CREATE a new policy only when no existing policy covers the rule.\n Follow core/knowledge/public/hq-core/policies-spec.md. Write under\n personal/policies/ or, only when the payload names a company and the rule is\n company-specific, companies/<company>/policies/.\n DELETION BOUNDS: delete a policy only as the MERGE or SUPERSEDE step above,\n only inside those two directories, and never one whose body marks it HARD \u2014\n if a hard policy now looks wrong, leave it untouched and flag it in the\n report for a human to decide. Never delete a file you have not read.\n Apply the same curation to durable facts (not rules) under\n personal/knowledge/ or companies/<company>/knowledge/: correct a stale fact\n in place rather than appending a second, contradictory copy of it.\n Store up to two explicit or transcript-derived insights per\n core/knowledge/public/hq-core/insights-spec.md when present, otherwise\n workspace/insights/.\n4. Close an active session journal fail-soft with\n bash .claude/skills/_shared/journal.sh close \"<project_dir>\" \"<one-line synthesis>\".\n Write a legacy checkpoint JSON under workspace/checkpoints/<id>.json with\n id, created_at, summary, files, and next_steps for backward compatibility.\n5. Update workspace/threads/recent.md and regenerate\n workspace/threads/INDEX.md. For each company whose knowledge path appears\n in files_touched, regenerate companies/<company>/knowledge/INDEX.md under\n core/knowledge/public/hq-core/index-md-spec.md. Mechanical index generation\n is allowed for those companies, but knowledge/policy content writes remain\n restricted to the payload's named company.\n<ontologyStep>6. Run .claude/skills/document-release/SKILL.md best-effort when it exists;\n skip silently on any failure. Hook or automation improvements go ONLY under\n personal/hooks/ as proposals.\n7. WRITE BOUNDS: you may write only under personal/, workspace/, and companies/<company>/ as constrained above. You must NEVER write into .claude/, core/, .agents/, .codex/, repos/, or anywhere outside the HQ root.\n8. Write <runDir>/report.md \u2014 full prose: what you read, what you changed\n (paths), and what you skipped and why. List every policy or knowledge file\n you amended, superseded, merged or deleted with the reason, so a human can\n audit and reverse it; a deletion you do not name in the report is a defect.\n Then drain the queue: while\n workspace/checkpoints/sibling/pending.jsonl exists and is non-empty, claim\n it atomically by renaming it aside \u2014\n mv workspace/checkpoints/sibling/pending.jsonl <runDir>/pending-claimed-N.jsonl\n (N counting up from 2) \u2014 and process the claimed payloads with this same\n flow. Repeat until a claim finds nothing left, then update the report.\n NEVER read the queue and truncate it in place: a payload appended between\n your read and the truncate is lost, and its checkpoint is never enriched.\n";
44
+ /**
45
+ * Rendered into the sibling prompt only when the payload names a company. The
46
+ * flag check happens inside `hq core ontology plan`, in the sibling, because
47
+ * the checkpoint parent performs no network I/O. Only this run's own payload
48
+ * gets the step; claimed queue payloads are covered by the next run, since the
49
+ * plan works from file hashes rather than from the payload's file list.
50
+ */
51
+ export declare const SIBLING_ONTOLOGY_STEP = "5b. ONTOLOGY (this run's own payload only; skip it for claimed queue payloads).\n Run: hq core ontology plan --run-dir \"<runDir>\" --hq-root \"<hqRoot>\"\n If it prints \"ontology: skipped\", do nothing more for this step. Otherwise\n read <runDir>/ontology-plan.json, follow its \"instructions\" exactly, and\n write <runDir>/ontology-candidates.json. Read only the files and line\n ranges the plan lists. Then run:\n hq core ontology apply --run-dir \"<runDir>\" --hq-root \"<hqRoot>\"\n Never write under the company's ontology/ folder yourself, and never edit\n payload.json or ontology-plan.json. Copy the command's one-line output into\n the report. If either command fails, record the error and continue.\n";
52
+ export declare function renderSiblingPrompt(runDir: string, payloadPath: string, options?: {
53
+ company?: string | null;
54
+ hqRoot?: string;
55
+ }): string;
45
56
  export type WriteStampsOptions = {
46
57
  /**
47
58
  * Filesystem seam (default the real `node:fs`); injected in tests to force a
@@ -165,7 +165,7 @@ report instead of guessing.
165
165
  core/knowledge/public/hq-core/index-md-spec.md. Mechanical index generation
166
166
  is allowed for those companies, but knowledge/policy content writes remain
167
167
  restricted to the payload's named company.
168
- 6. Run .claude/skills/document-release/SKILL.md best-effort when it exists;
168
+ <ontologyStep>6. Run .claude/skills/document-release/SKILL.md best-effort when it exists;
169
169
  skip silently on any failure. Hook or automation improvements go ONLY under
170
170
  personal/hooks/ as proposals.
171
171
  7. WRITE BOUNDS: you may write only under personal/, workspace/, and companies/<company>/ as constrained above. You must NEVER write into .claude/, core/, .agents/, .codex/, repos/, or anywhere outside the HQ root.
@@ -182,10 +182,31 @@ report instead of guessing.
182
182
  NEVER read the queue and truncate it in place: a payload appended between
183
183
  your read and the truncate is lost, and its checkpoint is never enriched.
184
184
  `;
185
- export function renderSiblingPrompt(runDir, payloadPath) {
185
+ /**
186
+ * Rendered into the sibling prompt only when the payload names a company. The
187
+ * flag check happens inside `hq core ontology plan`, in the sibling, because
188
+ * the checkpoint parent performs no network I/O. Only this run's own payload
189
+ * gets the step; claimed queue payloads are covered by the next run, since the
190
+ * plan works from file hashes rather than from the payload's file list.
191
+ */
192
+ export const SIBLING_ONTOLOGY_STEP = `5b. ONTOLOGY (this run's own payload only; skip it for claimed queue payloads).
193
+ Run: hq core ontology plan --run-dir "<runDir>" --hq-root "<hqRoot>"
194
+ If it prints "ontology: skipped", do nothing more for this step. Otherwise
195
+ read <runDir>/ontology-plan.json, follow its "instructions" exactly, and
196
+ write <runDir>/ontology-candidates.json. Read only the files and line
197
+ ranges the plan lists. Then run:
198
+ hq core ontology apply --run-dir "<runDir>" --hq-root "<hqRoot>"
199
+ Never write under the company's ontology/ folder yourself, and never edit
200
+ payload.json or ontology-plan.json. Copy the command's one-line output into
201
+ the report. If either command fails, record the error and continue.
202
+ `;
203
+ export function renderSiblingPrompt(runDir, payloadPath, options = {}) {
204
+ const ontologyStep = options.company && options.hqRoot ? SIBLING_ONTOLOGY_STEP : "";
186
205
  return SIBLING_PROMPT_TEMPLATE
206
+ .replaceAll("<ontologyStep>", () => ontologyStep)
187
207
  .replaceAll("<runDir>", () => runDir)
188
- .replaceAll("<payloadPath>", () => payloadPath);
208
+ .replaceAll("<payloadPath>", () => payloadPath)
209
+ .replaceAll("<hqRoot>", () => options.hqRoot ?? "");
189
210
  }
190
211
  function collect(value, previous) {
191
212
  return [...previous, value];
@@ -946,7 +967,7 @@ function startSibling(liveRoot, input, threadPath, backend) {
946
967
  payload.pending_payloads = claimPending(pendingPath, path.join(runDir, "pending-claimed.jsonl"));
947
968
  const payloadPath = path.join(runDir, "payload.json");
948
969
  fs.writeFileSync(payloadPath, `${JSON.stringify(payload, null, 2)}\n`);
949
- const prompt = renderSiblingPrompt(runDir, payloadPath);
970
+ const prompt = renderSiblingPrompt(runDir, payloadPath, { company: payload.company, hqRoot: liveRoot });
950
971
  fs.writeFileSync(path.join(runDir, "prompt.md"), prompt);
951
972
  const logPath = path.join(runDir, "run.log");
952
973
  const logFd = fs.openSync(logPath, "a");
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `hq core ontology plan|apply` — the checkpoint sibling's ontology steps.
3
+ *
4
+ * The checkpoint parent never runs these: `plan` reads the hq-flags registry,
5
+ * which is network I/O. The detached sibling runs `plan`, writes candidates
6
+ * from the files the plan lists, then runs `apply`. Both commands take the
7
+ * company from the run's `payload.json`, never from the agent, so an agent
8
+ * cannot point the writes at another company.
9
+ */
10
+ import { Command } from "commander";
11
+ import { type FlagReader } from "../lib/checkpoint-ontology/plan.js";
12
+ export declare const ONTOLOGY_PAYLOAD_FILE = "payload.json";
13
+ /** The hq-flags read, scoped to the payload company. */
14
+ export declare const registryFlagReader: FlagReader;
15
+ export interface OntologyCommandDeps {
16
+ readFlag?: FlagReader;
17
+ now?: () => Date;
18
+ }
19
+ export declare function runOntologyPlan(hqRoot: string, runDirOption: string | undefined, deps?: OntologyCommandDeps): Promise<string>;
20
+ export declare function runOntologyApply(hqRoot: string, runDirOption: string | undefined, deps?: OntologyCommandDeps): string;
21
+ export declare function registerCoreOntologyCommand(core: Command): void;
22
+ //# sourceMappingURL=core-ontology.d.ts.map
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `hq core ontology plan|apply` — the checkpoint sibling's ontology steps.
3
+ *
4
+ * The checkpoint parent never runs these: `plan` reads the hq-flags registry,
5
+ * which is network I/O. The detached sibling runs `plan`, writes candidates
6
+ * from the files the plan lists, then runs `apply`. Both commands take the
7
+ * company from the run's `payload.json`, never from the agent, so an agent
8
+ * cannot point the writes at another company.
9
+ */
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import { resolveLiveRoot } from "../utils/hq-roots.js";
13
+ import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
14
+ import { planOntologyRun } from "../lib/checkpoint-ontology/plan.js";
15
+ import { applyOntologyRun, ApplyError } from "../lib/checkpoint-ontology/apply.js";
16
+ import { WriteBoundsError } from "../lib/checkpoint-ontology/company.js";
17
+ export const ONTOLOGY_PAYLOAD_FILE = "payload.json";
18
+ class OntologyUsageError extends Error {
19
+ }
20
+ function readPayload(runDir) {
21
+ const file = path.join(runDir, ONTOLOGY_PAYLOAD_FILE);
22
+ let raw;
23
+ try {
24
+ raw = JSON.parse(fs.readFileSync(file, "utf8"));
25
+ }
26
+ catch (error) {
27
+ throw new OntologyUsageError(`ontology: cannot read ${file} (${error.name})`);
28
+ }
29
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
30
+ throw new OntologyUsageError(`ontology: ${file} is not a JSON object`);
31
+ }
32
+ return raw;
33
+ }
34
+ function requireRunDir(runDir) {
35
+ if (!runDir)
36
+ throw new OntologyUsageError("ontology: --run-dir is required");
37
+ const resolved = path.resolve(runDir);
38
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
39
+ throw new OntologyUsageError(`ontology: run directory not found: ${resolved}`);
40
+ }
41
+ return resolved;
42
+ }
43
+ /** The hq-flags read, scoped to the payload company. */
44
+ export const registryFlagReader = (flagKey, company) => readLanesRegistryFlag(flagKey, {
45
+ company: company.cloudUid ? { slug: company.slug, uid: company.cloudUid } : null,
46
+ });
47
+ export async function runOntologyPlan(hqRoot, runDirOption, deps = {}) {
48
+ const runDir = requireRunDir(runDirOption);
49
+ const outcome = await planOntologyRun({
50
+ hqRoot,
51
+ runDir,
52
+ payload: readPayload(runDir),
53
+ readFlag: deps.readFlag ?? registryFlagReader,
54
+ now: deps.now?.(),
55
+ });
56
+ if (outcome.status === "skipped")
57
+ return `ontology: skipped (${outcome.reason})`;
58
+ const { plan } = outcome;
59
+ return (`ontology: planned ${plan.inputs.length} input(s), ~${plan.budget.planned_tokens} tokens, ` +
60
+ `${plan.pending_after_run} left for later runs; plan at ${outcome.planPath}`);
61
+ }
62
+ export function runOntologyApply(hqRoot, runDirOption, deps = {}) {
63
+ const runDir = requireRunDir(runDirOption);
64
+ const company = readPayload(runDir).company;
65
+ if (typeof company !== "string" || company === "") {
66
+ throw new OntologyUsageError("ontology: the run payload names no company");
67
+ }
68
+ const report = applyOntologyRun({ hqRoot, runDir, company, now: deps.now?.() });
69
+ if (report.status !== "applied")
70
+ return `ontology: ${report.status.replaceAll("_", " ")}; nothing written`;
71
+ return (`ontology: ${report.created.length} created, ${report.updated.length} updated, ` +
72
+ `${report.matched} matched, ${report.rejected.length} rejected, ${report.deferred.length} deferred, ` +
73
+ `${report.suspect_duplicates.length} suspect duplicate(s), ${report.write_errors.length} write error(s)`);
74
+ }
75
+ function fail(error) {
76
+ if (error instanceof OntologyUsageError || error instanceof ApplyError || error instanceof WriteBoundsError) {
77
+ process.stderr.write(`${error.message}\n`);
78
+ process.exit(1);
79
+ }
80
+ throw error;
81
+ }
82
+ export function registerCoreOntologyCommand(core) {
83
+ const ontology = core
84
+ .command("ontology")
85
+ .description("Checkpoint ontology steps run by the checkpoint sibling (gated by checkpoint.ontology)");
86
+ const rootFor = (options) => resolveLiveRoot({ hqRoot: options.hqRoot ?? core.opts().hqRoot });
87
+ ontology
88
+ .command("plan")
89
+ .description("Check the flag and list the changed company files for this checkpoint run")
90
+ .option("--run-dir <path>", "checkpoint sibling run directory holding payload.json")
91
+ .option("--hq-root <path>", "HQ installation to operate on")
92
+ .action(async (options) => {
93
+ try {
94
+ process.stdout.write(`${await runOntologyPlan(rootFor(options), options.runDir)}\n`);
95
+ }
96
+ catch (error) {
97
+ fail(error);
98
+ }
99
+ });
100
+ ontology
101
+ .command("apply")
102
+ .description("Validate the run's entity candidates and write them to the payload company")
103
+ .option("--run-dir <path>", "checkpoint sibling run directory holding the plan and candidates")
104
+ .option("--hq-root <path>", "HQ installation to operate on")
105
+ .action((options) => {
106
+ try {
107
+ process.stdout.write(`${runOntologyApply(rootFor(options), options.runDir)}\n`);
108
+ }
109
+ catch (error) {
110
+ fail(error);
111
+ }
112
+ });
113
+ }
114
+ //# sourceMappingURL=core-ontology.js.map
@@ -30,6 +30,7 @@
30
30
  */
31
31
  import { Option } from "commander";
32
32
  import { registerCoreCheckpointCommand } from "./core-checkpoint.js";
33
+ import { registerCoreOntologyCommand } from "./core-ontology.js";
33
34
  import { resolveLiveRoot } from "../utils/hq-roots.js";
34
35
  import { runBundledScript } from "../utils/run-bundled-script.js";
35
36
  import { renderIndexTarget } from "../lib/index-render/index.js";
@@ -322,6 +323,7 @@ export function registerCoreCommands(program) {
322
323
  // This group primarily hosts manifest-driven bundled assets, but it also
323
324
  // hosts native TypeScript plumbing when a scaffold contract needs it.
324
325
  registerCoreCheckpointCommand(core);
326
+ registerCoreOntologyCommand(core);
325
327
  // These command names intentionally own native implementations. Their
326
328
  // similarly named scaffold assets are forwarders, so sending them through
327
329
  // runEntry would recurse back into this command forever.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * `hq core ontology apply`: turn the model's candidates into entity writes.
3
+ *
4
+ * Every rule that affects identity, scope or cost lives here (design
5
+ * sections 4.3, 5 and 14). The model output is untrusted input: a malformed
6
+ * candidate is rejected and counted, never repaired.
7
+ */
8
+ import { type WritableEntityType } from "./identity.js";
9
+ import { type EntityIndex } from "./tree.js";
10
+ export declare const CAPS: {
11
+ readonly creates: 25;
12
+ readonly updates: 100;
13
+ readonly bytes: number;
14
+ readonly newAliasesPerEntity: 3;
15
+ readonly aliasesPerCandidate: 5;
16
+ readonly nameLength: 200;
17
+ };
18
+ export declare const MIN_CREATE_CONFIDENCE = 0.7;
19
+ export interface Candidate {
20
+ input: string;
21
+ type: WritableEntityType;
22
+ name: string;
23
+ aliases: string[];
24
+ confidence: number;
25
+ evidence: string;
26
+ }
27
+ export interface ApplyReport {
28
+ schema: "hq-checkpoint-ontology-report.v1";
29
+ company: string;
30
+ run_id: string;
31
+ status: "applied" | "no_candidates" | "invalid_candidates";
32
+ planned_inputs: number;
33
+ planned_tokens: number;
34
+ candidates: number;
35
+ created: string[];
36
+ updated: string[];
37
+ matched: number;
38
+ session_links: Array<{
39
+ type: string;
40
+ canonical_name: string;
41
+ path: string;
42
+ }>;
43
+ /** Per input, the entities it resolved to: `link` for an existing entity, `create` for a new file. */
44
+ links: Array<{
45
+ input: string;
46
+ action: "link" | "create";
47
+ type: string;
48
+ canonical_name: string;
49
+ path: string;
50
+ }>;
51
+ rejected: Array<{
52
+ input: string;
53
+ type: string;
54
+ name: string;
55
+ reason: string;
56
+ }>;
57
+ deferred: Array<{
58
+ input: string;
59
+ type: string;
60
+ name: string;
61
+ reason: string;
62
+ }>;
63
+ suspect_duplicates: Array<{
64
+ name: string;
65
+ type: string;
66
+ paths: string[];
67
+ }>;
68
+ backrefs_added: number;
69
+ bytes_written: number;
70
+ inputs_done: string[];
71
+ inputs_pending: string[];
72
+ write_errors: Array<{
73
+ path: string;
74
+ reason: string;
75
+ }>;
76
+ duration_ms: number;
77
+ }
78
+ export interface ApplyOptions {
79
+ hqRoot: string;
80
+ runDir: string;
81
+ /** The payload company. Must equal the plan's company. */
82
+ company: string;
83
+ now?: Date;
84
+ }
85
+ export declare class ApplyError extends Error {
86
+ constructor(message: string);
87
+ }
88
+ /** Validate one raw candidate. Returns the candidate or a rejection reason. */
89
+ export declare function parseCandidate(raw: unknown): Candidate | string;
90
+ type Resolution = {
91
+ tier: 1;
92
+ target: string;
93
+ duplicates: string[];
94
+ } | {
95
+ tier: 2;
96
+ target: string;
97
+ } | {
98
+ tier: "ambiguous_alias";
99
+ paths: string[];
100
+ } | {
101
+ tier: "suspect_duplicate";
102
+ paths: string[];
103
+ } | {
104
+ tier: "type_conflict";
105
+ types: string[];
106
+ } | {
107
+ tier: "new";
108
+ };
109
+ /** Design section 5.3, tiers 1 to 4. */
110
+ export declare function resolveCandidate(index: EntityIndex, type: string, canonical: string): Resolution;
111
+ /**
112
+ * Existing entities of `type` that a new candidate probably is: one of the
113
+ * candidate's aliases is already their name or alias, or, for people, every
114
+ * word of their name (two words or more) is in the candidate's name
115
+ * (`Chaudhry Hassaan Saleem` and `Hassaan Saleem`).
116
+ */
117
+ export declare function overlappingEntities(index: EntityIndex, type: string, canonical: string, aliases: string[]): string[];
118
+ export declare function applyOntologyRun(options: ApplyOptions): ApplyReport;
119
+ export {};
120
+ //# sourceMappingURL=apply.d.ts.map