@dzhechkov/harness-cli 0.3.249 → 0.3.251

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/harness-cli",
3
- "version": "0.3.249",
3
+ "version": "0.3.251",
4
4
  "description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "sbom.json"
42
42
  ],
43
43
  "dependencies": {
44
- "@dzhechkov/harness-core": "^0.3.140",
44
+ "@dzhechkov/harness-core": "^0.3.141",
45
45
  "@dzhechkov/harness-presets": "^0.5.0",
46
46
  "@dzhechkov/scout": "^0.8.0",
47
47
  "@dzhechkov/skills-devops": "^0.3.0",
package/sbom.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "hashes": [
26
26
  {
27
27
  "alg": "SHA-256",
28
- "content": "0a4369c88d48323b3b2ceb18b89ec3a0b6f6f7016778de3ac583f8397531b1b3"
28
+ "content": "4ffd489223826f51e8777ce825d60552365012de11d408ed9cf8c407357c0229"
29
29
  }
30
30
  ]
31
31
  },
@@ -105,7 +105,7 @@
105
105
  "hashes": [
106
106
  {
107
107
  "alg": "SHA-256",
108
- "content": "e176f74bf8ff0fe938e1bf9b25db392c1adbbd4b7b5b2b63520cb8baa99df82d"
108
+ "content": "e41e9538474ad80901474841e66f3be8b9f4fdc24f85b4168a13958f19f0762c"
109
109
  }
110
110
  ]
111
111
  },
@@ -115,7 +115,7 @@
115
115
  "hashes": [
116
116
  {
117
117
  "alg": "SHA-256",
118
- "content": "8bfdc5551ea8d4897f8bb2ab50e374145965e73b4dca8d01325f5388412d804c"
118
+ "content": "038b4bf4c9295202129662e1caf3b8b139c91f69b2e292373066154b644cc97c"
119
119
  }
120
120
  ]
121
121
  },
@@ -125,7 +125,7 @@
125
125
  "hashes": [
126
126
  {
127
127
  "alg": "SHA-256",
128
- "content": "0ae60f97c9b1b9e7afabcc8aa540eae700950539f5adc511c6f02cbe634775bd"
128
+ "content": "dafbac9c642da206ddfadb9081f01ef2a4cfba98dee719ef64200241d2b76726"
129
129
  }
130
130
  ]
131
131
  },
@@ -185,7 +185,7 @@
185
185
  "hashes": [
186
186
  {
187
187
  "alg": "SHA-256",
188
- "content": "207f5a322bb9154eb34478746318abf1f67660f670a92d7bede2fd4a725d4962"
188
+ "content": "4926d5f944db1dc7361d0ebd80be28f99455133796ce10970df553eb57c198e8"
189
189
  }
190
190
  ]
191
191
  },
@@ -205,7 +205,7 @@
205
205
  "hashes": [
206
206
  {
207
207
  "alg": "SHA-256",
208
- "content": "1d3e6fb9e19aa8fd589487335a8004cc6f7a6af43472b40e31f9139a482f7e85"
208
+ "content": "9a32862f99d3d0211e08ea49c7091f5697acdd2789ac05137203abd6ed7a9b3e"
209
209
  }
210
210
  ]
211
211
  },
@@ -225,7 +225,7 @@
225
225
  "hashes": [
226
226
  {
227
227
  "alg": "SHA-256",
228
- "content": "a82b15dd9d166764479528e9f9ede45fc39d1b4068a96f19f158aa5a4c03369b"
228
+ "content": "2e013ab73b4eb3dd248ed0fbbd5c050911ad667bb78054fde0bb021fbd1815db"
229
229
  }
230
230
  ]
231
231
  },
package/src/cli.ts CHANGED
@@ -207,7 +207,28 @@ import {
207
207
  readQuarantineState,
208
208
  registrationExitCode,
209
209
  renderRegistrationReport,
210
+ // Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
211
+ readBacklogConfig,
212
+ readIdeas,
213
+ writeIdeas,
214
+ ideaId,
215
+ dedupIdea,
216
+ readGoalMap,
217
+ alignIdea,
218
+ mirrorIdeaVector,
219
+ snapshotIdeas,
220
+ spinRoulette,
221
+ rankRoulette,
222
+ seededRng,
223
+ eligibleIdeas,
224
+ stageEnrichment,
225
+ buildJiraDraft,
226
+ resolveJiraAdapter,
227
+ makeBacklogIO,
228
+ harmonizeBacklog,
229
+ BACKLOG_BACKENDS,
210
230
  } from '@dzhechkov/harness-core';
231
+ import type { IdeaRecord, IdeaStatus } from '@dzhechkov/harness-core';
211
232
  import type { Family, ModelRung, Candidate as BtoCandidate, DimScores } from '@dzhechkov/harness-core';
212
233
  import type { SetupSpec } from '@dzhechkov/harness-core';
213
234
  import type { ProvenanceMode, PackVerdict, ClaudeUsageModel, PatternRecord, TargetName, BookKU, HarmonizeReport, UsageCalibrationPlan, ClaimFinding, RecallUsagePatternRow, GateExecution, GateStep } from '@dzhechkov/harness-core';
@@ -240,6 +261,14 @@ Usage:
240
261
  dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--json] (does .claude/skills/ actually REGISTER? --static = instant layout scan for CI; default reads the authoritative system/init listing from a real session. exit 0 pass / 1 fail / 2 inconclusive — never a false pass)
241
262
  dz compounding [--project <dir>] [--json] (honest learning-loop payoff report: pool write-only ratio, guard repeat-violation trajectory, cold-vs-warm replay readiness, instrumentation health — a gate without enough data says INSUFFICIENT_DATA, never a fake verdict)
242
263
  dz score --slug <feature> [--project <dir>] [--json] (process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination, cross-model QE grade, live verification, README-first, learning loop, amendments — descriptive-only, a low score exits 0)
264
+ dz backlog add "<idea>" [--effort 1-5] [--proposal <text>] [--dry-run] [--project <dir>] [--json] (capture an idea: semantic dedup against existing ideas via the Brain vector engine (DUPLICATE>=0.92 merges, RELATED links, NEW creates) + GoalMap alignment; --dry-run classifies without writing)
265
+ dz backlog list [--status <s>] [--goal <id>] [--project <dir>] [--json] (list captured ideas, filterable by status/goal)
266
+ dz backlog show <id> [--project <dir>] [--json] (full record for one idea)
267
+ dz backlog goals [--validate] [--project <dir>] [--json] (list/validate the compass at .dz/backlog/goals.json)
268
+ dz backlog roulette [--pick <N>] [--seed <n>] [--commit] [--project <dir>] [--json] (WEIGHTED draw over eligible ideas: alignment^alpha * recencyDecay * 1/effort, seeded; --pick N = ranked shortlist; --commit flips the pick to in-progress (the only mutation))
269
+ dz backlog enrich <id> [--project <dir>] [--json] (stage the idea2prd input scaffold in features/<slug>/ and hand off to the idea2prd-manual skill — the CLI never fabricates a PRD)
270
+ dz backlog jira <id> [--project <dir>] [--json] (draft a Jira issue via the configurable adapter (backlog.jira.adapter: jira-mcp|copilot-mcp|none); none writes an auditable jira-outbox/<id>.json stub)
271
+ dz backlog harmonize [--apply] [--threshold <0-1>] [--project <dir>] [--json] (batch semantic dedup of the backlog ideas; --dry-run default, --apply snapshots first)
243
272
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
244
273
  dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] (--project pins the learned store to <dir>/.dz, not the cwd — pin to a canonical brain)
245
274
  dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
@@ -2218,6 +2247,8 @@ async function cmdVector(options: Map<string, string>, flags: Set<string>, cwd:
2218
2247
  return report.error !== undefined ? 1 : 0;
2219
2248
  }
2220
2249
  if (report.error !== undefined) {
2250
+ // Honest failure/skip. Under a non-agentdb engine backlog is reported `skipped` (it never touches the
2251
+ // shared manifest); a model bump needs a FULL reindex under the agentdb engine. Surface it, exit 1.
2221
2252
  write(`dz vector reindex: ${report.error}`);
2222
2253
  return 1;
2223
2254
  }
@@ -6149,6 +6180,241 @@ function cmdDeliveryCheck(options: Map<string, string>, flags: Set<string>, cwd:
6149
6180
  return 0;
6150
6181
  }
6151
6182
 
6183
+ /* ------------------------------------------------------------------ */
6184
+ /* backlog — the Smart Backlog idea pipeline (feature smart-backlog) */
6185
+ /* ------------------------------------------------------------------ */
6186
+
6187
+ /** Thin dispatcher — ALL logic lives in harness-core/src/backlog.ts (05 architecture: handlers stay dumb). */
6188
+ async function cmdBacklog(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): Promise<number> {
6189
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
6190
+ const json = flags.has('json');
6191
+ const sub = options.get('_positional_0');
6192
+ const cfg = readBacklogConfig(projectRoot);
6193
+
6194
+ const emitErr = (msg: string): number => {
6195
+ write(json ? JSON.stringify({ error: msg, exitCode: 1 }) : `dz backlog: ${msg}`);
6196
+ return 1;
6197
+ };
6198
+
6199
+ if (sub === 'add') {
6200
+ const text = (options.get('_positional_1') ?? '').trim();
6201
+ if (text === '') return emitErr('an idea text is required: dz backlog add "<idea>"');
6202
+ const effort = Number(options.get('effort') ?? cfg.roulette.defaultEffort);
6203
+ const dryRun = flags.has('dry-run');
6204
+ const verdict = await dedupIdea(projectRoot, text, cfg);
6205
+ if (verdict.action === 'duplicate') {
6206
+ // DUPLICATE ⇒ snapshot + reinforce the existing root; NO new record (ADR-002 T-002b).
6207
+ const ideas = readIdeas(projectRoot);
6208
+ const match = ideas.find((i) => i.id === verdict.matchedId);
6209
+ if (!dryRun && match !== undefined) {
6210
+ const snap = snapshotIdeas(projectRoot, join(projectRoot, '.dz', 'backlog', `ideas.pre-merge-${Date.now()}.jsonl`));
6211
+ if (snap.error !== undefined) return emitErr(snap.error);
6212
+ match.uses += 1;
6213
+ writeIdeas(projectRoot, ideas);
6214
+ }
6215
+ if (json) write(JSON.stringify({ action: 'duplicate', matchedId: verdict.matchedId, cosine: verdict.cosine, exitCode: 0 }, null, 2));
6216
+ else write(`dz backlog: DUPLICATE of ${verdict.matchedId} (cosine ${verdict.cosine.toFixed(3)}${verdict.exactTextOnly ? ', exact-text' : ''}) — reinforced, no new record`);
6217
+ return 0;
6218
+ }
6219
+ const goalMap = readGoalMap(projectRoot);
6220
+ const align = await alignIdea(projectRoot, text, goalMap);
6221
+ const createdTs = new Date().toISOString();
6222
+ const rec: IdeaRecord = {
6223
+ id: ideaId(text, createdTs),
6224
+ text,
6225
+ status: 'new',
6226
+ createdTs,
6227
+ effort: Number.isFinite(effort) ? Math.max(1, Math.min(5, Math.floor(effort))) : cfg.roulette.defaultEffort,
6228
+ goalId: align.goalId,
6229
+ goalAlignment: align.goalAlignment,
6230
+ relatedIds: [...verdict.relatedIds],
6231
+ uses: 0,
6232
+ tags: [],
6233
+ };
6234
+ const proposal = options.get('proposal');
6235
+ if (proposal !== undefined) rec.proposal = proposal; // agent prose ONLY — the CLI never fabricates it
6236
+ if (dryRun) {
6237
+ if (json) write(JSON.stringify({ action: verdict.action, dryRun: true, idea: rec, exitCode: 0 }, null, 2));
6238
+ else write(`dz backlog (dry-run): ${verdict.action.toUpperCase()} — would create ${rec.id}; align ${rec.goalAlignment.toFixed(3)}${rec.goalId !== null ? ` → ${rec.goalId}` : ''}`);
6239
+ return 0;
6240
+ }
6241
+ const ideas = readIdeas(projectRoot);
6242
+ ideas.push(rec);
6243
+ writeIdeas(projectRoot, ideas);
6244
+ const mirror = await mirrorIdeaVector(projectRoot, rec); // best-effort — never blocks capture
6245
+ if (json) write(JSON.stringify({ action: verdict.action, idea: rec, related: verdict.relatedIds, exitCode: 0 }, null, 2));
6246
+ else {
6247
+ write(`dz backlog: ${verdict.action.toUpperCase()} — captured ${rec.id}`);
6248
+ if (rec.goalId !== null) write(` top goal: ${rec.goalId} (alignment ${rec.goalAlignment.toFixed(3)})`);
6249
+ if (verdict.relatedIds.length > 0) write(` related: ${verdict.relatedIds.join(', ')}`);
6250
+ write(' proposal: (write your own — dz backlog add --proposal "<text>"; the CLI never fabricates it)');
6251
+ if (mirror.error !== undefined) write(` ↳ vector mirror deferred: ${mirror.error}`);
6252
+ }
6253
+ return 0;
6254
+ }
6255
+
6256
+ if (sub === 'list') {
6257
+ let ideas = readIdeas(projectRoot);
6258
+ const status = options.get('status');
6259
+ const goal = options.get('goal');
6260
+ if (status !== undefined) ideas = ideas.filter((i) => i.status === (status as IdeaStatus));
6261
+ if (goal !== undefined) ideas = ideas.filter((i) => i.goalId === goal);
6262
+ if (json) {
6263
+ write(JSON.stringify({ ideas, exitCode: 0 }, null, 2));
6264
+ return 0;
6265
+ }
6266
+ if (ideas.length === 0) {
6267
+ write('dz backlog: no ideas' + (status !== undefined || goal !== undefined ? ' match the filter' : ' yet — dz backlog add "<idea>"'));
6268
+ return 0;
6269
+ }
6270
+ write(`dz backlog — ${ideas.length} idea(s)`);
6271
+ for (const i of ideas) write(` ${i.id} [${i.status}] e${i.effort} align ${i.goalAlignment.toFixed(2)}${i.goalId !== null ? ` (${i.goalId})` : ''} ${i.text.slice(0, 70)}`);
6272
+ return 0;
6273
+ }
6274
+
6275
+ if (sub === 'show') {
6276
+ const id = options.get('_positional_1');
6277
+ if (id === undefined) return emitErr('an idea id is required: dz backlog show <id>');
6278
+ const rec = readIdeas(projectRoot).find((i) => i.id === id);
6279
+ if (rec === undefined) return emitErr(`no idea ${id}`);
6280
+ write(json ? JSON.stringify({ idea: rec, exitCode: 0 }, null, 2) : JSON.stringify(rec, null, 2));
6281
+ return 0;
6282
+ }
6283
+
6284
+ if (sub === 'goals') {
6285
+ const goalMap = readGoalMap(projectRoot);
6286
+ if (flags.has('validate')) {
6287
+ const problems: string[] = [];
6288
+ const seen = new Set<string>();
6289
+ for (const g of goalMap.goals) {
6290
+ if (seen.has(g.id)) problems.push(`duplicate goal id: ${g.id}`);
6291
+ seen.add(g.id);
6292
+ if (g.statement.trim() === '') problems.push(`goal ${g.id} has an empty statement`);
6293
+ if (!(g.weight > 0 && g.weight <= 1)) problems.push(`goal ${g.id} weight ${g.weight} is out of (0,1]`);
6294
+ }
6295
+ if (json) write(JSON.stringify({ valid: problems.length === 0, problems, goals: goalMap.goals.length, exitCode: problems.length === 0 ? 0 : 1 }, null, 2));
6296
+ else if (problems.length === 0) write(`dz backlog goals: valid (${goalMap.goals.length} goal(s))`);
6297
+ else { write('dz backlog goals: INVALID'); for (const p of problems) write(` - ${p}`); }
6298
+ return problems.length === 0 ? 0 : 1;
6299
+ }
6300
+ if (json) {
6301
+ write(JSON.stringify({ goals: goalMap.goals, exitCode: 0 }, null, 2));
6302
+ return 0;
6303
+ }
6304
+ if (goalMap.goals.length === 0) {
6305
+ write('dz backlog goals: no compass — create .dz/backlog/goals.json ({ "version":1, "goals":[{ "id","statement","weight","keywords" }] })');
6306
+ return 0;
6307
+ }
6308
+ write(`dz backlog — ${goalMap.goals.length} goal(s)`);
6309
+ for (const g of goalMap.goals) write(` ${g.id} (w=${g.weight}): ${g.statement}`);
6310
+ return 0;
6311
+ }
6312
+
6313
+ if (sub === 'roulette') {
6314
+ const ideas = readIdeas(projectRoot);
6315
+ const pool = eligibleIdeas(ideas);
6316
+ if (pool.length === 0) {
6317
+ write(json ? JSON.stringify({ pick: null, eligible: 0, exitCode: 0 }) : 'dz backlog roulette: no eligible ideas (status new/enriched)');
6318
+ return 0;
6319
+ }
6320
+ const now = Date.now();
6321
+ const pickN = options.get('pick');
6322
+ if (pickN !== undefined) {
6323
+ const n = Math.max(1, Math.floor(Number(pickN)) || 1);
6324
+ const ranked = rankRoulette(ideas, cfg.roulette, now).slice(0, n);
6325
+ if (json) write(JSON.stringify({ ranked, exitCode: 0 }, null, 2));
6326
+ else { write(`dz backlog roulette — top ${ranked.length} by weight`); for (const i of ranked) write(` ${i.id} align ${i.goalAlignment.toFixed(2)} e${i.effort} ${i.text.slice(0, 60)}`); }
6327
+ return 0;
6328
+ }
6329
+ const seedOpt = options.get('seed');
6330
+ const seed = seedOpt !== undefined && Number.isFinite(Number(seedOpt)) ? Number(seedOpt) : Math.floor(Math.random() * 2 ** 31);
6331
+ const pick = spinRoulette(ideas, cfg.roulette, seededRng(seed), now)!;
6332
+ let committed = false;
6333
+ if (flags.has('commit')) {
6334
+ const idx = ideas.findIndex((i) => i.id === pick.id);
6335
+ ideas[idx]!.status = 'in-progress';
6336
+ writeIdeas(projectRoot, ideas);
6337
+ committed = true;
6338
+ }
6339
+ if (json) write(JSON.stringify({ pick, seed, committed, exitCode: 0 }, null, 2));
6340
+ else {
6341
+ write(`dz backlog roulette (seed ${seed}): ${pick.id} — ${pick.text.slice(0, 70)}`);
6342
+ write(` align ${pick.goalAlignment.toFixed(3)}${pick.goalId !== null ? ` (${pick.goalId})` : ''} · effort ${pick.effort}${committed ? ' · → in-progress' : ' · (add --commit to start it)'}`);
6343
+ }
6344
+ return 0;
6345
+ }
6346
+
6347
+ if (sub === 'enrich') {
6348
+ const id = options.get('_positional_1');
6349
+ if (id === undefined) return emitErr('an idea id is required: dz backlog enrich <id>');
6350
+ const ideas = readIdeas(projectRoot);
6351
+ const rec = ideas.find((i) => i.id === id);
6352
+ if (rec === undefined) return emitErr(`no idea ${id}`);
6353
+ const goalMap = readGoalMap(projectRoot);
6354
+ const related = ideas.filter((i) => rec.relatedIds.includes(i.id));
6355
+ const staging = stageEnrichment(projectRoot, rec, related, goalMap);
6356
+ rec.status = 'enriched';
6357
+ rec.enrichedPath = `features/${staging.slug}`;
6358
+ writeIdeas(projectRoot, ideas);
6359
+ if (json) write(JSON.stringify({ slug: staging.slug, scaffoldPath: staging.scaffoldPath, handoff: 'idea2prd-manual', exitCode: 0 }, null, 2));
6360
+ else {
6361
+ write(`dz backlog enrich: staged ${rec.id} → ${staging.scaffoldPath}`);
6362
+ write(' HAND OFF to the idea2prd-manual skill (agent phase) — the CLI stages the input; the agent writes the PRD.');
6363
+ write(' honesty/verification (claim-check, red-team, Brain recall) live at THIS layer, not assumed from idea2prd.');
6364
+ }
6365
+ return 0;
6366
+ }
6367
+
6368
+ if (sub === 'jira') {
6369
+ const id = options.get('_positional_1');
6370
+ if (id === undefined) return emitErr('an idea id is required: dz backlog jira <id>');
6371
+ const ideas = readIdeas(projectRoot);
6372
+ const rec = ideas.find((i) => i.id === id);
6373
+ if (rec === undefined) return emitErr(`no idea ${id}`);
6374
+ const adapter = resolveJiraAdapter(cfg);
6375
+ const draft = buildJiraDraft(rec, readGoalMap(projectRoot));
6376
+ const ref = await adapter.createIssue(draft, makeBacklogIO(projectRoot));
6377
+ rec.jiraKey = ref.key ?? `outbox:${rec.id}`;
6378
+ writeIdeas(projectRoot, ideas);
6379
+ const verify = await adapter.verify();
6380
+ if (json) write(JSON.stringify({ ref, verify, adapter: adapter.backend, exitCode: 0 }, null, 2));
6381
+ else {
6382
+ write(`dz backlog jira: ${adapter.backend} → ${ref.stub ? 'stub' : 'live'} ${ref.key ?? '(no key)'}`);
6383
+ write(` outbox: ${ref.outboxPath}`);
6384
+ if (!verify.ready) write(` ⚠ manual wiring: ${verify.instruction}`);
6385
+ }
6386
+ return 0;
6387
+ }
6388
+
6389
+ if (sub === 'harmonize') {
6390
+ const apply = flags.has('apply');
6391
+ const thr = options.get('threshold');
6392
+ const report = await harmonizeBacklog(projectRoot, { apply, ...(thr !== undefined ? { threshold: Number(thr) } : {}) });
6393
+ if (json) {
6394
+ write(JSON.stringify({ ...report, exitCode: 0 }, null, 2));
6395
+ return 0;
6396
+ }
6397
+ write(`dz backlog harmonize (${report.mode}${report.fellBackToExact ? ', exact-text' : ''}): ${report.kept} cluster(s), ${report.dropped} merge-able, ${report.unique} unique (θ ${report.threshold})`);
6398
+ for (const c of report.clusters) write(` keep ${c.keep} ← ${c.drops.join(', ')}`);
6399
+ if (report.snapshotPath !== undefined) write(` snapshot: ${report.snapshotPath}`);
6400
+ if (report.pruneError !== undefined) write(` ⚠ ${report.pruneError} — run dz vector reindex or re-run harmonize when the store is available`);
6401
+ if (!apply && report.dropped > 0) write(' (dry-run — re-run with --apply to merge, after the snapshot)');
6402
+ return 0;
6403
+ }
6404
+
6405
+ // bare / unknown sub — help.
6406
+ write('dz backlog — the Smart Backlog: a goal-directed idea pipeline over the Brain vector engine');
6407
+ write(' add "<idea>" [--effort N][--proposal <t>][--dry-run] capture (semantic dedup + GoalMap alignment)');
6408
+ write(' list [--status <s>][--goal <id>] list ideas');
6409
+ write(' show <id> full record');
6410
+ write(' goals [--validate] the compass (.dz/backlog/goals.json)');
6411
+ write(' roulette [--pick N][--seed n][--commit] weighted draw for what to work on next');
6412
+ write(' enrich <id> stage the idea2prd hand-off (agent expands)');
6413
+ write(` jira <id> draft a Jira issue (adapter: ${[...BACKLOG_BACKENDS].join('|')})`);
6414
+ write(' harmonize [--apply][--threshold 0-1] batch semantic dedup of the backlog');
6415
+ return sub === undefined ? 0 : 1;
6416
+ }
6417
+
6152
6418
  /**
6153
6419
  * `dz routing` — inspect the learned cost-optimal routing outcome store (feature learned-cost-routing). Shows
6154
6420
  * what `args.models[stage]='auto-cost'` currently believes per (stage, complexity-tier, model): gated
@@ -6581,6 +6847,8 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
6581
6847
  return cmdCompounding(options, flags, cwd, write);
6582
6848
  case 'score':
6583
6849
  return cmdScore(options, flags, cwd, write);
6850
+ case 'backlog':
6851
+ return await cmdBacklog(options, flags, cwd, write);
6584
6852
  case 'routing':
6585
6853
  return cmdRouting(options, flags, cwd, write);
6586
6854
  case 'bto-optimize':