@hone-ai/cli 1.24.0 → 1.25.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
@@ -2377,16 +2377,32 @@ program
2377
2377
  console.log(' Migrate any REPO-SPECIFIC edits then `rm -rf .github/agents/`');
2378
2378
  }
2379
2379
 
2380
+ // HC-COMM-010 P3: in editor-mode repos, EDITOR-side agents sync as thin
2381
+ // stubs — their instructions are composed server-side and delivered
2382
+ // ephemerally at runtime, so the full prompt never persists to disk.
2383
+ // Server-mode repos are UNTOUCHED (full sync — daily editor-chat use of
2384
+ // these agents is a feature, not a leak to close).
2385
+ const { isEditorStubbedAgent, editorStubContent } = require('./lib/editor-stub');
2386
+ const { readExecutionMode } = require('./lib/pipeline-config');
2387
+ const syncExecutionMode = readExecutionMode(process.cwd());
2388
+
2380
2389
  let synced = 0;
2381
2390
  for (const agent of agents) {
2382
2391
  try {
2383
- const r = await client.get(`/prompts/${agent}`, {
2384
- responseType: 'text',
2385
- headers: { Accept: 'text/markdown' },
2386
- });
2392
+ let content;
2393
+ if (isEditorStubbedAgent(agent, syncExecutionMode)) {
2394
+ content = editorStubContent(agent);
2395
+ console.log(` → ${agent}: editor mode — syncing runtime stub (full prompt served ephemerally)`);
2396
+ } else {
2397
+ const r = await client.get(`/prompts/${agent}`, {
2398
+ responseType: 'text',
2399
+ headers: { Accept: 'text/markdown' },
2400
+ });
2401
+ content = r.data;
2402
+ }
2387
2403
  const dest = path.join(agentsDir, `${agent}.agent.md`);
2388
2404
  const rel = `.claude/agents/${agent}.agent.md`;
2389
- const result = safeWrite(dest, rel, r.data);
2405
+ const result = safeWrite(dest, rel, content);
2390
2406
  if (result !== 'skipped') {
2391
2407
  console.log(` ✓ ${rel}`);
2392
2408
  synced++;
@@ -5518,6 +5534,7 @@ program
5518
5534
  .option('--branch <name>', 'Git branch (default: current)')
5519
5535
  .option('--status', 'Show status of the latest workflow run for this story')
5520
5536
  .option('--kill', 'Kill the latest workflow run for this story')
5537
+ .option('--axes <axes>', 'Comma-separated classifier axes for this run (e.g. shared-chokepoint) — composition input (HC-COMM-010 C0)')
5521
5538
  .option('--approve <step>', 'Approve a paused gate (e.g., --approve step_0)')
5522
5539
  .option('--format <fmt>', 'Output format: pretty or json', 'pretty')
5523
5540
  .option('--poll-interval <s>', 'Poll interval in seconds', '5')
@@ -5898,6 +5915,28 @@ program
5898
5915
  orchestrateConfig.execution_mode = 'server';
5899
5916
  }
5900
5917
 
5918
+ // HC-COMM-010 C0: composition transports — INERT until C1 reads them.
5919
+ // Overlays: .hone-local/ is adopter disk the server can never read
5920
+ // (the cross-validation finding), so raw texts travel in run config —
5921
+ // the codebaseFiles pattern, size/count-capped, degrade-to-empty.
5922
+ // Axes: explicit --axes flag for now; automated wiring from classify
5923
+ // runs is future work (documented in the sprint plan).
5924
+ try {
5925
+ const { readRuleOverlays } = require('./lib/overlay-transport');
5926
+ const ov = readRuleOverlays(process.cwd());
5927
+ if (ov.overlays.length > 0) {
5928
+ orchestrateConfig.rule_overlays = ov.overlays;
5929
+ console.log(` → bundled ${ov.overlays.length} rule overlay(s) for server-side composition`);
5930
+ }
5931
+ for (const skip of ov.skipped) console.warn(` ⚠ overlay skipped: ${skip}`);
5932
+ } catch (e) {
5933
+ console.warn(` ⚠ overlay read failed (non-fatal): ${(e && e.message || String(e)).split('\n')[0]}`);
5934
+ }
5935
+ if (opts.axes) {
5936
+ const axes = String(opts.axes).split(',').map(a => a.trim().toLowerCase()).filter(Boolean);
5937
+ if (axes.length > 0) orchestrateConfig.classifier_axes = axes;
5938
+ }
5939
+
5901
5940
  // HC-019n-followup-28: resolve the E2E spec-generation mode (step_3b /
5902
5941
  // Playwright). Precedence mirrors ci.gate: per-run flag > .pipeline-config.yml
5903
5942
  // `e2e.spec_generation` > default `auto`. `auto` = the orchestrator runs
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+ /**
3
+ * editor-stub.js — HC-COMM-010 P3: thin-stub content for editor-side agents.
4
+ *
5
+ * When a repo opts into `execution.mode: editor`, the agents of the server's
6
+ * EDITOR_SIDE_STEPS are DELIVERED AT RUNTIME (ephemeral, via
7
+ * `hone step-instruction` / the hone_step_instruction MCP tool) — so `hone
8
+ * sync` ships a stub instead of persisting the full prompt to disk. This
9
+ * closes the dominant prompt-exposure leak for editor-mode repos (memo §5 /
10
+ * analysis G1) WITHOUT touching server-mode repos, whose adopters use the
11
+ * full synced prompts for daily editor-chat work.
12
+ *
13
+ * EDITOR_STUBBED_AGENTS must stay equal to the agents of the server's
14
+ * EDITOR_SIDE_STEPS (orchestrator.js) — pinned by a regression test that
15
+ * derives the truth from workflow-dag, since the two packages share no lib.
16
+ */
17
+
18
+ const EDITOR_STUBBED_AGENTS = Object.freeze(['code-builder']);
19
+
20
+ function isEditorStubbedAgent(agent, executionMode) {
21
+ return executionMode === 'editor' && EDITOR_STUBBED_AGENTS.includes(agent);
22
+ }
23
+
24
+ function editorStubContent(agent) {
25
+ return [
26
+ `# ${agent} — served at runtime by Hone (editor mode)`,
27
+ '',
28
+ 'This repo runs with `execution.mode: editor`, so this agent\'s pipeline',
29
+ 'instruction is COMPOSED SERVER-SIDE and delivered ephemerally at',
30
+ 'invocation time — it is not stored on disk.',
31
+ '',
32
+ 'To execute a parked step:',
33
+ ' 1. `hone step-instruction <runId>` (or the hone_step_instruction MCP tool)',
34
+ ' 2. run the returned instruction in your editor',
35
+ ' 3. `hone submit-step <runId> <stepKey> --file <output>`',
36
+ '',
37
+ 'Output is judged by real verification (check-patch / verify-patch),',
38
+ 'never taken on trust. Switch `execution.mode` back to `server` and',
39
+ 're-run `hone sync` to restore the full local prompt.',
40
+ '',
41
+ ].join('\n');
42
+ }
43
+
44
+ module.exports = { EDITOR_STUBBED_AGENTS, isEditorStubbedAgent, editorStubContent };
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+ /**
3
+ * overlay-transport.js — HC-COMM-010 C0: read adopter rule-overlays for
4
+ * transport to the server at run creation.
5
+ *
6
+ * `.hone-local/rule-overlays/*.md` lives on ADOPTER DISK — the server can
7
+ * never read it (the cross-validation finding that refuted "merge overlays
8
+ * server-side" as originally sketched). So the CLI bundles the raw overlay
9
+ * texts into orchestrateConfig, exactly the codebaseFiles/§12-A3 transport
10
+ * pattern. Parsing (front-matter, insertion targets) happens SERVER-side in
11
+ * C1 via the mergeOverlays machinery — this reader stays deliberately dumb:
12
+ * raw {name, content} pairs, size/count-capped, degrade-to-empty on every
13
+ * failure path.
14
+ */
15
+ const fs = require('node:fs');
16
+ const path = require('node:path');
17
+
18
+ const DEFAULT_MAX_FILES = 10;
19
+ const DEFAULT_MAX_TOTAL_BYTES = 50_000; // mirrors the codebaseFiles budget
20
+
21
+ function readRuleOverlays(repoRoot, { maxFiles = DEFAULT_MAX_FILES, maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES } = {}) {
22
+ const result = { overlays: [], skipped: [] };
23
+ if (!repoRoot || typeof repoRoot !== 'string') return result;
24
+ const dir = path.join(repoRoot, '.hone-local', 'rule-overlays');
25
+ let names;
26
+ try { names = fs.readdirSync(dir).filter(n => n.endsWith('.md')).sort(); } catch { return result; }
27
+ let total = 0;
28
+ for (const name of names) {
29
+ if (result.overlays.length >= maxFiles) { result.skipped.push(`${name} (file cap ${maxFiles})`); continue; }
30
+ let content;
31
+ try { content = fs.readFileSync(path.join(dir, name), 'utf8'); } catch { result.skipped.push(`${name} (unreadable)`); continue; }
32
+ const bytes = Buffer.byteLength(content, 'utf8');
33
+ if (total + bytes > maxTotalBytes) { result.skipped.push(`${name} (byte budget ${maxTotalBytes})`); continue; }
34
+ total += bytes;
35
+ result.overlays.push({ name, content });
36
+ }
37
+ return result;
38
+ }
39
+
40
+ module.exports = { readRuleOverlays, DEFAULT_MAX_FILES, DEFAULT_MAX_TOTAL_BYTES };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hone-ai/cli",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "Hone AI — Enterprise SDLC Pipeline CLI",
5
5
  "main": "hone-cli.js",
6
6
  "bin": {