@indigoai-us/hq-cli 5.319.0 → 5.321.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 +13 -0
- package/assets/scaffold/core/scripts/repo-run-registry.sh +3 -1
- package/assets/scaffold/core/scripts/session-journal.sh +40 -2
- package/dist/bin/hq-auth-refresh.d.ts +3 -1
- package/dist/bin/hq-auth-refresh.js +41 -6
- package/dist/command-catalog.generated.d.ts +24 -0
- package/dist/command-catalog.generated.js +30 -0
- package/dist/commands/core-hook-batch-command.d.ts +3 -0
- package/dist/commands/core-hook-batch-command.js +27 -0
- package/dist/commands/core-hook-batch-flag.d.ts +4 -0
- package/dist/commands/core-hook-batch-flag.js +7 -0
- package/dist/commands/core-hook-batch-native.d.ts +10 -0
- package/dist/commands/core-hook-batch-native.js +85 -0
- package/dist/commands/core-repo-run-registry-native.js +6 -1
- package/dist/commands/core-session-journal-native.d.ts +8 -1
- package/dist/commands/core-session-journal-native.js +77 -18
- package/dist/commands/core.js +2 -0
- package/dist/commands/lanes.js +28 -5
- package/dist/fast-core.js +13 -0
- package/dist/fast-scaffold.d.ts +2 -0
- package/dist/fast-scaffold.js +10 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +135 -63
- package/dist/lib/daemon/run.d.ts +2 -0
- package/dist/lib/daemon/run.js +10 -1
- package/dist/lib/lanes/flag-keys.d.ts +3 -0
- package/dist/lib/lanes/flag-keys.js +3 -0
- package/dist/lib/work-context/bind-best.d.ts +1 -1
- package/dist/lib/work-context/bind-best.js +3 -3
- package/dist/utils/package-use-lease.d.ts +25 -0
- package/dist/utils/package-use-lease.js +213 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.321.0] — 2026-10-02
|
|
6
|
+
|
|
7
|
+
- Project catalog cache keys include their source directory, and runtime-probe tests isolate the host CLI and scope checks to hooks.
|
|
8
|
+
|
|
9
|
+
- Fixed intermittent timeouts in the concurrent journal parity test.
|
|
10
|
+
- Fix repo-run-registry so an unremovable stale lock exits with a clear error instead of retrying forever.
|
|
11
|
+
- Added the default-off `hq core hook-batch journal-due` command for one-process PostToolUse journal counter reads.
|
|
12
|
+
|
|
13
|
+
## [5.320.0] — 2026-10-02
|
|
14
|
+
- Write session-journal titles as escaped YAML strings so free-form titles remain parseable.
|
|
15
|
+
- Added a default-off hq-flags gate that lets lane list pollers skip remote owner refreshes to reduce poll latency.
|
|
16
|
+
- Publish per-process package-use leases so Desktop updates can wait for active hq-cli work without native locks.
|
|
17
|
+
|
|
5
18
|
## [5.319.0] — 2026-10-02
|
|
6
19
|
- Let `hq access` use ACL-checked company presigned reads when STS vend rejects a truncated write-policy scope.
|
|
7
20
|
- Lead autopilot on an Outpost can wake leads that were created on that Outpost, behind the default-off `lanes.autopilot-outpost-native-leads` flag. Before, those leads were skipped as a host mismatch and got no wakes.
|
|
@@ -96,7 +96,9 @@ _try_lock() {
|
|
|
96
96
|
mtime="$(portable_stat_mtime "$LOCK_DIR" 2>/dev/null || echo 0)"
|
|
97
97
|
age=$(( $(date +%s) - mtime ))
|
|
98
98
|
if [[ $age -gt 60 ]]; then
|
|
99
|
-
rm -rf "$LOCK_DIR" 2>/dev/null
|
|
99
|
+
if ! rm -rf "$LOCK_DIR" 2>/dev/null; then
|
|
100
|
+
_die "could not remove stale registry lock: $LOCK_DIR"
|
|
101
|
+
fi
|
|
100
102
|
continue
|
|
101
103
|
fi
|
|
102
104
|
fi
|
|
@@ -43,6 +43,41 @@ slugify() {
|
|
|
43
43
|
| cut -c1-40
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
yaml_double_quoted() {
|
|
47
|
+
local value="$1" output='"' character next codepoint hex
|
|
48
|
+
local LC_ALL=C index
|
|
49
|
+
for ((index = 0; index < ${#value}; index++)); do
|
|
50
|
+
character="${value:index:1}"
|
|
51
|
+
if [[ "$character" == $'\xc2' ]] && (( index + 1 < ${#value} )); then
|
|
52
|
+
next="${value:index+1:1}"
|
|
53
|
+
printf -v codepoint '%d' "'${next}"
|
|
54
|
+
if (( codepoint >= 128 && codepoint <= 159 )); then
|
|
55
|
+
printf -v hex '%04x' "$codepoint"
|
|
56
|
+
output+="\\u$hex"
|
|
57
|
+
index=$((index + 1))
|
|
58
|
+
continue
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
case "$character" in
|
|
62
|
+
\\) output+='\\' ;;
|
|
63
|
+
'"') output+='\"' ;;
|
|
64
|
+
$'\n') output+='\n' ;;
|
|
65
|
+
$'\r') output+='\r' ;;
|
|
66
|
+
$'\t') output+='\t' ;;
|
|
67
|
+
*)
|
|
68
|
+
if [[ "$character" == [[:cntrl:]] ]]; then
|
|
69
|
+
printf -v codepoint '%d' "'${character}"
|
|
70
|
+
printf -v hex '%02x' "$codepoint"
|
|
71
|
+
output+="\\x$hex"
|
|
72
|
+
else
|
|
73
|
+
output+="$character"
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
printf '%s"' "$output"
|
|
79
|
+
}
|
|
80
|
+
|
|
46
81
|
# Counters are per-session. A date-global counter lets concurrent Claude and
|
|
47
82
|
# Codex tasks trip (and reset) each other's journal reminders.
|
|
48
83
|
counter_key() {
|
|
@@ -140,6 +175,9 @@ case "$cmd" in
|
|
|
140
175
|
|
|
141
176
|
slug="$(slugify "$title")"
|
|
142
177
|
[ -z "$slug" ] && slug="entry"
|
|
178
|
+
yaml_title="$(yaml_double_quoted "$title")"
|
|
179
|
+
yaml_slug="$slug"
|
|
180
|
+
[[ "$slug" != *$'\n'* ]] || yaml_slug="$(yaml_double_quoted "$slug")"
|
|
143
181
|
JOURNAL_WRITE_LOCK=""
|
|
144
182
|
acquire_journal_write_lock "$dir" || { warn "write: failed to acquire journal lock"; exit 0; }
|
|
145
183
|
trap release_journal_write_lock EXIT
|
|
@@ -161,8 +199,8 @@ case "$cmd" in
|
|
|
161
199
|
{
|
|
162
200
|
printf -- '---\n'
|
|
163
201
|
printf 'ts: %s\n' "$(now_iso_z)"
|
|
164
|
-
printf 'title: %s\n' "$
|
|
165
|
-
printf 'slug: %s\n' "$
|
|
202
|
+
printf 'title: %s\n' "$yaml_title"
|
|
203
|
+
printf 'slug: %s\n' "$yaml_slug"
|
|
166
204
|
if [ -n "$files_csv" ]; then
|
|
167
205
|
printf 'files:\n'
|
|
168
206
|
old_ifs="$IFS"
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* non-interactively.
|
|
12
12
|
*/
|
|
13
13
|
import "../node-preflight.js";
|
|
14
|
+
import { acquireCurrentPackageUseLease } from "../utils/package-use-lease.js";
|
|
14
15
|
import "../node-network-compat.js";
|
|
15
|
-
import { refreshCachedSession } from "../utils/cognito-session.js";
|
|
16
|
+
import type { refreshCachedSession } from "../utils/cognito-session.js";
|
|
16
17
|
export declare function runAuthRefresh(refreshSession?: typeof refreshCachedSession): Promise<number>;
|
|
18
|
+
export declare function startAuthRefresh(acquireLease?: typeof acquireCurrentPackageUseLease): Promise<number>;
|
|
17
19
|
//# sourceMappingURL=hq-auth-refresh.d.ts.map
|
|
@@ -13,15 +13,16 @@
|
|
|
13
13
|
// MUST be first: guard the Node version before any dependency that needs a
|
|
14
14
|
// Node 20+ API (e.g. util.styleText) or a newer native ABI is evaluated.
|
|
15
15
|
import "../node-preflight.js";
|
|
16
|
+
import { acquireCurrentPackageUseLease } from "../utils/package-use-lease.js";
|
|
16
17
|
import "../node-network-compat.js";
|
|
17
18
|
import { fileURLToPath } from "node:url";
|
|
18
19
|
import path from "node:path";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
export async function runAuthRefresh(refreshSession) {
|
|
21
|
+
const { finishSentrySession, Sentry } = await import("../sentry.js");
|
|
22
|
+
const { refreshCachedSession: refresh } = await import("../utils/cognito-session.js");
|
|
22
23
|
let exitCode = 1;
|
|
23
24
|
try {
|
|
24
|
-
const result = await refreshSession();
|
|
25
|
+
const result = await (refreshSession ?? refresh)();
|
|
25
26
|
if (result.refreshed) {
|
|
26
27
|
exitCode = 0;
|
|
27
28
|
}
|
|
@@ -41,8 +42,42 @@ export async function runAuthRefresh(refreshSession = refreshCachedSession) {
|
|
|
41
42
|
}
|
|
42
43
|
return exitCode;
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
function isPackageUseUpdatePendingError(error) {
|
|
46
|
+
return Boolean(error &&
|
|
47
|
+
typeof error === "object" &&
|
|
48
|
+
error.name === "PackageUseUpdatePendingError");
|
|
49
|
+
}
|
|
50
|
+
function packageUseErrorClass(error) {
|
|
51
|
+
if (!error || typeof error !== "object")
|
|
52
|
+
return "unknown";
|
|
53
|
+
const value = error;
|
|
54
|
+
if (typeof value.code === "string")
|
|
55
|
+
return value.code;
|
|
56
|
+
if (value.cause && typeof value.cause === "object") {
|
|
57
|
+
const cause = value.cause;
|
|
58
|
+
if (typeof cause.code === "string")
|
|
59
|
+
return cause.code;
|
|
60
|
+
if (typeof cause.name === "string")
|
|
61
|
+
return cause.name;
|
|
62
|
+
}
|
|
63
|
+
return typeof value.name === "string" ? value.name : "unknown";
|
|
64
|
+
}
|
|
65
|
+
export async function startAuthRefresh(acquireLease = acquireCurrentPackageUseLease) {
|
|
66
|
+
try {
|
|
67
|
+
acquireLease();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (isPackageUseUpdatePendingError(error)) {
|
|
71
|
+
process.stderr.write(`hq-auth-refresh: ${error instanceof Error ? error.message : "The HQ CLI package is being updated"}\n`);
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
process.stderr.write(`hq-auth-refresh: package-use lease unavailable (${packageUseErrorClass(error)}); continuing without lease\n`);
|
|
75
|
+
}
|
|
76
|
+
const { initSentry } = await import("../sentry.js");
|
|
45
77
|
initSentry();
|
|
46
|
-
|
|
78
|
+
return runAuthRefresh();
|
|
79
|
+
}
|
|
80
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
81
|
+
void startAuthRefresh().then((exitCode) => process.exit(exitCode));
|
|
47
82
|
}
|
|
48
83
|
//# sourceMappingURL=hq-auth-refresh.js.map
|
|
@@ -5971,6 +5971,30 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5971
5971
|
}];
|
|
5972
5972
|
readonly options: readonly [];
|
|
5973
5973
|
readonly subcommands: readonly [];
|
|
5974
|
+
}, {
|
|
5975
|
+
readonly name: "hook-batch";
|
|
5976
|
+
readonly description: "Batch PostToolUse hook helper calls";
|
|
5977
|
+
readonly aliases: readonly [];
|
|
5978
|
+
readonly hidden: false;
|
|
5979
|
+
readonly usage: "[options] [command]";
|
|
5980
|
+
readonly arguments: readonly [];
|
|
5981
|
+
readonly options: readonly [];
|
|
5982
|
+
readonly subcommands: readonly [{
|
|
5983
|
+
readonly name: "journal-due";
|
|
5984
|
+
readonly description: "Resolve the journal path, increment the tool counter, and return its value";
|
|
5985
|
+
readonly aliases: readonly [];
|
|
5986
|
+
readonly hidden: false;
|
|
5987
|
+
readonly usage: "[options]";
|
|
5988
|
+
readonly arguments: readonly [];
|
|
5989
|
+
readonly options: readonly [{
|
|
5990
|
+
readonly flags: "--session <key>";
|
|
5991
|
+
readonly description: "Session key used by the journal counter";
|
|
5992
|
+
}, {
|
|
5993
|
+
readonly flags: "--reset";
|
|
5994
|
+
readonly description: "Reset the tool counter before incrementing it";
|
|
5995
|
+
}];
|
|
5996
|
+
readonly subcommands: readonly [];
|
|
5997
|
+
}];
|
|
5974
5998
|
}, {
|
|
5975
5999
|
readonly name: "session-journal";
|
|
5976
6000
|
readonly description: "Write and read session journal entries";
|
|
@@ -7681,6 +7681,36 @@ export const COMMAND_CATALOG = [
|
|
|
7681
7681
|
"options": [],
|
|
7682
7682
|
"subcommands": []
|
|
7683
7683
|
},
|
|
7684
|
+
{
|
|
7685
|
+
"name": "hook-batch",
|
|
7686
|
+
"description": "Batch PostToolUse hook helper calls",
|
|
7687
|
+
"aliases": [],
|
|
7688
|
+
"hidden": false,
|
|
7689
|
+
"usage": "[options] [command]",
|
|
7690
|
+
"arguments": [],
|
|
7691
|
+
"options": [],
|
|
7692
|
+
"subcommands": [
|
|
7693
|
+
{
|
|
7694
|
+
"name": "journal-due",
|
|
7695
|
+
"description": "Resolve the journal path, increment the tool counter, and return its value",
|
|
7696
|
+
"aliases": [],
|
|
7697
|
+
"hidden": false,
|
|
7698
|
+
"usage": "[options]",
|
|
7699
|
+
"arguments": [],
|
|
7700
|
+
"options": [
|
|
7701
|
+
{
|
|
7702
|
+
"flags": "--session <key>",
|
|
7703
|
+
"description": "Session key used by the journal counter"
|
|
7704
|
+
},
|
|
7705
|
+
{
|
|
7706
|
+
"flags": "--reset",
|
|
7707
|
+
"description": "Reset the tool counter before incrementing it"
|
|
7708
|
+
}
|
|
7709
|
+
],
|
|
7710
|
+
"subcommands": []
|
|
7711
|
+
}
|
|
7712
|
+
]
|
|
7713
|
+
},
|
|
7684
7714
|
{
|
|
7685
7715
|
"name": "session-journal",
|
|
7686
7716
|
"description": "Write and read session journal entries",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
2
|
+
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
3
|
+
import { CORE_HOOK_BATCH_HELP, runCoreHookBatchFast } from "./core-hook-batch-native.js";
|
|
4
|
+
import { coreHookBatchEnabled } from "./core-hook-batch-flag.js";
|
|
5
|
+
export function registerCoreHookBatchCommand(core) {
|
|
6
|
+
const group = core.command("hook-batch")
|
|
7
|
+
.description("Batch PostToolUse hook helper calls");
|
|
8
|
+
const journalDue = group.command("journal-due")
|
|
9
|
+
.description("Resolve the journal path, increment the tool counter, and return its value")
|
|
10
|
+
.requiredOption("--session <key>", "Session key used by the journal counter")
|
|
11
|
+
.option("--reset", "Reset the tool counter before incrementing it")
|
|
12
|
+
.addHelpText("after", `\n${CORE_HOOK_BATCH_HELP}`)
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
if (!await coreHookBatchEnabled()) {
|
|
15
|
+
process.stderr.write("hook-batch: journal-due requires the core-hook-batch flag\n");
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const root = resolveLiveRoot({ hqRoot: core.opts().hqRoot });
|
|
20
|
+
const args = ["--session", options.session, ...(options.reset ? ["--reset"] : [])];
|
|
21
|
+
const code = runCoreHookBatchFast(args, "", process.cwd(), root);
|
|
22
|
+
if (code !== 0)
|
|
23
|
+
process.exitCode = code;
|
|
24
|
+
});
|
|
25
|
+
describeCoreCommand(journalDue, { execution: "native", root: "live" });
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=core-hook-batch-command.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Default-off gate for the batched PostToolUse helper command. */
|
|
2
|
+
export const CORE_HOOK_BATCH_FLAG = "core-hook-batch";
|
|
3
|
+
export async function coreHookBatchEnabled() {
|
|
4
|
+
const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
|
|
5
|
+
return resolveProcessFlagGate(CORE_HOOK_BATCH_FLAG, { fallback: false }, () => false);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=core-hook-batch-flag.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type SessionJournalOperationResult } from "./core-session-journal-native.js";
|
|
2
|
+
import type { SessionHelperRuntimeOverrides } from "./core-session-helper-runtime.js";
|
|
3
|
+
export declare const CORE_HOOK_BATCH_HELP = "Usage: hq core hook-batch journal-due --session <key> [--reset]\n\nRuns session-journal dir-path, tool-counter increment, and tool-counter read\ninside one process. With --reset, tool-counter reset runs before increment.\n\nOutput framing: one ASCII line per helper call, in execution order:\n HQHB1|<step>|<exit-code>|<stdout-base64>|<stderr-base64>\nThe delimiter is |; the final newline ends each record. Base64 preserves each\nhelper stream byte-for-byte. In Bash, read each field and decode a stream with\nbase64 --decode (GNU) or base64 -D (BSD); jq is not needed. A helper's code 0\ndoes not imply empty output, and empty streams have empty base64 fields.\n";
|
|
4
|
+
type BatchResult = SessionJournalOperationResult;
|
|
5
|
+
/** Build the no-jq frame over the same in-process operation used by session-journal. */
|
|
6
|
+
export declare function runCoreHookBatch(args: readonly string[], stdin: string, cwd: string, hqRoot: string | undefined, dependencies?: SessionHelperRuntimeOverrides): BatchResult;
|
|
7
|
+
/** Fast-route entrypoint; the encoded records are the only top-level output. */
|
|
8
|
+
export declare function runCoreHookBatchFast(args: readonly string[], stdin: string, cwd: string, hqRoot: string | undefined): number | undefined;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=core-hook-batch-native.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { runSessionJournalOperation } from "./core-session-journal-native.js";
|
|
2
|
+
export const CORE_HOOK_BATCH_HELP = `Usage: hq core hook-batch journal-due --session <key> [--reset]
|
|
3
|
+
|
|
4
|
+
Runs session-journal dir-path, tool-counter increment, and tool-counter read
|
|
5
|
+
inside one process. With --reset, tool-counter reset runs before increment.
|
|
6
|
+
|
|
7
|
+
Output framing: one ASCII line per helper call, in execution order:
|
|
8
|
+
HQHB1|<step>|<exit-code>|<stdout-base64>|<stderr-base64>
|
|
9
|
+
The delimiter is |; the final newline ends each record. Base64 preserves each
|
|
10
|
+
helper stream byte-for-byte. In Bash, read each field and decode a stream with
|
|
11
|
+
base64 --decode (GNU) or base64 -D (BSD); jq is not needed. A helper's code 0
|
|
12
|
+
does not imply empty output, and empty streams have empty base64 fields.
|
|
13
|
+
`;
|
|
14
|
+
function usageError() {
|
|
15
|
+
return { code: 1, stdout: Buffer.alloc(0), stderr: Buffer.from(CORE_HOOK_BATCH_HELP) };
|
|
16
|
+
}
|
|
17
|
+
function frame(name, result) {
|
|
18
|
+
const line = [
|
|
19
|
+
"HQHB1",
|
|
20
|
+
name,
|
|
21
|
+
String(result.code),
|
|
22
|
+
result.stdout.toString("base64"),
|
|
23
|
+
result.stderr.toString("base64"),
|
|
24
|
+
].join("|");
|
|
25
|
+
return Buffer.from(`${line}\n`, "ascii");
|
|
26
|
+
}
|
|
27
|
+
function parseArgs(args) {
|
|
28
|
+
if (args.includes("--help") || args.includes("-h"))
|
|
29
|
+
return "help";
|
|
30
|
+
let session;
|
|
31
|
+
let reset = false;
|
|
32
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
33
|
+
const arg = args[index];
|
|
34
|
+
if (arg === "--session") {
|
|
35
|
+
const value = args[index + 1];
|
|
36
|
+
if (!value || value.startsWith("--"))
|
|
37
|
+
return undefined;
|
|
38
|
+
session = value;
|
|
39
|
+
index += 1;
|
|
40
|
+
}
|
|
41
|
+
else if (arg === "--reset") {
|
|
42
|
+
reset = true;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!session)
|
|
49
|
+
return undefined;
|
|
50
|
+
return { session, reset };
|
|
51
|
+
}
|
|
52
|
+
function runParsedCoreHookBatch(parsed, stdin, cwd, hqRoot, dependencies) {
|
|
53
|
+
const sessionArgs = ["--session", parsed.session];
|
|
54
|
+
const steps = [
|
|
55
|
+
{ name: "dir-path", args: ["dir-path"] },
|
|
56
|
+
...(parsed.reset ? [{ name: "reset", args: ["tool-counter", "reset", ...sessionArgs] }] : []),
|
|
57
|
+
{ name: "increment", args: ["tool-counter", "increment", ...sessionArgs] },
|
|
58
|
+
{ name: "read", args: ["tool-counter", "read", ...sessionArgs] },
|
|
59
|
+
];
|
|
60
|
+
const records = steps.map((step) => {
|
|
61
|
+
const result = runSessionJournalOperation(step.args, stdin, cwd, hqRoot, dependencies);
|
|
62
|
+
return frame(step.name, result);
|
|
63
|
+
});
|
|
64
|
+
return { code: 0, stdout: Buffer.concat(records), stderr: Buffer.alloc(0) };
|
|
65
|
+
}
|
|
66
|
+
/** Build the no-jq frame over the same in-process operation used by session-journal. */
|
|
67
|
+
export function runCoreHookBatch(args, stdin, cwd, hqRoot, dependencies = {}) {
|
|
68
|
+
const parsed = parseArgs(args);
|
|
69
|
+
if (parsed === "help")
|
|
70
|
+
return { code: 0, stdout: Buffer.from(CORE_HOOK_BATCH_HELP), stderr: Buffer.alloc(0) };
|
|
71
|
+
if (!parsed)
|
|
72
|
+
return usageError();
|
|
73
|
+
return runParsedCoreHookBatch(parsed, stdin, cwd, hqRoot, dependencies);
|
|
74
|
+
}
|
|
75
|
+
/** Fast-route entrypoint; the encoded records are the only top-level output. */
|
|
76
|
+
export function runCoreHookBatchFast(args, stdin, cwd, hqRoot) {
|
|
77
|
+
const parsed = parseArgs(args);
|
|
78
|
+
if (!parsed || parsed === "help")
|
|
79
|
+
return undefined;
|
|
80
|
+
const result = runParsedCoreHookBatch(parsed, stdin, cwd, hqRoot, {});
|
|
81
|
+
process.stdout.write(result.stdout);
|
|
82
|
+
process.stderr.write(result.stderr);
|
|
83
|
+
return result.code;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=core-hook-batch-native.js.map
|
|
@@ -131,7 +131,12 @@ export function runRepoRunRegistry(args, deps) {
|
|
|
131
131
|
catch { /* shell falls back to epoch zero when stat fails */ }
|
|
132
132
|
const age = Math.floor(now().getTime() / 1000) - Math.floor(mtimeMs / 1000);
|
|
133
133
|
if (age > 60) {
|
|
134
|
-
|
|
134
|
+
try {
|
|
135
|
+
f.rmSync(lockDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
throw new Error(`ERROR: could not remove stale registry lock: ${lockDir}`);
|
|
139
|
+
}
|
|
135
140
|
continue;
|
|
136
141
|
}
|
|
137
142
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { type SessionHelperRuntimeOverrides } from "./core-session-helper-runtime.js";
|
|
2
|
+
export type SessionJournalOperationResult = {
|
|
3
|
+
code: number;
|
|
4
|
+
stdout: Buffer;
|
|
5
|
+
stderr: Buffer;
|
|
6
|
+
};
|
|
7
|
+
/** Execute one native helper call while retaining its two output byte streams. */
|
|
8
|
+
export declare function runSessionJournalOperation(args: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined, dependencies?: SessionHelperRuntimeOverrides): SessionJournalOperationResult;
|
|
2
9
|
/** Native implementation of the fail-soft session journal helper. */
|
|
3
|
-
export declare function runSessionJournalFast(args: readonly string[],
|
|
10
|
+
export declare function runSessionJournalFast(args: readonly string[], stdin: string, cwd: string, hqRoot: string | undefined, dependencies?: SessionHelperRuntimeOverrides): number;
|
|
4
11
|
//# sourceMappingURL=core-session-journal-native.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { resolveSessionHelperRuntime } from "./core-session-helper-runtime.js";
|
|
4
|
+
import { resolveBundledAsset } from "../utils/hq-roots.js";
|
|
4
5
|
const USAGE = `session-journal.sh: usage:
|
|
5
6
|
session-journal.sh write "<title>" [--files f1,f2,...] [--body-file PATH] [--session KEY]
|
|
6
7
|
session-journal.sh list [--date YYYY-MM-DD]
|
|
@@ -24,7 +25,35 @@ const counterKey = (value) => {
|
|
|
24
25
|
return key.length > 96 ? key.slice(0, 96) : key;
|
|
25
26
|
};
|
|
26
27
|
const slugify = (value) => value.toLowerCase()
|
|
27
|
-
.
|
|
28
|
+
.split("\n")
|
|
29
|
+
.map((line) => line.replace(/[^a-z0-9]+/g, "-").replace(/^-+/, "").replace(/-+$/, "").slice(0, 40))
|
|
30
|
+
.join("\n");
|
|
31
|
+
function yamlDoubleQuoted(value) {
|
|
32
|
+
let escaped = "";
|
|
33
|
+
for (const character of value) {
|
|
34
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
35
|
+
if (character === "\\")
|
|
36
|
+
escaped += "\\\\";
|
|
37
|
+
else if (character === '"')
|
|
38
|
+
escaped += '\\"';
|
|
39
|
+
else if (character === "\n")
|
|
40
|
+
escaped += "\\n";
|
|
41
|
+
else if (character === "\r")
|
|
42
|
+
escaped += "\\r";
|
|
43
|
+
else if (character === "\t")
|
|
44
|
+
escaped += "\\t";
|
|
45
|
+
else if (codePoint < 0x20 || codePoint === 0x7f) {
|
|
46
|
+
escaped += `\\x${codePoint.toString(16).padStart(2, "0")}`;
|
|
47
|
+
}
|
|
48
|
+
else if (codePoint >= 0x80 && codePoint <= 0x9f) {
|
|
49
|
+
escaped += `\\u${codePoint.toString(16).padStart(4, "0")}`;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
escaped += character;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return `"${escaped}"`;
|
|
56
|
+
}
|
|
28
57
|
const warning = (message) => { process.stderr.write(`session-journal: ${message}\n`); };
|
|
29
58
|
const journalDir = (root, date) => path.join(root, "workspace/threads/journal", date);
|
|
30
59
|
const hasFile = (file) => {
|
|
@@ -66,7 +95,7 @@ function acquireJournalWriteLock(dir) {
|
|
|
66
95
|
try {
|
|
67
96
|
fs.writeFileSync(ownerFile, `${process.pid}\n`, { flag: "wx" });
|
|
68
97
|
}
|
|
69
|
-
catch
|
|
98
|
+
catch {
|
|
70
99
|
fs.rmSync(lock, { recursive: true, force: true });
|
|
71
100
|
return undefined;
|
|
72
101
|
}
|
|
@@ -150,7 +179,7 @@ function options(args, valueFlags) {
|
|
|
150
179
|
}
|
|
151
180
|
return values;
|
|
152
181
|
}
|
|
153
|
-
function runToolCounter(root, action, sessionArg, env, now) {
|
|
182
|
+
function runToolCounter(root, action, sessionArg, env, now, writers) {
|
|
154
183
|
const rawKey = sessionArg || env.HQ_JOURNAL_SESSION || env.HQ_HOOK_SESSION_ID || "";
|
|
155
184
|
const key = counterKey(rawKey);
|
|
156
185
|
const dir = journalDir(root, now().toISOString().slice(0, 10));
|
|
@@ -160,31 +189,60 @@ function runToolCounter(root, action, sessionArg, env, now) {
|
|
|
160
189
|
catch { /* legacy mkdir failure is ignored */ }
|
|
161
190
|
const counter = path.join(dir, `.tool-count-${key}`);
|
|
162
191
|
const lock = `${counter}.lock`;
|
|
192
|
+
const reportDirectoryWriteFailure = (error, line) => {
|
|
193
|
+
const code = error.code;
|
|
194
|
+
if (code !== "EISDIR")
|
|
195
|
+
return;
|
|
196
|
+
const asset = resolveBundledAsset("core/scripts/session-journal.sh");
|
|
197
|
+
writers.stderr(`${asset}: line ${line}: ${counter}: Is a directory\n`);
|
|
198
|
+
};
|
|
163
199
|
if (action === "increment") {
|
|
164
200
|
withCounterLock(lock, () => {
|
|
165
201
|
try {
|
|
166
202
|
fs.writeFileSync(counter, `${readCounter(counter) + 1}\n`);
|
|
167
203
|
}
|
|
168
|
-
catch {
|
|
204
|
+
catch (error) {
|
|
205
|
+
reportDirectoryWriteFailure(error, 312);
|
|
206
|
+
}
|
|
169
207
|
});
|
|
170
208
|
}
|
|
171
209
|
else if (action === "read") {
|
|
172
|
-
|
|
210
|
+
writers.stdout(`${readCounter(counter)}\n`);
|
|
173
211
|
}
|
|
174
212
|
else if (action === "reset") {
|
|
175
213
|
withCounterLock(lock, () => {
|
|
176
214
|
try {
|
|
177
215
|
fs.writeFileSync(counter, "0\n");
|
|
178
216
|
}
|
|
179
|
-
catch {
|
|
217
|
+
catch (error) {
|
|
218
|
+
reportDirectoryWriteFailure(error, 320);
|
|
219
|
+
}
|
|
180
220
|
});
|
|
181
221
|
}
|
|
182
222
|
else {
|
|
183
|
-
|
|
223
|
+
writers.stderr("session-journal: tool-counter: subcommand required (increment|read|reset)\n");
|
|
184
224
|
}
|
|
185
225
|
}
|
|
226
|
+
/** Execute one native helper call while retaining its two output byte streams. */
|
|
227
|
+
export function runSessionJournalOperation(args, _stdin, cwd, hqRoot, dependencies = {}) {
|
|
228
|
+
const stdout = [];
|
|
229
|
+
const stderr = [];
|
|
230
|
+
const writers = {
|
|
231
|
+
stdout: (chunk) => stdout.push(Buffer.from(chunk)),
|
|
232
|
+
stderr: (chunk) => stderr.push(Buffer.from(chunk)),
|
|
233
|
+
};
|
|
234
|
+
const code = executeSessionJournal(args, cwd, hqRoot, dependencies, writers);
|
|
235
|
+
return { code, stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr) };
|
|
236
|
+
}
|
|
186
237
|
/** Native implementation of the fail-soft session journal helper. */
|
|
187
|
-
export function runSessionJournalFast(args,
|
|
238
|
+
export function runSessionJournalFast(args, stdin, cwd, hqRoot, dependencies = {}) {
|
|
239
|
+
const result = runSessionJournalOperation(args, stdin, cwd, hqRoot, dependencies);
|
|
240
|
+
process.stdout.write(result.stdout);
|
|
241
|
+
process.stderr.write(result.stderr);
|
|
242
|
+
return result.code;
|
|
243
|
+
}
|
|
244
|
+
function executeSessionJournal(args, cwd, hqRoot, dependencies, writers) {
|
|
245
|
+
const warning = (message) => { writers.stderr(`session-journal: ${message}\n`); };
|
|
188
246
|
const runtime = resolveSessionHelperRuntime(dependencies);
|
|
189
247
|
const today = () => runtime.now().toISOString().slice(0, 10);
|
|
190
248
|
const nowIso = () => runtime.now().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -211,6 +269,7 @@ export function runSessionJournalFast(args, _stdin, cwd, hqRoot, dependencies =
|
|
|
211
269
|
return 0;
|
|
212
270
|
}
|
|
213
271
|
const slug = slugify(title) || "entry";
|
|
272
|
+
const yamlSlug = slug.includes("\n") ? yamlDoubleQuoted(slug) : slug;
|
|
214
273
|
const body = bodyFile && hasFile(bodyFile)
|
|
215
274
|
? fs.readFileSync(bodyFile, "utf8")
|
|
216
275
|
: "## Goal\n\n## Findings\n\n## Decisions\n\n## Next\n";
|
|
@@ -226,7 +285,7 @@ export function runSessionJournalFast(args, _stdin, cwd, hqRoot, dependencies =
|
|
|
226
285
|
for (let attempt = 0; attempt < 10_000; attempt += 1) {
|
|
227
286
|
seq = nextSequence(dir);
|
|
228
287
|
output = path.join(dir, `${seq}-${slug}.md`);
|
|
229
|
-
const lines = ["---\n", `ts: ${nowIso()}\n`, `title: ${title}\n`, `slug: ${
|
|
288
|
+
const lines = ["---\n", `ts: ${nowIso()}\n`, `title: ${yamlDoubleQuoted(title)}\n`, `slug: ${yamlSlug}\n`];
|
|
230
289
|
if (files) {
|
|
231
290
|
lines.push("files:\n");
|
|
232
291
|
for (const file of files.split(","))
|
|
@@ -267,8 +326,8 @@ export function runSessionJournalFast(args, _stdin, cwd, hqRoot, dependencies =
|
|
|
267
326
|
finally {
|
|
268
327
|
releaseJournalWriteLock(writeLock);
|
|
269
328
|
}
|
|
270
|
-
runToolCounter(root, "reset", counterKey(session || runtime.env.HQ_JOURNAL_SESSION || runtime.env.HQ_HOOK_SESSION_ID || ""), runtime.env, runtime.now);
|
|
271
|
-
|
|
329
|
+
runToolCounter(root, "reset", counterKey(session || runtime.env.HQ_JOURNAL_SESSION || runtime.env.HQ_HOOK_SESSION_ID || ""), runtime.env, runtime.now, writers);
|
|
330
|
+
writers.stdout(`${output}\n`);
|
|
272
331
|
return 0;
|
|
273
332
|
}
|
|
274
333
|
if (command === "list" || command === "read" || command === "index-path" || command === "dir-path") {
|
|
@@ -277,19 +336,19 @@ export function runSessionJournalFast(args, _stdin, cwd, hqRoot, dependencies =
|
|
|
277
336
|
const date = options(afterSequence, ["--date"]).get("--date") || today();
|
|
278
337
|
const dir = journalDir(root, date);
|
|
279
338
|
if (command === "dir-path") {
|
|
280
|
-
|
|
339
|
+
writers.stdout(dir);
|
|
281
340
|
return 0;
|
|
282
341
|
}
|
|
283
342
|
if (command === "index-path") {
|
|
284
|
-
|
|
343
|
+
writers.stdout(`${dir}/INDEX.md\n`);
|
|
285
344
|
return 0;
|
|
286
345
|
}
|
|
287
346
|
if (command === "list") {
|
|
288
347
|
const index = path.join(dir, "INDEX.md");
|
|
289
348
|
if (hasFile(index))
|
|
290
|
-
|
|
349
|
+
writers.stdout(fs.readFileSync(index));
|
|
291
350
|
else
|
|
292
|
-
|
|
351
|
+
writers.stdout(`(no journal for ${date})\n`);
|
|
293
352
|
return 0;
|
|
294
353
|
}
|
|
295
354
|
if (!sequence) {
|
|
@@ -310,7 +369,7 @@ export function runSessionJournalFast(args, _stdin, cwd, hqRoot, dependencies =
|
|
|
310
369
|
}
|
|
311
370
|
})();
|
|
312
371
|
if (match)
|
|
313
|
-
|
|
372
|
+
writers.stdout(fs.readFileSync(path.join(dir, match)));
|
|
314
373
|
else
|
|
315
374
|
warning(`read: no entry ${normalized} for ${date}`);
|
|
316
375
|
return 0;
|
|
@@ -318,10 +377,10 @@ export function runSessionJournalFast(args, _stdin, cwd, hqRoot, dependencies =
|
|
|
318
377
|
if (command === "tool-counter") {
|
|
319
378
|
const action = rest[0] ?? "";
|
|
320
379
|
const session = options(rest.slice(1), ["--session"]).get("--session") ?? "";
|
|
321
|
-
runToolCounter(root, action, session, runtime.env, runtime.now);
|
|
380
|
+
runToolCounter(root, action, session, runtime.env, runtime.now, writers);
|
|
322
381
|
return 0;
|
|
323
382
|
}
|
|
324
|
-
|
|
383
|
+
writers.stderr(USAGE);
|
|
325
384
|
return 1;
|
|
326
385
|
}
|
|
327
386
|
//# sourceMappingURL=core-session-journal-native.js.map
|
package/dist/commands/core.js
CHANGED
|
@@ -47,6 +47,7 @@ import { registerCoreRefreshVaultAccessNative } from "./core-refresh-vault-acces
|
|
|
47
47
|
import { registerCoreWorkMeshBindTrustedNative } from "./core-work-mesh-bind-trusted-native.js";
|
|
48
48
|
import { registerCoreHookHelperNativeCommands } from "./core-native-hook-helper-commands.js";
|
|
49
49
|
import { registerCoreHookStateHelpers } from "./core-hook-state-helpers-native.js";
|
|
50
|
+
import { registerCoreHookBatchCommand } from "./core-hook-batch-command.js";
|
|
50
51
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
51
52
|
import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
|
|
52
53
|
import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
|
|
@@ -397,6 +398,7 @@ export function registerCoreCommands(program) {
|
|
|
397
398
|
registerCoreWorkMeshBindTrustedNative(core);
|
|
398
399
|
registerCoreHookHelperNativeCommands(core);
|
|
399
400
|
registerCoreHookStateHelpers(core);
|
|
401
|
+
registerCoreHookBatchCommand(core);
|
|
400
402
|
const sessionHelperCommands = [
|
|
401
403
|
{
|
|
402
404
|
name: "session-journal", asset: "core/scripts/session-journal.sh", root: "live",
|