@skyf0xx/hedgehog 4.3.4 → 4.4.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/bin/cli.mjs +112 -22
- package/package.json +1 -1
- package/src/agents/planner.md +6 -1
- package/src/hosts/version.mjs +154 -0
- package/src/templates/CLAUDE.md +23 -14
package/bin/cli.mjs
CHANGED
|
@@ -50,6 +50,7 @@ import { rebuildDb } from '../src/db/rebuild.mjs';
|
|
|
50
50
|
import { loadOverrides, addOverride, orphanedOverrides, OVERRIDES_DIR } from '../src/db/overrides.mjs';
|
|
51
51
|
import { HOSTS, HOST_FLAGS, DEFAULT_HOST, availableHosts } from '../src/hosts/index.mjs';
|
|
52
52
|
import { recordHosts, installedHosts } from '../src/hosts/installed.mjs';
|
|
53
|
+
import { recordVersion, checkForUpdate, installedVersion } from '../src/hosts/version.mjs';
|
|
53
54
|
|
|
54
55
|
const AUTHORED_CORE_PATH = '.hedgehog/core.yaml';
|
|
55
56
|
|
|
@@ -65,6 +66,12 @@ const DEST_ROOT = process.cwd();
|
|
|
65
66
|
const CORES_ROOT = join(PKG_ROOT, 'src/golden-cores');
|
|
66
67
|
const DEFAULT_CORE = 'full-stack-app';
|
|
67
68
|
|
|
69
|
+
// The version of the payload this CLI carries — what `init` and
|
|
70
|
+
// `update` stamp into the project they write to.
|
|
71
|
+
const PKG_VERSION = JSON.parse(
|
|
72
|
+
await readFile(join(PKG_ROOT, 'package.json'), 'utf8'),
|
|
73
|
+
).version;
|
|
74
|
+
|
|
68
75
|
// One install flag per core, named for what a user is asking to build
|
|
69
76
|
// rather than the internal src/golden-cores/<name> directory — the two
|
|
70
77
|
// diverge deliberately so the CLI's public surface can stay stable
|
|
@@ -384,6 +391,7 @@ ${bold('Usage')}
|
|
|
384
391
|
npx @skyf0xx/hedgehog init --all-hosts install for every supported coding agent
|
|
385
392
|
npx @skyf0xx/hedgehog init --force overwrite existing files
|
|
386
393
|
npx @skyf0xx/hedgehog update refresh the installed agents + skills
|
|
394
|
+
npx @skyf0xx/hedgehog update --check report whether a newer release is published
|
|
387
395
|
npx @skyf0xx/hedgehog db init create .hedgehog/hedgehog.db if absent
|
|
388
396
|
npx @skyf0xx/hedgehog db rebuild re-derive the build graph from committed intents + git history
|
|
389
397
|
npx @skyf0xx/hedgehog plan compile pending intents into tasks + dependencies
|
|
@@ -449,6 +457,15 @@ those directories. The instructions file, the build graph, the core
|
|
|
449
457
|
workspace, and vendor-skills/BMAD and vendor-skills/GSAP stay as they
|
|
450
458
|
are — those are project-specific or updated deliberately, not by this
|
|
451
459
|
command.
|
|
460
|
+
|
|
461
|
+
Both ${bold('init')} and ${bold('update')} stamp the version they wrote into
|
|
462
|
+
.hedgehog/version.json. ${bold('update --check')} compares that stamp against
|
|
463
|
+
the newest published release and exits 1 when a newer one exists, without
|
|
464
|
+
writing anything. The same comparison rides on ${bold('status')} and
|
|
465
|
+
${bold('next')} as a one-line notice, from a once-a-day cached lookup, so a
|
|
466
|
+
stale project surfaces itself; set HEDGEHOG_NO_UPDATE_CHECK=1 to silence it.
|
|
467
|
+
Run ${bold('npx @skyf0xx/hedgehog@latest update')} to pull a newer release —
|
|
468
|
+
the @latest tag matters, since a bare npx may reuse a cached older CLI.
|
|
452
469
|
`);
|
|
453
470
|
}
|
|
454
471
|
|
|
@@ -500,6 +517,7 @@ async function init({ force, core, explicitCore, host = DEFAULT_HOST, hostOnly =
|
|
|
500
517
|
// Recorded before anything is written: the routing doc is generated
|
|
501
518
|
// from this list, so it has to already name the host being installed.
|
|
502
519
|
await recordHosts(DEST_ROOT, [host]);
|
|
520
|
+
await recordVersion(DEST_ROOT, PKG_VERSION);
|
|
503
521
|
|
|
504
522
|
let written = 0;
|
|
505
523
|
let overwritten = 0;
|
|
@@ -548,31 +566,22 @@ async function init({ force, core, explicitCore, host = DEFAULT_HOST, hostOnly =
|
|
|
548
566
|
);
|
|
549
567
|
console.log(dim(' instead of polling tightly or narrating the wait.'));
|
|
550
568
|
console.log(` 2. ${bold('git add -A && git commit -m "chore: install Hedgehog"')}`);
|
|
551
|
-
console.log(
|
|
552
|
-
` 3. Start a ${bold('new')} ${HOSTS[host].label} session and describe what you want to build.`,
|
|
553
|
-
);
|
|
569
|
+
console.log(` 3. Describe what you want to build.`);
|
|
554
570
|
} else {
|
|
555
571
|
console.log(` 1. ${bold('git add -A && git commit -m "chore: install Hedgehog"')}`);
|
|
556
|
-
console.log(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
);
|
|
565
|
-
// A host enumerates its agents once, at session start. This install just
|
|
566
|
-
// wrote them mid-session, so in THIS session they are not dispatchable —
|
|
567
|
-
// and the failure is worse than a plain error: names like `planner` and
|
|
568
|
-
// `reviewer` often resolve to the user's own unrelated global agents,
|
|
569
|
-
// silently running a different discipline instead of erroring.
|
|
572
|
+
console.log(` 2. Describe what you want to build.`);
|
|
573
|
+
}
|
|
574
|
+
// Hand off by path, not by name. Reading the file works on every host in
|
|
575
|
+
// the session that ran this install — no restart, no dependence on whether
|
|
576
|
+
// the harness re-scans its agent directory, and no chance of a bare
|
|
577
|
+
// `planner` resolving to an unrelated global agent of the same name.
|
|
578
|
+
const agents = HOSTS[host].agentsDir;
|
|
579
|
+
const skills = HOSTS[host].skillsDir;
|
|
570
580
|
console.log(
|
|
571
581
|
dim(
|
|
572
|
-
`
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
' inline rather than dispatching to a name that may resolve elsewhere.',
|
|
582
|
+
` Read ${bold(`${agents}/planner.md`)} and follow it — it runs planning\n` +
|
|
583
|
+
` intake (${skills}/hedgehog-planning-intake/SKILL.md), then hands\n` +
|
|
584
|
+
` off to ${agents}/bootstrap.md.`,
|
|
576
585
|
),
|
|
577
586
|
);
|
|
578
587
|
console.log();
|
|
@@ -597,6 +606,42 @@ async function init({ force, core, explicitCore, host = DEFAULT_HOST, hostOnly =
|
|
|
597
606
|
}
|
|
598
607
|
}
|
|
599
608
|
|
|
609
|
+
// `update --check` answers the question without acting on it: what's
|
|
610
|
+
// installed, what's published, and whether they differ. Exit code 0 when
|
|
611
|
+
// current or unknown, 1 when an update is available, so a script or a
|
|
612
|
+
// host's own tooling can branch on it without parsing the output.
|
|
613
|
+
async function updateCheck() {
|
|
614
|
+
const { installed, latest, stale } = await checkForUpdate(DEST_ROOT, {
|
|
615
|
+
force: true,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
if (!installed) {
|
|
619
|
+
console.log(
|
|
620
|
+
`${yellow('Installed version unknown.')} ${dim(
|
|
621
|
+
'This project predates version stamping — run `hedgehog update` to refresh and stamp it.',
|
|
622
|
+
)}\n`,
|
|
623
|
+
);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (!latest) {
|
|
627
|
+
console.log(
|
|
628
|
+
`Installed: ${bold(installed)}\n${dim(
|
|
629
|
+
"Couldn't reach the npm registry — no update check performed.",
|
|
630
|
+
)}\n`,
|
|
631
|
+
);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (stale) {
|
|
635
|
+
console.log(
|
|
636
|
+
`${yellow(bold('Update available.'))} installed ${bold(installed)} → latest ${bold(latest)}\n\n` +
|
|
637
|
+
` Run ${bold('npx @skyf0xx/hedgehog@latest update')} to refresh this project's agents and skills.\n`,
|
|
638
|
+
);
|
|
639
|
+
process.exitCode = 1;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
console.log(`${green('Up to date.')} ${dim(`installed ${installed}, latest ${latest}`)}\n`);
|
|
643
|
+
}
|
|
644
|
+
|
|
600
645
|
async function update({ hosts }) {
|
|
601
646
|
const targets = hosts?.length ? hosts : await installedHosts(DEST_ROOT);
|
|
602
647
|
|
|
@@ -623,10 +668,19 @@ async function update({ hosts }) {
|
|
|
623
668
|
}
|
|
624
669
|
}
|
|
625
670
|
|
|
671
|
+
// Stamped after the writes land, so the recorded version always
|
|
672
|
+
// describes the payload actually on disk.
|
|
673
|
+
const previous = await installedVersion(DEST_ROOT);
|
|
674
|
+
await recordVersion(DEST_ROOT, PKG_VERSION);
|
|
675
|
+
|
|
626
676
|
const label = targets.map((h) => HOSTS[h].label).join(', ');
|
|
677
|
+
const versionNote =
|
|
678
|
+
previous && previous !== PKG_VERSION
|
|
679
|
+
? `${previous} → ${PKG_VERSION}`
|
|
680
|
+
: PKG_VERSION;
|
|
627
681
|
console.log(
|
|
628
682
|
`\n${green(bold('Hedgehog agents/skills updated.'))} ${dim(
|
|
629
|
-
`${written} files written for ${label}`,
|
|
683
|
+
`${versionNote} — ${written} files written for ${label}`,
|
|
630
684
|
)}\n`,
|
|
631
685
|
);
|
|
632
686
|
console.log('Next steps:');
|
|
@@ -1113,6 +1167,34 @@ function warnSingularModuleIdsAtPlan(core, db) {
|
|
|
1113
1167
|
);
|
|
1114
1168
|
}
|
|
1115
1169
|
|
|
1170
|
+
// The passive half of update detection. Printed to stderr, after a
|
|
1171
|
+
// command's real output, on the commands every host's loop runs anyway —
|
|
1172
|
+
// so a stale project surfaces itself without anyone thinking to ask.
|
|
1173
|
+
//
|
|
1174
|
+
// This lives in the CLI rather than in any one host's hook because every
|
|
1175
|
+
// host drives the same binary: Claude Code, Cursor, and Gemini CLI all
|
|
1176
|
+
// reach it through `hedgehog next`, and one implementation here covers
|
|
1177
|
+
// all of them.
|
|
1178
|
+
//
|
|
1179
|
+
// Never throws and never blocks: the answer comes from a day-long cache,
|
|
1180
|
+
// a missing or unreachable registry reads as "no news", and the notice is
|
|
1181
|
+
// skipped entirely when output isn't a terminal so it can't corrupt
|
|
1182
|
+
// anything parsing stdout.
|
|
1183
|
+
async function noteAvailableUpdate() {
|
|
1184
|
+
if (process.env.HEDGEHOG_NO_UPDATE_CHECK) return;
|
|
1185
|
+
try {
|
|
1186
|
+
const { installed, latest, stale } = await checkForUpdate(DEST_ROOT);
|
|
1187
|
+
if (!stale) return;
|
|
1188
|
+
console.error(
|
|
1189
|
+
`\n${yellow(bold('Hedgehog update available.'))} ${dim(
|
|
1190
|
+
`${installed} → ${latest}. Run \`npx @skyf0xx/hedgehog@latest update\` to refresh this project's agents and skills.`,
|
|
1191
|
+
)}`,
|
|
1192
|
+
);
|
|
1193
|
+
} catch {
|
|
1194
|
+
// Advisory only — a failed check is never worth failing a command over.
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1116
1198
|
async function nextCommand() {
|
|
1117
1199
|
await ensureDb();
|
|
1118
1200
|
|
|
@@ -1149,6 +1231,7 @@ async function nextCommand() {
|
|
|
1149
1231
|
return;
|
|
1150
1232
|
}
|
|
1151
1233
|
console.log(`${dim('No ready task.')} Nothing is planned with all dependencies complete.\n`);
|
|
1234
|
+
await noteAvailableUpdate();
|
|
1152
1235
|
return;
|
|
1153
1236
|
}
|
|
1154
1237
|
|
|
@@ -1165,6 +1248,7 @@ async function nextCommand() {
|
|
|
1165
1248
|
}
|
|
1166
1249
|
|
|
1167
1250
|
console.log(formatNext(packet, await resolveCoreId(), packetExists));
|
|
1251
|
+
await noteAvailableUpdate();
|
|
1168
1252
|
}
|
|
1169
1253
|
|
|
1170
1254
|
function printStalledTasks(stalled) {
|
|
@@ -1834,6 +1918,8 @@ async function statusCommand() {
|
|
|
1834
1918
|
|
|
1835
1919
|
const warningLines = await coreWarningLines();
|
|
1836
1920
|
if (warningLines.length > 0) console.log(warningLines.join('\n'));
|
|
1921
|
+
|
|
1922
|
+
await noteAvailableUpdate();
|
|
1837
1923
|
}
|
|
1838
1924
|
|
|
1839
1925
|
// `hedgehog ready` — read-only preview of what a `hedgehog claim` call
|
|
@@ -2450,6 +2536,10 @@ async function main() {
|
|
|
2450
2536
|
}
|
|
2451
2537
|
|
|
2452
2538
|
if (cmd === 'update') {
|
|
2539
|
+
if (args.includes('--check')) {
|
|
2540
|
+
await updateCheck();
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2453
2543
|
await update({ hosts });
|
|
2454
2544
|
return;
|
|
2455
2545
|
}
|
package/package.json
CHANGED
package/src/agents/planner.md
CHANGED
|
@@ -87,7 +87,12 @@ and the scaffolded workspace.
|
|
|
87
87
|
real app beyond a single page. If in doubt between this and
|
|
88
88
|
landing-page because the project has *both* a marketing page and a
|
|
89
89
|
real app behind it, this is `full-stack-app` — the page becomes routes
|
|
90
|
-
inside `apps/web`, not a separate project.
|
|
90
|
+
inside `apps/web`, not a separate project. Data that gets stored is
|
|
91
|
+
this core, at any size: a todo list, a notes app, a tracker of any
|
|
92
|
+
kind. Never talk the user down to browser-local storage, an in-memory
|
|
93
|
+
array, or a single-file page because the app sounds small, and never
|
|
94
|
+
offer that as a quicker start — the core ships a real database, and
|
|
95
|
+
reaching for less is the drift this discipline exists to prevent.
|
|
91
96
|
- **`landing-page`** — the description is a marketing/announcement/
|
|
92
97
|
waitlist/portfolio page (or a small handful of such pages) with no
|
|
93
98
|
persistent domain data of its own. A page that only collects an email
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// What version of the payload a project has, and whether a newer one exists.
|
|
2
|
+
//
|
|
3
|
+
// `init` and `update` stamp the version they wrote into
|
|
4
|
+
// `.hedgehog/version.json`. Without that stamp a project's installed
|
|
5
|
+
// agents and skills are indistinguishable from any other release's, so
|
|
6
|
+
// staleness can only be discovered by reading the files themselves.
|
|
7
|
+
//
|
|
8
|
+
// The registry lookup is cached and best-effort: a project that can't
|
|
9
|
+
// reach npm, or is offline entirely, gets no answer rather than an error.
|
|
10
|
+
// Nothing here is on the path of any command that has real work to do.
|
|
11
|
+
|
|
12
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const VERSION_PATH = '.hedgehog/version.json';
|
|
16
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/@skyf0xx/hedgehog/latest';
|
|
17
|
+
|
|
18
|
+
// How long a registry answer stays good. The payload ships daily, so a
|
|
19
|
+
// once-a-day question matches how often the answer can actually change,
|
|
20
|
+
// and keeps a session that runs many commands to a single network call.
|
|
21
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
// The check is advisory — it must never delay a command that has real
|
|
24
|
+
// work to do, so a registry that hangs is treated the same as one that
|
|
25
|
+
// says nothing.
|
|
26
|
+
const FETCH_TIMEOUT_MS = 1500;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Record the payload version now installed in `root`. Called by both
|
|
30
|
+
* `init` and `update`, so the stamp tracks the payload on disk rather
|
|
31
|
+
* than whichever release first landed.
|
|
32
|
+
*/
|
|
33
|
+
export async function recordVersion(root, version) {
|
|
34
|
+
const path = join(root, VERSION_PATH);
|
|
35
|
+
const prior = await readVersionFile(root);
|
|
36
|
+
await mkdir(dirname(path), { recursive: true });
|
|
37
|
+
await writeFile(
|
|
38
|
+
path,
|
|
39
|
+
`${JSON.stringify({ ...prior, version, installedAt: new Date().toISOString() }, null, 2)}\n`,
|
|
40
|
+
);
|
|
41
|
+
return version;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function readVersionFile(root) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(await readFile(join(root, VERSION_PATH), 'utf8'));
|
|
47
|
+
} catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The payload version installed in `root`, or null for a project
|
|
54
|
+
* installed before the stamp existed.
|
|
55
|
+
*/
|
|
56
|
+
export async function installedVersion(root) {
|
|
57
|
+
const { version } = await readVersionFile(root);
|
|
58
|
+
return typeof version === 'string' ? version : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Compare two semver strings. Returns true when `a` is strictly newer
|
|
63
|
+
* than `b`. Prerelease tags sort before their release, matching semver;
|
|
64
|
+
* anything unparseable compares as equal so a malformed version can
|
|
65
|
+
* never fabricate an update prompt.
|
|
66
|
+
*/
|
|
67
|
+
export function isNewer(a, b) {
|
|
68
|
+
const parse = (v) => {
|
|
69
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(String(v ?? '').trim());
|
|
70
|
+
return m
|
|
71
|
+
? { nums: [+m[1], +m[2], +m[3]], pre: m[4] ?? null }
|
|
72
|
+
: null;
|
|
73
|
+
};
|
|
74
|
+
const pa = parse(a);
|
|
75
|
+
const pb = parse(b);
|
|
76
|
+
if (!pa || !pb) return false;
|
|
77
|
+
for (let i = 0; i < 3; i++) {
|
|
78
|
+
if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] > pb.nums[i];
|
|
79
|
+
}
|
|
80
|
+
// Equal release numbers: a release outranks its own prereleases.
|
|
81
|
+
if (pa.pre === pb.pre) return false;
|
|
82
|
+
if (pa.pre === null) return true;
|
|
83
|
+
if (pb.pre === null) return false;
|
|
84
|
+
return pa.pre > pb.pre;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The newest published version, from cache when it's fresh and from the
|
|
89
|
+
* registry otherwise. Returns null on any failure — offline, timeout,
|
|
90
|
+
* rate limit, malformed response. A null answer means "don't know", and
|
|
91
|
+
* every caller treats that as "say nothing".
|
|
92
|
+
*/
|
|
93
|
+
export async function latestVersion(root, { force = false } = {}) {
|
|
94
|
+
const cached = await readVersionFile(root);
|
|
95
|
+
if (!force && cached.checkedAt && cached.latest) {
|
|
96
|
+
const age = Date.now() - Date.parse(cached.checkedAt);
|
|
97
|
+
if (age >= 0 && age < CACHE_TTL_MS) return cached.latest;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const latest = await fetchLatest();
|
|
101
|
+
if (!latest) return null;
|
|
102
|
+
|
|
103
|
+
// Cache alongside the install stamp rather than in a separate file, so
|
|
104
|
+
// one read answers both halves of the question.
|
|
105
|
+
const path = join(root, VERSION_PATH);
|
|
106
|
+
try {
|
|
107
|
+
await mkdir(dirname(path), { recursive: true });
|
|
108
|
+
await writeFile(
|
|
109
|
+
path,
|
|
110
|
+
`${JSON.stringify({ ...cached, latest, checkedAt: new Date().toISOString() }, null, 2)}\n`,
|
|
111
|
+
);
|
|
112
|
+
} catch {
|
|
113
|
+
// A read-only or missing .hedgehog just means no caching; the
|
|
114
|
+
// answer we fetched is still good for this call.
|
|
115
|
+
}
|
|
116
|
+
return latest;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function fetchLatest() {
|
|
120
|
+
try {
|
|
121
|
+
// No `accept` header. The abbreviated-metadata type
|
|
122
|
+
// (application/vnd.npm.install-v1+json) is only valid on the
|
|
123
|
+
// packument root — sending it to /latest returns 406, which this
|
|
124
|
+
// function would swallow as "no answer" and the check would
|
|
125
|
+
// silently never fire. Plain /latest returns just the one version
|
|
126
|
+
// object, which is also far smaller than the full packument.
|
|
127
|
+
const res = await fetch(REGISTRY_URL, {
|
|
128
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
129
|
+
});
|
|
130
|
+
if (!res.ok) return null;
|
|
131
|
+
const { version } = await res.json();
|
|
132
|
+
return typeof version === 'string' ? version : null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The full staleness picture for a project: what's installed, what's
|
|
140
|
+
* published, and whether the gap is worth telling anyone about.
|
|
141
|
+
*
|
|
142
|
+
* `stale` is true only when both versions are known and the published
|
|
143
|
+
* one is newer — an unknown on either side reads as "no", so a project
|
|
144
|
+
* installed before stamping and an offline machine both stay quiet.
|
|
145
|
+
*/
|
|
146
|
+
export async function checkForUpdate(root, { force = false } = {}) {
|
|
147
|
+
const installed = await installedVersion(root);
|
|
148
|
+
const latest = await latestVersion(root, { force });
|
|
149
|
+
return {
|
|
150
|
+
installed,
|
|
151
|
+
latest,
|
|
152
|
+
stale: Boolean(installed && latest && isNewer(latest, installed)),
|
|
153
|
+
};
|
|
154
|
+
}
|
package/src/templates/CLAUDE.md
CHANGED
|
@@ -38,20 +38,19 @@ Lock and the `bootstrap` handoff. This same rule holds at every later
|
|
|
38
38
|
handoff to a newly-installed agent or skill in this session: read its
|
|
39
39
|
file, don't assume its content.
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
status`, not a greeting.
|
|
41
|
+
The build continues in this session — nothing here needs a restart. Hosts
|
|
42
|
+
differ in whether they pick up agents written mid-session, so prefer
|
|
43
|
+
reading an agent's file from `.claude/agents/` and following it inline;
|
|
44
|
+
each one is self-contained, and that path works everywhere. Reading also
|
|
45
|
+
avoids the quiet failure of dispatch by name, where `planner` or
|
|
46
|
+
`reviewer` resolves to the user's own unrelated global agent and runs a
|
|
47
|
+
different discipline with no symptom. If a dispatch does error with a
|
|
48
|
+
list of available agents that omits Hedgehog's, that is the same case —
|
|
49
|
+
read the file and carry on.
|
|
50
|
+
|
|
51
|
+
Don't re-explain the discipline or summarize this file; the greeting is
|
|
52
|
+
one line, not a tour. Skip this entirely once the placeholder is filled
|
|
53
|
+
in — every later session starts with `hedgehog status`, not a greeting.
|
|
55
54
|
|
|
56
55
|
## How to work here
|
|
57
56
|
|
|
@@ -86,6 +85,16 @@ loop) is what a generic skill pack has no notion of, and running one
|
|
|
86
85
|
alongside Hedgehog's own skills produces work that bypasses the very
|
|
87
86
|
discipline this file describes.
|
|
88
87
|
|
|
88
|
+
**Build through the loop, including when the work looks too small to need
|
|
89
|
+
it.** A task that seems like a quick edit is not a reason to skip the
|
|
90
|
+
graph, the step sequence, or the gate — scope grows, and the shortcut is
|
|
91
|
+
what turns a small change into debt the next step has to work around.
|
|
92
|
+
Don't propose going around the discipline to save a step, and don't treat
|
|
93
|
+
a user's impatience as license to: say what the loop's next step is and
|
|
94
|
+
take it. Where a rule genuinely conflicts with what the user is asking
|
|
95
|
+
for, name the conflict and let them decide — never resolve it by quietly
|
|
96
|
+
taking the faster path.
|
|
97
|
+
|
|
89
98
|
{{CORE_SECTION}}
|
|
90
99
|
|
|
91
100
|
## Consuming the graph
|