@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 +8 -8
- package/README.md +73 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +165 -33
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/sbom.json +7 -7
- package/src/cli.ts +151 -22
package/.dz-manifest.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "README.md",
|
|
12
|
-
"sha256": "
|
|
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": "
|
|
44
|
+
"sha256": "c9b415287eda0a7c2ad32dbb7cfc3ecce19aa3b90c96d3fbeedd62d20fe34bc7"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "dist/cli.js",
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "56b4da2639c1721180387f28166d84d065718a4002f556307a6598570d335809"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "dist/cli.js.map",
|
|
52
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
92
|
+
"sha256": "7d6ba26ec1188db3b3ac89f50b015ba1f0c6117b5c30d99b22da41da10c96c12"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "tsconfig.json",
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
}
|
|
102
102
|
]
|
|
103
103
|
},
|
|
104
|
-
"signature": "
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
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
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
6093
|
-
|
|
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' :
|
|
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]
|
|
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');
|