@hone-ai/cli 1.13.0 → 1.15.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 +100 -1
  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);
@@ -4447,9 +4448,107 @@ program
4447
4448
  try { branch = execSync('git symbolic-ref --short HEAD', { encoding: 'utf8' }).trim(); } catch { branch = null; }
4448
4449
  }
4449
4450
 
4451
+ // HC-019n-followup-11: when storyId is a bare integer, treat it as a
4452
+ // GitHub issue number and fetch the issue's title + body via the local
4453
+ // `gh` CLI. Inject into config.story_description so the orchestrator's
4454
+ // buildStepContext (HC-019n root-cause fix B) hands the real story
4455
+ // text to step_0 instead of letting the LLM produce placeholder
4456
+ // [ASSUMPTION] grooming. Hook reused — no server change required.
4457
+ //
4458
+ // Gracefully degrade on any error (gh missing, no remote, network
4459
+ // fail, private repo without auth): warn and proceed without context.
4460
+ // The existing HC-019n-followup-7 hard_pause safety net catches the
4461
+ // resulting placeholder cascade at step_1.
4462
+ const orchestrateConfig = {};
4463
+ let issueBodyForFiles = null;
4464
+ if (/^\d+$/.test(storyIdOrRunId)) {
4465
+ try {
4466
+ const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
4467
+ const m = remoteUrl.match(/[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
4468
+ if (!m) throw new Error(`could not parse owner/repo from remote: ${remoteUrl}`);
4469
+ const ownerRepo = `${m[1]}/${m[2]}`;
4470
+ const issueJson = execSync(
4471
+ `gh issue view ${storyIdOrRunId} --repo ${ownerRepo} --json title,body,url`,
4472
+ { encoding: 'utf8' }
4473
+ );
4474
+ const issue = JSON.parse(issueJson);
4475
+ const desc = `# ${issue.title}\n\n${issue.body || '_(issue body is empty)_'}\n\n_Source: ${issue.url}_`;
4476
+ orchestrateConfig.story_description = desc;
4477
+ issueBodyForFiles = `${issue.title}\n${issue.body || ''}`;
4478
+ console.log(` → fetched GitHub issue #${storyIdOrRunId} for story context (${desc.length} chars)`);
4479
+ } catch (e) {
4480
+ const msg = (e && e.message) ? e.message.split('\n')[0] : String(e);
4481
+ console.warn(` ⚠ could not fetch GitHub issue context: ${msg}`);
4482
+ console.warn(' → proceeding without story_description; step_0 may produce placeholder output');
4483
+ }
4484
+ }
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
+ const FILE_PATH_RE = /(?<![\w/])([a-zA-Z_][\w-]*\/(?:[\w.-]+\/)*[\w.-]+\.(?:py|js|ts|jsx|tsx|go|rs|java|rb|sh|sql))/g;
4507
+ const candidates = new Set();
4508
+ if (issueBodyForFiles) {
4509
+ for (const m of issueBodyForFiles.matchAll(FILE_PATH_RE)) candidates.add(m[1]);
4510
+ }
4511
+ if (opts.includePaths) {
4512
+ // --include-paths a/b.py,c/d.py
4513
+ for (const p of String(opts.includePaths).split(',').map(s => s.trim()).filter(Boolean)) {
4514
+ candidates.add(p);
4515
+ }
4516
+ }
4517
+ const MAX_FILES = 10;
4518
+ const MAX_TOTAL_CHARS = 50_000;
4519
+ const MAX_PER_FILE_CHARS = 10_000;
4520
+ const codebaseFiles = [];
4521
+ let totalChars = 0;
4522
+ for (const candidate of candidates) {
4523
+ if (codebaseFiles.length >= MAX_FILES) break;
4524
+ if (candidate.startsWith('/') || candidate.startsWith('~') || candidate.includes('..')) continue;
4525
+ const abs = path.resolve(process.cwd(), candidate);
4526
+ if (!abs.startsWith(process.cwd() + path.sep)) continue; // escape guard
4527
+ if (!fs.existsSync(abs)) continue;
4528
+ let content;
4529
+ try {
4530
+ content = fs.readFileSync(abs, 'utf8');
4531
+ } catch { continue; }
4532
+ if (content.length > MAX_PER_FILE_CHARS) {
4533
+ 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]`;
4534
+ }
4535
+ if (totalChars + content.length > MAX_TOTAL_CHARS) break;
4536
+ totalChars += content.length;
4537
+ codebaseFiles.push({ path: candidate, content });
4538
+ }
4539
+ if (codebaseFiles.length > 0) {
4540
+ orchestrateConfig.codebaseFiles = codebaseFiles;
4541
+ console.log(` → bundled ${codebaseFiles.length} codebase file(s) for step_1/step_4 context (${totalChars} chars)`);
4542
+ }
4543
+ } catch (e) {
4544
+ const msg = (e && e.message) ? e.message.split('\n')[0] : String(e);
4545
+ console.warn(` ⚠ codebase bundling failed (non-fatal): ${msg}`);
4546
+ }
4547
+ }
4548
+
4450
4549
  try {
4451
4550
  const { data } = await client.post('/orchestrate', {
4452
- storyId: storyIdOrRunId, repoName, branch, mode: opts.mode, config: {},
4551
+ storyId: storyIdOrRunId, repoName, branch, mode: opts.mode, config: orchestrateConfig,
4453
4552
  });
4454
4553
 
4455
4554
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hone-ai/cli",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "Hone AI — Enterprise SDLC Pipeline CLI",
5
5
  "main": "hone-cli.js",
6
6
  "bin": {