@hone-ai/cli 1.7.1 → 1.8.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 +194 -0
  2. package/package.json +1 -1
package/hone-cli.js CHANGED
@@ -4235,6 +4235,200 @@ program
4235
4235
  process.exit(results.failed + results.errors > 0 ? 1 : 0);
4236
4236
  });
4237
4237
 
4238
+ // ── HC-041: Run Story (Orchestrator) ─────────────────────────────────────────
4239
+ program
4240
+ .command('run-story <storyId>')
4241
+ .description('Run the SDLC pipeline for a story via the orchestrator')
4242
+ .option('--mode <mode>', 'Execution mode: interactive or batch', 'interactive')
4243
+ .option('--repo <name>', 'Repository name override (default: directory name)')
4244
+ .option('--branch <name>', 'Git branch (default: current)')
4245
+ .option('--status', 'Show status of the latest workflow run for this story')
4246
+ .option('--kill', 'Kill the latest workflow run for this story')
4247
+ .option('--approve <step>', 'Approve a paused gate (e.g., --approve step_0)')
4248
+ .option('--format <fmt>', 'Output format: pretty or json', 'pretty')
4249
+ .option('--poll-interval <s>', 'Poll interval in seconds', '5')
4250
+ .action(async (storyIdOrRunId, opts) => {
4251
+ const config = getConfig();
4252
+ const client = api(config);
4253
+
4254
+ // Resolve: if it looks like a UUID, use as runId directly.
4255
+ // Otherwise treat as storyId — will be used for POST /orchestrate body.
4256
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(storyIdOrRunId);
4257
+ const storyId = isUuid ? null : storyIdOrRunId;
4258
+ const runId = isUuid ? storyIdOrRunId : null;
4259
+
4260
+ // Status mode
4261
+ if (opts.status) {
4262
+ const statusId = runId || storyIdOrRunId;
4263
+ try {
4264
+ const { data } = await client.get(`/orchestrate/${statusId}`);
4265
+ if (opts.format === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
4266
+ console.log('');
4267
+ console.log(`Workflow: ${data.runId}`);
4268
+ console.log(`Story: ${data.storyId}`);
4269
+ console.log(`Status: ${data.status}`);
4270
+ console.log(`Mode: ${data.mode}`);
4271
+ console.log(`Tokens: ${(data.totalTokens || 0).toLocaleString()}`);
4272
+ if (data.errorMessage) console.log(`Error: ${data.errorMessage}`);
4273
+ console.log('');
4274
+ console.log('Steps:');
4275
+ for (const s of (data.steps || [])) {
4276
+ const icon = s.status === 'completed' ? '✓' : s.status === 'running' ? '⏳' : s.status === 'failed' ? '✗' : '⬜';
4277
+ const gate = s.gateResult ? ` (${s.gateResult})` : '';
4278
+ console.log(` ${icon} ${s.stepKey} (${s.agent}) ${s.status}${gate}`);
4279
+ }
4280
+ if (data.awaitingApproval) {
4281
+ console.log('');
4282
+ console.log(`Awaiting approval at ${data.currentStep}.`);
4283
+ console.log(`Approve: hone run-story ${data.runId} --approve ${data.currentStep}`);
4284
+ }
4285
+ console.log('');
4286
+ } catch (e) {
4287
+ console.error(`Failed: ${e.response?.data?.error || e.message}`);
4288
+ process.exit(1);
4289
+ }
4290
+ return;
4291
+ }
4292
+
4293
+ // Kill mode
4294
+ if (opts.kill) {
4295
+ const killId = runId || storyIdOrRunId;
4296
+ try {
4297
+ const { data } = await client.post(`/orchestrate/${killId}/kill`, { reason: 'CLI kill' });
4298
+ console.log(`Workflow ${data.runId} killed.`);
4299
+ } catch (e) {
4300
+ console.error(`Failed: ${e.response?.data?.error || e.message}`);
4301
+ process.exit(1);
4302
+ }
4303
+ return;
4304
+ }
4305
+
4306
+ // Approve mode
4307
+ if (opts.approve) {
4308
+ const approveId = runId || storyIdOrRunId;
4309
+ try {
4310
+ const { data } = await client.post(`/orchestrate/${approveId}/approve`, { stepKey: opts.approve });
4311
+ console.log(`Gate ${opts.approve} approved. Workflow resuming.`);
4312
+ console.log(`Poll: hone run-story ${approveId} --status`);
4313
+ } catch (e) {
4314
+ console.error(`Failed: ${e.response?.data?.error || e.message}`);
4315
+ process.exit(1);
4316
+ }
4317
+ return;
4318
+ }
4319
+
4320
+ // Start workflow (storyId required, runId not accepted for starting)
4321
+ if (isUuid) {
4322
+ console.error('Cannot start workflow with a runId. Use a story ID like HC-042.');
4323
+ console.error('To check status: hone run-story <runId> --status');
4324
+ process.exit(1);
4325
+ }
4326
+
4327
+ const repoName = opts.repo || path.basename(process.cwd());
4328
+ let branch = opts.branch;
4329
+ if (!branch) {
4330
+ try { branch = execSync('git symbolic-ref --short HEAD', { encoding: 'utf8' }).trim(); } catch { branch = null; }
4331
+ }
4332
+
4333
+ try {
4334
+ const { data } = await client.post('/orchestrate', {
4335
+ storyId: storyIdOrRunId, repoName, branch, mode: opts.mode, config: {},
4336
+ });
4337
+
4338
+ console.log('');
4339
+ console.log(`Workflow started: ${data.runId}`);
4340
+ console.log(`Mode: ${data.mode} | Story: ${data.storyId}`);
4341
+ console.log(`Poll: ${data.pollUrl}`);
4342
+ console.log('');
4343
+
4344
+ // Poll loop
4345
+ const interval = parseInt(opts.pollInterval, 10) * 1000;
4346
+ while (true) {
4347
+ await new Promise(r => setTimeout(r, interval));
4348
+
4349
+ let status;
4350
+ try {
4351
+ const { data: pollData } = await client.get(`/orchestrate/${data.runId}`);
4352
+ status = pollData;
4353
+ } catch (e) {
4354
+ console.error(`Poll error: ${e.message}`);
4355
+ continue;
4356
+ }
4357
+
4358
+ if (status.status === 'completed') {
4359
+ console.log(`✓ Workflow completed! Total tokens: ${(status.totalTokens || 0).toLocaleString()}`);
4360
+ process.exit(0);
4361
+ }
4362
+
4363
+ if (status.status === 'failed' || status.status === 'killed') {
4364
+ console.error(`✗ Workflow ${status.status}: ${status.errorMessage || 'unknown'}`);
4365
+ process.exit(1);
4366
+ }
4367
+
4368
+ if (status.status === 'paused' && status.awaitingApproval) {
4369
+ const step = status.steps.find(s => s.stepKey === status.currentStep);
4370
+ console.log(`⏳ Step ${status.currentStep} (${step?.agent || 'unknown'}) — awaiting approval`);
4371
+
4372
+ // Interactive prompt
4373
+ if (process.stdin.isTTY) {
4374
+ const readline = require('readline');
4375
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4376
+ const answer = await new Promise(resolve => {
4377
+ rl.question(' Approve? [y/n/kill] ', resolve);
4378
+ });
4379
+ rl.close();
4380
+
4381
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
4382
+ try {
4383
+ await client.post(`/orchestrate/${data.runId}/approve`, { stepKey: status.currentStep });
4384
+ console.log(' Approved — workflow resuming...');
4385
+ } catch (e) {
4386
+ console.error(` Approve failed: ${e.response?.data?.error || e.message}`);
4387
+ }
4388
+ } else if (answer.toLowerCase() === 'kill') {
4389
+ try {
4390
+ await client.post(`/orchestrate/${data.runId}/kill`, { reason: 'CLI kill' });
4391
+ console.log(' Workflow killed.');
4392
+ process.exit(0);
4393
+ } catch (e) {
4394
+ console.error(` Kill failed: ${e.message}`);
4395
+ }
4396
+ } else {
4397
+ console.log(` Skipped. Resume later: hone run-story ${data.runId} --status`);
4398
+ process.exit(0);
4399
+ }
4400
+ } else {
4401
+ // Non-interactive (CI): exit with code 2 (paused, needs approval)
4402
+ console.log(` Non-interactive mode. Approve with: hone run-story ${data.runId} --approve ${status.currentStep}`);
4403
+ process.exit(2);
4404
+ }
4405
+ continue;
4406
+ }
4407
+
4408
+ // Still running
4409
+ const currentStep = status.steps.find(s => s.status === 'running');
4410
+ if (currentStep) {
4411
+ process.stdout.write(` Running: ${currentStep.stepKey} (${currentStep.agent})...\r`);
4412
+ }
4413
+ }
4414
+ } catch (e) {
4415
+ if (e.response?.status === 429) {
4416
+ console.error(`Max concurrent workflows reached. ${e.response.data.error}`);
4417
+ } else {
4418
+ console.error(`Failed to start: ${e.response?.data?.error || e.message}`);
4419
+ }
4420
+ process.exit(1);
4421
+ }
4422
+ });
4423
+
4424
+ // SIGINT handler for run-story
4425
+ process.on('SIGINT', () => {
4426
+ console.log('\n\nWorkflow continues on the server.');
4427
+ console.log('Resume: hone run-story <storyId> --status');
4428
+ console.log('Kill: hone run-story <storyId> --kill');
4429
+ process.exit(0);
4430
+ });
4431
+
4238
4432
  // ── CLI setup ─────────────────────────────────────────────────────────────────
4239
4433
  program
4240
4434
  .name('hone')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hone-ai/cli",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Hone AI — Enterprise SDLC Pipeline CLI",
5
5
  "main": "hone-cli.js",
6
6
  "bin": {