@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 +29 -0
- package/dist/command-catalog.generated.d.ts +39 -0
- package/dist/command-catalog.generated.js +49 -0
- package/dist/commands/core-checkpoint.d.ts +13 -2
- package/dist/commands/core-checkpoint.js +25 -4
- package/dist/commands/core-ontology.d.ts +22 -0
- package/dist/commands/core-ontology.js +114 -0
- package/dist/commands/core.js +2 -0
- package/dist/lib/checkpoint-ontology/apply.d.ts +120 -0
- package/dist/lib/checkpoint-ontology/apply.js +531 -0
- package/dist/lib/checkpoint-ontology/company.d.ts +40 -0
- package/dist/lib/checkpoint-ontology/company.js +110 -0
- package/dist/lib/checkpoint-ontology/entity-file.d.ts +83 -0
- package/dist/lib/checkpoint-ontology/entity-file.js +264 -0
- package/dist/lib/checkpoint-ontology/identity.d.ts +37 -0
- package/dist/lib/checkpoint-ontology/identity.js +95 -0
- package/dist/lib/checkpoint-ontology/plan.d.ts +124 -0
- package/dist/lib/checkpoint-ontology/plan.js +284 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +51 -0
- package/dist/lib/checkpoint-ontology/tree.js +163 -0
- package/dist/lib/lanes/reconcile.js +2 -3
- package/package.json +1 -1
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.\
|
|
44
|
-
|
|
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
|
-
|
|
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
|
package/dist/commands/core.js
CHANGED
|
@@ -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
|