@indigoai-us/hq-cli 5.345.78 → 5.345.79

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,12 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [5.345.78] — 2026-10-10
5
+ ## [5.345.79] — 2026-10-11
6
6
  - `hq lanes` no longer fails every list, message, or admission command when
7
7
  one lane still has leftover queue details from an older worker-profile wait.
8
8
  Those leftover details are dropped on read, cleared when the queue reason
9
9
  changes, and one unreadable lane is skipped so the rest of the host stays
10
10
  usable.
11
+ - `hq doctor --fix` still refreshes a stale company sync journal when
12
+ `.claude/`, `.codex/`, or `.grok/` has uncommitted changes. Those dirty
13
+ files still block hook-file repairs unless you pass `--force`.
11
14
 
12
15
  ## [5.345.77] — 2026-10-10
13
16
  - Local bots no longer fail a turn when the installed Codex is too old for the
@@ -5,9 +5,12 @@
5
5
  * wrapped in guardrails that are NOT polish — each is a PRD acceptance criterion:
6
6
  *
7
7
  * 1. Dirty-tree refusal. If the working tree has uncommitted changes under
8
- * `.claude/`, `.codex/`, or `.grok/`, `--fix` refuses and exits non-zero
9
- * unless `--force`, so a repair can never be tangled up with unrelated
10
- * in-flight edits to the security layer.
8
+ * `.claude/`, `.codex/`, or `.grok/`, `--fix` refuses hook-file repairs
9
+ * and exits non-zero unless `--force`, so those writes can never be
10
+ * tangled up with unrelated in-flight edits to the security layer.
11
+ * Repairs that do not write those trees (a stale-company `refresh-sync`
12
+ * pull) still run: the SessionStart health hook never passes `--force`,
13
+ * and blocking the pull left `sync.journal.<slug>` WARN after `--fix`.
11
14
  * 2. Allowlist only. It repairs the classified safe classes — restore an
12
15
  * execute bit, add a hook id to the gate profiles it is missing from,
13
16
  * re-register a script present on disk, or pull a company whose local
@@ -5,9 +5,12 @@
5
5
  * wrapped in guardrails that are NOT polish — each is a PRD acceptance criterion:
6
6
  *
7
7
  * 1. Dirty-tree refusal. If the working tree has uncommitted changes under
8
- * `.claude/`, `.codex/`, or `.grok/`, `--fix` refuses and exits non-zero
9
- * unless `--force`, so a repair can never be tangled up with unrelated
10
- * in-flight edits to the security layer.
8
+ * `.claude/`, `.codex/`, or `.grok/`, `--fix` refuses hook-file repairs
9
+ * and exits non-zero unless `--force`, so those writes can never be
10
+ * tangled up with unrelated in-flight edits to the security layer.
11
+ * Repairs that do not write those trees (a stale-company `refresh-sync`
12
+ * pull) still run: the SessionStart health hook never passes `--force`,
13
+ * and blocking the pull left `sync.journal.<slug>` WARN after `--fix`.
11
14
  * 2. Allowlist only. It repairs the classified safe classes — restore an
12
15
  * execute bit, add a hook id to the gate profiles it is missing from,
13
16
  * re-register a script present on disk, or pull a company whose local
@@ -107,20 +110,34 @@ export async function applyFixes(options) {
107
110
  ...over,
108
111
  });
109
112
  // 1. Dirty-tree refusal (AC8). Never write over uncommitted security-layer
110
- // edits unless the user forces it.
111
- if (!options.force) {
112
- const dirty = (options.dirtyCheck ?? uncommittedHookConfigChanges)(hqRoot);
113
- if (dirty.length > 0) {
114
- writeErr(`hq doctor --fix: refusing to run — uncommitted changes under ` +
115
- `${HOOK_CONFIG_DIRS.join(", ")}:\n` +
116
- dirty.map((line) => ` ${line}`).join("\n") +
117
- `\nCommit or stash them, or re-run with --force.\n`);
118
- return empty({ exitCode: 1, refused: "dirty-tree" });
119
- }
120
- }
113
+ // edits unless the user forces it. Probe once; hook-file plans are
114
+ // dropped per pass so a stale-journal pull can still run.
115
+ const dirty = options.force
116
+ ? []
117
+ : (options.dirtyCheck ?? uncommittedHookConfigChanges)(hqRoot);
118
+ const refuseDirtyHookWrites = (plans) => {
119
+ if (dirty.length === 0)
120
+ return plans;
121
+ const allowed = plans.filter((plan) => plan.relpath.length === 0);
122
+ if (allowed.length === plans.length)
123
+ return plans;
124
+ if (allowed.length === 0)
125
+ return "refuse";
126
+ writeErr(`hq doctor --fix: skipping hook-file repairs — uncommitted changes under ` +
127
+ `${HOOK_CONFIG_DIRS.join(", ")}. Refreshing sync journals still runs; ` +
128
+ `re-run with --force to repair hook files.\n`);
129
+ return allowed;
130
+ };
131
+ const refuseDirtyTree = () => {
132
+ writeErr(`hq doctor --fix: refusing to run — uncommitted changes under ` +
133
+ `${HOOK_CONFIG_DIRS.join(", ")}:\n` +
134
+ dirty.map((line) => ` ${line}`).join("\n") +
135
+ `\nCommit or stash them, or re-run with --force.\n`);
136
+ return empty({ exitCode: 1, refused: "dirty-tree" });
137
+ };
121
138
  // Resolve the customer-visible manual-only output gate once per fix run.
122
- // Keep it behind the dirty-tree guard, and fail open to the pre-change
123
- // output if the identity is absent or the registry lookup fails.
139
+ // Fail open to the pre-change output if the identity is absent or the
140
+ // registry lookup fails.
124
141
  let showManualOnlyRemediations = false;
125
142
  try {
126
143
  showManualOnlyRemediations =
@@ -172,7 +189,16 @@ export async function applyFixes(options) {
172
189
  ];
173
190
  };
174
191
  for (let pass = 1; pass <= MAX_FIX_PASSES; pass += 1) {
175
- const plans = buildPlans(findings);
192
+ const plannedThisPass = refuseDirtyHookWrites(buildPlans(findings));
193
+ if (plannedThisPass === "refuse") {
194
+ if (planned.length === 0)
195
+ return refuseDirtyTree();
196
+ write(`Pass ${pass}/${MAX_FIX_PASSES}: remaining repairs write hook files; ` +
197
+ `stopped because the tree is dirty.\n`);
198
+ refused = "dirty-tree";
199
+ break;
200
+ }
201
+ const plans = plannedThisPass;
176
202
  if (plans.length === 0)
177
203
  break;
178
204
  fixableCount += plans.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.78",
3
+ "version": "5.345.79",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {