@skyf0xx/hedgehog 6.2.4 → 6.2.5

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 CHANGED
@@ -21,7 +21,7 @@ import { spawn, execFileSync } from 'node:child_process';
21
21
  import { dbInit, DB_PATH, dbAbsPath, openDb, openDbAt } from '../src/db/init.mjs';
22
22
  import { loadCore, lintCore, isModuleAxis } from '../src/db/core.mjs';
23
23
  import { planTasks, CORE_INTENT_ID } from '../src/db/plan.mjs';
24
- import { addIntent, INTENTS_DIR } from '../src/db/intent.mjs';
24
+ import { addIntent, INTENTS_DIR, intentFilePath } from '../src/db/intent.mjs';
25
25
  import {
26
26
  nextTask,
27
27
  formatNext,
@@ -595,9 +595,13 @@ ${bold('Usage')}
595
595
  npx @skyf0xx/hedgehog abandon <intent-id> --reason "<why>"
596
596
  drop an intent that will never finish: records why,
597
597
  resets its tasks to planned on trunk, removes its
598
- worktree and branch
598
+ worktree and branch — the id stays taken; to change
599
+ an abandoned intent, edit its .hedgehog/intents/<id>.json
600
+ by hand and run 'hedgehog plan', not 'intent add' again
599
601
  npx @skyf0xx/hedgehog intent add [flags] add an intent (rules/requirements/dependencies)
600
- npx @skyf0xx/hedgehog intent add --file <path> add an intent from a JSON file
602
+ npx @skyf0xx/hedgehog intent add --file <path> add an intent from a JSON file — fails on an id already
603
+ in the build graph, including one reset by 'hedgehog
604
+ abandon' (still 'planned', not removed); see 'abandon' above
601
605
  npx @skyf0xx/hedgehog next print the task packet for one ready task
602
606
  npx @skyf0xx/hedgehog show <task-id> print the task packet for any task, at any status
603
607
  npx @skyf0xx/hedgehog claim --owner <owner> [--count <n>] atomically claim up to n ready tasks
@@ -1555,7 +1559,11 @@ async function planCommand(args = []) {
1555
1559
  }
1556
1560
 
1557
1561
  for (const { intentId, branch, path } of worktreesCreated) {
1558
- console.log(` ${green('worktree')} ${bold(intentId)} ${dim(`${branch} → ${path}`)}`);
1562
+ console.log(
1563
+ ` ${green('worktree')} ${bold(intentId)} ${dim(`${branch} → ${path}`)}\n` +
1564
+ ` ${dim(`compiling here, not onto trunk, because every intent ${intentId} depends_on is already complete —`)}\n` +
1565
+ ` ${dim(`see \`hedgehog merge ${intentId}\`/\`hedgehog abandon ${intentId}\` to close it out later`)}`,
1566
+ );
1559
1567
  }
1560
1568
 
1561
1569
  // Compiles the new worktree's own graph from inside it — a plain
@@ -1728,8 +1736,15 @@ async function intentCommand(args) {
1728
1736
  try {
1729
1737
  intent = await addIntent(db, record);
1730
1738
  } catch (err) {
1739
+ const idTaken = /UNIQUE constraint failed: intents\.id/.test(err.message);
1731
1740
  console.error(
1732
- `${red('Failed to add intent:')} ${err.message}\n\nUsage: hedgehog intent add --id <id> --goal <goal> --outcome <outcome> [--rule <r>]... [--depends-on <id>]...\n or: hedgehog intent add --file <path.json>\n`,
1741
+ `${red('Failed to add intent:')} ${err.message}\n\n` +
1742
+ (idTaken
1743
+ ? `${bold(record.id)} already exists in the build graph — this includes an intent \`hedgehog abandon\`\n` +
1744
+ `reset to 'planned' rather than removed. To change it, edit ${intentFilePath(record.id)}\n` +
1745
+ `by hand and run \`hedgehog plan\` to recompile; don't run \`intent add\` again for this id.\n\n`
1746
+ : '') +
1747
+ `Usage: hedgehog intent add --id <id> --goal <goal> --outcome <outcome> [--rule <r>]... [--depends-on <id>]...\n or: hedgehog intent add --file <path.json>\n`,
1733
1748
  );
1734
1749
  process.exitCode = 1;
1735
1750
  return;
@@ -3705,7 +3720,10 @@ async function abandonCommand(args) {
3705
3720
  console.log(
3706
3721
  `\n${bold('Abandoned.')} ${dim(`${id} is reset to planned on trunk. Commit ${ABANDONED_DIR}/${id.toLowerCase()}.json —`)}\n` +
3707
3722
  `${dim('the build graph is derived and gitignored, and an uncommitted abandonment')}\n` +
3708
- `${dim('is reverted by the next `hedgehog db rebuild`.')}\n`,
3723
+ `${dim('is reverted by the next `hedgehog db rebuild`.')}\n\n` +
3724
+ `${dim(`${bold(id)} still exists in the build graph at 'planned' — do not run \`hedgehog intent add\` for`)}\n` +
3725
+ `${dim(`this id again (it will fail: the id is already taken). To change the intent, edit`)}\n` +
3726
+ `${dim(`${intentFilePath(id)} by hand and run \`hedgehog plan\` to recompile it.`)}\n`,
3709
3727
  );
3710
3728
  }
3711
3729
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyf0xx/hedgehog",
3
- "version": "6.2.4",
3
+ "version": "6.2.5",
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": {
@@ -431,6 +431,26 @@ function resetIntentTasksToPlanned(db, intentId) {
431
431
  .all(intentId);
432
432
  }
433
433
 
434
+ // Deletes every `intent_dependencies` row naming `intentId` on either
435
+ // side. An abandoned intent is reset to `planned` with no compiled tasks
436
+ // on trunk, so a `depends_on` edge pointing at it (something else declared
437
+ // as depending on it) or away from it (its own declared dependency) is
438
+ // both stale the moment abandonment lands: `eligibleIntents` (this file)
439
+ // reads that table directly to decide worktree eligibility, and a row
440
+ // surviving abandonment lets an unrelated intent read as "its dependency
441
+ // is satisfied" against an intent that no longer has any real status to
442
+ // satisfy anything with, or lets this intent re-read as eligible against a
443
+ // dependency it no longer declares. Re-adding the same id later
444
+ // (`hedgehog intent add --file` with a JSON that still declares the
445
+ // dependency) recreates the row from that file, same as any first-time
446
+ // add — this only clears what abandonment made stale, not what a future
447
+ // add might legitimately restate.
448
+ function clearIntentDependencies(db, intentId) {
449
+ db.prepare(
450
+ 'DELETE FROM intent_dependencies WHERE intent_id = ? OR depends_on_intent_id = ?',
451
+ ).run(intentId, intentId);
452
+ }
453
+
434
454
  // Resets every task of `intentId` to `planned` on whichever DB `db` is
435
455
  // open against (trunk, by the CLI's own contract — see abandonCommand),
436
456
  // clears any lease, and reopens the intent itself to `planned` so a later
@@ -440,6 +460,11 @@ function resetIntentTasksToPlanned(db, intentId) {
440
460
  // tasks that don't exist yet). Mirrors reconcile.mjs#applyReconciliation's
441
461
  // shape: the committed file is written first, this is applied second.
442
462
  //
463
+ // Also clears every `intent_dependencies` row naming this intent, on
464
+ // either side (clearIntentDependencies, above) — an abandoned intent's own
465
+ // declared dependency and any other intent's dependency on it are both
466
+ // stale the instant it resets to `planned` with no shipped work.
467
+ //
443
468
  // Refuses an intent already `complete` (merged, real shipped work) —
444
469
  // same reasoning as reconcile.mjs#confirmReconciliation refusing an
445
470
  // already-complete task: applyAbandonment resets tasks to 'planned'
@@ -458,6 +483,7 @@ export function applyAbandonment(db, intentId) {
458
483
  );
459
484
  }
460
485
  const resetTasks = resetIntentTasksToPlanned(db, intentId);
486
+ clearIntentDependencies(db, intentId);
461
487
 
462
488
  if (intent) {
463
489
  db.prepare("UPDATE intents SET status = 'planned' WHERE id = ?").run(intentId);
@@ -501,6 +527,7 @@ export function replayAbandonments(db, abandonments) {
501
527
  }
502
528
  setPlanned.run(intentId);
503
529
  resetIntentTasksToPlanned(db, intentId);
530
+ clearIntentDependencies(db, intentId);
504
531
  replayed.push({ intentId, reason: record.reason });
505
532
  }
506
533
  return { replayed, orphaned };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgehog",
3
- "version": "6.2.4",
3
+ "version": "6.2.5",
4
4
  "description": "Hedgehog build discipline: ordered, tested, verified build steps.",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }