@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 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 first = line.match(/^\s*(\S+)/)?.[1] ?? "";
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: line.replace(/^[^:]+:[\t ]*/, "").replace(/^"|"$/g, "") };
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 line = source.subarray(start, lineEnd);
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
- let displayPath = file.startsWith(`${root}/`) ? file.slice(root.length + 1) : file;
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
- return filePath.startsWith(`${root}/`) ? filePath.slice(root.length + 1) : filePath;
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 dir = journalDir(root, now().toISOString().slice(0, 10));
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}: ${counter}: Is a directory\n`);
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: ${dir}`);
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 = path.join(dir, `${seq}-${slug}.md`);
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(output, lines.join("") + body, { flag: "wx" });
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(dir);
351
+ writers.stdout(printableJournalDir(root, date));
345
352
  return 0;
346
353
  }
347
354
  if (command === "index-path") {
348
- writers.stdout(`${dir}/INDEX.md\n`);
355
+ writers.stdout(`${printableJournalDir(root, date)}/INDEX.md\n`);
349
356
  return 0;
350
357
  }
351
358
  if (command === "list") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.341.0",
3
+ "version": "5.341.1",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {