@indigoai-us/hq-cli 5.89.1 → 5.89.2
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,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.89.2]
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- The `hq core checkpoint` maintenance sibling no longer loses queued payloads.
|
|
10
|
+
Both drain paths read `pending.jsonl` and then truncated it, so a checkpoint
|
|
11
|
+
queued between the read and the truncate was discarded unread and never
|
|
12
|
+
enriched. The queue is now claimed atomically by renaming it into the run
|
|
13
|
+
directory, leaving producers a fresh queue and keeping the claimed file as an
|
|
14
|
+
audit and crash-recovery trail; the sibling prompt claims by `mv` in a loop
|
|
15
|
+
rather than truncating in place. (#317)
|
|
16
|
+
|
|
5
17
|
## [5.89.1]
|
|
6
18
|
|
|
7
19
|
### Fixed
|
|
@@ -11,7 +11,7 @@ import { Command } from "commander";
|
|
|
11
11
|
* Kept in TypeScript rather than in a bundled asset: it is an instruction to
|
|
12
12
|
* a locally-installed agent, not a scaffold script that should be packaged.
|
|
13
13
|
*/
|
|
14
|
-
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 distill a non-duplicate policy file under personal/policies/ or, only when\n the payload names a company and the rule is company-specific,\n companies/<company>/policies/, following\n core/knowledge/public/hq-core/policies-spec.md. Store up to two explicit or\n transcript-derived insights per core/knowledge/public/hq-core/insights-spec.md\n when present, otherwise workspace/insights/. Durable facts (not rules) may\n go under personal/knowledge/ or companies/<company>/knowledge/ only.\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.
|
|
14
|
+
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 distill a non-duplicate policy file under personal/policies/ or, only when\n the payload names a company and the rule is company-specific,\n companies/<company>/policies/, following\n core/knowledge/public/hq-core/policies-spec.md. Store up to two explicit or\n transcript-derived insights per core/knowledge/public/hq-core/insights-spec.md\n when present, otherwise workspace/insights/. Durable facts (not rules) may\n go under personal/knowledge/ or companies/<company>/knowledge/ only.\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. 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";
|
|
15
15
|
export declare function renderSiblingPrompt(runDir: string, payloadPath: string): string;
|
|
16
16
|
/** Attach the native checkpoint command to the hidden `hq core` group. */
|
|
17
17
|
export declare function registerCoreCheckpointCommand(core: Command): void;
|
|
@@ -78,9 +78,14 @@ report instead of guessing.
|
|
|
78
78
|
personal/hooks/ as proposals.
|
|
79
79
|
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.
|
|
80
80
|
8. Write <runDir>/report.md — full prose: what you read, what you changed
|
|
81
|
-
(paths), and what you skipped and why.
|
|
82
|
-
workspace/checkpoints/sibling/pending.jsonl is non-empty
|
|
83
|
-
|
|
81
|
+
(paths), and what you skipped and why. Then drain the queue: while
|
|
82
|
+
workspace/checkpoints/sibling/pending.jsonl exists and is non-empty, claim
|
|
83
|
+
it atomically by renaming it aside —
|
|
84
|
+
mv workspace/checkpoints/sibling/pending.jsonl <runDir>/pending-claimed-N.jsonl
|
|
85
|
+
(N counting up from 2) — and process the claimed payloads with this same
|
|
86
|
+
flow. Repeat until a claim finds nothing left, then update the report.
|
|
87
|
+
NEVER read the queue and truncate it in place: a payload appended between
|
|
88
|
+
your read and the truncate is lost, and its checkpoint is never enriched.
|
|
84
89
|
`;
|
|
85
90
|
export function renderSiblingPrompt(runDir, payloadPath) {
|
|
86
91
|
return SIBLING_PROMPT_TEMPLATE
|
|
@@ -457,26 +462,43 @@ function appendPending(pendingPath, payload) {
|
|
|
457
462
|
.filter((line) => line.trim());
|
|
458
463
|
if (lines.length <= MAX_PENDING_PAYLOADS)
|
|
459
464
|
return lines.length;
|
|
465
|
+
// Best-effort trim: this rewrite can race a concurrent append, but it only
|
|
466
|
+
// runs once the queue is already over cap, where newest-wins is the point.
|
|
460
467
|
const kept = lines.slice(-MAX_PENDING_PAYLOADS);
|
|
461
468
|
fs.writeFileSync(pendingPath, `${kept.join("\n")}\n`);
|
|
462
469
|
return kept.length;
|
|
463
470
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Take ownership of the queued payloads by renaming the queue aside.
|
|
473
|
+
*
|
|
474
|
+
* The queue must never be claimed by read-then-truncate: a payload appended
|
|
475
|
+
* between the read and the truncate is discarded unread, and the checkpoint it
|
|
476
|
+
* describes is never enriched. Renaming is atomic, so this run receives exactly
|
|
477
|
+
* the file that existed at the moment of the claim while concurrent callers go
|
|
478
|
+
* on appending to a fresh queue. The claimed file is left in the run directory
|
|
479
|
+
* as an audit trail and as a recovery point if this sibling dies.
|
|
480
|
+
*/
|
|
481
|
+
function claimPending(pendingPath, claimPath) {
|
|
482
|
+
try {
|
|
483
|
+
fs.renameSync(pendingPath, claimPath);
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
if (error?.code === "ENOENT")
|
|
487
|
+
return [];
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
467
490
|
const entries = [];
|
|
468
|
-
for (const line of fs.readFileSync(
|
|
491
|
+
for (const line of fs.readFileSync(claimPath, "utf8").split("\n")) {
|
|
469
492
|
if (!line.trim())
|
|
470
493
|
continue;
|
|
471
494
|
try {
|
|
472
495
|
entries.push(JSON.parse(line));
|
|
473
496
|
}
|
|
474
497
|
catch {
|
|
475
|
-
// A partial line must not prevent
|
|
476
|
-
//
|
|
498
|
+
// A partial line must not prevent this run from processing the valid
|
|
499
|
+
// queued payloads around it.
|
|
477
500
|
}
|
|
478
501
|
}
|
|
479
|
-
fs.writeFileSync(pendingPath, "");
|
|
480
502
|
return entries;
|
|
481
503
|
}
|
|
482
504
|
function startSibling(liveRoot, input, threadPath, backend) {
|
|
@@ -494,7 +516,7 @@ function startSibling(liveRoot, input, threadPath, backend) {
|
|
|
494
516
|
}
|
|
495
517
|
const runDir = path.join(siblingRoot, `${formatTimestamp(new Date())}-${summarySlug(input.summary ?? "checkpoint")}`);
|
|
496
518
|
fs.mkdirSync(runDir, { recursive: true });
|
|
497
|
-
payload.pending_payloads =
|
|
519
|
+
payload.pending_payloads = claimPending(pendingPath, path.join(runDir, "pending-claimed.jsonl"));
|
|
498
520
|
const payloadPath = path.join(runDir, "payload.json");
|
|
499
521
|
fs.writeFileSync(payloadPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
500
522
|
const prompt = renderSiblingPrompt(runDir, payloadPath);
|