@skyf0xx/hedgehog 6.3.4 → 6.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 -2
- package/package.json +1 -1
- package/src/agents/bootstrap.md +5 -3
- package/src/db/ready.mjs +4 -0
- package/src/db/status.mjs +9 -0
- package/src/hosts/claude/DISPATCH.md +2 -11
- package/src/hosts/claude-md-merge.mjs +68 -0
- package/src/hosts/cursor/DISPATCH.md +2 -3
- package/src/hosts/gemini/DISPATCH.md +2 -3
- package/src/hosts/gemini/gemini-extension.json +1 -1
- package/src/skills/hedgehog-orchestrating/SKILL.md +154 -0
- package/src/templates/CLAUDE.md +13 -151
package/bin/cli.mjs
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// npx @skyf0xx/hedgehog --help
|
|
15
15
|
|
|
16
16
|
import { cp, mkdir, access, readdir, stat, rm, readFile, writeFile, mkdtemp } from 'node:fs/promises';
|
|
17
|
+
import { Buffer } from 'node:buffer';
|
|
17
18
|
import { constants, existsSync, realpathSync } from 'node:fs';
|
|
18
19
|
import { fileURLToPath } from 'node:url';
|
|
19
20
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
@@ -104,7 +105,7 @@ import { NOOP_DIR } from '../src/db/noop.mjs';
|
|
|
104
105
|
import { runFastpath, loadFastpaths, orphanedFastpathTasks, FASTPATH_DIR } from '../src/db/fastpath.mjs';
|
|
105
106
|
import { HOSTS, HOST_FLAGS, DEFAULT_HOST, availableHosts } from '../src/hosts/index.mjs';
|
|
106
107
|
import { recordHosts, installedHosts } from '../src/hosts/installed.mjs';
|
|
107
|
-
import { wrapSection } from '../src/hosts/claude-md-merge.mjs';
|
|
108
|
+
import { wrapSection, stripPhaseBlocks } from '../src/hosts/claude-md-merge.mjs';
|
|
108
109
|
import {
|
|
109
110
|
recordVersion,
|
|
110
111
|
checkForUpdate,
|
|
@@ -372,6 +373,11 @@ function corePayload(core, h, { hostOnly = false } = {}) {
|
|
|
372
373
|
// re-vendor, per each shelf's ATTRIBUTION.md) — none of those belong in
|
|
373
374
|
// an update, so a core's `workspace` and `vendor_skills` are left alone
|
|
374
375
|
// here while its agents and skills are refreshed.
|
|
376
|
+
// The bootstrap file is excluded here on purpose: `hedgehog shed` (and
|
|
377
|
+
// writePlannedFile's own self-heal) is the only thing allowed to remove
|
|
378
|
+
// content from it, and an update pass that also touched it would race
|
|
379
|
+
// that removal against whatever bootstrap-only content a fresh shell
|
|
380
|
+
// still carries.
|
|
375
381
|
function updatePlan(host = DEFAULT_HOST, core = null) {
|
|
376
382
|
const h = HOSTS[host];
|
|
377
383
|
return [
|
|
@@ -478,6 +484,98 @@ function warnOrphanedNotes({ orphanedNotes }) {
|
|
|
478
484
|
console.log('');
|
|
479
485
|
}
|
|
480
486
|
|
|
487
|
+
const PHASE_NAME = 'bootstrap-only';
|
|
488
|
+
|
|
489
|
+
// Engine-generic conditions for "this project's build graph is past
|
|
490
|
+
// bootstrap" — never core-specific facts like nx.json or
|
|
491
|
+
// astro.config.mjs, which the engine has no business knowing about. Both
|
|
492
|
+
// must hold: a build graph exists, and at least one task has been
|
|
493
|
+
// compiled into it. The bootstrap file's own {{PROJECT_SUMMARY}}
|
|
494
|
+
// placeholder is the third condition (see shedCommand and
|
|
495
|
+
// writePlannedFile's self-heal) but is checked per-file, since a
|
|
496
|
+
// multi-host project can have several bootstrap files to test.
|
|
497
|
+
async function graphPastBootstrap() {
|
|
498
|
+
if (!(await exists(DB_PATH))) return false;
|
|
499
|
+
const db = openDb();
|
|
500
|
+
try {
|
|
501
|
+
return db.prepare('SELECT 1 FROM tasks LIMIT 1').get() !== undefined;
|
|
502
|
+
} finally {
|
|
503
|
+
db.close();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function canShed(bootstrapContent) {
|
|
508
|
+
if (!(await graphPastBootstrap())) return false;
|
|
509
|
+
return !bootstrapContent.includes('{{PROJECT_SUMMARY}}');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// `hedgehog shed` — strips bootstrap-only content from every host
|
|
513
|
+
// bootstrap file actually on disk, once the project has provably moved
|
|
514
|
+
// past bootstrap. Guard conditions are engine-generic only (see
|
|
515
|
+
// graphPastBootstrap/canShed above); this command names which one failed
|
|
516
|
+
// rather than a single generic refusal, in the style of `hedgehog
|
|
517
|
+
// boundary`.
|
|
518
|
+
async function shedCommand() {
|
|
519
|
+
if (!(await exists(DB_PATH))) {
|
|
520
|
+
console.error(
|
|
521
|
+
`${red('No build graph found.')} ${bold(dbAbsPath())} does not exist — run ${bold('hedgehog init')} or ${bold('hedgehog db init')} first.\n`,
|
|
522
|
+
);
|
|
523
|
+
process.exitCode = 1;
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (!(await graphPastBootstrap())) {
|
|
527
|
+
console.error(
|
|
528
|
+
`${red('No task has been compiled into the build graph yet.')} Run ${bold('hedgehog plan')} first.\n`,
|
|
529
|
+
);
|
|
530
|
+
process.exitCode = 1;
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const hosts = await installedHosts(DEST_ROOT);
|
|
535
|
+
const bootstrapFiles = [
|
|
536
|
+
...new Set(hosts.map((name) => HOSTS[name].bootstrapFile).filter(Boolean)),
|
|
537
|
+
];
|
|
538
|
+
|
|
539
|
+
const present = [];
|
|
540
|
+
for (const rel of bootstrapFiles) {
|
|
541
|
+
const abs = join(DEST_ROOT, rel);
|
|
542
|
+
if (await exists(abs)) present.push({ rel, abs });
|
|
543
|
+
}
|
|
544
|
+
if (present.length === 0) {
|
|
545
|
+
console.error(`${red('No bootstrap file found.')} Expected one of: ${bootstrapFiles.join(', ')}\n`);
|
|
546
|
+
process.exitCode = 1;
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const unfilled = [];
|
|
551
|
+
for (const { rel, abs } of present) {
|
|
552
|
+
const content = await readFile(abs, 'utf8');
|
|
553
|
+
if (content.includes('{{PROJECT_SUMMARY}}')) unfilled.push(rel);
|
|
554
|
+
}
|
|
555
|
+
if (unfilled.length > 0) {
|
|
556
|
+
console.error(
|
|
557
|
+
`${red('{{PROJECT_SUMMARY}} is still unfilled in:')} ${unfilled.join(', ')} — nothing has been built yet.\n`,
|
|
558
|
+
);
|
|
559
|
+
process.exitCode = 1;
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
let anyStripped = false;
|
|
564
|
+
for (const { rel, abs } of present) {
|
|
565
|
+
const before = await readFile(abs, 'utf8');
|
|
566
|
+
const after = stripPhaseBlocks(before, PHASE_NAME);
|
|
567
|
+
if (after === before) continue;
|
|
568
|
+
anyStripped = true;
|
|
569
|
+
await writeFile(abs, after);
|
|
570
|
+
const beforeBytes = Buffer.byteLength(before, 'utf8').toLocaleString('en-US');
|
|
571
|
+
const afterBytes = Buffer.byteLength(after, 'utf8').toLocaleString('en-US');
|
|
572
|
+
console.log(`${rel} ${beforeBytes} → ${afterBytes} B`);
|
|
573
|
+
}
|
|
574
|
+
if (!anyStripped) {
|
|
575
|
+
console.log('nothing to shed');
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
481
579
|
// Writes one planned file to disk — a straight copy, or for a `merge`
|
|
482
580
|
// entry, the shell template with {{CORE_SECTION}} replaced by the
|
|
483
581
|
// chosen core's include.
|
|
@@ -528,7 +626,12 @@ async function writePlannedFile(f) {
|
|
|
528
626
|
out = out.replaceAll('{{CORE_SECTION}}', wrapSection(section));
|
|
529
627
|
}
|
|
530
628
|
const dispatch = await readFile(join(PKG_ROOT, f.merge.dispatch), 'utf8');
|
|
531
|
-
|
|
629
|
+
out = out.replaceAll('{{HOST_DISPATCH}}', dispatch.trimEnd());
|
|
630
|
+
// Self-heal: a project past bootstrap that re-runs `init --force`
|
|
631
|
+
// must not silently regain bootstrap-only content just because this
|
|
632
|
+
// write started from the pristine shell again.
|
|
633
|
+
if (await canShed(out)) out = stripPhaseBlocks(out, PHASE_NAME);
|
|
634
|
+
await writeFile(f.dest, out);
|
|
532
635
|
return;
|
|
533
636
|
}
|
|
534
637
|
// Rendered from the payload rather than copied from it — the routing
|
|
@@ -675,6 +778,8 @@ ${bold('Usage')}
|
|
|
675
778
|
npx @skyf0xx/hedgehog debt resolve <debt-id> --reason "<why>" mark a debt note resolved
|
|
676
779
|
npx @skyf0xx/hedgehog decision add <task-id> "<note>" declare a decision that lands in dependent tasks' packets
|
|
677
780
|
npx @skyf0xx/hedgehog decision list [<task-id>] list declared decisions, oldest first
|
|
781
|
+
npx @skyf0xx/hedgehog shed strip bootstrap-only content from the installed
|
|
782
|
+
bootstrap file(s), once the project is past bootstrap
|
|
678
783
|
npx @skyf0xx/hedgehog db migrate bring the graph's schema up to the latest version
|
|
679
784
|
npx @skyf0xx/hedgehog community star --answer <a> record the star prompt's answer
|
|
680
785
|
npx @skyf0xx/hedgehog community showcase --repo <url> [--description <text>]
|
|
@@ -4547,6 +4652,11 @@ async function main() {
|
|
|
4547
4652
|
return;
|
|
4548
4653
|
}
|
|
4549
4654
|
|
|
4655
|
+
if (cmd === 'shed') {
|
|
4656
|
+
await shedCommand();
|
|
4657
|
+
return;
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4550
4660
|
console.error(`${red('Unknown command:')} ${cmd}\n`);
|
|
4551
4661
|
await help();
|
|
4552
4662
|
process.exitCode = 1;
|
package/package.json
CHANGED
package/src/agents/bootstrap.md
CHANGED
|
@@ -61,9 +61,11 @@ workspace is scaffolded (see `planner.md`'s Workflow step 7 and step 9).
|
|
|
61
61
|
This compiles those intents into tasks so the core's loop skill has
|
|
62
62
|
something to pick up from `hedgehog next`. Then run `hedgehog graph` to
|
|
63
63
|
start (or reuse) the live graph server and open it, so the build graph is
|
|
64
|
-
on screen before the first build step starts.
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
on screen before the first build step starts. Run `hedgehog shed` to
|
|
65
|
+
strip bootstrap-only content from the bootstrap file now that it no
|
|
66
|
+
longer applies. Then state plainly that Bootstrap is closed and name the
|
|
67
|
+
loop skill that owns everything from here. Don't hand off to another
|
|
68
|
+
instance of yourself.
|
|
67
69
|
|
|
68
70
|
|
|
69
71
|
## Constraints
|
package/src/db/ready.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { findClaimableTasks, findInFlightTasks } from './claim.mjs';
|
|
9
9
|
import { conflicts, verifyRadius } from './conflict.mjs';
|
|
10
|
+
import { ORCHESTRATING_FOOTER } from './status.mjs';
|
|
10
11
|
|
|
11
12
|
// Walks the same candidates claimTasks would, in the same priority/id
|
|
12
13
|
// order, greedily sorting each into CLAIMABLE (doesn't conflict with
|
|
@@ -92,5 +93,8 @@ export function formatReady({ claimable, heldBack }) {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
lines.push('');
|
|
97
|
+
lines.push(ORCHESTRATING_FOOTER);
|
|
98
|
+
|
|
95
99
|
return lines.join('\n');
|
|
96
100
|
}
|
package/src/db/status.mjs
CHANGED
|
@@ -274,6 +274,12 @@ export async function graphWorktreeStatus(db, opts) {
|
|
|
274
274
|
return worktreeStatus(db, opts);
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
// Printed at the bottom of both `hedgehog status` and `hedgehog ready` —
|
|
278
|
+
// the two commands every session provably runs — since no static check
|
|
279
|
+
// can verify an orchestrating session actually reads the skill this
|
|
280
|
+
// names.
|
|
281
|
+
export const ORCHESTRATING_FOOTER = 'See the hedgehog-orchestrating skill for the claim → dispatch → verify cycle.';
|
|
282
|
+
|
|
277
283
|
const BLOCKED_REASON_LABELS = {
|
|
278
284
|
verification_failed: 'verification failed',
|
|
279
285
|
scope_violation: 'scope violation',
|
|
@@ -470,5 +476,8 @@ export function formatStatus({
|
|
|
470
476
|
lines.push(' See: hedgehog reconcile list');
|
|
471
477
|
}
|
|
472
478
|
|
|
479
|
+
lines.push('');
|
|
480
|
+
lines.push(ORCHESTRATING_FOOTER);
|
|
481
|
+
|
|
473
482
|
return lines.join('\n');
|
|
474
483
|
}
|
|
@@ -6,14 +6,5 @@ context with its own tool grant. The skills in `.claude/skills/` are
|
|
|
6
6
|
available the same way; invoke one by name rather than reimplementing what
|
|
7
7
|
it describes.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
just now) is not yet dispatchable by name in this session. If a name-based
|
|
12
|
-
dispatch reports it as not found, read the file directly from
|
|
13
|
-
`.claude/agents/` or `.claude/skills/` and follow it inline instead of
|
|
14
|
-
retrying the dispatch; it becomes dispatchable by name after a session
|
|
15
|
-
restart or a fresh context.
|
|
16
|
-
|
|
17
|
-
Clear context with `/clear` at the unit boundaries described above —
|
|
18
|
-
`hedgehog boundary` tells you whether you're at one (exit 0), and
|
|
19
|
-
`hedgehog boundary --handoff` is what the next session starts from.
|
|
9
|
+
Clear context with `/clear` at the unit boundaries the
|
|
10
|
+
`hedgehog-orchestrating` skill states.
|
|
@@ -64,6 +64,74 @@ export function appendCoreSection(existingContent, section) {
|
|
|
64
64
|
return `${existingContent.trimEnd()}\n\n${block}\n`;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// One phase name only, `bootstrap-only` — there is exactly one
|
|
68
|
+
// transition in a project's life (still bootstrapping vs. not), so a
|
|
69
|
+
// predicate grammar for a one-bit state would be a maintenance burden
|
|
70
|
+
// with no second case to justify it.
|
|
71
|
+
const PHASE_MARKER_START = (phase) => `<!-- hedgehog:${phase} start -->`;
|
|
72
|
+
const PHASE_MARKER_END = (phase) => `<!-- hedgehog:${phase} end -->`;
|
|
73
|
+
|
|
74
|
+
// Removes every `<!-- hedgehog:<phase> start -->...end -->` block from
|
|
75
|
+
// `content`, along with the blank lines immediately surrounding each
|
|
76
|
+
// removed block, so stripping never leaves a double blank line behind.
|
|
77
|
+
// Byte-for-byte identity when no markers are present is the contract
|
|
78
|
+
// every core package not yet using markers relies on.
|
|
79
|
+
//
|
|
80
|
+
// A block is self-contained by convention (no section outside it may
|
|
81
|
+
// reference into it), so removal never leaves a dangling reference for
|
|
82
|
+
// this function to worry about — that's enforced by review of what gets
|
|
83
|
+
// marked, not by this code.
|
|
84
|
+
export function stripPhaseBlocks(content, phase) {
|
|
85
|
+
const start = PHASE_MARKER_START(phase);
|
|
86
|
+
const end = PHASE_MARKER_END(phase);
|
|
87
|
+
const startCount = countOccurrences(content, start);
|
|
88
|
+
const endCount = countOccurrences(content, end);
|
|
89
|
+
if (startCount === 0 && endCount === 0) return content;
|
|
90
|
+
if (startCount !== endCount) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`stripPhaseBlocks: ${startCount} "${start}" marker(s) but ${endCount} "${end}" marker(s) — unterminated or orphaned`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Flat, non-nesting blocks only: a start found before the matching end
|
|
97
|
+
// of the previous block indicates nesting, which this vocabulary
|
|
98
|
+
// deliberately does not support.
|
|
99
|
+
const blockRe = new RegExp(`${escapeRe(start)}[\\s\\S]*?${escapeRe(end)}`, 'g');
|
|
100
|
+
// Not a valid marker (the phase name can't contain whitespace), and not
|
|
101
|
+
// realistic page content either, so it can't collide with anything
|
|
102
|
+
// already in `content` — used as a removal placeholder instead of a
|
|
103
|
+
// control character, which linting disallows anywhere a regex pattern
|
|
104
|
+
// could embed it.
|
|
105
|
+
const PLACEHOLDER = 'hedgehog-stripped-block';
|
|
106
|
+
let matchCount = 0;
|
|
107
|
+
let stripped = content.replace(blockRe, () => {
|
|
108
|
+
matchCount++;
|
|
109
|
+
return PLACEHOLDER;
|
|
110
|
+
});
|
|
111
|
+
if (matchCount !== startCount) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`stripPhaseBlocks: found ${startCount} "${start}" marker(s) but only ${matchCount} well-formed block(s) — check for nesting or an orphaned marker`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Each removed block leaves a placeholder; collapse it and the blank
|
|
118
|
+
// lines around it so no double blank line remains.
|
|
119
|
+
const placeholderRe = new RegExp(`[ \\t]*\\n?[ \\t]*${escapeRe(PLACEHOLDER.trim())}[ \\t]*\\n?[ \\t]*\\n?`, 'g');
|
|
120
|
+
stripped = stripped.replace(placeholderRe, '\n');
|
|
121
|
+
stripped = stripped.replace(/\n{3,}/g, '\n\n');
|
|
122
|
+
return stripped;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function countOccurrences(haystack, needle) {
|
|
126
|
+
let count = 0;
|
|
127
|
+
let i = 0;
|
|
128
|
+
while ((i = haystack.indexOf(needle, i)) !== -1) {
|
|
129
|
+
count++;
|
|
130
|
+
i += needle.length;
|
|
131
|
+
}
|
|
132
|
+
return count;
|
|
133
|
+
}
|
|
134
|
+
|
|
67
135
|
function escapeRe(s) {
|
|
68
136
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
69
137
|
}
|
|
@@ -16,6 +16,5 @@ gates the commit either way.
|
|
|
16
16
|
The skills in `.cursor/skills/` are procedures to follow — read the one
|
|
17
17
|
whose situation applies rather than improvising the steps.
|
|
18
18
|
|
|
19
|
-
Clear the conversation at the unit boundaries
|
|
20
|
-
|
|
21
|
-
`hedgehog boundary --handoff` is what the next session starts from.
|
|
19
|
+
Clear the conversation at the unit boundaries the `hedgehog-orchestrating`
|
|
20
|
+
skill states.
|
|
@@ -22,8 +22,7 @@ commit lands.
|
|
|
22
22
|
The skills in `.gemini/skills/` are procedures to follow — read the one
|
|
23
23
|
whose situation applies rather than improvising the steps.
|
|
24
24
|
|
|
25
|
-
Clear the conversation at the unit boundaries
|
|
26
|
-
|
|
27
|
-
`hedgehog boundary --handoff` is what the next session starts from.
|
|
25
|
+
Clear the conversation at the unit boundaries the `hedgehog-orchestrating`
|
|
26
|
+
skill states.
|
|
28
27
|
|
|
29
28
|
@./AGENTS.md
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hedgehog-orchestrating
|
|
3
|
+
description: Use at the start of any session on a project with a build graph (`.hedgehog/hedgehog.db`), and again at every claim/dispatch/verify cycle within it. Owns consuming the build graph — claiming task packets, delegating each to the layer's agent, running `hedgehog verify` to gate the commit, recording debt and decisions, the intent check at an intent's last layer, and when to clear context. Not for a build agent handed an already-claimed packet — that agent never claims, dispatches, or verifies; this skill is the orchestrating session's own.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Orchestrating the build
|
|
7
|
+
|
|
8
|
+
`.hedgehog/hedgehog.db` is the source of truth for what's next — never
|
|
9
|
+
re-derive build state from prose. To work from it:
|
|
10
|
+
|
|
11
|
+
1. Run `hedgehog claim --count N --owner <owner>`. It's atomic and
|
|
12
|
+
lease-based, and returns up to N tasks (each with its own full
|
|
13
|
+
STATUS/INTENT/RELEVANT RULES/INHERITED DEBT/INHERITED DECISIONS/WHY
|
|
14
|
+
NOW/BLOCKED DOWNSTREAM/ALLOWED SCOPE/VERIFICATION packet) that the
|
|
15
|
+
scheduler has already verified are safe to run together right now —
|
|
16
|
+
scope and verify-radius disjoint. `--count` is a maximum, not a
|
|
17
|
+
promise: a call may return fewer than N, or zero. `hedgehog ready` is
|
|
18
|
+
a read-only preview of the claimable/held-back split (and why a task
|
|
19
|
+
is held back — conflict with another claimable task, or exclusivity)
|
|
20
|
+
before you claim.
|
|
21
|
+
2. Delegate each claimed packet to this core's loop skill (named in the
|
|
22
|
+
project's core section), one dispatch per packet, running concurrently.
|
|
23
|
+
3. As each agent reports its packet done, run `hedgehog verify
|
|
24
|
+
<task-id> --owner <owner>` **serially** — one at a time, even though
|
|
25
|
+
the building happened concurrently, because verify writes git commits
|
|
26
|
+
and those must land one at a time. It checks the touched files
|
|
27
|
+
against the packet's ALLOWED SCOPE, runs the verification command,
|
|
28
|
+
and on a pass writes the commit and unlocks whatever the task was
|
|
29
|
+
blocking. An agent reporting success never moves the task — only a
|
|
30
|
+
passing `hedgehog verify` exit code does.
|
|
31
|
+
|
|
32
|
+
`hedgehog claim` hands out only tasks safe to run together. Never run
|
|
33
|
+
two tasks it didn't hand you together.
|
|
34
|
+
|
|
35
|
+
The packet's **INTENT** block names the goal and outcome of the *whole*
|
|
36
|
+
intent, not just this layer. A layer's own verify command runs the tests
|
|
37
|
+
that layer wrote, so it measures internal consistency and never coverage
|
|
38
|
+
of what was asked — a layer that builds half the intent and tests that
|
|
39
|
+
half exhaustively is green. Build the layer's share of the goal, and say
|
|
40
|
+
so when the packet doesn't account for something the goal asks for.
|
|
41
|
+
When `hedgehog verify` closes the **last** layer of an intent it prints
|
|
42
|
+
that goal and outcome back as an **INTENT CHECK**: read the built work
|
|
43
|
+
against it there, because nothing else in the build does.
|
|
44
|
+
|
|
45
|
+
A layer that discovers a limitation the next layer has to compensate for
|
|
46
|
+
records it with `hedgehog debt add <task-id> "<note>"` — it lands in the
|
|
47
|
+
**INHERITED DEBT** section of every packet that depends on that task. A
|
|
48
|
+
comment in a source file is not a mechanism; nothing reads it.
|
|
49
|
+
|
|
50
|
+
A layer that makes a choice a dependent layer needs to know about — a
|
|
51
|
+
pattern, a library, a trade-off, anything the next task should follow
|
|
52
|
+
rather than reinvent or contradict — records it with `hedgehog decision
|
|
53
|
+
add <task-id> "<note>"`, landing in the **INHERITED DECISIONS** section
|
|
54
|
+
the same way. Debt is what's still wrong with a task; a decision is why
|
|
55
|
+
it was built the way it was.
|
|
56
|
+
|
|
57
|
+
`planner` owns writing intents (`hedgehog intent add`) at planning
|
|
58
|
+
intake; `hedgehog plan` compiles them into the task graph the loop
|
|
59
|
+
consumes. Nothing checks a box — there is no checklist, only queryable
|
|
60
|
+
state.
|
|
61
|
+
|
|
62
|
+
**When the build is done:** once `hedgehog status` shows every task
|
|
63
|
+
`complete` and `hedgehog boundary` exits 0 (see **Managing context**
|
|
64
|
+
below — it checks nothing-in-flight, a clean tree, and a closed intent
|
|
65
|
+
together), the build session is complete. The permanent record is the
|
|
66
|
+
committed intents (`.hedgehog/intents/*.json`), the friction log
|
|
67
|
+
(`.hedgehog/friction/*.md`), the core definition (root `core.yaml` for a
|
|
68
|
+
shipped core, `.hedgehog/core.yaml` for an authored one), and the git
|
|
69
|
+
commit history itself — not the database. `.hedgehog/hedgehog.db` is
|
|
70
|
+
gitignored: a derived index, rebuildable at any time via `hedgehog db
|
|
71
|
+
rebuild`, which replays those committed sources against git history.
|
|
72
|
+
That rebuild also runs automatically on a fresh clone when the DB is
|
|
73
|
+
missing but `.hedgehog/intents/` exists. That's what makes every later
|
|
74
|
+
session cheap.
|
|
75
|
+
|
|
76
|
+
A completed build is **extendable, not sealed**. Offer the user a
|
|
77
|
+
fresh-context handoff, and name both ways forward:
|
|
78
|
+
|
|
79
|
+
- **Adjustments to what's built** → the `tweaker` agent, from a *new*
|
|
80
|
+
chat window, not a subagent call inside this one — this session's
|
|
81
|
+
context has been building the whole project and is exactly what
|
|
82
|
+
"clearing context now costs nothing" (below) means to discard. Tell
|
|
83
|
+
the user plainly: close this chat window and open a new one, then
|
|
84
|
+
paste this to start it:
|
|
85
|
+
|
|
86
|
+
> The build for {{PROJECT_NAME}} is complete. Use the tweaker agent:
|
|
87
|
+
> first review the friction log and ask me for feedback on the build,
|
|
88
|
+
> then take my tweak requests one at a time.
|
|
89
|
+
|
|
90
|
+
In the new window, `tweaker` starts clean, once reviews the friction
|
|
91
|
+
log (`hedgehog friction list`) for possible discipline-improvement
|
|
92
|
+
issues and separately asks the user directly for feedback on the
|
|
93
|
+
build, filing each real pattern or piece of feedback as its own GitHub
|
|
94
|
+
issue against the Hedgehog repo itself, never this project's repo
|
|
95
|
+
(friction as `bug`/`help wanted`, feedback as `suggestion`, each only
|
|
96
|
+
after showing the exact content and getting explicit approval), then
|
|
97
|
+
takes any tweak requests one at a time.
|
|
98
|
+
- **New scope** — a new module or feature, anything beyond adjusting what
|
|
99
|
+
exists → on a core with a module axis, the `planner` agent, which runs
|
|
100
|
+
`hedgehog-planning-intake`'s **Re-entry pass**. It reads the existing
|
|
101
|
+
planning archive as context and elicits only what's new, then adds
|
|
102
|
+
intents and runs `hedgehog plan`. This is append-only: `plan` skips
|
|
103
|
+
intents already compiled, so every `complete` task keeps its status and
|
|
104
|
+
its commits, and `hedgehog claim` resumes at the first tasks of the new
|
|
105
|
+
work. Planning is not re-run from scratch, and the workspace is not
|
|
106
|
+
re-scaffolded. (This core's own section states where new scope goes if
|
|
107
|
+
this core has no module axis to add an intent to.)
|
|
108
|
+
|
|
109
|
+
If a request turns out to be structural rather than either of those —
|
|
110
|
+
something already built is wrong at its source — that's the Correction
|
|
111
|
+
Protocol's post-build entry, in this core's own loop skill.
|
|
112
|
+
|
|
113
|
+
## Managing context
|
|
114
|
+
|
|
115
|
+
Hedgehog is designed so the conversation is disposable. Keep the working
|
|
116
|
+
context small:
|
|
117
|
+
|
|
118
|
+
- **Clear context at natural boundaries** — a module's Phase A, a
|
|
119
|
+
landing page section, whatever this core's own unit boundary is — once
|
|
120
|
+
that unit is done and committed. Ask `hedgehog boundary` rather than
|
|
121
|
+
judging it: it exits 0 only when all three of nothing-in-flight, a
|
|
122
|
+
clean working tree, and a last closed task that completed its intent
|
|
123
|
+
hold, and names which one failed otherwise. Clear the conversation and
|
|
124
|
+
start fresh, then run `hedgehog status`/`hedgehog claim` and continue.
|
|
125
|
+
Nothing is lost, because the build graph, commits, and code hold all
|
|
126
|
+
the state. Prefer this over letting one session accumulate the entire
|
|
127
|
+
project.
|
|
128
|
+
- **`hedgehog quiesce` and `hedgehog boundary` answer different
|
|
129
|
+
questions.** `quiesce` reports whether anything is still in flight —
|
|
130
|
+
necessary before clearing (clearing while a lease is outstanding
|
|
131
|
+
orphans that lease until it expires), but not sufficient: a graph can
|
|
132
|
+
be perfectly settled halfway through an intent, with a dirty working
|
|
133
|
+
tree. `boundary` is the whole question — is this a moment to throw the
|
|
134
|
+
conversation away — and it includes the `quiesce` check as its first
|
|
135
|
+
condition. Use `quiesce` when you're waiting for dispatched work to
|
|
136
|
+
land (the Correction Protocol), `boundary` when you're deciding whether
|
|
137
|
+
to clear.
|
|
138
|
+
- **A cleared or new session recovers by running `hedgehog status` and
|
|
139
|
+
reading the commit log**, never by needing the prior conversation.
|
|
140
|
+
`hedgehog boundary --handoff` prints that recovery block directly —
|
|
141
|
+
where the build is, what's next and why, what's in flight, what's
|
|
142
|
+
blocked — derived from the graph, so no session hands a summary to the
|
|
143
|
+
next one.
|
|
144
|
+
- **Delegate heavy work to agents.** Scaffolding and every build step
|
|
145
|
+
run in their own isolated context, so work doesn't pile up in the
|
|
146
|
+
main thread. Planning intake's BMAD Phase 0 is the exception — the
|
|
147
|
+
project's own instructions file states it — and stays in that session
|
|
148
|
+
through Confirm & Lock; the mining, `bootstrap`, and per-module steps
|
|
149
|
+
after it delegate as usual.
|
|
150
|
+
- **Don't paste large context back in.** If you find yourself
|
|
151
|
+
re-explaining the architecture, stop — it's fixed and stated in the
|
|
152
|
+
project's core section, not something to reconstruct. If you need a
|
|
153
|
+
project specific, read it from the code. That's the self-documenting
|
|
154
|
+
design working as intended.
|
package/src/templates/CLAUDE.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<!-- hedgehog:bootstrap-only start -->
|
|
1
2
|
<!--
|
|
2
3
|
Hedgehog project CLAUDE.md template.
|
|
3
4
|
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
|
|
12
13
|
Delete this comment block after the placeholders are filled in.
|
|
13
14
|
-->
|
|
15
|
+
<!-- hedgehog:bootstrap-only end -->
|
|
14
16
|
|
|
15
17
|
# {{PROJECT_NAME}}
|
|
16
18
|
|
|
@@ -24,6 +26,7 @@ This project is built with **Hedgehog**: a one-step-at-a-time build
|
|
|
24
26
|
discipline. The rules below aren't project preferences — they're how the
|
|
25
27
|
build stays mechanically correct. Follow them exactly.
|
|
26
28
|
|
|
29
|
+
<!-- hedgehog:bootstrap-only start -->
|
|
27
30
|
## First message in a fresh install
|
|
28
31
|
|
|
29
32
|
If `{{PROJECT_SUMMARY}}` above is still an unfilled placeholder, this is a
|
|
@@ -51,6 +54,7 @@ read the file and carry on.
|
|
|
51
54
|
Don't re-explain the discipline or summarize this file; the greeting is
|
|
52
55
|
one line, not a tour. Skip this entirely once the placeholder is filled
|
|
53
56
|
in — every later session starts with `hedgehog status`, not a greeting.
|
|
57
|
+
<!-- hedgehog:bootstrap-only end -->
|
|
54
58
|
|
|
55
59
|
## How to work here
|
|
56
60
|
|
|
@@ -74,8 +78,8 @@ whole plan in context — the plan lives in the structure:
|
|
|
74
78
|
|
|
75
79
|
Because state lives in those places and not in the conversation, a fresh
|
|
76
80
|
context loses nothing: the architecture is known a priori, and the
|
|
77
|
-
project's specifics are re-read on demand.
|
|
78
|
-
|
|
81
|
+
project's specifics are re-read on demand. The `hedgehog-orchestrating`
|
|
82
|
+
skill (see **Running the build** below) is what uses that.
|
|
79
83
|
|
|
80
84
|
**Use only the skills and agents this repo provides**, including its
|
|
81
85
|
vendored BMAD shelf — never a general-purpose build-tool skill pack
|
|
@@ -97,154 +101,12 @@ taking the faster path.
|
|
|
97
101
|
|
|
98
102
|
{{CORE_SECTION}}
|
|
99
103
|
|
|
100
|
-
##
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
STATUS/INTENT/RELEVANT RULES/INHERITED DEBT/INHERITED DECISIONS/WHY
|
|
108
|
-
NOW/BLOCKED DOWNSTREAM/ALLOWED SCOPE/VERIFICATION packet) that the
|
|
109
|
-
scheduler has already verified are safe to run together right now —
|
|
110
|
-
scope and verify-radius disjoint. `--count` is a maximum, not a
|
|
111
|
-
promise: a call may return fewer than N, or zero. `hedgehog ready` is
|
|
112
|
-
a read-only preview of the claimable/held-back split (and why a task
|
|
113
|
-
is held back — conflict with another claimable task, or exclusivity)
|
|
114
|
-
before you claim.
|
|
115
|
-
2. Delegate each claimed packet to this core's loop skill (named in the
|
|
116
|
-
section above), one dispatch per packet, running concurrently.
|
|
117
|
-
3. As each agent reports its packet done, run `hedgehog verify
|
|
118
|
-
<task-id> --owner <owner>` **serially** — one at a time, even though
|
|
119
|
-
the building happened concurrently, because verify writes git commits
|
|
120
|
-
and those must land one at a time. It checks the touched files
|
|
121
|
-
against the packet's ALLOWED SCOPE, runs the verification command,
|
|
122
|
-
and on a pass writes the commit and unlocks whatever the task was
|
|
123
|
-
blocking. An agent reporting success never moves the task — only a
|
|
124
|
-
passing `hedgehog verify` exit code does.
|
|
125
|
-
|
|
126
|
-
`hedgehog claim` hands out only tasks safe to run together. Never run
|
|
127
|
-
two tasks it didn't hand you together.
|
|
128
|
-
|
|
129
|
-
The packet's **INTENT** block names the goal and outcome of the *whole*
|
|
130
|
-
intent, not just this layer. A layer's own verify command runs the tests
|
|
131
|
-
that layer wrote, so it measures internal consistency and never coverage
|
|
132
|
-
of what was asked — a layer that builds half the intent and tests that
|
|
133
|
-
half exhaustively is green. Build the layer's share of the goal, and say
|
|
134
|
-
so when the packet doesn't account for something the goal asks for.
|
|
135
|
-
When `hedgehog verify` closes the **last** layer of an intent it prints
|
|
136
|
-
that goal and outcome back as an **INTENT CHECK**: read the built work
|
|
137
|
-
against it there, because nothing else in the build does.
|
|
138
|
-
|
|
139
|
-
A layer that discovers a limitation the next layer has to compensate for
|
|
140
|
-
records it with `hedgehog debt add <task-id> "<note>"` — it lands in the
|
|
141
|
-
**INHERITED DEBT** section of every packet that depends on that task. A
|
|
142
|
-
comment in a source file is not a mechanism; nothing reads it.
|
|
143
|
-
|
|
144
|
-
A layer that makes a choice a dependent layer needs to know about — a
|
|
145
|
-
pattern, a library, a trade-off, anything the next task should follow
|
|
146
|
-
rather than reinvent or contradict — records it with `hedgehog decision
|
|
147
|
-
add <task-id> "<note>"`, landing in the **INHERITED DECISIONS** section
|
|
148
|
-
the same way. Debt is what's still wrong with a task; a decision is why
|
|
149
|
-
it was built the way it was.
|
|
150
|
-
|
|
151
|
-
`planner` owns writing intents (`hedgehog intent add`) at planning
|
|
152
|
-
intake; `hedgehog plan` compiles them into the task graph the loop
|
|
153
|
-
consumes. Nothing checks a box — there is no checklist, only queryable
|
|
154
|
-
state.
|
|
155
|
-
|
|
156
|
-
**When the build is done:** once `hedgehog status` shows every task
|
|
157
|
-
`complete` and `hedgehog boundary` exits 0 (see **Managing context**
|
|
158
|
-
below — it checks nothing-in-flight, a clean tree, and a closed intent
|
|
159
|
-
together), the build session is complete. The permanent record is the committed
|
|
160
|
-
intents (`.hedgehog/intents/*.json`), the friction log
|
|
161
|
-
(`.hedgehog/friction/*.md`), the core definition (root `core.yaml` for a
|
|
162
|
-
shipped core, `.hedgehog/core.yaml` for an authored one), and the git
|
|
163
|
-
commit history itself — not the database. `.hedgehog/hedgehog.db` is gitignored: a
|
|
164
|
-
derived index, rebuildable at any time via `hedgehog db rebuild`, which
|
|
165
|
-
replays those committed sources against git history. That rebuild also
|
|
166
|
-
runs automatically on a fresh clone when the DB is missing but
|
|
167
|
-
`.hedgehog/intents/` exists. That's what makes every later session
|
|
168
|
-
cheap.
|
|
169
|
-
|
|
170
|
-
A completed build is **extendable, not sealed**. Offer the user a
|
|
171
|
-
fresh-context handoff, and name both ways forward:
|
|
172
|
-
|
|
173
|
-
- **Adjustments to what's built** → the `tweaker` agent, from a *new*
|
|
174
|
-
chat window, not a subagent call inside this one — this session's
|
|
175
|
-
context has been building the whole project and is exactly what
|
|
176
|
-
"clearing context now costs nothing" (above) means to discard. Tell
|
|
177
|
-
the user plainly: close this chat window and open a new one, then
|
|
178
|
-
paste this to start it:
|
|
179
|
-
|
|
180
|
-
> The build for {{PROJECT_NAME}} is complete. Use the tweaker agent:
|
|
181
|
-
> first review the friction log and ask me for feedback on the build,
|
|
182
|
-
> then take my tweak requests one at a time.
|
|
183
|
-
|
|
184
|
-
In the new window, `tweaker` starts clean, once reviews the friction
|
|
185
|
-
log (`hedgehog friction list`) for possible discipline-improvement
|
|
186
|
-
issues and separately asks the user directly for feedback on the
|
|
187
|
-
build, filing each real pattern or piece of feedback as its own GitHub
|
|
188
|
-
issue against the Hedgehog repo itself, never this project's repo
|
|
189
|
-
(friction as `bug`/`help wanted`, feedback as `suggestion`, each only
|
|
190
|
-
after showing the exact content and getting explicit approval), then
|
|
191
|
-
takes any tweak requests one at a time.
|
|
192
|
-
- **New scope** — a new module or feature, anything beyond adjusting what
|
|
193
|
-
exists → on a core with a module axis, the `planner` agent, which runs
|
|
194
|
-
`hedgehog-planning-intake`'s **Re-entry pass**. It reads the existing
|
|
195
|
-
planning archive as context and elicits only what's new, then adds
|
|
196
|
-
intents and runs `hedgehog plan`. This is append-only: `plan` skips
|
|
197
|
-
intents already compiled, so every `complete` task keeps its status and
|
|
198
|
-
its commits, and `hedgehog claim` resumes at the first tasks of the new
|
|
199
|
-
work. Planning is not re-run from scratch, and the workspace is not
|
|
200
|
-
re-scaffolded. (This core's own section above states where new scope
|
|
201
|
-
goes if this core has no module axis to add an intent to.)
|
|
202
|
-
|
|
203
|
-
If a request turns out to be structural rather than either of those —
|
|
204
|
-
something already built is wrong at its source — that's the Correction
|
|
205
|
-
Protocol's post-build entry, in this core's own loop skill.
|
|
206
|
-
|
|
207
|
-
## Managing context
|
|
208
|
-
|
|
209
|
-
Hedgehog is designed so the conversation is disposable. Keep the working
|
|
210
|
-
context small:
|
|
211
|
-
|
|
212
|
-
- **Clear context at natural boundaries** — a module's Phase A, a
|
|
213
|
-
landing page section, whatever this core's own unit boundary is — once
|
|
214
|
-
that unit is done and committed. Ask `hedgehog boundary` rather than
|
|
215
|
-
judging it: it exits 0 only when all three of nothing-in-flight, a
|
|
216
|
-
clean working tree, and a last closed task that completed its intent
|
|
217
|
-
hold, and names which one failed otherwise. Clear the conversation and
|
|
218
|
-
start fresh, then run `hedgehog status`/`hedgehog claim` and continue.
|
|
219
|
-
Nothing is lost, because the build graph, commits, and code hold all
|
|
220
|
-
the state. Prefer this over letting one session accumulate the entire
|
|
221
|
-
project.
|
|
222
|
-
- **`hedgehog quiesce` and `hedgehog boundary` answer different
|
|
223
|
-
questions.** `quiesce` reports whether anything is still in flight —
|
|
224
|
-
necessary before clearing (clearing while a lease is outstanding
|
|
225
|
-
orphans that lease until it expires), but not sufficient: a graph can
|
|
226
|
-
be perfectly settled halfway through an intent, with a dirty working
|
|
227
|
-
tree. `boundary` is the whole question — is this a moment to throw the
|
|
228
|
-
conversation away — and it includes the `quiesce` check as its first
|
|
229
|
-
condition. Use `quiesce` when you're waiting for dispatched work to
|
|
230
|
-
land (the Correction Protocol), `boundary` when you're deciding whether
|
|
231
|
-
to clear.
|
|
232
|
-
- **A cleared or new session recovers by running `hedgehog status` and
|
|
233
|
-
reading the commit log**, never by needing the prior conversation.
|
|
234
|
-
`hedgehog boundary --handoff` prints that recovery block directly —
|
|
235
|
-
where the build is, what's next and why, what's in flight, what's
|
|
236
|
-
blocked — derived from the graph, so no session hands a summary to the
|
|
237
|
-
next one.
|
|
238
|
-
- **Delegate heavy work to agents.** Scaffolding and every build step
|
|
239
|
-
run in their own isolated context, so work doesn't pile up in the
|
|
240
|
-
main thread. Planning intake's BMAD Phase 0 is the exception — see
|
|
241
|
-
**First message in a fresh install** above — and stays here through
|
|
242
|
-
Confirm & Lock; the mining, `bootstrap`, and per-module steps after it
|
|
243
|
-
delegate as usual.
|
|
244
|
-
- **Don't paste large context back in.** If you find yourself
|
|
245
|
-
re-explaining the architecture, stop — it's fixed and stated in this
|
|
246
|
-
file's core section, not something to reconstruct. If you need a
|
|
247
|
-
project specific, read it from the code. That's the self-documenting
|
|
248
|
-
design working as intended.
|
|
104
|
+
## Running the build
|
|
105
|
+
|
|
106
|
+
The build graph is the source of truth for what's next — never re-derive
|
|
107
|
+
build state from prose. The `hedgehog-orchestrating` skill owns the claim →
|
|
108
|
+
dispatch → verify cycle, the intent check, debt and decision recording, the
|
|
109
|
+
context boundaries to clear at, and the post-build handoff. Read it at the
|
|
110
|
+
start of every session and follow it.
|
|
249
111
|
|
|
250
112
|
{{HOST_DISPATCH}}
|