@dzhechkov/harness-cli 0.3.247 → 0.3.250
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 +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +371 -1
- package/dist/cli.js.map +1 -1
- package/package.json +3 -3
- package/sbom.json +7 -7
- package/src/cli.ts +354 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.250",
|
|
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,6 +41,7 @@
|
|
|
41
41
|
"sbom.json"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
+
"@dzhechkov/harness-core": "^0.3.141",
|
|
44
45
|
"@dzhechkov/harness-presets": "^0.5.0",
|
|
45
46
|
"@dzhechkov/scout": "^0.8.0",
|
|
46
47
|
"@dzhechkov/skills-devops": "^0.3.0",
|
|
@@ -56,8 +57,7 @@
|
|
|
56
57
|
"@dzhechkov/skills-idea2prd": "^0.1.0",
|
|
57
58
|
"@dzhechkov/skills-reverse-engineering": "^0.1.0",
|
|
58
59
|
"@dzhechkov/skills-presentation-storyteller": "^0.1.0",
|
|
59
|
-
"@dzhechkov/skills-website-cloner": "^0.1.0"
|
|
60
|
-
"@dzhechkov/harness-core": "0.3.138"
|
|
60
|
+
"@dzhechkov/skills-website-cloner": "^0.1.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^25.6.0",
|
package/sbom.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"hashes": [
|
|
26
26
|
{
|
|
27
27
|
"alg": "SHA-256",
|
|
28
|
-
"content": "
|
|
28
|
+
"content": "1559995482381fd6ac146e6d229cb7d33476cdca8fad21fd361467dd698bd065"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
},
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"hashes": [
|
|
106
106
|
{
|
|
107
107
|
"alg": "SHA-256",
|
|
108
|
-
"content": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
188
|
+
"content": "4e22f0553a19181eea750db59bd3eec84fd880473ac523705a1a26197665e87e"
|
|
189
189
|
}
|
|
190
190
|
]
|
|
191
191
|
},
|
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
"hashes": [
|
|
206
206
|
{
|
|
207
207
|
"alg": "SHA-256",
|
|
208
|
-
"content": "
|
|
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": "
|
|
228
|
+
"content": "2e013ab73b4eb3dd248ed0fbbd5c050911ad667bb78054fde0bb021fbd1815db"
|
|
229
229
|
}
|
|
230
230
|
]
|
|
231
231
|
},
|
package/src/cli.ts
CHANGED
|
@@ -200,12 +200,35 @@ import {
|
|
|
200
200
|
renderContentProbe,
|
|
201
201
|
findNonRegistrableSkillDirs,
|
|
202
202
|
assembleCompoundingReport,
|
|
203
|
+
scoreRun,
|
|
204
|
+
renderScorecard,
|
|
203
205
|
renderCompoundingReport,
|
|
204
206
|
readReinforcementState,
|
|
205
207
|
readQuarantineState,
|
|
206
208
|
registrationExitCode,
|
|
207
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,
|
|
208
230
|
} from '@dzhechkov/harness-core';
|
|
231
|
+
import type { IdeaRecord, IdeaStatus } from '@dzhechkov/harness-core';
|
|
209
232
|
import type { Family, ModelRung, Candidate as BtoCandidate, DimScores } from '@dzhechkov/harness-core';
|
|
210
233
|
import type { SetupSpec } from '@dzhechkov/harness-core';
|
|
211
234
|
import type { ProvenanceMode, PackVerdict, ClaudeUsageModel, PatternRecord, TargetName, BookKU, HarmonizeReport, UsageCalibrationPlan, ClaimFinding, RecallUsagePatternRow, GateExecution, GateStep } from '@dzhechkov/harness-core';
|
|
@@ -237,6 +260,15 @@ Usage:
|
|
|
237
260
|
dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] (portable Step-10 Delivery Gate: prints the 4-plane review brief + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked)
|
|
238
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)
|
|
239
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)
|
|
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)
|
|
240
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]
|
|
241
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)
|
|
242
274
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
@@ -2215,6 +2247,8 @@ async function cmdVector(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
2215
2247
|
return report.error !== undefined ? 1 : 0;
|
|
2216
2248
|
}
|
|
2217
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.
|
|
2218
2252
|
write(`dz vector reindex: ${report.error}`);
|
|
2219
2253
|
return 1;
|
|
2220
2254
|
}
|
|
@@ -5625,6 +5659,87 @@ function nameFor(t: { file: string; name?: string }, outcome: 'pass' | 'fail' |
|
|
|
5625
5659
|
* --author <model> cosmetic: the reviewer to dispatch, printed alongside the brief
|
|
5626
5660
|
* --json machine contract { planesChecked, planesSkipped, findings, handoff, artifact }
|
|
5627
5661
|
*/
|
|
5662
|
+
/**
|
|
5663
|
+
* `dz score --slug <feature>` — score a feature-adr RUN's process discipline from its artifacts
|
|
5664
|
+
* (feature dz-score, Reading C). Descriptive-only: exit 0 on any score; non-zero only on usage errors.
|
|
5665
|
+
*/
|
|
5666
|
+
function cmdScore(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
|
|
5667
|
+
const json = flags.has('json');
|
|
5668
|
+
if (flags.has('help')) {
|
|
5669
|
+
const usage = 'dz score --slug <feature> [--project <dir>] [--json] — process scorecard for one feature-adr run (descriptive-only, never a gate)';
|
|
5670
|
+
if (json) write(JSON.stringify({ help: usage, exitCode: 0 })); // --json stays ONE document even for help
|
|
5671
|
+
else {
|
|
5672
|
+
write(usage);
|
|
5673
|
+
write(' disciplines: ADR confirmation · discrimination · cross-model QE · live verification · README-first · learning loop · amendments');
|
|
5674
|
+
write(' descriptive-only, never a gate: a low score exits 0');
|
|
5675
|
+
}
|
|
5676
|
+
return 0;
|
|
5677
|
+
}
|
|
5678
|
+
for (const flag of flags) {
|
|
5679
|
+
if (!new Set(['json', 'help']).has(flag)) {
|
|
5680
|
+
write(json ? JSON.stringify({ error: `unknown option --${flag}`, exitCode: 1 }) : `dz score: unknown option --${flag}\n allowed: --slug <feature>, --project <dir>, --json`);
|
|
5681
|
+
return 1;
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
for (const key of options.keys()) {
|
|
5685
|
+
if (key.startsWith('_positional_') || !new Set(['slug', 'project']).has(key)) {
|
|
5686
|
+
const what = key.startsWith('_positional_') ? `unexpected argument "${options.get(key)}"` : `unknown option --${key}`;
|
|
5687
|
+
write(json ? JSON.stringify({ error: what, exitCode: 1 }) : `dz score: ${what}\n allowed: --slug <feature>, --project <dir>, --json`);
|
|
5688
|
+
return 1;
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
const slug = options.get('slug') ?? '';
|
|
5692
|
+
// The delivery-check traversal lesson, upgraded to a WHITELIST: `.` slipped the blacklist and
|
|
5693
|
+
// silently aggregated the entire features/ tree as one "run" (Codex QE #2).
|
|
5694
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(slug) || slug === '.' || slug === '..' || slug.includes('..')) {
|
|
5695
|
+
write(json ? JSON.stringify({ error: 'a plain --slug <feature> is required (no path separators)', exitCode: 1 }) : 'dz score: a plain --slug <feature> is required (no path separators)');
|
|
5696
|
+
return 1;
|
|
5697
|
+
}
|
|
5698
|
+
const root = resolve(cwd, options.get('project') ?? '.');
|
|
5699
|
+
const dir = join(root, 'features', slug);
|
|
5700
|
+
if (!existsSync(dir)) {
|
|
5701
|
+
write(json ? JSON.stringify({ error: `no run at features/${slug}/`, exitCode: 1 }) : `dz score: no run at features/${slug}/`);
|
|
5702
|
+
return 1;
|
|
5703
|
+
}
|
|
5704
|
+
|
|
5705
|
+
// Gather the artifact texts (bounded, best-effort — an unreadable file is skipped, not fatal).
|
|
5706
|
+
const artifacts: Record<string, string> = {};
|
|
5707
|
+
let fileBudget = 200; // a run has ~a dozen artifacts; a wide tree must degrade, not exhaust memory
|
|
5708
|
+
const walk = (d: string, prefix: string, depth: number): void => {
|
|
5709
|
+
if (depth > 3 || fileBudget <= 0) return;
|
|
5710
|
+
let entries: string[];
|
|
5711
|
+
try {
|
|
5712
|
+
entries = readdirSync(d);
|
|
5713
|
+
} catch {
|
|
5714
|
+
return;
|
|
5715
|
+
}
|
|
5716
|
+
for (const name of entries) {
|
|
5717
|
+
if (fileBudget <= 0) return;
|
|
5718
|
+
const full = join(d, name);
|
|
5719
|
+
const rel = prefix === '' ? name : `${prefix}/${name}`;
|
|
5720
|
+
try {
|
|
5721
|
+
// lstat: a symlinked artifact (or a symlinked features/<slug> subtree) could smuggle
|
|
5722
|
+
// OUTSIDE text into the scorecard's evidence (Codex QE #5).
|
|
5723
|
+
const st = lstatSync(full);
|
|
5724
|
+
if (st.isSymbolicLink()) continue;
|
|
5725
|
+
if (st.isDirectory()) walk(full, rel, depth + 1);
|
|
5726
|
+
else if (st.isFile() && name.endsWith('.md') && st.size < 512 * 1024) {
|
|
5727
|
+
artifacts[rel] = readFileSync(full, 'utf-8');
|
|
5728
|
+
fileBudget -= 1;
|
|
5729
|
+
}
|
|
5730
|
+
} catch {
|
|
5731
|
+
/* skip unreadable */
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
};
|
|
5735
|
+
walk(dir, '', 0);
|
|
5736
|
+
|
|
5737
|
+
const card = scoreRun(slug, artifacts);
|
|
5738
|
+
if (json) write(JSON.stringify({ ...card, exitCode: 0 }, null, 2));
|
|
5739
|
+
else write(renderScorecard(card));
|
|
5740
|
+
return 0;
|
|
5741
|
+
}
|
|
5742
|
+
|
|
5628
5743
|
/**
|
|
5629
5744
|
* `dz compounding` — does the learning loop actually PAY? (feature compounding, scout C2.)
|
|
5630
5745
|
* Gathers the facts (store rows, apply-leg usage log, guard audit) and hands them to the PURE
|
|
@@ -6065,6 +6180,241 @@ function cmdDeliveryCheck(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
6065
6180
|
return 0;
|
|
6066
6181
|
}
|
|
6067
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
|
+
|
|
6068
6418
|
/**
|
|
6069
6419
|
* `dz routing` — inspect the learned cost-optimal routing outcome store (feature learned-cost-routing). Shows
|
|
6070
6420
|
* what `args.models[stage]='auto-cost'` currently believes per (stage, complexity-tier, model): gated
|
|
@@ -6495,6 +6845,10 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
|
|
|
6495
6845
|
return cmdSkillsVerify(options, flags, cwd, write);
|
|
6496
6846
|
case 'compounding':
|
|
6497
6847
|
return cmdCompounding(options, flags, cwd, write);
|
|
6848
|
+
case 'score':
|
|
6849
|
+
return cmdScore(options, flags, cwd, write);
|
|
6850
|
+
case 'backlog':
|
|
6851
|
+
return await cmdBacklog(options, flags, cwd, write);
|
|
6498
6852
|
case 'routing':
|
|
6499
6853
|
return cmdRouting(options, flags, cwd, write);
|
|
6500
6854
|
case 'bto-optimize':
|