@indigoai-us/hq-cli 5.317.0 → 5.318.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 CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.318.0] — 2026-10-02
6
+ - `hq lanes autopilot run` now marks missing lead registry entries as stale and includes a command to remove each stale row.
7
+ - Keep retired policies retired regardless of YAML field order in the session helper.
8
+ - `publish.yml` refuses a manual run with `dry_run=false` and always passes dry-run to the publish job unless the run comes from a tag push.
9
+
5
10
  ## [5.317.0] — 2026-10-02
6
11
  - Add native `hq core session-title`, `session-title-config`, `session-journal` and `session-project` on the fast route behind the default-off `core-native-hook-helpers` flag.
7
12
  - Build required npm self-updates outside the active prefix, then activate only after the staged package is ready, so an interrupted download or unpack leaves the installed CLI runnable.
@@ -368,12 +368,12 @@ emit_company_hard_policies() {
368
368
  lines="$(awk -v co="$co" '
369
369
  function bn(p, n,a,b){ n=split(p,a,"/"); b=a[n]; sub(/\.md$/,"",b); return b }
370
370
  function policy_file(p, n,a){ n=split(p,a,"/"); return a[n] }
371
- function flush(){ if(enf=="hard" && rule!=""){ if(id=="")id=bn(fn); printf "- [hard] **%s**: %s Full text: `companies/%s/policies/%s`.\n", id, rule, co, policy_file(fn) } }
372
- FNR==1 { if(seen) flush(); d=0;id="";enf="";rule="";rsec=0;rcap=0;fn=FILENAME;seen=1 }
371
+ function flush(){ if(enf=="hard" && !retired && rule!=""){ if(id=="")id=bn(fn); printf "- [hard] **%s**: %s Full text: `companies/%s/policies/%s`.\n", id, rule, co, policy_file(fn) } }
372
+ FNR==1 { if(seen) flush(); d=0;id="";enf="";retired=0;rule="";rsec=0;rcap=0;fn=FILENAME;seen=1 }
373
373
  /^---[ \t]*$/ { d++; next }
374
374
  d==1 && /^id:/ { s=$0; sub(/^id:[ \t]*/,"",s); gsub(/^["'"'"']|["'"'"']$/,"",s); id=s; next }
375
375
  d==1 && /^enforcement:/ { s=$0; sub(/^enforcement:[ \t]*/,"",s); gsub(/[ \t]/,"",s); enf=s; next }
376
- d==1 && /^status:[ \t]*retired/ { enf="" ; next }
376
+ d==1 && /^status:[ \t]*retired/ { retired=1; next }
377
377
  d>=2 && /^## Rule[ \t]*$/ { rsec=1; next }
378
378
  d>=2 && rsec && /^## / { rsec=0 }
379
379
  d>=2 && rsec && !rcap && NF { line=$0; gsub(/\*\*/,"",line); if(length(line)>160)line=substr(line,1,157)"..."; rule=line; rcap=1 }
@@ -318,6 +318,7 @@ function hardPolicyLines(root, company, deps) {
318
318
  let delimiters = 0;
319
319
  let id = "";
320
320
  let enforcement = "";
321
+ let retired = false;
321
322
  let ruleSection = false;
322
323
  let rule = "";
323
324
  for (const line of lines) {
@@ -335,7 +336,7 @@ function hardPolicyLines(root, company, deps) {
335
336
  if (enforceMatch)
336
337
  enforcement = (enforceMatch[1] ?? "").replace(/[\t ]/g, "");
337
338
  if (/^status:[\t ]*retired/.test(line))
338
- enforcement = "";
339
+ retired = true;
339
340
  }
340
341
  if (delimiters >= 2 && line === "## Rule") {
341
342
  ruleSection = true;
@@ -351,7 +352,7 @@ function hardPolicyLines(root, company, deps) {
351
352
  rule = `${rule.slice(0, 157)}...`;
352
353
  }
353
354
  }
354
- if (enforcement !== "hard" || !rule)
355
+ if (enforcement !== "hard" || retired || !rule)
355
356
  continue;
356
357
  const policyId = id || basename.replace(/\.md$/, "");
357
358
  result.push(`- [hard] **${policyId}**: ${rule} Full text: \`companies/${company}/policies/${basename}\`.`);
@@ -83,6 +83,13 @@ export interface AutopilotPassResult {
83
83
  skipped: Array<{
84
84
  lead_lane: string;
85
85
  reason: string;
86
+ status?: undefined;
87
+ fix_command?: undefined;
88
+ } | {
89
+ lead_lane: string;
90
+ reason: "lead_lane_missing";
91
+ status: "stale";
92
+ fix_command: string;
86
93
  } | {
87
94
  lane_id: string;
88
95
  error: string;
@@ -35,6 +35,13 @@ function isOpenProjectStory(story) {
35
35
  function isRecord(value) {
36
36
  return value !== null && typeof value === "object" && !Array.isArray(value);
37
37
  }
38
+ function shellQuote(value) {
39
+ return `'${value.replace(/'/g, "'\\''")}'`;
40
+ }
41
+ function staleLeadFixCommand(laneId) {
42
+ const registryTemp = `${LEAD_REGISTRY_REL}.tmp`;
43
+ return `jq --arg lane ${shellQuote(laneId)} '(.juniors) |= map(select(.lane != $lane))' ${shellQuote(LEAD_REGISTRY_REL)} > ${shellQuote(registryTemp)} && mv ${shellQuote(registryTemp)} ${shellQuote(LEAD_REGISTRY_REL)}`;
44
+ }
38
45
  function readJsonFile(filePath, code) {
39
46
  let text;
40
47
  try {
@@ -755,9 +762,23 @@ async function runLeadAutopilotPassUnlocked(input) {
755
762
  const stateFile = path.join(hqRoot, "workspace", "lane-briefs", "autopilot-state.json");
756
763
  const state = readState(stateFile);
757
764
  for (const registered of leads) {
765
+ const registeredLocalHost = registered.host === "local" || registered.host === input.hostname;
758
766
  const lead = byId.get(registered.lane) ?? lanes.find((candidate) => candidate.lane_id.startsWith(`${registered.lane}_`));
759
767
  if (!lead) {
760
- result.skipped.push({ lead_lane: registered.lane, reason: "lead_lane_missing" });
768
+ // A row for another host has no lane record here; that host's pass owns it.
769
+ const registryTargetsThisHost = registeredLocalHost
770
+ ? effectiveOutpostId === null
771
+ : input.outpostId === registered.host;
772
+ if (!registryTargetsThisHost) {
773
+ result.skipped.push({ lead_lane: registered.lane, reason: "lead_host_mismatch" });
774
+ continue;
775
+ }
776
+ result.skipped.push({
777
+ lead_lane: registered.lane,
778
+ reason: "lead_lane_missing",
779
+ status: "stale",
780
+ fix_command: staleLeadFixCommand(registered.lane),
781
+ });
761
782
  continue;
762
783
  }
763
784
  const company = resolveLaneCompany(hqRoot, lead).company;
@@ -765,7 +786,6 @@ async function runLeadAutopilotPassUnlocked(input) {
765
786
  result.skipped.push({ lead_lane: lead.lane_id, reason: "lead_company_unresolved" });
766
787
  continue;
767
788
  }
768
- const registeredLocalHost = registered.host === "local" || registered.host === input.hostname;
769
789
  const hostMatchesRegistry = registeredLocalHost
770
790
  ? lead.host.kind === "local"
771
791
  : lead.host.kind === "outpost" && lead.host.id === registered.host && input.outpostId === registered.host;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.317.0",
3
+ "version": "5.318.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {