@indigoai-us/hq-cli 5.93.2 → 5.94.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 +37 -0
- package/dist/commands/core.js +5 -13
- package/dist/lib/search-index/index.d.ts +7 -1
- package/dist/lib/search-index/index.js +58 -13
- package/package.json +1 -1
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +0 -73
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +0 -51
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.94.1]
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- The CLI now actually uses its bundled qmd. Package-local resolution had never
|
|
10
|
+
worked, so every install silently fell through to whatever qmd was on `PATH`,
|
|
11
|
+
and a host without a global qmd reported `skipped` — defeating the reason
|
|
12
|
+
`@tobilu/qmd` is a dependency and undoing in practice what 5.93.x set out to
|
|
13
|
+
fix for Node 26 global installs. Two independent causes, both silent: the
|
|
14
|
+
resolver looked for `<pkg>/qmd` while the package declares `bin/qmd`, and the
|
|
15
|
+
package-manifest lookup throws `ERR_PACKAGE_PATH_NOT_EXPORTED` on qmd 2.x,
|
|
16
|
+
which also blanked the version in `hq index status`. Both now follow the
|
|
17
|
+
package-manager contract: the binary via `node_modules/.bin/qmd`, the version
|
|
18
|
+
read from `node_modules/@tobilu/qmd/package.json`. (#333)
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- A shell-vs-native differential for `hq index background`, run against a frozen
|
|
23
|
+
copy of the pre-migration `qmd-reindex-bg.sh`: agent-box short-circuit before
|
|
24
|
+
any qmd lookup or lock, empty-`HOME` silence, `cleanup → update → embed`
|
|
25
|
+
ordering with completion stamp, no stamp after a failed update, and no
|
|
26
|
+
competing writer behind a held lock. It is what surfaced the resolution bug
|
|
27
|
+
above. (#333)
|
|
28
|
+
|
|
29
|
+
## [5.94.0]
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- `hq core backfill-company-skill-mirrors` and `hq core backfill-workspace-mirror`
|
|
34
|
+
are retired, along with their bundled shell assets. Both were one-shot
|
|
35
|
+
migrations for installs predating the current company-skill and
|
|
36
|
+
workspace-mirror layouts, and nothing in the package, the scaffold, or the
|
|
37
|
+
shipped skills invoked either. An install still upgrading from that era should
|
|
38
|
+
run those migrations on 5.93.x first. No `hq core` command dispatches a
|
|
39
|
+
bundled script directly now; `hq core worker lint|share` remain the only
|
|
40
|
+
bundled dispatches. (#331)
|
|
41
|
+
|
|
5
42
|
## [5.93.0]
|
|
6
43
|
|
|
7
44
|
### Fixed
|
package/dist/commands/core.js
CHANGED
|
@@ -164,19 +164,11 @@ export const REBUILD_INDEX_TARGETS = [
|
|
|
164
164
|
* would make the forwarders harder to audit against this table.
|
|
165
165
|
*/
|
|
166
166
|
export const SCAFFOLD_COMMANDS = [
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
summary: "One-shot: mirror company skills into place",
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
name: "backfill-workspace-mirror",
|
|
176
|
-
asset: "core/scripts/backfill-workspace-mirror.sh",
|
|
177
|
-
root: "cwd",
|
|
178
|
-
summary: "One-shot: mirror threads into company workspaces",
|
|
179
|
-
},
|
|
167
|
+
// Empty by design. Every former entry is either native now or retired: the
|
|
168
|
+
// two one-shot backfills (company skill mirrors, workspace mirror) were
|
|
169
|
+
// removed outright, having served installs that predate the current layout.
|
|
170
|
+
// `hq core worker lint|share` are the only remaining bundled dispatches and
|
|
171
|
+
// live in WORKER_SUBCOMMANDS.
|
|
180
172
|
];
|
|
181
173
|
/**
|
|
182
174
|
* Bundled solely for HQ-root scaffolds, which still need these as loose files.
|
|
@@ -28,7 +28,13 @@ export type ResolveQmdBinOptions = {
|
|
|
28
28
|
packageBin?: () => string | undefined;
|
|
29
29
|
pathBin?: () => string | undefined;
|
|
30
30
|
};
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Return the pinned package version when qmd is supplied by this CLI.
|
|
33
|
+
*
|
|
34
|
+
* Read from disk rather than `require('@tobilu/qmd/package.json')`: qmd 2.x does
|
|
35
|
+
* not export package.json, so the require form throws and the version silently
|
|
36
|
+
* disappeared from `hq index status`. Same root cause as packageLocalBin.
|
|
37
|
+
*/
|
|
32
38
|
export declare function resolveQmdVersion(): string | undefined;
|
|
33
39
|
/** Resolve qmd without relying on a globally installed copy. */
|
|
34
40
|
export declare function resolveQmdBin(options?: ResolveQmdBinOptions): string;
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
export class QmdBinaryMissingError extends Error {
|
|
7
8
|
name = 'QmdBinaryMissingError';
|
|
@@ -32,24 +33,68 @@ function isExecutable(candidate) {
|
|
|
32
33
|
return false;
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Locate the bundled qmd through the dependency-bin link the package manager
|
|
38
|
+
* installs, walking up from this module.
|
|
39
|
+
*
|
|
40
|
+
* Two earlier approaches were wrong and both failed silently, so every host fell
|
|
41
|
+
* through to PATH and a machine without a global qmd reported "skipped" — which
|
|
42
|
+
* defeats the point of depending on qmd at all:
|
|
43
|
+
*
|
|
44
|
+
* - `<pkg>/qmd` guessed a filename; the package declares `bin/qmd`.
|
|
45
|
+
* - `require.resolve('@tobilu/qmd/package.json')` throws
|
|
46
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED on qmd 2.x, whose `exports` map does not
|
|
47
|
+
* expose package.json.
|
|
48
|
+
*
|
|
49
|
+
* `node_modules/.bin/qmd` is the contract every package manager honours and is
|
|
50
|
+
* independent of the dependency's own exports map.
|
|
51
|
+
*/
|
|
35
52
|
function packageLocalBin() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
const names = process.platform === 'win32' ? ['qmd.cmd', 'qmd.exe', 'qmd'] : ['qmd'];
|
|
54
|
+
let directory = path.dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
56
|
+
for (const name of names) {
|
|
57
|
+
const candidate = path.join(directory, 'node_modules', '.bin', name);
|
|
58
|
+
if (fs.existsSync(candidate))
|
|
59
|
+
return candidate;
|
|
60
|
+
}
|
|
61
|
+
const parent = path.dirname(directory);
|
|
62
|
+
if (parent === directory)
|
|
63
|
+
break;
|
|
64
|
+
directory = parent;
|
|
42
65
|
}
|
|
66
|
+
return undefined;
|
|
43
67
|
}
|
|
44
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Return the pinned package version when qmd is supplied by this CLI.
|
|
70
|
+
*
|
|
71
|
+
* Read from disk rather than `require('@tobilu/qmd/package.json')`: qmd 2.x does
|
|
72
|
+
* not export package.json, so the require form throws and the version silently
|
|
73
|
+
* disappeared from `hq index status`. Same root cause as packageLocalBin.
|
|
74
|
+
*/
|
|
45
75
|
export function resolveQmdVersion() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
// Walk for the package directory itself. Neither require() nor the .bin entry
|
|
77
|
+
// works: qmd 2.x does not export package.json, and .bin/qmd is a generated
|
|
78
|
+
// shim (not a symlink) under pnpm's hoisted linker, so resolving it lands back
|
|
79
|
+
// in this package rather than qmd's.
|
|
80
|
+
let directory = path.dirname(fileURLToPath(import.meta.url));
|
|
81
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
82
|
+
const manifest = path.join(directory, 'node_modules', '@tobilu', 'qmd', 'package.json');
|
|
83
|
+
if (fs.existsSync(manifest)) {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(fs.readFileSync(manifest, 'utf8'));
|
|
86
|
+
if (typeof parsed.version === 'string')
|
|
87
|
+
return parsed.version;
|
|
88
|
+
}
|
|
89
|
+
catch { /* unreadable manifest: report no version rather than throwing */ }
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const parent = path.dirname(directory);
|
|
93
|
+
if (parent === directory)
|
|
94
|
+
break;
|
|
95
|
+
directory = parent;
|
|
52
96
|
}
|
|
97
|
+
return undefined;
|
|
53
98
|
}
|
|
54
99
|
function pathBin() {
|
|
55
100
|
const paths = (process.env.PATH ?? '').split(path.delimiter).filter(Boolean);
|
package/package.json
CHANGED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# backfill-company-skill-mirrors.sh
|
|
3
|
-
#
|
|
4
|
-
# One-shot, idempotent. Walks every company under companies/{co}/skills/ and
|
|
5
|
-
# companies/{co}/commands/ and ensures a mirror symlink exists at
|
|
6
|
-
# .claude/skills/{prefix}-{name}/ (or .claude/commands/{prefix}-{name}.md).
|
|
7
|
-
#
|
|
8
|
-
# Logic is delegated to .claude/hooks/auto-mirror-company-skill.sh — this
|
|
9
|
-
# script just discovers candidate paths and feeds them in. Re-run safely.
|
|
10
|
-
#
|
|
11
|
-
# Usage: bash core/scripts/backfill-company-skill-mirrors.sh
|
|
12
|
-
|
|
13
|
-
set -euo pipefail
|
|
14
|
-
|
|
15
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
16
|
-
HOOK="$PROJECT_DIR/.claude/hooks/auto-mirror-company-skill.sh"
|
|
17
|
-
|
|
18
|
-
if [[ ! -x "$HOOK" ]]; then
|
|
19
|
-
echo "ERROR: auto-mirror hook not found or not executable: $HOOK" >&2
|
|
20
|
-
exit 1
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
cd "$PROJECT_DIR"
|
|
24
|
-
|
|
25
|
-
CREATED=0
|
|
26
|
-
SKIPPED=0
|
|
27
|
-
|
|
28
|
-
emit() {
|
|
29
|
-
local rel_path="$1"
|
|
30
|
-
rel_path="${rel_path//\/\//\/}"
|
|
31
|
-
local out
|
|
32
|
-
out=$(echo "{\"tool_input\":{\"file_path\":\"$rel_path\"}}" | CLAUDE_PROJECT_DIR="$PROJECT_DIR" bash "$HOOK" 2>&1 || true)
|
|
33
|
-
if [[ -n "$out" ]]; then
|
|
34
|
-
echo "$out"
|
|
35
|
-
if [[ "$out" == *"linked"* ]]; then
|
|
36
|
-
CREATED=$((CREATED + 1))
|
|
37
|
-
else
|
|
38
|
-
SKIPPED=$((SKIPPED + 1))
|
|
39
|
-
fi
|
|
40
|
-
fi
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
shopt -s nullglob
|
|
44
|
-
for co_dir in companies/*/; do
|
|
45
|
-
co="${co_dir%/}"
|
|
46
|
-
co="${co##*/}"
|
|
47
|
-
|
|
48
|
-
# Skip if no manifest entry (defensive — shouldn't happen).
|
|
49
|
-
[[ -d "$co_dir" ]] || continue
|
|
50
|
-
|
|
51
|
-
# Top-level skills: directory form (skills/{name}/SKILL.md) and flat-file form (skills/{name}.md).
|
|
52
|
-
if [[ -d "$co_dir/skills" ]]; then
|
|
53
|
-
for skill_md in "$co_dir/skills"/*/SKILL.md; do
|
|
54
|
-
[[ -f "$skill_md" ]] || continue
|
|
55
|
-
emit "$skill_md"
|
|
56
|
-
done
|
|
57
|
-
for flat_md in "$co_dir/skills"/*.md; do
|
|
58
|
-
[[ -f "$flat_md" ]] || continue
|
|
59
|
-
emit "$flat_md"
|
|
60
|
-
done
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# Top-level commands: commands/{name}.md
|
|
64
|
-
if [[ -d "$co_dir/commands" ]]; then
|
|
65
|
-
for cmd_md in "$co_dir/commands"/*.md; do
|
|
66
|
-
[[ -f "$cmd_md" ]] || continue
|
|
67
|
-
emit "$cmd_md"
|
|
68
|
-
done
|
|
69
|
-
fi
|
|
70
|
-
done
|
|
71
|
-
|
|
72
|
-
echo ""
|
|
73
|
-
echo "backfill complete: $CREATED linked, $SKIPPED skipped (already-correct or no-prefix)"
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# One-time backfill: walk workspace/threads/*.json and replay each through
|
|
3
|
-
# the mirror hook so existing sessions are represented in their companies'
|
|
4
|
-
# workspace/ folders.
|
|
5
|
-
#
|
|
6
|
-
# Idempotent: hardlinks use ln -f, index.jsonl rows are deduped by
|
|
7
|
-
# (thread_id, ts, kind). Safe to re-run.
|
|
8
|
-
#
|
|
9
|
-
# Usage: bash core/scripts/backfill-workspace-mirror.sh [HQ_ROOT]
|
|
10
|
-
|
|
11
|
-
set -euo pipefail
|
|
12
|
-
|
|
13
|
-
HQ_ROOT="${1:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
14
|
-
HOOK="$HQ_ROOT/.claude/hooks/mirror-thread-to-company.sh"
|
|
15
|
-
THREADS_DIR="$HQ_ROOT/workspace/threads"
|
|
16
|
-
|
|
17
|
-
[ -x "$HOOK" ] || { echo "Mirror hook missing or not executable: $HOOK" >&2; exit 1; }
|
|
18
|
-
[ -d "$THREADS_DIR" ] || { echo "No threads dir at $THREADS_DIR" >&2; exit 1; }
|
|
19
|
-
|
|
20
|
-
mirrored=0
|
|
21
|
-
skipped=0
|
|
22
|
-
total=0
|
|
23
|
-
|
|
24
|
-
for thread in "$THREADS_DIR"/T-*.json; do
|
|
25
|
-
[ -f "$thread" ] || continue
|
|
26
|
-
total=$((total + 1))
|
|
27
|
-
|
|
28
|
-
has_company=$(jq -r 'if .metadata.company then "yes" else "no" end' "$thread" 2>/dev/null || echo "no")
|
|
29
|
-
if [ "$has_company" != "yes" ]; then
|
|
30
|
-
skipped=$((skipped + 1))
|
|
31
|
-
continue
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
printf '{"tool_name":"Write","tool_input":{"file_path":"%s"}}' "$thread" \
|
|
35
|
-
| bash "$HOOK"
|
|
36
|
-
|
|
37
|
-
mirrored=$((mirrored + 1))
|
|
38
|
-
done
|
|
39
|
-
|
|
40
|
-
echo "Backfill complete:"
|
|
41
|
-
echo " Total threads: $total"
|
|
42
|
-
echo " Mirrored: $mirrored"
|
|
43
|
-
echo " Skipped (no co): $skipped"
|
|
44
|
-
echo
|
|
45
|
-
echo "Per-company index.jsonl row counts:"
|
|
46
|
-
for index_file in "$HQ_ROOT"/companies/*/workspace/index.jsonl; do
|
|
47
|
-
[ -f "$index_file" ] || continue
|
|
48
|
-
co=$(basename "$(dirname "$(dirname "$index_file")")")
|
|
49
|
-
rows=$(wc -l < "$index_file" | tr -d ' ')
|
|
50
|
-
printf " %-20s %s rows\n" "$co" "$rows"
|
|
51
|
-
done
|