@hone-ai/cli 1.14.0 → 1.16.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.
Files changed (2) hide show
  1. package/hone-cli.js +81 -0
  2. package/package.json +1 -1
package/hone-cli.js CHANGED
@@ -4364,6 +4364,7 @@ program
4364
4364
  .option('--approve <step>', 'Approve a paused gate (e.g., --approve step_0)')
4365
4365
  .option('--format <fmt>', 'Output format: pretty or json', 'pretty')
4366
4366
  .option('--poll-interval <s>', 'Poll interval in seconds', '5')
4367
+ .option('--include-paths <paths>', 'Comma-separated list of source file paths to bundle into the codebase context (HC-019n-followup-12). Augments auto-detected paths from the issue body.')
4367
4368
  .action(async (storyIdOrRunId, opts) => {
4368
4369
  const config = getConfig();
4369
4370
  const client = api(config);
@@ -4459,6 +4460,7 @@ program
4459
4460
  // The existing HC-019n-followup-7 hard_pause safety net catches the
4460
4461
  // resulting placeholder cascade at step_1.
4461
4462
  const orchestrateConfig = {};
4463
+ let issueBodyForFiles = null;
4462
4464
  if (/^\d+$/.test(storyIdOrRunId)) {
4463
4465
  try {
4464
4466
  const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
@@ -4472,6 +4474,7 @@ program
4472
4474
  const issue = JSON.parse(issueJson);
4473
4475
  const desc = `# ${issue.title}\n\n${issue.body || '_(issue body is empty)_'}\n\n_Source: ${issue.url}_`;
4474
4476
  orchestrateConfig.story_description = desc;
4477
+ issueBodyForFiles = `${issue.title}\n${issue.body || ''}`;
4475
4478
  console.log(` → fetched GitHub issue #${storyIdOrRunId} for story context (${desc.length} chars)`);
4476
4479
  } catch (e) {
4477
4480
  const msg = (e && e.message) ? e.message.split('\n')[0] : String(e);
@@ -4480,6 +4483,84 @@ program
4480
4483
  }
4481
4484
  }
4482
4485
 
4486
+ // HC-019n-followup-12: auto-bundle codebase files referenced in the
4487
+ // issue body (or by the operator via --include-paths). Without this,
4488
+ // step_1/step_4 have only training-data priors and produce code that
4489
+ // matches the SHAPE of the adopter's stack but not its actual
4490
+ // architecture (cf. OptionsFlow #104 step_4 hallucinating SQLAlchemy
4491
+ // for a JSON-lines repo). The bundle is capped at 10 files / 50K
4492
+ // chars total to stay well inside the LLM context window.
4493
+ //
4494
+ // Detection scans the issue body for code-file-like paths:
4495
+ // - foo/bar/baz.py (relative paths with extensions)
4496
+ // - tests/unit/test_foo.py (typical pytest structure)
4497
+ // - src/web/routes.py (src trees)
4498
+ // Excludes:
4499
+ // - https:// or http:// URLs (those aren't local files)
4500
+ // - paths starting with / or ~ (absolute / home — bundle would
4501
+ // escape the repo)
4502
+ // - .md / .txt / .yml docs (rarely needed for code-builder; would
4503
+ // blow context with prose)
4504
+ if (issueBodyForFiles || opts.includePaths) {
4505
+ try {
4506
+ // HC-019n-followup-13c: word-boundary `\b` at end of extension
4507
+ // group so `.jsonl` doesn't match as `.js` and `.tsx` doesn't
4508
+ // match as `.ts`. Pre-fix, both `logs/calibration_runs.jsonl`
4509
+ // and `src/web/app.tsx` got truncated. The truncated paths
4510
+ // failed the downstream `fs.existsSync` check so they didn't
4511
+ // crash the bundling, but they polluted the regex output set
4512
+ // and would have produced confusing "file not found" warnings
4513
+ // if we ever tightened the diagnostic logging.
4514
+ const FILE_PATH_RE = /(?<![\w/])([a-zA-Z_][\w-]*\/(?:[\w.-]+\/)*[\w.-]+\.(?:py|js|ts|jsx|tsx|go|rs|java|rb|sh|sql))\b/g;
4515
+ const candidates = new Set();
4516
+ if (issueBodyForFiles) {
4517
+ for (const m of issueBodyForFiles.matchAll(FILE_PATH_RE)) candidates.add(m[1]);
4518
+ }
4519
+ if (opts.includePaths) {
4520
+ // --include-paths a/b.py,c/d.py
4521
+ for (const p of String(opts.includePaths).split(',').map(s => s.trim()).filter(Boolean)) {
4522
+ candidates.add(p);
4523
+ }
4524
+ }
4525
+ // HC-019n-followup-13d: bump caps. Pre-fix MAX_FILES=10 /
4526
+ // MAX_TOTAL_CHARS=50_000 / MAX_PER_FILE_CHARS=10_000 was overly
4527
+ // conservative — OptionsFlow #96 hit 49,783/50,000 with 6 files
4528
+ // (217 chars to spare), meaning the next slightly-larger story
4529
+ // silently dropped files. LLM context windows are 200K+; the
4530
+ // bundle should be able to fill ~25% of that without surprise.
4531
+ // New caps: 30 / 150_000 / 25_000.
4532
+ const MAX_FILES = 30;
4533
+ const MAX_TOTAL_CHARS = 150_000;
4534
+ const MAX_PER_FILE_CHARS = 25_000;
4535
+ const codebaseFiles = [];
4536
+ let totalChars = 0;
4537
+ for (const candidate of candidates) {
4538
+ if (codebaseFiles.length >= MAX_FILES) break;
4539
+ if (candidate.startsWith('/') || candidate.startsWith('~') || candidate.includes('..')) continue;
4540
+ const abs = path.resolve(process.cwd(), candidate);
4541
+ if (!abs.startsWith(process.cwd() + path.sep)) continue; // escape guard
4542
+ if (!fs.existsSync(abs)) continue;
4543
+ let content;
4544
+ try {
4545
+ content = fs.readFileSync(abs, 'utf8');
4546
+ } catch { continue; }
4547
+ if (content.length > MAX_PER_FILE_CHARS) {
4548
+ content = content.slice(0, MAX_PER_FILE_CHARS) + `\n\n# [HC-019n-followup-12: truncated at ${MAX_PER_FILE_CHARS} chars; full file is ${content.length} chars]`;
4549
+ }
4550
+ if (totalChars + content.length > MAX_TOTAL_CHARS) break;
4551
+ totalChars += content.length;
4552
+ codebaseFiles.push({ path: candidate, content });
4553
+ }
4554
+ if (codebaseFiles.length > 0) {
4555
+ orchestrateConfig.codebaseFiles = codebaseFiles;
4556
+ console.log(` → bundled ${codebaseFiles.length} codebase file(s) for step_1/step_4 context (${totalChars} chars)`);
4557
+ }
4558
+ } catch (e) {
4559
+ const msg = (e && e.message) ? e.message.split('\n')[0] : String(e);
4560
+ console.warn(` ⚠ codebase bundling failed (non-fatal): ${msg}`);
4561
+ }
4562
+ }
4563
+
4483
4564
  try {
4484
4565
  const { data } = await client.post('/orchestrate', {
4485
4566
  storyId: storyIdOrRunId, repoName, branch, mode: opts.mode, config: orchestrateConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hone-ai/cli",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "Hone AI — Enterprise SDLC Pipeline CLI",
5
5
  "main": "hone-cli.js",
6
6
  "bin": {