@skyf0xx/hedgehog 6.2.9 → 6.2.11
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/bin/cli.mjs +77 -20
- package/package.json +2 -3
- package/src/hosts/gemini/gemini-extension.json +1 -1
- package/src/registry/fetch.mjs +13 -0
package/bin/cli.mjs
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
// npx @skyf0xx/hedgehog update refresh the installed agents + skills
|
|
14
14
|
// npx @skyf0xx/hedgehog --help
|
|
15
15
|
|
|
16
|
-
import { cp, mkdir, access, readdir, stat, rm, readFile, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { cp, mkdir, access, readdir, stat, rm, readFile, writeFile, mkdtemp } from 'node:fs/promises';
|
|
17
17
|
import { constants, existsSync, realpathSync } from 'node:fs';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
20
|
+
import { tmpdir } from 'node:os';
|
|
20
21
|
import { spawn, execFileSync } from 'node:child_process';
|
|
21
22
|
import { dbInit, DB_PATH, dbAbsPath, openDb, openDbAt } from '../src/db/init.mjs';
|
|
22
23
|
import { loadCore, lintCore, isModuleAxis } from '../src/db/core.mjs';
|
|
@@ -121,6 +122,40 @@ const BLOCKED_REASON_LABELS = {
|
|
|
121
122
|
|
|
122
123
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
123
124
|
const PKG_ROOT = resolve(__dirname, '..');
|
|
125
|
+
|
|
126
|
+
// The copywriting core never installs into the directory it was invoked
|
|
127
|
+
// from — every draft it produces is ephemeral scratch, and only the
|
|
128
|
+
// finished piece is meant to reach the user's real project (as a plain
|
|
129
|
+
// file, copied out by hedgehog-copywriting-loop's own courtesy-export
|
|
130
|
+
// step). Relying on every caller (an agent reading a skill's prose, a
|
|
131
|
+
// human typing the command by hand) to remember to cd into a scratch
|
|
132
|
+
// directory first is exactly the kind of instruction that gets skipped
|
|
133
|
+
// under context pressure — so `init --copywriting` enforces it here
|
|
134
|
+
// instead, unconditionally, before DEST_ROOT is ever read. This has to
|
|
135
|
+
// run before any other module-level state is computed from `process.cwd()`
|
|
136
|
+
// — a raw argv scan rather than the full async arg parser lower in this
|
|
137
|
+
// file, since nothing below has run yet at this point in module load.
|
|
138
|
+
const ORIGDIR = process.cwd();
|
|
139
|
+
{
|
|
140
|
+
// Catches both the shorthand (`--copywriting`) and the generic named-core
|
|
141
|
+
// form (`--core copywriting`, `--core=copywriting`) — namedCore()/
|
|
142
|
+
// coreFlags() do the real resolution later, async, off the registry, but
|
|
143
|
+
// this has to run synchronously before that and before DEST_ROOT below,
|
|
144
|
+
// so it matches the raw argv directly rather than waiting for it.
|
|
145
|
+
const rest = process.argv.slice(2);
|
|
146
|
+
const coreIdx = rest.indexOf('--core');
|
|
147
|
+
const isCopywriting =
|
|
148
|
+
rest.includes('--copywriting') ||
|
|
149
|
+
rest[coreIdx + 1] === 'copywriting' ||
|
|
150
|
+
rest.includes('--core=copywriting');
|
|
151
|
+
if (isCopywriting) {
|
|
152
|
+
const scratch = await mkdtemp(join(tmpdir(), 'hedgehog-copywriting-'));
|
|
153
|
+
process.chdir(scratch);
|
|
154
|
+
// No ANSI helpers yet — `dim` etc. are defined further down, after
|
|
155
|
+
// DEST_ROOT, and this has to run before DEST_ROOT is computed.
|
|
156
|
+
console.log(` (copywriting installs to a scratch directory, never here — using ${scratch})`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
124
159
|
const DEST_ROOT = process.cwd();
|
|
125
160
|
|
|
126
161
|
// The version of the payload this CLI carries — what `init` and
|
|
@@ -865,9 +900,22 @@ async function init({ force, core, host = DEFAULT_HOST, hostOnly = false, global
|
|
|
865
900
|
` 0. ${bold(`npx @skyf0xx/hedgehog@latest update`)} ${dim(`(picks up ${globalInstall.latest} before intake begins)`)}`,
|
|
866
901
|
);
|
|
867
902
|
}
|
|
868
|
-
//
|
|
869
|
-
//
|
|
870
|
-
|
|
903
|
+
// Copywriting never lands anywhere the caller can see directly (see the
|
|
904
|
+
// chdir into a scratch tempdir at module load, above) — the ordinary
|
|
905
|
+
// "commit here, describe what you want to build" steps assume a project
|
|
906
|
+
// the human is looking at, which this scratch directory isn't. Point at
|
|
907
|
+
// the loop skill instead; it owns pnpm install, the draft/checkCopy()
|
|
908
|
+
// cycle, and the courtesy export of the finished piece back to ORIGDIR.
|
|
909
|
+
if (core?.manifest.name === 'copywriting') {
|
|
910
|
+
console.log(
|
|
911
|
+
` 1. Read ${bold(`${HOSTS[host].skillsDir}/hedgehog-copywriting-loop/SKILL.md`)} and follow it`,
|
|
912
|
+
);
|
|
913
|
+
console.log(dim(` for everything from here, staying inside ${bold(DEST_ROOT)}.`));
|
|
914
|
+
console.log(
|
|
915
|
+
dim(` The finished piece lands back at ${bold(ORIGDIR)} on its own — nothing`),
|
|
916
|
+
);
|
|
917
|
+
console.log(dim(' else needs doing there.'));
|
|
918
|
+
} else if (core?.manifest.workspace) {
|
|
871
919
|
// `pnpm install` before the first commit, not after it. The core's
|
|
872
920
|
// commit gate is lefthook, and lefthook's hooks are written by its
|
|
873
921
|
// own postinstall — so a commit made before the install is a commit
|
|
@@ -900,19 +948,23 @@ async function init({ force, core, host = DEFAULT_HOST, hostOnly = false, global
|
|
|
900
948
|
// the session that ran this install — no restart, no dependence on whether
|
|
901
949
|
// the harness re-scans its agent directory, and no chance of a bare
|
|
902
950
|
// `planner` resolving to an unrelated global agent of the same name.
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
`
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
951
|
+
// Copywriting's own branch above already named its hand-off (the loop
|
|
952
|
+
// skill, not planner/bootstrap — this core has no bootstrap step).
|
|
953
|
+
if (core?.manifest.name !== 'copywriting') {
|
|
954
|
+
const agents = HOSTS[host].agentsDir;
|
|
955
|
+
const skills = HOSTS[host].skillsDir;
|
|
956
|
+
console.log(
|
|
957
|
+
dim(
|
|
958
|
+
` Read ${bold(`${agents}/planner.md`)} and follow it — it runs planning\n` +
|
|
959
|
+
` intake (${skills}/hedgehog-planning-intake/SKILL.md), then hands\n` +
|
|
960
|
+
` off to ${agents}/bootstrap.md.\n` +
|
|
961
|
+
` Some hosts register agents/skills once, at session start: if a\n` +
|
|
962
|
+
` later dispatch by name reports one of these as not found, read\n` +
|
|
963
|
+
` its file directly instead — it becomes dispatchable by name after\n` +
|
|
964
|
+
` a session restart or a fresh context.`,
|
|
965
|
+
),
|
|
966
|
+
);
|
|
967
|
+
}
|
|
916
968
|
console.log();
|
|
917
969
|
if (core) {
|
|
918
970
|
console.log(dim(`Core: ${bold(core.manifest.name)} ${dim(`(${core.version})`)}.`));
|
|
@@ -2250,8 +2302,12 @@ async function verifyCommand(args) {
|
|
|
2250
2302
|
}
|
|
2251
2303
|
|
|
2252
2304
|
// Fires once per project — see community.mjs. Deliberately last: after
|
|
2253
|
-
// the gate's own output, not before it.
|
|
2254
|
-
|
|
2305
|
+
// the gate's own output, not before it. Suppressed by HEDGEHOG_NO_COMMUNITY_PROMPT
|
|
2306
|
+
// for ephemeral workflows (temp-dir installs with no persistent .hedgehog/ to track
|
|
2307
|
+
// cooldown state) that would otherwise re-fire on every invocation.
|
|
2308
|
+
const starJustShown = process.env.HEDGEHOG_NO_COMMUNITY_PROMPT
|
|
2309
|
+
? false
|
|
2310
|
+
: await shouldPromptForStar(DEST_ROOT, { intentComplete: result.intentComplete });
|
|
2255
2311
|
if (starJustShown) {
|
|
2256
2312
|
console.log(formatStarPrompt());
|
|
2257
2313
|
console.log('');
|
|
@@ -2262,7 +2318,8 @@ async function verifyCommand(args) {
|
|
|
2262
2318
|
// just showed the star prompt for the first time. See
|
|
2263
2319
|
// shouldPromptForShowcase for why `starJustShown` has to come from
|
|
2264
2320
|
// here rather than being re-derived from state.
|
|
2265
|
-
if (
|
|
2321
|
+
if (!process.env.HEDGEHOG_NO_COMMUNITY_PROMPT &&
|
|
2322
|
+
(await shouldPromptForShowcase(DEST_ROOT, { intentComplete: result.intentComplete, starJustShown }))) {
|
|
2266
2323
|
console.log(formatShowcasePrompt());
|
|
2267
2324
|
console.log('');
|
|
2268
2325
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyf0xx/hedgehog",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.11",
|
|
4
4
|
"description": "Install the Hedgehog build discipline (agents + skills) into a repo, for Claude Code, Cursor, or Gemini CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"lint": "eslint .",
|
|
18
18
|
"check": "npm run lint && node scripts/check.mjs",
|
|
19
19
|
"repro": "node --experimental-sqlite repro/run-all.mjs",
|
|
20
|
-
"release": "node scripts/bump-package-version.mjs"
|
|
21
|
-
"release:plugin": "node scripts/bump-plugin-version.mjs"
|
|
20
|
+
"release": "node scripts/bump-package-version.mjs"
|
|
22
21
|
},
|
|
23
22
|
"files": [
|
|
24
23
|
"bin",
|
package/src/registry/fetch.mjs
CHANGED
|
@@ -41,6 +41,12 @@ export const CORE_CACHE_ROOT = join(homedir(), '.hedgehog', 'cores');
|
|
|
41
41
|
// exercised end to end before it is published.
|
|
42
42
|
const SOURCE_ENV = 'HEDGEHOG_CORE_SOURCE';
|
|
43
43
|
|
|
44
|
+
// Skip cache (both reads and writes) when set; every invocation gets a fresh
|
|
45
|
+
// npm pack with no persistence to ~/.hedgehog/cores/. Useful when the
|
|
46
|
+
// caller's use of the fetched core is ephemeral or when accurate npm
|
|
47
|
+
// download stats are needed.
|
|
48
|
+
const NO_CACHE_ENV = 'HEDGEHOG_CORE_NO_CACHE';
|
|
49
|
+
|
|
44
50
|
const exists = (p) =>
|
|
45
51
|
access(p, constants.F_OK).then(
|
|
46
52
|
() => true,
|
|
@@ -83,6 +89,13 @@ export async function fetchCore(entry) {
|
|
|
83
89
|
throw err;
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
// When NO_CACHE_ENV is set, skip cache entirely and return the staged
|
|
93
|
+
// extraction directly, leaving staged.tmp in place (since staged.root
|
|
94
|
+
// lives inside it).
|
|
95
|
+
if (process.env[NO_CACHE_ENV]) {
|
|
96
|
+
return { ...read, root: staged.root, version };
|
|
97
|
+
}
|
|
98
|
+
|
|
86
99
|
// Another install may have cached this exact version between the pack
|
|
87
100
|
// and now; its copy is as good as this one, so keep it and drop ours.
|
|
88
101
|
if (await exists(cached)) {
|