@hone-ai/cli 1.10.0 → 1.12.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/hone-cli.js CHANGED
@@ -387,7 +387,12 @@ program
387
387
  process.exit(1);
388
388
  }
389
389
 
390
- // ── Phase 1b: Install Claude Code files (CLAUDE.md + .claude/agents/) ────
390
+ // ── Phase 1b: Install CLAUDE.md ──────────────────────────────────────────
391
+ // HC-019y: removed the install-time .github/agents/ -> .claude/agents/
392
+ // mirror. The bash setup script now writes agents directly to
393
+ // .claude/agents/ (the path Claude Code actually reads). Pre-fix the
394
+ // mirror ran only at install time, so post-install edits to
395
+ // .github/agents/ were silently dropped (OptionsFlow E34-A: 6+ PRs).
391
396
  const repoRoot = process.cwd();
392
397
  const claudeMdSrc = path.join(tmpDir, 'CLAUDE.md');
393
398
  const claudeMdDst = path.join(repoRoot, 'CLAUDE.md');
@@ -402,20 +407,6 @@ program
402
407
  }
403
408
  }
404
409
 
405
- // Create .claude/agents/ with symlinks or copies of .github/agents/
406
- const ghAgentsDir = path.join(repoRoot, '.github', 'agents');
407
- const claudeAgentsDir = path.join(repoRoot, '.claude', 'agents');
408
- if (fs.existsSync(ghAgentsDir)) {
409
- fs.mkdirSync(claudeAgentsDir, { recursive: true });
410
- const agentFiles = fs.readdirSync(ghAgentsDir).filter(f => f.endsWith('.agent.md'));
411
- for (const file of agentFiles) {
412
- const src = path.join(ghAgentsDir, file);
413
- const dst = path.join(claudeAgentsDir, file);
414
- fs.copyFileSync(src, dst);
415
- }
416
- console.log(` ✓ .claude/agents/ created (${agentFiles.length} agents mirrored from .github/agents/)`);
417
- }
418
-
419
410
  // ── Phase 1c: Platform grounding (HC-013b-setup) ──────────────────────
420
411
  // Runs AFTER bash script writes .pipeline-config.yml.
421
412
  // Discovers metadata types, detects MCP, selects doc registry URLs,
@@ -1096,13 +1087,24 @@ program
1096
1087
  // Write generated skills to disk
1097
1088
  console.log('Writing generated skills...');
1098
1089
  if (result.skills) {
1090
+ let preservedCount = 0;
1099
1091
  for (const [skillName, content] of Object.entries(result.skills)) {
1100
1092
  if (!content || content.length < 50) continue;
1101
1093
  const skillDir = path.join(repoRoot, '.github', 'skills', skillName);
1102
1094
  const skillFile = path.join(skillDir, 'SKILL.md');
1103
1095
  fs.mkdirSync(skillDir, { recursive: true });
1104
- fs.writeFileSync(skillFile, content);
1105
- console.log(` ✓ .github/skills/${skillName}/SKILL.md`);
1096
+ const { merged, preserved } = splicePreserveRepoSpecific(skillFile, content);
1097
+ fs.writeFileSync(skillFile, merged);
1098
+ if (preserved) {
1099
+ preservedCount++;
1100
+ console.log(` ✓ .github/skills/${skillName}/SKILL.md (preserved adopter REPO-SPECIFIC content)`);
1101
+ } else {
1102
+ console.log(` ✓ .github/skills/${skillName}/SKILL.md`);
1103
+ }
1104
+ }
1105
+ if (preservedCount > 0) {
1106
+ console.log(`\n Preserved adopter REPO-SPECIFIC content in ${preservedCount} skill(s).`);
1107
+ console.log(` (HC-019y-followup-1: derive no longer overwrites curated REPO-SPECIFIC sections)`);
1106
1108
  }
1107
1109
  }
1108
1110
 
@@ -1505,7 +1507,12 @@ program
1505
1507
  }
1506
1508
  }
1507
1509
 
1508
- // ── 2. Sync agent prompts into .github/agents/*.agent.md ───────────────
1510
+ // ── 2. Sync agent prompts into .claude/agents/*.agent.md ───────────────
1511
+ // HC-019y: writes directly to .claude/agents/ (the path Claude Code
1512
+ // actually reads). Pre-fix `hone sync` wrote to .github/agents/ and
1513
+ // mirrored to .claude/agents/, but the dual-directory layout caused
1514
+ // OptionsFlow E34-A's silent-drop trap when adopters edited the wrong
1515
+ // path. Mirror eliminated; .github/agents/ is now legacy.
1509
1516
  if (!opts.skillsOnly) {
1510
1517
  const agents = [
1511
1518
  'story-groomer',
@@ -1520,9 +1527,16 @@ program
1520
1527
  ];
1521
1528
 
1522
1529
  console.log('\nSyncing agent prompts...');
1523
- const agentsDir = path.join(process.cwd(), '.github', 'agents');
1530
+ const agentsDir = path.join(process.cwd(), '.claude', 'agents');
1524
1531
  fs.mkdirSync(agentsDir, { recursive: true });
1525
1532
 
1533
+ // Legacy-path migration warning (one-time, non-destructive).
1534
+ const legacyGhAgents = path.join(process.cwd(), '.github', 'agents');
1535
+ if (fs.existsSync(legacyGhAgents)) {
1536
+ console.log(' ⚠ Legacy .github/agents/ detected — Claude Code reads .claude/agents/');
1537
+ console.log(' Migrate any REPO-SPECIFIC edits then `rm -rf .github/agents/`');
1538
+ }
1539
+
1526
1540
  let synced = 0;
1527
1541
  for (const agent of agents) {
1528
1542
  try {
@@ -1531,7 +1545,7 @@ program
1531
1545
  headers: { Accept: 'text/markdown' },
1532
1546
  });
1533
1547
  const dest = path.join(agentsDir, `${agent}.agent.md`);
1534
- const rel = `agents/${agent}.agent.md`;
1548
+ const rel = `.claude/agents/${agent}.agent.md`;
1535
1549
  const result = safeWrite(dest, rel, r.data);
1536
1550
  if (result !== 'skipped') {
1537
1551
  console.log(` ✓ ${rel}`);
@@ -1539,28 +1553,13 @@ program
1539
1553
  }
1540
1554
  } catch (e) {
1541
1555
  if (e.response?.status === 404) {
1542
- console.log(` ⚠ agents/${agent}.agent.md — prompt not seeded yet (run seed-agent-prompts.js)`);
1556
+ console.log(` ⚠ .claude/agents/${agent}.agent.md — prompt not seeded yet (run seed-agent-prompts.js)`);
1543
1557
  } else {
1544
- console.log(` ⚠ agents/${agent}.agent.md — ${e.message}`);
1558
+ console.log(` ⚠ .claude/agents/${agent}.agent.md — ${e.message}`);
1545
1559
  }
1546
1560
  }
1547
1561
  }
1548
1562
  if (synced > 0) console.log(`\nSynced ${synced} agent prompts.`);
1549
-
1550
- // Mirror to .claude/agents/ for Claude Code. This mirror is a derived
1551
- // copy of .github/agents/, so it's safe to overwrite — local edits
1552
- // belong in the source-of-truth dir. We still respect skipped files:
1553
- // if the source was skipped, don't overwrite the mirror either.
1554
- const claudeAgentsDir = path.join(process.cwd(), '.claude', 'agents');
1555
- const ghAgentsDir = path.join(process.cwd(), '.github', 'agents');
1556
- if (fs.existsSync(ghAgentsDir)) {
1557
- fs.mkdirSync(claudeAgentsDir, { recursive: true });
1558
- const files = fs.readdirSync(ghAgentsDir).filter(f => f.endsWith('.agent.md'));
1559
- for (const f of files) {
1560
- fs.copyFileSync(path.join(ghAgentsDir, f), path.join(claudeAgentsDir, f));
1561
- }
1562
- console.log(` ✓ .claude/agents/ mirrored (${files.length} agents)`);
1563
- }
1564
1563
  }
1565
1564
 
1566
1565
  // ── 3. Sync copilot-instructions.md from server ──────────────────────
@@ -2029,7 +2028,7 @@ program
2029
2028
  console.log('╚══════════════════════════════════════════════╝');
2030
2029
  console.log('');
2031
2030
  console.log(' Your repo now has:');
2032
- console.log(' • 9 agents (.github/agents/ + .claude/agents/)');
2031
+ console.log(' • 9 agents (.claude/agents/)');
2033
2032
  console.log(' • 11 enterprise skills + domain skills');
2034
2033
  console.log(' • CI workflow (ai-review.yml)');
2035
2034
  console.log(' • CLAUDE.md + copilot-instructions.md');
@@ -2611,6 +2610,50 @@ function sleep(ms) {
2611
2610
  return new Promise(resolve => setTimeout(resolve, ms));
2612
2611
  }
2613
2612
 
2613
+ /**
2614
+ * HC-019y-followup-1: when `hone derive` writes a SKILL.md, splice the
2615
+ * existing adopter REPO-SPECIFIC content over whatever the LLM produced
2616
+ * for that section. The semantic guarantee: adopter-curated content
2617
+ * (everything below the `<!-- REPO-SPECIFIC -->` marker) is NEVER lost
2618
+ * across derive runs.
2619
+ *
2620
+ * Pre-fix: derive wrote the file blind (fs.writeFileSync(skillFile,
2621
+ * content)). 5 OptionsFlow regression tests broke after a single derive
2622
+ * because 378 lines of E33-A/E34-B-added content disappeared.
2623
+ *
2624
+ * Returns { merged, preserved }:
2625
+ * - merged: the bytes to write (always a string)
2626
+ * - preserved: true if adopter REPO-SPECIFIC content was preserved
2627
+ * across the splice; false for fresh writes
2628
+ */
2629
+ function splicePreserveRepoSpecific(existingPath, newContent) {
2630
+ const MARKER = '<!-- REPO-SPECIFIC';
2631
+ if (!fs.existsSync(existingPath)) {
2632
+ // Fresh adopter — no existing file. Write new content as-is.
2633
+ return { merged: newContent, preserved: false };
2634
+ }
2635
+ const existing = fs.readFileSync(existingPath, 'utf8');
2636
+ const oldIdx = existing.indexOf(MARKER);
2637
+ if (oldIdx < 0) {
2638
+ // Existing file has no REPO-SPECIFIC marker (likely a non-skill or
2639
+ // legacy file). Nothing to preserve.
2640
+ return { merged: newContent, preserved: false };
2641
+ }
2642
+ const newIdx = newContent.indexOf(MARKER);
2643
+ if (newIdx < 0) {
2644
+ // New content has no REPO-SPECIFIC section. Append the existing one.
2645
+ return { merged: newContent.trimEnd() + '\n\n' + existing.slice(oldIdx), preserved: true };
2646
+ }
2647
+ // Both have REPO-SPECIFIC. Take new content up to the marker, then
2648
+ // append everything from existing's marker to EOF. This preserves
2649
+ // adopter-curated content while updating the body before the marker.
2650
+ return {
2651
+ merged: newContent.slice(0, newIdx) + existing.slice(oldIdx),
2652
+ preserved: true,
2653
+ };
2654
+ }
2655
+ module.exports.splicePreserveRepoSpecific = splicePreserveRepoSpecific;
2656
+
2614
2657
  // ── STATUS command (H-009) ────────────────────────────────────────────────────
2615
2658
  // Show pipeline state for a story (or use --all for every in-flight story).
2616
2659
  // Reads .github/pipeline/<STORY-ID>/metadata.yml + filesystem; uses
@@ -5252,4 +5295,9 @@ program
5252
5295
  .description('Hone AI — Enterprise SDLC Pipeline CLI')
5253
5296
  .version(pkg.version);
5254
5297
 
5255
- program.parse(process.argv);
5298
+ // HC-019y-followup-1: gate commander invocation so the file can be
5299
+ // require()'d by tests for helpers like splicePreserveRepoSpecific
5300
+ // without auto-running the CLI's --help.
5301
+ if (require.main === module) {
5302
+ program.parse(process.argv);
5303
+ }
@@ -26,9 +26,12 @@ const path = require('node:path');
26
26
  const crypto = require('node:crypto');
27
27
  const { execSync } = require('node:child_process');
28
28
 
29
+ // HC-019y (2026-05-29): removed `.github/agents` from refresh paths.
30
+ // Agents live exclusively at `.claude/agents/` (Claude Code's read path)
31
+ // after the install-time mirror was eliminated. `.github/agents/` is now
32
+ // a legacy directory adopters are expected to manually delete.
29
33
  const KNOWLEDGE_DIRS = [
30
34
  '.github/skills',
31
- '.github/agents',
32
35
  '.claude/agents',
33
36
  '.github/copilot-instructions.md',
34
37
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hone-ai/cli",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Hone AI — Enterprise SDLC Pipeline CLI",
5
5
  "main": "hone-cli.js",
6
6
  "bin": {