@dzhechkov/harness-cli 0.3.250 → 0.3.252

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/.dz-manifest.json CHANGED
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "1559995482381fd6ac146e6d229cb7d33476cdca8fad21fd361467dd698bd065"
12
+ "sha256": "ff1b1a2239a254f500c999c5c25ab9303561b433fb1cac420a2b28a7bd0d9fd6"
13
13
  },
14
14
  {
15
15
  "path": "coverage/coverage-final.json",
@@ -41,15 +41,15 @@
41
41
  },
42
42
  {
43
43
  "path": "dist/cli.d.ts.map",
44
- "sha256": "e41e9538474ad80901474841e66f3be8b9f4fdc24f85b4168a13958f19f0762c"
44
+ "sha256": "c9b415287eda0a7c2ad32dbb7cfc3ecce19aa3b90c96d3fbeedd62d20fe34bc7"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "038b4bf4c9295202129662e1caf3b8b139c91f69b2e292373066154b644cc97c"
48
+ "sha256": "56b4da2639c1721180387f28166d84d065718a4002f556307a6598570d335809"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "dafbac9c642da206ddfadb9081f01ef2a4cfba98dee719ef64200241d2b76726"
52
+ "sha256": "4a172ba5384c9716fa48570979b7ffba969599ed5c639d7e7268a829d2b9b4e1"
53
53
  },
54
54
  {
55
55
  "path": "dist/index.d.ts",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  {
75
75
  "path": "package.json",
76
- "sha256": "4e22f0553a19181eea750db59bd3eec84fd880473ac523705a1a26197665e87e"
76
+ "sha256": "65e245ef567ccecdc382bb040f27dd98e01ce2c3a104935aa2dc0558b22220e2"
77
77
  },
78
78
  {
79
79
  "path": "src/bin.ts",
@@ -81,7 +81,7 @@
81
81
  },
82
82
  {
83
83
  "path": "src/cli.ts",
84
- "sha256": "9a32862f99d3d0211e08ea49c7091f5697acdd2789ac05137203abd6ed7a9b3e"
84
+ "sha256": "1aceceef391cac55d00f7872632d28c4116a82025b9235343b81398fc515f944"
85
85
  },
86
86
  {
87
87
  "path": "src/index.ts",
@@ -89,7 +89,7 @@
89
89
  },
90
90
  {
91
91
  "path": "test/cli.test.ts",
92
- "sha256": "2e013ab73b4eb3dd248ed0fbbd5c050911ad667bb78054fde0bb021fbd1815db"
92
+ "sha256": "7d6ba26ec1188db3b3ac89f50b015ba1f0c6117b5c30d99b22da41da10c96c12"
93
93
  },
94
94
  {
95
95
  "path": "tsconfig.json",
@@ -101,5 +101,5 @@
101
101
  }
102
102
  ]
103
103
  },
104
- "signature": "d+++ms/ySpdJ2w5VE7F3b8IaZLWhIYcrvTRT1nPQmi2OHDdrQZ4YBHHW2KNxrauUdTXuUf/7tD//NQiGCQgQDw=="
104
+ "signature": "QkTucRDewQaD109O+GaZcrFfuzX3jp/bFPbLBYq5CmLT7BCzeHAGDdGfJogI8yZA81HE5oQLRzixyq6IdNPDCw=="
105
105
  }
package/README.md CHANGED
@@ -785,7 +785,7 @@ dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict
785
785
  dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json] # does .claude/skills/ actually REGISTER? --static = instant layout scan (CI-safe, no session): flags dirs that can never register; default also starts a real session and reads the authoritative system/init listing. exit 0 pass / 1 fail / 2 inconclusive — an unobservable registration is NEVER a pass
786
786
  dz compounding [--project <dir>] [--json] # honest learning-loop payoff report: pool write-only ratio, guard repeat-violation trajectory (rate per audit-half), cold-vs-warm replay readiness over unique untruncated prompt events, apply-leg staleness — a gate without enough data says INSUFFICIENT_DATA, never a verdict
787
787
  dz score --slug <feature> [--project <dir>] [--json] # process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination proof, cross-model QE grade, live verification, README-first, learning loop, amendments — DESCRIPTIVE-ONLY (a low score exits 0); evidence lines are shown so the reader judges the heuristics
788
- dz backlog <sub> add "<idea>" | list | show <id> | goals | roulette [--seed n] | enrich <id> | jira <id> | harmonize [--apply] # brain-backed idea backlog: capture an idea → semantic dedup against past ideas/features via the REUSED agentdb vector engine (DUPLICATE≥0.92 / RELATED / NEW) + GoalMap alignment ("map+compass") → weighted seeded roulette picks one to work on → enrich STAGES an idea2prd hand-off → jira writes an auditable outbox via a configurable MCP adapter seam (jira-mcp|copilot-mcp|none). No 2nd vector store; without agentdb it degrades to exact-text dedup (honest)
788
+ dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roulette [--seed n] [--commit <id>] | enrich <id> | jira <id> | harmonize [--apply] # brain-backed idea backlog: capture an idea → semantic dedup against past ideas/features via the REUSED agentdb vector engine (DUPLICATE≥0.92 / RELATED / NEW) + GoalMap alignment ("map+compass") → weighted seeded roulette picks one to work on → enrich STAGES an idea2prd hand-off → jira writes an auditable outbox via a configurable MCP adapter seam (jira-mcp|copilot-mcp|none). No 2nd vector store; without agentdb it degrades to exact-text dedup (honest)
789
789
  dz sign --init --out <path> | --pack <dir> --key <path> # --init: generate the Ed25519 keypair (private OUTSIDE the repo, prints the public key for keys/dz.pub); else sign a pack's manifest + CycloneDX SBOM
790
790
  dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack standalone (file-level bill of materials); print to stdout or write to a file
791
791
  dz guard check --op <publish|teach|consolidate|reindex> [--text <s>] [--json] [--force <reason>] # declarative constraint layer before self-mutating ops: HARD violation → block (exit 1), SOFT → warn; zero-config defaults, .dz/guard.json to customise; dz guard --init | dz guard log (append-only audit). dz publish runs it automatically (--no-guard "<reason>" = logged escape hatch)
@@ -1175,6 +1175,60 @@ The `--bto` flag generates eval templates compatible with `/bto-test`:
1175
1175
 
1176
1176
  After scaffolding, fill in the SKILL.md protocol and run `/bto-test .claude/skills/my-skill` to evaluate.
1177
1177
 
1178
+ ### dz backlog — brain-backed idea inbox (smart backlog)
1179
+
1180
+ Throw ideas in as they come; the backlog dedups them semantically against everything you already
1181
+ captured (via the same agentdb vector engine the Brain uses — no second store), scores them against
1182
+ your goals, and picks what to work on when you have free time.
1183
+
1184
+ ```bash
1185
+ # 1. Capture an idea — it is instantly deduped + aligned
1186
+ dz backlog add "add a compounding metric to strengthen dz recall" --effort 3
1187
+ # → [new] e3 align 0.72 (or: [duplicate of 268d3cb1 @ cosine 0.95] / [related: …])
1188
+ # A RELATED/DUPLICATE verdict now names the pair, so you can calibrate the 0.92 band on real data:
1189
+ # dz backlog: RELATED — captured 67c5883e…
1190
+ # top match 9286f5eb… @ cosine 0.793 (DUPLICATE band ≥ 0.92)
1191
+ # An out-of-scale --effort is ECHOED, never silently clamped:
1192
+ dz backlog add "an idea I mis-scaled" --effort 13
1193
+ # → dz backlog: effort 13 → clamped to 5 (scale 1-5)
1194
+ # The first capture also gitignores its own store (`.dz/backlog/` — raw ideas are private): it adds
1195
+ # the entry only when no existing rule covers it (`/.dz/`, `.dz/**`, … all count), the write is atomic
1196
+ # and keeps the file's line endings, a `!.dz/backlog/` negation is obeyed as an explicit opt-out, and a
1197
+ # FAILED scaffold is announced ("your ideas are visible to git") rather than passing in silence.
1198
+
1199
+ # 2. Define your goals once ("map + compass") — alignment is scored against these
1200
+ dz backlog goals # opens/creates .dz/backlog/goals.json; --validate checks it
1201
+ dz backlog goals --validate # a malformed entry is WARNED about, never silently dropped:
1202
+ # → dz backlog goals: WARNING goal[0]: missing "statement" — dropped
1203
+ # dz backlog goals: INVALID (exit 1 when every entry was dropped — an empty compass is not "valid")
1204
+ # A repaired field is reported with the value YOU wrote, not the clamped one the runtime uses:
1205
+ # → dz backlog goals: WARNING goal[0] (g1): weight 7 is out of (0,1] — using 1
1206
+
1207
+ # 3. When you have free time — spin the roulette (weighted: alignment^α · recency · 1/effort)
1208
+ dz backlog roulette --seed 7
1209
+ # → 268d3cb1 — add a compounding metric … align 0.72 · effort 3
1210
+ # (start it with: dz backlog roulette --commit 268d3cb1)
1211
+
1212
+ # 3b. Start it — --commit takes the id you were SHOWN (a bare --commit is refused: the pool can change
1213
+ # between spins, so the spin must not decide a mutation). An id ALONE never mutates — a spin is
1214
+ # read-only, and the --commit flag is the only thing that authorises a status change.
1215
+ dz backlog roulette --commit 268d3cb1 # → 268d3cb1 · → in-progress
1216
+ dz backlog roulette 268d3cb1 # → refused (exit 1): "an idea id alone does not start it"
1217
+
1218
+ # 4. Work it: stage an idea2prd hand-off (writes the PRD-pipeline input scaffold)
1219
+ dz backlog enrich 268d3cb1 # → features/<slug>/ scaffold; run the idea2prd-manual skill on it
1220
+
1221
+ # 5. Or push it to Jira via the configured adapter (jira-mcp | copilot-mcp | none)
1222
+ dz backlog jira 268d3cb1 # 'none' (default) writes an auditable .dz/backlog/jira-outbox/<id>.json
1223
+
1224
+ # Housekeeping: cluster near-duplicates (dry-run by default; --apply prunes vectors too)
1225
+ dz backlog harmonize --apply
1226
+ ```
1227
+
1228
+ When to use: any time an idea strikes mid-work — capture beats context-switching. Without the
1229
+ agentdb memory backend installed the dedup honestly degrades to exact-text and alignment to 0
1230
+ (`dz setup --memory agentdb` enables the semantic tier).
1231
+
1178
1232
  ### dz install — install skills from any npm package
1179
1233
 
1180
1234
  ```bash
@@ -1895,7 +1949,7 @@ invariants: HARD rules **block** the operation, SOFT rules warn. Zero config nee
1895
1949
  cover the known rakes; `.dz/guard.json` (via `dz guard --init`) exists only if you want to tune a severity
1896
1950
  or disable a rule.
1897
1951
  ```bash
1898
- dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first
1952
+ dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first · skills-registrable · lockfile-in-sync
1899
1953
  dz guard check --op teach --text "the fix: export sk-abc..." # → BLOCK (exit 1): looks like a credential
1900
1954
  dz guard log # append-only audit: every verdict + every forced override
1901
1955
  ```
@@ -1913,6 +1967,23 @@ site** in the contour: the command count on the Starlight CLI overview and the f
1913
1967
  match the cli README (the site once drifted 35→55 silently; now a mismatch is a warning here and a red
1914
1968
  test in CI). In a repo without the docs site those pairs simply aren't gathered — no false gate.
1915
1969
 
1970
+ The SOFT `lockfile-in-sync` rule closes the CI break that follows a dependency bump: when a workspace
1971
+ `packages/*/package.json` declares a `@dzhechkov/*` spec that differs from the specifier `pnpm-lock.yaml`
1972
+ records for that importer, CI's `pnpm install --frozen-lockfile` dies with `ERR_PNPM_OUTDATED_LOCKFILE`
1973
+ long after the commit. Now it is a warning at publish time:
1974
+ ```
1975
+ [warn] lockfile-in-sync: packages/@dzhechkov/harness-cli: @dzhechkov/harness-core = "^0.3.999" in
1976
+ package.json but "^0.3.141" in pnpm-lock.yaml — run `pnpm install` to refresh the lockfile
1977
+ ```
1978
+ It FAILS OPEN by construction — no lockfile, or one its parser cannot read, yields no warning at all (a
1979
+ guard that cannot read its evidence must never invent a violation), and it only compares `@dzhechkov/*`
1980
+ specs. "Tolerant" here means **recognise-or-refuse**, not *guess quietly*: the parser reads exactly the
1981
+ `lockfileVersion: 9`+ importer layout and returns nothing at all for a legacy v5/v6 file (whose deps are
1982
+ `dep: version` one-liners) or a truncated one — because a half-parse would find the importer keys, read
1983
+ zero specifiers, and then report every real dependency as "not recorded". The rule is also **SOFT-only**:
1984
+ `.dz/guard.json` cannot promote it to HARD, since a parser designed to admit "I may not understand this
1985
+ file" must never be able to block a publish.
1986
+
1916
1987
  ### `dz feature-adr-setup --guards` — when a project rule should be a TEST, not a reviewer's memory
1917
1988
 
1918
1989
  A 700-line cap enforced by reviewer judgment catches one file and misses its sibling in the same change;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6TH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAouM9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0J5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkUH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAg2M9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0J5E"}
package/dist/cli.js CHANGED
@@ -9,9 +9,9 @@ import { fileURLToPath } from 'node:url';
9
9
  import { execSync, spawn } from 'node:child_process';
10
10
  import { homedir, tmpdir } from 'node:os';
11
11
  import { createRequire } from 'node:module';
12
- import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport, scoreRun, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
12
+ import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport, scoreRun, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
13
13
  // Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
14
- readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, alignIdea, mirrorIdeaVector, snapshotIdeas, spinRoulette, rankRoulette, seededRng, eligibleIdeas, stageEnrichment, buildJiraDraft, resolveJiraAdapter, makeBacklogIO, harmonizeBacklog, BACKLOG_BACKENDS, } from '@dzhechkov/harness-core';
14
+ readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, readGoalMapDetailed, parseEffort, ensureBacklogGitignored, isSafeId, alignIdea, mirrorIdeaVector, snapshotIdeas, spinRoulette, rankRoulette, seededRng, eligibleIdeas, stageEnrichment, buildJiraDraft, resolveJiraAdapter, makeBacklogIO, harmonizeBacklog, BACKLOG_BACKENDS, } from '@dzhechkov/harness-core';
15
15
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
16
16
  import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
17
17
  const USAGE = `dz - DZ cross-platform harness CLI
@@ -4501,18 +4501,15 @@ function gatherReadmeCounts(root) {
4501
4501
  function gatherGuardFacts(op, root, text, storeCap) {
4502
4502
  const facts = { op };
4503
4503
  if (op === 'publish') {
4504
- // Read every workspace manifest ONCE: build a name→version map, then resolve each `workspace:*` dep to the
4505
- // version pnpm WOULD publish it as. In a pnpm workspace (pnpm-workspace.yaml present) `workspace:*` in source
4506
- // is correct and gets rewritten at publish — so reporting it raw would be a FALSE gate. We mirror the rewrite:
4507
- // a resolvable workspace dep becomes its real semver (safe → the rule passes); an UNRESOLVABLE one (points at
4508
- // no workspace package, or not a pnpm workspace) stays `workspace:*` so the rule catches a dep that WOULD ship
4509
- // raw. That is the genuinely dangerous case the rule exists for.
4510
4504
  const manifests = [];
4505
+ const located = [];
4511
4506
  try {
4512
4507
  const out = execSync('git ls-files "packages/@dzhechkov/*/package.json"', { cwd: root, encoding: 'utf-8' });
4513
4508
  for (const rel of out.split('\n').map((s) => s.trim()).filter(Boolean)) {
4514
4509
  try {
4515
- manifests.push(JSON.parse(readFileSync(join(root, rel), 'utf8')));
4510
+ const m = JSON.parse(readFileSync(join(root, rel), 'utf8'));
4511
+ manifests.push(m);
4512
+ located.push({ dir: rel.replace(/\/package\.json$/, ''), m });
4516
4513
  }
4517
4514
  catch { /* skip unreadable */ }
4518
4515
  }
@@ -4576,6 +4573,32 @@ function gatherGuardFacts(op, root, text, storeCap) {
4576
4573
  facts['skillPacks'] = skillPacks;
4577
4574
  }
4578
4575
  catch { /* unreadable packages dir — rule reports nothing rather than inventing a violation */ }
4576
+ // lockfile-in-sync: the ERR_PNPM_OUTDATED_LOCKFILE class — a @dzhechkov/* dep spec bumped in a
4577
+ // package.json while pnpm-lock.yaml still records the old specifier. FAIL-OPEN by construction: an
4578
+ // unreadable/unparseable lockfile yields `{parsed:false}` and the rule reports NOTHING.
4579
+ try {
4580
+ const lockText = readFileSync(join(root, 'pnpm-lock.yaml'), 'utf8');
4581
+ const importers = parsePnpmLockImporters(lockText);
4582
+ if (importers === undefined) {
4583
+ facts['lockfile'] = { parsed: false };
4584
+ }
4585
+ else {
4586
+ const rows = located
4587
+ .map(({ dir, m }) => {
4588
+ const declared = {};
4589
+ for (const [dep, spec] of Object.entries({ ...(m.dependencies ?? {}), ...(m.devDependencies ?? {}) })) {
4590
+ if (dep.startsWith('@dzhechkov/') && typeof spec === 'string')
4591
+ declared[dep] = spec;
4592
+ }
4593
+ return { importer: dir, declared, locked: importers[dir] };
4594
+ })
4595
+ .filter((r) => Object.keys(r.declared).length > 0);
4596
+ facts['lockfile'] = { parsed: true, importers: rows };
4597
+ }
4598
+ }
4599
+ catch {
4600
+ facts['lockfile'] = { parsed: false }; /* no lockfile (not a pnpm workspace) — rule stays silent */
4601
+ }
4579
4602
  }
4580
4603
  if (op === 'consolidate') {
4581
4604
  try {
@@ -5932,9 +5955,19 @@ async function cmdBacklog(options, flags, cwd, write) {
5932
5955
  const text = (options.get('_positional_1') ?? '').trim();
5933
5956
  if (text === '')
5934
5957
  return emitErr('an idea text is required: dz backlog add "<idea>"');
5935
- const effort = Number(options.get('effort') ?? cfg.roulette.defaultEffort);
5958
+ // The clamp is ECHOED, never silent (idea 86096d6d): `--effort 13` used to store 5 and say nothing.
5959
+ const eff = parseEffort(options.get('effort'), cfg.roulette.defaultEffort);
5960
+ if (eff.adjusted && !json && eff.note !== undefined)
5961
+ write(`dz backlog: ${eff.note}`);
5936
5962
  const dryRun = flags.has('dry-run');
5937
5963
  const verdict = await dedupIdea(projectRoot, text, cfg);
5964
+ // The TOP-MATCH pair (id @ cosine) is the ADR-002 calibration surface (idea ce914ac2) — observational
5965
+ // only: the band itself is unchanged, but a RELATED verdict now shows WHICH idea produced the cosine.
5966
+ // A negative cosine is a VALID observation (anti-correlated embedding) — only non-finite is absent
5967
+ // (Codex re-QE LOW: `>= 0` suppressed a legitimate negative top match).
5968
+ const topMatch = verdict.topMatchId !== undefined && Number.isFinite(verdict.cosine)
5969
+ ? { id: verdict.topMatchId, cosine: verdict.cosine }
5970
+ : undefined;
5938
5971
  if (verdict.action === 'duplicate') {
5939
5972
  // DUPLICATE ⇒ snapshot + reinforce the existing root; NO new record (ADR-002 T-002b).
5940
5973
  const ideas = readIdeas(projectRoot);
@@ -5947,9 +5980,12 @@ async function cmdBacklog(options, flags, cwd, write) {
5947
5980
  writeIdeas(projectRoot, ideas);
5948
5981
  }
5949
5982
  if (json)
5950
- write(JSON.stringify({ action: 'duplicate', matchedId: verdict.matchedId, cosine: verdict.cosine, exitCode: 0 }, null, 2));
5951
- else
5983
+ write(JSON.stringify({ action: 'duplicate', matchedId: verdict.matchedId, cosine: verdict.cosine, ...(topMatch !== undefined ? { topMatch } : {}), ...(eff.note !== undefined ? { effortNote: eff.note } : {}), exitCode: 0 }, null, 2));
5984
+ else {
5952
5985
  write(`dz backlog: DUPLICATE of ${verdict.matchedId} (cosine ${verdict.cosine.toFixed(3)}${verdict.exactTextOnly ? ', exact-text' : ''}) — reinforced, no new record`);
5986
+ if (topMatch !== undefined)
5987
+ write(` top match ${topMatch.id} @ cosine ${topMatch.cosine.toFixed(3)} (DUPLICATE band ≥ ${cfg.dedup.duplicateThreshold})`);
5988
+ }
5953
5989
  return 0;
5954
5990
  }
5955
5991
  const goalMap = readGoalMap(projectRoot);
@@ -5960,7 +5996,7 @@ async function cmdBacklog(options, flags, cwd, write) {
5960
5996
  text,
5961
5997
  status: 'new',
5962
5998
  createdTs,
5963
- effort: Number.isFinite(effort) ? Math.max(1, Math.min(5, Math.floor(effort))) : cfg.roulette.defaultEffort,
5999
+ effort: eff.effort,
5964
6000
  goalId: align.goalId,
5965
6001
  goalAlignment: align.goalAlignment,
5966
6002
  relatedIds: [...verdict.relatedIds],
@@ -5972,23 +6008,45 @@ async function cmdBacklog(options, flags, cwd, write) {
5972
6008
  rec.proposal = proposal; // agent prose ONLY — the CLI never fabricates it
5973
6009
  if (dryRun) {
5974
6010
  if (json)
5975
- write(JSON.stringify({ action: verdict.action, dryRun: true, idea: rec, exitCode: 0 }, null, 2));
5976
- else
6011
+ write(JSON.stringify({ action: verdict.action, dryRun: true, idea: rec, ...(topMatch !== undefined ? { topMatch } : {}), ...(eff.note !== undefined ? { effortNote: eff.note } : {}), exitCode: 0 }, null, 2));
6012
+ else {
5977
6013
  write(`dz backlog (dry-run): ${verdict.action.toUpperCase()} — would create ${rec.id}; align ${rec.goalAlignment.toFixed(3)}${rec.goalId !== null ? ` → ${rec.goalId}` : ''}`);
6014
+ // The calibration surface belongs on the dry-run too (QE LOW-8) — a dry-run is exactly where a
6015
+ // user checks whether a near-duplicate should have crossed the band.
6016
+ if (topMatch !== undefined)
6017
+ write(` top match ${topMatch.id} @ cosine ${topMatch.cosine.toFixed(3)} (DUPLICATE band ≥ ${cfg.dedup.duplicateThreshold})`);
6018
+ }
5978
6019
  return 0;
5979
6020
  }
6021
+ // Privacy scaffold (idea ec4cd60d): the store we are about to create holds raw prompt-class ideas —
6022
+ // ensure `.dz/backlog/` is gitignored BEFORE the first write. No-op once covered.
6023
+ const ignore = ensureBacklogGitignored(projectRoot);
5980
6024
  const ideas = readIdeas(projectRoot);
5981
6025
  ideas.push(rec);
5982
6026
  writeIdeas(projectRoot, ideas);
5983
6027
  const mirror = await mirrorIdeaVector(projectRoot, rec); // best-effort — never blocks capture
5984
6028
  if (json)
5985
- write(JSON.stringify({ action: verdict.action, idea: rec, related: verdict.relatedIds, exitCode: 0 }, null, 2));
6029
+ write(JSON.stringify({ action: verdict.action, idea: rec, related: verdict.relatedIds, ...(topMatch !== undefined ? { topMatch } : {}), ...(eff.note !== undefined ? { effortNote: eff.note } : {}), gitignore: ignore, exitCode: 0 }, null, 2));
5986
6030
  else {
5987
6031
  write(`dz backlog: ${verdict.action.toUpperCase()} — captured ${rec.id}`);
6032
+ if (verdict.action === 'related' && topMatch !== undefined) {
6033
+ write(` top match ${topMatch.id} @ cosine ${topMatch.cosine.toFixed(3)} (DUPLICATE band ≥ ${cfg.dedup.duplicateThreshold})`);
6034
+ }
5988
6035
  if (rec.goalId !== null)
5989
6036
  write(` top goal: ${rec.goalId} (alignment ${rec.goalAlignment.toFixed(3)})`);
5990
6037
  if (verdict.relatedIds.length > 0)
5991
6038
  write(` related: ${verdict.relatedIds.join(', ')}`);
6039
+ if (ignore.action === 'created' || ignore.action === 'appended') {
6040
+ write(` privacy: ${ignore.action} \`.dz/backlog/\` in ${ignore.path} (ideas are private prompt-class content)`);
6041
+ }
6042
+ else if (ignore.action === 'skipped') {
6043
+ // A privacy scaffold that FAILED must never be silent (QE MED-5): the store now holds raw ideas
6044
+ // that git can see, and only the user can fix it.
6045
+ write(` ⚠ privacy: could NOT gitignore \`.dz/backlog/\` (${ignore.reason ?? 'unknown error'}) — your ideas are visible to git; add \`.dz/backlog/\` to ${ignore.path} yourself`);
6046
+ }
6047
+ else if (ignore.action === 'user-opted-out') {
6048
+ write(' privacy: `.dz/backlog/` is explicitly un-ignored (a `!` negation in .gitignore) — respecting that; ideas WILL be tracked by git');
6049
+ }
5992
6050
  write(' proposal: (write your own — dz backlog add --proposal "<text>"; the CLI never fabricates it)');
5993
6051
  if (mirror.error !== undefined)
5994
6052
  write(` ↳ vector mirror deferred: ${mirror.error}`);
@@ -6027,9 +6085,23 @@ async function cmdBacklog(options, flags, cwd, write) {
6027
6085
  return 0;
6028
6086
  }
6029
6087
  if (sub === 'goals') {
6030
- const goalMap = readGoalMap(projectRoot);
6031
6088
  if (flags.has('validate')) {
6089
+ // A vacuous "valid (0 goal(s))" HID the user's mistake (idea 960c9f26): the defensive reader silently
6090
+ // dropped every malformed entry (e.g. `text` where `statement` belongs). Validate reads the DETAILED
6091
+ // view, warns per dropped entry, and refuses to call a fully-dropped compass valid.
6092
+ const detail = readGoalMapDetailed(projectRoot);
6093
+ const goalMap = detail.goalMap;
6032
6094
  const problems = [];
6095
+ const warnings = [];
6096
+ if (detail.parseError !== undefined)
6097
+ problems.push(detail.parseError);
6098
+ for (const d of detail.dropped)
6099
+ warnings.push(`goal[${d.index}]: ${d.reason} — dropped`);
6100
+ // The reader CLAMPS a bad weight before any validation could see it, so validating the clamped
6101
+ // value can only ever pass (QE MED-7 — the branch below was dead code). Warn from the RAW value
6102
+ // the user wrote, and say which value the runtime is actually using.
6103
+ for (const r of detail.repaired)
6104
+ warnings.push(`goal[${r.index}] (${r.id}): ${r.reason} — using ${r.used}`);
6033
6105
  const seen = new Set();
6034
6106
  for (const g of goalMap.goals) {
6035
6107
  if (seen.has(g.id))
@@ -6037,20 +6109,32 @@ async function cmdBacklog(options, flags, cwd, write) {
6037
6109
  seen.add(g.id);
6038
6110
  if (g.statement.trim() === '')
6039
6111
  problems.push(`goal ${g.id} has an empty statement`);
6112
+ // Defence in depth: the reader guarantees the clamp, so this can only fire if that ever regresses.
6040
6113
  if (!(g.weight > 0 && g.weight <= 1))
6041
6114
  problems.push(`goal ${g.id} weight ${g.weight} is out of (0,1]`);
6042
6115
  }
6043
- if (json)
6044
- write(JSON.stringify({ valid: problems.length === 0, problems, goals: goalMap.goals.length, exitCode: problems.length === 0 ? 0 : 1 }, null, 2));
6045
- else if (problems.length === 0)
6046
- write(`dz backlog goals: valid (${goalMap.goals.length} goal(s))`);
6116
+ // A compass whose entries were ALL dropped is a user error, not an empty compass.
6117
+ const allDropped = detail.present > 0 && goalMap.goals.length === 0;
6118
+ if (allDropped)
6119
+ problems.push(`all ${detail.present} goal entr${detail.present === 1 ? 'y was' : 'ies were'} dropped as malformed — the compass is EMPTY, not valid`);
6120
+ const valid = problems.length === 0;
6121
+ if (json) {
6122
+ write(JSON.stringify({ valid, problems, warnings, dropped: detail.dropped, repaired: detail.repaired, goals: goalMap.goals.length, present: detail.present, exitCode: valid ? 0 : 1 }, null, 2));
6123
+ }
6047
6124
  else {
6048
- write('dz backlog goals: INVALID');
6049
- for (const p of problems)
6050
- write(` - ${p}`);
6125
+ for (const w of warnings)
6126
+ write(`dz backlog goals: WARNING ${w}`);
6127
+ if (valid)
6128
+ write(`dz backlog goals: valid (${goalMap.goals.length} goal(s)${detail.dropped.length > 0 ? `, ${detail.dropped.length} dropped` : ''})`);
6129
+ else {
6130
+ write('dz backlog goals: INVALID');
6131
+ for (const p of problems)
6132
+ write(` - ${p}`);
6133
+ }
6051
6134
  }
6052
- return problems.length === 0 ? 0 : 1;
6135
+ return valid ? 0 : 1;
6053
6136
  }
6137
+ const goalMap = readGoalMap(projectRoot);
6054
6138
  if (json) {
6055
6139
  write(JSON.stringify({ goals: goalMap.goals, exitCode: 0 }, null, 2));
6056
6140
  return 0;
@@ -6067,7 +6151,40 @@ async function cmdBacklog(options, flags, cwd, write) {
6067
6151
  if (sub === 'roulette') {
6068
6152
  const ideas = readIdeas(projectRoot);
6069
6153
  const pool = eligibleIdeas(ideas);
6070
- if (pool.length === 0) {
6154
+ // ── Commit-argument validation runs BEFORE any early return (QE HIGH-2): an empty eligible pool
6155
+ // used to return 0 first, so `--commit <garbage>` reported success having validated nothing. A
6156
+ // mutation request is answered on its own merits, whatever the pool looks like.
6157
+ // parseArgs turns `--commit <id>` into an OPTION and a bare `--commit` into a FLAG. A trailing
6158
+ // positional id is only a commit target when --commit was actually passed (QE HIGH-1): a bare
6159
+ // `dz backlog roulette <id>` must never mutate.
6160
+ const positionalId = options.get('_positional_1');
6161
+ const commitOpt = options.get('commit');
6162
+ const wantsCommit = flags.has('commit') || commitOpt !== undefined;
6163
+ if (!wantsCommit && positionalId !== undefined) {
6164
+ return emitErr(`an idea id alone does not start it — a spin never mutates. Use: dz backlog roulette --commit ${positionalId}`);
6165
+ }
6166
+ const commitId = wantsCommit ? commitOpt ?? positionalId : undefined;
6167
+ if (wantsCommit && commitId !== undefined) {
6168
+ if (!isSafeId(commitId))
6169
+ return emitErr(`refusing an unsafe idea id: ${JSON.stringify(commitId)}`);
6170
+ const target = ideas.find((i) => i.id === commitId);
6171
+ if (target === undefined)
6172
+ return emitErr(`no idea ${commitId} — run dz backlog list to see the ids`);
6173
+ // TERMINAL ideas must not be silently resurrected (Codex re-QE). in-progress is allowed —
6174
+ // re-committing it is an idempotent no-op, not a resurrection.
6175
+ if (target.status === 'shipped' || target.status === 'dropped') {
6176
+ return emitErr(`idea ${commitId} is ${target.status} — a terminal idea cannot be (re)started`);
6177
+ }
6178
+ }
6179
+ // `--pick N` is a read-only ranking view; combining it with a mutation request is ambiguous and was
6180
+ // silently ignoring the commit (Codex re-QE) — refuse the combination explicitly.
6181
+ if (wantsCommit && options.get('pick') !== undefined) {
6182
+ return emitErr('--pick cannot be combined with --commit — commit a single explicit id without --pick');
6183
+ }
6184
+ if (pool.length === 0 && commitId === undefined) {
6185
+ // A bare `--commit` with nothing to spin cannot name a pick to paste, so it says exactly that.
6186
+ if (wantsCommit)
6187
+ return emitErr('roulette --commit requires an idea id, and there is no eligible idea to suggest one — run dz backlog list');
6071
6188
  write(json ? JSON.stringify({ pick: null, eligible: 0, exitCode: 0 }) : 'dz backlog roulette: no eligible ideas (status new/enriched)');
6072
6189
  return 0;
6073
6190
  }
@@ -6087,19 +6204,34 @@ async function cmdBacklog(options, flags, cwd, write) {
6087
6204
  }
6088
6205
  const seedOpt = options.get('seed');
6089
6206
  const seed = seedOpt !== undefined && Number.isFinite(Number(seedOpt)) ? Number(seedOpt) : Math.floor(Math.random() * 2 ** 31);
6090
- const pick = spinRoulette(ideas, cfg.roulette, seededRng(seed), now);
6207
+ // With an empty pool but a valid --commit id, there is nothing to spin — the commit still stands.
6208
+ const spun = pool.length > 0 ? spinRoulette(ideas, cfg.roulette, seededRng(seed), now) : undefined;
6209
+ if (wantsCommit && commitId === undefined) {
6210
+ // --commit takes an EXPLICIT id (idea 5fcd9a2d): the spin is a function of the CURRENT pool, so a
6211
+ // bare `--commit` after the pool changed could start a DIFFERENT idea than the dry-run showed. The
6212
+ // id the user saw is the id that gets committed — the spin no longer decides a mutation.
6213
+ const msg = `roulette --commit requires the idea id you are committing to: dz backlog roulette --commit ${spun.id} (this spin's pick) — a bare --commit could start a different idea than the one you were shown`;
6214
+ if (json)
6215
+ write(JSON.stringify({ error: msg, pickId: spun.id, exitCode: 1 }, null, 2));
6216
+ else
6217
+ write(`dz backlog: ${msg}`);
6218
+ return 1;
6219
+ }
6220
+ let pick = spun;
6091
6221
  let committed = false;
6092
- if (flags.has('commit')) {
6093
- const idx = ideas.findIndex((i) => i.id === pick.id);
6222
+ if (commitId !== undefined) {
6223
+ // Validated above (safe id + known id) BEFORE any early return.
6224
+ const idx = ideas.findIndex((i) => i.id === commitId);
6094
6225
  ideas[idx].status = 'in-progress';
6095
6226
  writeIdeas(projectRoot, ideas);
6227
+ pick = ideas[idx];
6096
6228
  committed = true;
6097
6229
  }
6098
6230
  if (json)
6099
- write(JSON.stringify({ pick, seed, committed, exitCode: 0 }, null, 2));
6100
- else {
6231
+ write(JSON.stringify({ pick: pick ?? null, seed, committed, exitCode: 0 }, null, 2));
6232
+ else if (pick !== undefined) {
6101
6233
  write(`dz backlog roulette (seed ${seed}): ${pick.id} — ${pick.text.slice(0, 70)}`);
6102
- write(` align ${pick.goalAlignment.toFixed(3)}${pick.goalId !== null ? ` (${pick.goalId})` : ''} · effort ${pick.effort}${committed ? ' · → in-progress' : ' · (add --commit to start it)'}`);
6234
+ write(` align ${pick.goalAlignment.toFixed(3)}${pick.goalId !== null ? ` (${pick.goalId})` : ''} · effort ${pick.effort}${committed ? ' · → in-progress' : ` · (start it with: dz backlog roulette --commit ${pick.id})`}`);
6103
6235
  }
6104
6236
  return 0;
6105
6237
  }
@@ -6175,7 +6307,7 @@ async function cmdBacklog(options, flags, cwd, write) {
6175
6307
  write(' list [--status <s>][--goal <id>] list ideas');
6176
6308
  write(' show <id> full record');
6177
6309
  write(' goals [--validate] the compass (.dz/backlog/goals.json)');
6178
- write(' roulette [--pick N][--seed n][--commit] weighted draw for what to work on next');
6310
+ write(' roulette [--pick N][--seed n][--commit <id>] weighted draw; --commit takes the id you saw');
6179
6311
  write(' enrich <id> stage the idea2prd hand-off (agent expands)');
6180
6312
  write(` jira <id> draft a Jira issue (adapter: ${[...BACKLOG_BACKENDS].join('|')})`);
6181
6313
  write(' harmonize [--apply][--threshold 0-1] batch semantic dedup of the backlog');