@indigoai-us/hq-cli 5.193.0 → 5.194.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 +32 -0
- package/dist/command-catalog.generated.d.ts +42 -0
- package/dist/command-catalog.generated.js +53 -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/commands/lanes.js +26 -5
- package/dist/commands/mesh.js +45 -6
- package/dist/lib/checkpoint-ontology/apply.d.ts +113 -0
- package/dist/lib/checkpoint-ontology/apply.js +493 -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/mesh/live/daemon/board-refresh.js +2 -1
- package/dist/lib/mesh/project-board.js +5 -3
- package/dist/lib/mesh/story-status.d.ts +22 -0
- package/dist/lib/mesh/story-status.js +49 -0
- package/dist/lib/work-context/prd-sync.d.ts +1 -1
- package/dist/lib/work-context/prd-sync.js +8 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.194.0] — 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `hq core checkpoint` can now maintain the company ontology, the job the
|
|
10
|
+
cloud ontology gardener did. It is off unless the hq-flags flag
|
|
11
|
+
`checkpoint.ontology` is on for the checkpoint's company
|
|
12
|
+
(`HQ_FLAG_CHECKPOINT_ONTOLOGY` is the local override). When a checkpoint
|
|
13
|
+
names a company, the background sibling runs `hq core ontology plan`, which
|
|
14
|
+
checks the flag and lists the company knowledge and signal files that
|
|
15
|
+
changed since the last run, within a fixed input and token budget. The
|
|
16
|
+
sibling writes entity candidates for those files, and
|
|
17
|
+
`hq core ontology apply` validates them and writes them. It updates an
|
|
18
|
+
existing entity rather than creating a duplicate, and keeps entity ids in
|
|
19
|
+
the cloud's format. It writes only to the checkpoint's own company and never
|
|
20
|
+
follows a symlink. It adds a back-reference to each signal that names an
|
|
21
|
+
entity, skipping signals the gardener already linked, and a rerun changes
|
|
22
|
+
nothing. Files under `@{key}` audience folders are not read.
|
|
23
|
+
|
|
24
|
+
## [5.193.1] — 2026-09-25
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Work Mesh project sync maps ledger states (`todo` to `queued`, `in_progress` to
|
|
29
|
+
`in_progress`, `in_review`/`verification`/`blocked` to `review`, and `done`/`dropped`
|
|
30
|
+
to `done`), with explicit `status` taking precedence and `passes` preserved. PRD
|
|
31
|
+
sync emits review transitions; Board readers preserve unknown status strings and
|
|
32
|
+
ignore unknown ledger states. `hq mesh project ensure --dry-run` previews merged
|
|
33
|
+
Board statuses without writing.
|
|
34
|
+
- Claude lane balance reports now normalize Outpost lane-list and queued show
|
|
35
|
+
views before strict lane-record validation, including `no_story_reason: null`.
|
|
36
|
+
|
|
5
37
|
## [5.193.0] — 2026-09-25
|
|
6
38
|
|
|
7
39
|
### 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";
|
|
@@ -5777,6 +5816,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5777
5816
|
}, {
|
|
5778
5817
|
readonly flags: "--stories-file <path>";
|
|
5779
5818
|
readonly description: "PRD JSON file with userStories and metadata.repos";
|
|
5819
|
+
}, {
|
|
5820
|
+
readonly flags: "--dry-run";
|
|
5821
|
+
readonly description: "Read the current Board and print computed story statuses without writing";
|
|
5780
5822
|
}, {
|
|
5781
5823
|
readonly flags: "--json";
|
|
5782
5824
|
readonly description: "Print {projectId, threadId, channelId, version, stories}";
|
|
@@ -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",
|
|
@@ -7458,6 +7507,10 @@ export const COMMAND_CATALOG = [
|
|
|
7458
7507
|
"flags": "--stories-file <path>",
|
|
7459
7508
|
"description": "PRD JSON file with userStories and metadata.repos"
|
|
7460
7509
|
},
|
|
7510
|
+
{
|
|
7511
|
+
"flags": "--dry-run",
|
|
7512
|
+
"description": "Read the current Board and print computed story statuses without writing"
|
|
7513
|
+
},
|
|
7461
7514
|
{
|
|
7462
7515
|
"flags": "--json",
|
|
7463
7516
|
"description": "Print {projectId, threadId, channelId, version, stories}"
|
|
@@ -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.
|
package/dist/commands/lanes.js
CHANGED
|
@@ -534,6 +534,30 @@ function ownerErrorFromOutput(stdout) {
|
|
|
534
534
|
: `Outpost owner returned ${owner.error}`;
|
|
535
535
|
return { error: owner.error, message, details };
|
|
536
536
|
}
|
|
537
|
+
const LANE_RECORD_VIEW_FIELDS = [
|
|
538
|
+
"story_ids",
|
|
539
|
+
"hire_alive_children",
|
|
540
|
+
"hire",
|
|
541
|
+
"hire_summary",
|
|
542
|
+
"hire_source",
|
|
543
|
+
"hire_warning",
|
|
544
|
+
"senior_lost",
|
|
545
|
+
"remote_state",
|
|
546
|
+
];
|
|
547
|
+
/** Remove presentation-only fields before validating a remote lane snapshot. */
|
|
548
|
+
function normalizeLaneRecordView(raw) {
|
|
549
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
550
|
+
return raw;
|
|
551
|
+
const normalized = { ...raw };
|
|
552
|
+
if (normalized.state === "remote" && normalized.remote_state !== undefined) {
|
|
553
|
+
normalized.state = normalized.remote_state;
|
|
554
|
+
}
|
|
555
|
+
for (const field of LANE_RECORD_VIEW_FIELDS)
|
|
556
|
+
delete normalized[field];
|
|
557
|
+
if (normalized.no_story_reason === null)
|
|
558
|
+
delete normalized.no_story_reason;
|
|
559
|
+
return normalized;
|
|
560
|
+
}
|
|
537
561
|
async function ownerLaneHireFields(ctx, lane) {
|
|
538
562
|
if (lane.host.kind !== "outpost") {
|
|
539
563
|
throw new LanesError("outpost_lane_hire_summary_invalid", "Owner summary requires an Outpost-hosted lane", {
|
|
@@ -567,7 +591,7 @@ async function ownerLaneHireFields(ctx, lane) {
|
|
|
567
591
|
}
|
|
568
592
|
let ownerLane;
|
|
569
593
|
try {
|
|
570
|
-
ownerLane = parseLaneRecord(remote.lane);
|
|
594
|
+
ownerLane = parseLaneRecord(normalizeLaneRecordView(remote.lane));
|
|
571
595
|
}
|
|
572
596
|
catch (err) {
|
|
573
597
|
throw new LanesError("outpost_lane_hire_summary_invalid", `Outpost ${outpostId} returned an invalid lane snapshot for ${lane.lane_id}`, { lane_id: lane.lane_id, outpost_id: outpostId, cause: dispatchErrorMessage(err) });
|
|
@@ -890,11 +914,8 @@ async function readOutpostLaneRecords(ctx, outpostId) {
|
|
|
890
914
|
typeof rowHost.id === "string" && rowHost.id.trim() !== "";
|
|
891
915
|
if (!belongsToOutpost && !hasLocalHostIdentity)
|
|
892
916
|
continue;
|
|
893
|
-
const normalized = raw.state === "remote" && raw.remote_state !== undefined
|
|
894
|
-
? { ...raw, state: raw.remote_state }
|
|
895
|
-
: raw;
|
|
896
917
|
try {
|
|
897
|
-
lanes.push(parseLaneRecord(
|
|
918
|
+
lanes.push(parseLaneRecord(normalizeLaneRecordView(raw)));
|
|
898
919
|
}
|
|
899
920
|
catch (err) {
|
|
900
921
|
throw new LanesError("outpost_lane_list_invalid", `Outpost ${outpostId} returned an invalid owned lane record: ${dispatchErrorMessage(err)}`, { outpost_id: outpostId, index });
|
package/dist/commands/mesh.js
CHANGED
|
@@ -14,7 +14,8 @@ import chalk from "chalk";
|
|
|
14
14
|
import { loadCachedTokens, isMachineIdentity } from "../utils/cognito-session.js";
|
|
15
15
|
import * as readline from "node:readline/promises";
|
|
16
16
|
import { STORY_STATUSES, appendThreadEvent, ensureProjectThread, eventPayload, getProjectView, listActiveMembershipCompanies, listActiveThreads, patchStory, putProjectView, registerMeshProject, resolveActiveMembershipCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
17
|
-
import { closeSupersededProjectThread, ensureMissingBoardStories, ensureProjectBoard, formatStoryStatusChanges, mergeProjectStories, prdSyncEventsThatChangeBoard, } from "../lib/mesh/project-board.js";
|
|
17
|
+
import { closeSupersededProjectThread, ensureMissingBoardStories, ensureProjectBoard, boardStoryStatus, formatStoryStatusChanges, mergeProjectStories, prdSyncEventsThatChangeBoard, } from "../lib/mesh/project-board.js";
|
|
18
|
+
import { mapSourceStoryStatus } from "../lib/mesh/story-status.js";
|
|
18
19
|
import { buildPreflightFixtures } from "../lib/work-context/preflight-contract.js";
|
|
19
20
|
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
20
21
|
import { clearDefaultCompany, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, } from "../lib/work-context/config.js";
|
|
@@ -258,10 +259,7 @@ function projectStoriesFromJson(value, source) {
|
|
|
258
259
|
if (!story || !id || !title) {
|
|
259
260
|
fail(`${source} userStories[${index}] requires string id and title.`);
|
|
260
261
|
}
|
|
261
|
-
const status =
|
|
262
|
-
if (status && !STORY_STATUSES.has(status)) {
|
|
263
|
-
fail(`${source} userStories[${index}].status must be queued|in_progress|review|done.`);
|
|
264
|
-
}
|
|
262
|
+
const status = mapSourceStoryStatus(story, `${source} userStories[${index}]`);
|
|
265
263
|
const resolvedStatus = status || (story.passes === true ? "done" : undefined);
|
|
266
264
|
return {
|
|
267
265
|
id,
|
|
@@ -475,6 +473,42 @@ async function runProjectEnsure(projectId, opts) {
|
|
|
475
473
|
if (httpStatus(err) !== 404)
|
|
476
474
|
throw err;
|
|
477
475
|
}
|
|
476
|
+
if (opts.dryRun) {
|
|
477
|
+
if (current && (current.companyUid !== company.companyUid || current.projectId !== projectId)) {
|
|
478
|
+
fail("Work mesh project ensure dry-run: Board company or project id did not match the request.");
|
|
479
|
+
}
|
|
480
|
+
const stories = current ? mergeProjectStories(current.stories, source.stories) : source.stories;
|
|
481
|
+
const sourceStatuses = source.stories.map((story) => ({
|
|
482
|
+
id: story.id,
|
|
483
|
+
title: story.title,
|
|
484
|
+
status: boardStoryStatus(story),
|
|
485
|
+
}));
|
|
486
|
+
const boardStatuses = stories.map((story) => ({
|
|
487
|
+
id: story.id,
|
|
488
|
+
title: story.title,
|
|
489
|
+
status: boardStoryStatus(story),
|
|
490
|
+
}));
|
|
491
|
+
if (opts.json) {
|
|
492
|
+
console.log(JSON.stringify({
|
|
493
|
+
ok: true,
|
|
494
|
+
action: "project.ensure.dry-run",
|
|
495
|
+
projectId,
|
|
496
|
+
companyUid: company.companyUid,
|
|
497
|
+
sourceStatuses,
|
|
498
|
+
stories: boardStatuses,
|
|
499
|
+
}, null, 2));
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
console.log(`Work mesh: project ${projectId} dry run (${sourceStatuses.length} source stories)`);
|
|
503
|
+
console.log("Source statuses:");
|
|
504
|
+
for (const story of sourceStatuses)
|
|
505
|
+
console.log(`${story.id}\t${story.status}`);
|
|
506
|
+
console.log("Merged Board statuses:");
|
|
507
|
+
for (const story of boardStatuses)
|
|
508
|
+
console.log(`${story.id}\t${story.status}`);
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
478
512
|
// Genesis thread, then the view, then register. Register returns 403
|
|
479
513
|
// PROJECT_NOT_IN_COMPANY until both exist. A missing view uses that chain
|
|
480
514
|
// from ensureProjectBoard; an existing view is merged, then registered.
|
|
@@ -659,7 +693,11 @@ async function runPrdSync(opts) {
|
|
|
659
693
|
source: "hooks",
|
|
660
694
|
taskId: event.taskId,
|
|
661
695
|
status: event.status,
|
|
662
|
-
summary: event.status === "done"
|
|
696
|
+
summary: event.status === "done"
|
|
697
|
+
? `${event.taskId} passes`
|
|
698
|
+
: event.status === "in_progress"
|
|
699
|
+
? `${event.taskId} in progress`
|
|
700
|
+
: `${event.taskId} in review`,
|
|
663
701
|
evidence: { ...evidenceBase, task: event.taskId },
|
|
664
702
|
}));
|
|
665
703
|
const poster = createEmitPoster({ token, baseUrl: resolveVaultApiBase(process.env) });
|
|
@@ -1855,6 +1893,7 @@ export function registerMeshCommand(program) {
|
|
|
1855
1893
|
.description("Idempotently register a PRD on the Board and verify the view")
|
|
1856
1894
|
.requiredOption("--company <slug|uid>", "Company slug or cloud uid")
|
|
1857
1895
|
.requiredOption("--stories-file <path>", "PRD JSON file with userStories and metadata.repos")
|
|
1896
|
+
.option("--dry-run", "Read the current Board and print computed story statuses without writing")
|
|
1858
1897
|
.option("--json", "Print {projectId, threadId, channelId, version, stories}")
|
|
1859
1898
|
.action((projectId, opts) => wrap(() => runProjectEnsure(projectId, opts))());
|
|
1860
1899
|
project
|