@indigoai-us/hq-cli 5.341.0 → 5.341.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 +4 -0
- package/assets/scaffold/core/scripts/policy-age-report.sh +1 -1
- package/dist/commands/core-hq-session-native.js +3 -2
- package/dist/commands/core-policy-frontmatter.js +4 -1
- package/dist/commands/core-policy.js +23 -12
- package/dist/commands/core-session-journal-native.js +14 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.341.1] — 2026-10-03
|
|
6
|
+
- Match native policy frontmatter and report bytes to the bundled shell on Windows, including paths, diagnostics, and JSON newlines.
|
|
7
|
+
- Keep session-journal printable paths byte-compatible with the shell on Windows.
|
|
8
|
+
|
|
5
9
|
## [5.341.0] — 2026-10-03
|
|
6
10
|
- Keep-alive reconciliation polls every 15 seconds and admits due lane resumes within the same sweep.
|
|
7
11
|
- Add flag-gated `hq daemon login --stdin` for machine sign-in on disposable hosts. Credentials are piped over the local daemon socket, and the resulting token stays in hqd memory until restart or token expiry. Login is deadline-bounded, and the generic argv-based `hq daemon call` command cannot submit credentials.
|
|
@@ -183,7 +183,7 @@ done > "$TMP/rows.tsv"
|
|
|
183
183
|
if [ "$JSON" = 1 ]; then
|
|
184
184
|
jq -R -s -c --argjson cand "$CAND" '
|
|
185
185
|
split("\n") | map(select(length>0) | split("\t") | {id:.[0], path:.[1], enforcement:.[2], age_days:(.[3]|tonumber), fired:(.[4]|tonumber), last_fired:(if .[5]=="-" then null else .[5] end), stale_refs:(if .[7]=="" then [] else (.[7]|split(",")) end), single_incident:(.[8]=="1"), candidate_classes:(if .[9]=="" then [] else (.[9]|split(",")) end), retrieved:((.[10] // "0")|tonumber), refs_judged:((.[11] // "0")|tonumber), renamed_refs:(if (.[12] // "")=="" then [] else (.[12]|split(",")) end)})
|
|
186
|
-
| if $cand == 1 then map(select(.candidate_classes|length>0)) else . end' "$TMP/rows.tsv"
|
|
186
|
+
| if $cand == 1 then map(select(.candidate_classes|length>0)) else . end' "$TMP/rows.tsv" | tr -d '\r'
|
|
187
187
|
exit 0
|
|
188
188
|
fi
|
|
189
189
|
total="$(wc -l < "$TMP/rows.tsv" | tr -d ' ')"
|
|
@@ -145,10 +145,11 @@ function resolveSessionId(root, deps) {
|
|
|
145
145
|
}
|
|
146
146
|
function findMetaValue(contents, key) {
|
|
147
147
|
for (const line of contents.split("\n")) {
|
|
148
|
-
const
|
|
148
|
+
const record = process.platform === "win32" ? line.replace(/\r$/, "") : line;
|
|
149
|
+
const first = record.match(/^\s*(\S+)/)?.[1] ?? "";
|
|
149
150
|
if (first !== `${key}:`)
|
|
150
151
|
continue;
|
|
151
|
-
return { found: true, value:
|
|
152
|
+
return { found: true, value: record.replace(/^[^:]+:[\t ]*/, "").replace(/^"|"$/g, "") };
|
|
152
153
|
}
|
|
153
154
|
return { found: false, value: "" };
|
|
154
155
|
}
|
|
@@ -39,7 +39,10 @@ export function runPolicyFrontmatter(args, cwd, pathValue = process.env.PATH, pl
|
|
|
39
39
|
while (start < source.length) {
|
|
40
40
|
const end = source.indexOf(0x0a, start);
|
|
41
41
|
const lineEnd = end < 0 ? source.length : end;
|
|
42
|
-
const
|
|
42
|
+
const rawLine = source.subarray(start, lineEnd);
|
|
43
|
+
const line = platform === 'win32' && rawLine.at(-1) === 0x0d
|
|
44
|
+
? rawLine.subarray(0, rawLine.length - 1)
|
|
45
|
+
: rawLine;
|
|
43
46
|
if (line.length === 3 && line[0] === 0x2d && line[1] === 0x2d && line[2] === 0x2d) {
|
|
44
47
|
markers += 1;
|
|
45
48
|
}
|
|
@@ -378,12 +378,12 @@ function bashInteger(value) {
|
|
|
378
378
|
const parsed = BigInt(value);
|
|
379
379
|
return parsed < BASH_INTEGER_MIN || parsed > BASH_INTEGER_MAX ? undefined : parsed;
|
|
380
380
|
}
|
|
381
|
-
function bashTestGreaterOrEqual(left, right, scriptPath, line) {
|
|
381
|
+
function bashTestGreaterOrEqual(left, right, scriptPath, line, platform) {
|
|
382
382
|
const parsed = bashInteger(right);
|
|
383
383
|
if (parsed === undefined) {
|
|
384
384
|
return {
|
|
385
385
|
value: false,
|
|
386
|
-
stderr: `${scriptPath}: line ${line}: [: ${right}: integer expression expected\n`,
|
|
386
|
+
stderr: `${scriptPath}: line ${line}: [: ${right}: ${platform === 'win32' ? 'integer expected' : 'integer expression expected'}\n`,
|
|
387
387
|
};
|
|
388
388
|
}
|
|
389
389
|
return { value: BigInt(left) >= parsed, stderr: '' };
|
|
@@ -448,13 +448,13 @@ export function runPolicyAgeReport(args, root, env = process.env, nowEpoch = Mat
|
|
|
448
448
|
const singleIncident = ['user-correction', 'session-learning', 'back-pressure-failure'].includes(source) && (version || '1') === '1';
|
|
449
449
|
const classes = [];
|
|
450
450
|
if (fired === 0) {
|
|
451
|
-
const comparison = bashTestGreaterOrEqual(ageDays, parsed.minAge, resolveBundledAsset(POLICY_AGE_REPORT_ASSET), 173);
|
|
451
|
+
const comparison = bashTestGreaterOrEqual(ageDays, parsed.minAge, resolveBundledAsset(POLICY_AGE_REPORT_ASSET), 173, platform);
|
|
452
452
|
stderr += comparison.stderr;
|
|
453
453
|
if (comparison.value && usage.anyFiles)
|
|
454
454
|
classes.push('never-fired');
|
|
455
455
|
}
|
|
456
456
|
if (fired > 0) {
|
|
457
|
-
const comparison = bashTestGreaterOrEqual(lastAge, parsed.dormantDays, resolveBundledAsset(POLICY_AGE_REPORT_ASSET), 174);
|
|
457
|
+
const comparison = bashTestGreaterOrEqual(lastAge, parsed.dormantDays, resolveBundledAsset(POLICY_AGE_REPORT_ASSET), 174, platform);
|
|
458
458
|
stderr += comparison.stderr;
|
|
459
459
|
if (comparison.value)
|
|
460
460
|
classes.push('dormant');
|
|
@@ -464,14 +464,12 @@ export function runPolicyAgeReport(args, root, env = process.env, nowEpoch = Mat
|
|
|
464
464
|
if (refs.renamed.length > 0)
|
|
465
465
|
classes.push('renamed-refs');
|
|
466
466
|
if (singleIncident) {
|
|
467
|
-
const comparison = bashTestGreaterOrEqual(ageDays, parsed.incidentDays, resolveBundledAsset(POLICY_AGE_REPORT_ASSET), 178);
|
|
467
|
+
const comparison = bashTestGreaterOrEqual(ageDays, parsed.incidentDays, resolveBundledAsset(POLICY_AGE_REPORT_ASSET), 178, platform);
|
|
468
468
|
stderr += comparison.stderr;
|
|
469
469
|
if (comparison.value && fired <= 1)
|
|
470
470
|
classes.push('single-incident-old');
|
|
471
471
|
}
|
|
472
|
-
|
|
473
|
-
if (!path.isAbsolute(file) && !file.startsWith(`${root}/`))
|
|
474
|
-
displayPath = file;
|
|
472
|
+
const displayPath = displayPolicyPath(file, root);
|
|
475
473
|
rows.push({
|
|
476
474
|
id,
|
|
477
475
|
path: displayPath,
|
|
@@ -648,7 +646,7 @@ function setPolicyStatus(filePath, status, reason, by, env, now, cwd = process.c
|
|
|
648
646
|
const trimmedDiagnostic = diagnostic.replace(/\n+$/, '');
|
|
649
647
|
return {
|
|
650
648
|
ok: false,
|
|
651
|
-
error: `policy-retire: refusing to write ${filePath}; canonical policy validation failed:\n${trimmedDiagnostic}\n`,
|
|
649
|
+
error: `policy-retire: refusing to write ${shellPolicyPath(filePath, env.HQ_ROOT || cwd)}; canonical policy validation failed:\n${trimmedDiagnostic}\n`,
|
|
652
650
|
};
|
|
653
651
|
}
|
|
654
652
|
const tempPath = `${resolvedPath}.tmp`;
|
|
@@ -657,7 +655,20 @@ function setPolicyStatus(filePath, status, reason, by, env, now, cwd = process.c
|
|
|
657
655
|
return { ok: true };
|
|
658
656
|
}
|
|
659
657
|
function displayPolicyPath(filePath, root) {
|
|
660
|
-
|
|
658
|
+
if (!path.isAbsolute(filePath))
|
|
659
|
+
return filePath;
|
|
660
|
+
const relative = path.relative(root, filePath);
|
|
661
|
+
if (relative === '' || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
|
|
662
|
+
return filePath;
|
|
663
|
+
return relative.split(path.sep).join('/');
|
|
664
|
+
}
|
|
665
|
+
function shellPolicyPath(filePath, root) {
|
|
666
|
+
if (process.platform !== 'win32' || !path.isAbsolute(filePath))
|
|
667
|
+
return filePath;
|
|
668
|
+
const relative = path.relative(root, filePath);
|
|
669
|
+
if (relative === '' || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
|
|
670
|
+
return filePath;
|
|
671
|
+
return `${root}/${relative.split(path.sep).join('/')}`;
|
|
661
672
|
}
|
|
662
673
|
function resolvePolicyTarget(target, root, cwd) {
|
|
663
674
|
let found = isFile(path.isAbsolute(target) ? target : path.resolve(cwd, target))
|
|
@@ -817,7 +828,7 @@ function autoRetire(options, root, cwd, env, now) {
|
|
|
817
828
|
wouldRetire += 1;
|
|
818
829
|
continue;
|
|
819
830
|
}
|
|
820
|
-
const updated = setPolicyStatus(resolvedForRead, 'retired', `auto: ${why}`, options.by, env, now);
|
|
831
|
+
const updated = setPolicyStatus(resolvedForRead, 'retired', `auto: ${why}`, options.by, env, now, cwd);
|
|
821
832
|
if (updated.ok) {
|
|
822
833
|
stdout += `retired: ${candidate.id} — ${why}\n`;
|
|
823
834
|
reportText += `- **${candidate.id}** (${candidate.path}): ${why}\n`;
|
|
@@ -875,7 +886,7 @@ function fromReport(options, root, cwd, env, now) {
|
|
|
875
886
|
let selected = 0;
|
|
876
887
|
let stdout = '';
|
|
877
888
|
let stderr = report === null
|
|
878
|
-
? `jq: error (at ${reportPath}:0): Cannot iterate over null (null)\n`
|
|
889
|
+
? `jq: error (at ${reportPath}:0): Cannot iterate over null (null)${process.platform === 'win32' ? '\r\n' : '\n'}`
|
|
879
890
|
: '';
|
|
880
891
|
for (const value of rows) {
|
|
881
892
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -55,6 +55,10 @@ function yamlDoubleQuoted(value) {
|
|
|
55
55
|
}
|
|
56
56
|
const warning = (message) => { process.stderr.write(`session-journal: ${message}\n`); };
|
|
57
57
|
const journalDir = (root, date) => path.join(root, "workspace/threads/journal", date);
|
|
58
|
+
// The shell companion builds printable paths by appending slash-delimited
|
|
59
|
+
// suffixes to HQ_ROOT. Keep those bytes on Windows while filesystem access
|
|
60
|
+
// continues to use the host path API.
|
|
61
|
+
const printableJournalDir = (root, date) => path.posix.join(root, "workspace/threads/journal", date);
|
|
58
62
|
const sessionJournalAsset = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../assets/scaffold/core/scripts/session-journal.sh");
|
|
59
63
|
const hasFile = (file) => {
|
|
60
64
|
try {
|
|
@@ -182,7 +186,9 @@ function options(args, valueFlags) {
|
|
|
182
186
|
function runToolCounter(root, action, sessionArg, env, now, writers) {
|
|
183
187
|
const rawKey = sessionArg || env.HQ_JOURNAL_SESSION || env.HQ_HOOK_SESSION_ID || "";
|
|
184
188
|
const key = counterKey(rawKey);
|
|
185
|
-
const
|
|
189
|
+
const date = now().toISOString().slice(0, 10);
|
|
190
|
+
const dir = journalDir(root, date);
|
|
191
|
+
const printableCounter = `${printableJournalDir(root, date)}/.tool-count-${key}`;
|
|
186
192
|
try {
|
|
187
193
|
fs.mkdirSync(dir, { recursive: true });
|
|
188
194
|
}
|
|
@@ -196,7 +202,7 @@ function runToolCounter(root, action, sessionArg, env, now, writers) {
|
|
|
196
202
|
throw new Error("Bundled asset is missing from the installed package: assets/scaffold/core/scripts/session-journal.sh\n" +
|
|
197
203
|
"This is a packaging fault — the file exists in source but was not published.");
|
|
198
204
|
}
|
|
199
|
-
writers.stderr(`${sessionJournalAsset}: line ${line}: ${
|
|
205
|
+
writers.stderr(`${sessionJournalAsset}: line ${line}: ${printableCounter}: Is a directory\n`);
|
|
200
206
|
};
|
|
201
207
|
if (action === "increment") {
|
|
202
208
|
withCounterLock(lock, () => {
|
|
@@ -270,7 +276,7 @@ function executeSessionJournal(args, cwd, hqRoot, dependencies, writers) {
|
|
|
270
276
|
fs.mkdirSync(dir, { recursive: true });
|
|
271
277
|
}
|
|
272
278
|
catch {
|
|
273
|
-
warning(`write: mkdir failed: ${
|
|
279
|
+
warning(`write: mkdir failed: ${printableJournalDir(root, date)}`);
|
|
274
280
|
return 0;
|
|
275
281
|
}
|
|
276
282
|
const slug = slugify(title) || "entry";
|
|
@@ -289,7 +295,8 @@ function executeSessionJournal(args, cwd, hqRoot, dependencies, writers) {
|
|
|
289
295
|
try {
|
|
290
296
|
for (let attempt = 0; attempt < 10_000; attempt += 1) {
|
|
291
297
|
seq = nextSequence(dir);
|
|
292
|
-
output =
|
|
298
|
+
output = `${printableJournalDir(root, date)}/${seq}-${slug}.md`;
|
|
299
|
+
const outputFile = path.join(dir, `${seq}-${slug}.md`);
|
|
293
300
|
const lines = ["---\n", `ts: ${nowIso()}\n`, `title: ${yamlDoubleQuoted(title)}\n`, `slug: ${yamlSlug}\n`];
|
|
294
301
|
if (files) {
|
|
295
302
|
lines.push("files:\n");
|
|
@@ -299,7 +306,7 @@ function executeSessionJournal(args, cwd, hqRoot, dependencies, writers) {
|
|
|
299
306
|
}
|
|
300
307
|
lines.push("status: closed\n", "---\n\n", `# ${seq} — ${title}\n\n`);
|
|
301
308
|
try {
|
|
302
|
-
fs.writeFileSync(
|
|
309
|
+
fs.writeFileSync(outputFile, lines.join("") + body, { flag: "wx" });
|
|
303
310
|
created = true;
|
|
304
311
|
break;
|
|
305
312
|
}
|
|
@@ -341,11 +348,11 @@ function executeSessionJournal(args, cwd, hqRoot, dependencies, writers) {
|
|
|
341
348
|
const date = options(afterSequence, ["--date"]).get("--date") || today();
|
|
342
349
|
const dir = journalDir(root, date);
|
|
343
350
|
if (command === "dir-path") {
|
|
344
|
-
writers.stdout(
|
|
351
|
+
writers.stdout(printableJournalDir(root, date));
|
|
345
352
|
return 0;
|
|
346
353
|
}
|
|
347
354
|
if (command === "index-path") {
|
|
348
|
-
writers.stdout(`${
|
|
355
|
+
writers.stdout(`${printableJournalDir(root, date)}/INDEX.md\n`);
|
|
349
356
|
return 0;
|
|
350
357
|
}
|
|
351
358
|
if (command === "list") {
|