@dzhechkov/harness-cli 0.8.5 → 0.8.6
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 +7 -7
- package/README.md +22 -10
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +159 -33
- package/dist/cli.js.map +1 -1
- package/package.json +23 -20
- package/sbom.json +6 -6
- package/src/cli.ts +133 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 83 commands, 14 presets, 10 platform targets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,23 +41,23 @@
|
|
|
41
41
|
"sbom.json"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@dzhechkov/harness-core": "^0.8.
|
|
45
|
-
"@dzhechkov/
|
|
46
|
-
"@dzhechkov/
|
|
47
|
-
"@dzhechkov/
|
|
48
|
-
"@dzhechkov/skills-
|
|
49
|
-
"@dzhechkov/skills-web3": "^0.2.
|
|
50
|
-
"@dzhechkov/skills-qe": "^0.3.
|
|
51
|
-
"@dzhechkov/skills-meta": "^0.9.
|
|
52
|
-
"@dzhechkov/skills-academic": "^0.3.
|
|
53
|
-
"@dzhechkov/skills-ecc": "^0.1.
|
|
54
|
-
"@dzhechkov/skills-news": "^0.1.
|
|
55
|
-
"@dzhechkov/skills-
|
|
56
|
-
"@dzhechkov/skills-
|
|
57
|
-
"@dzhechkov/skills-
|
|
58
|
-
"@dzhechkov/skills-
|
|
59
|
-
"@dzhechkov/skills-
|
|
60
|
-
"@dzhechkov/skills-website-cloner": "^0.1.
|
|
44
|
+
"@dzhechkov/harness-core": "^0.8.6",
|
|
45
|
+
"@dzhechkov/scout": "^0.8.11",
|
|
46
|
+
"@dzhechkov/skills-mcp": "^0.3.10",
|
|
47
|
+
"@dzhechkov/harness-presets": "^0.5.18",
|
|
48
|
+
"@dzhechkov/skills-devops": "^0.3.17",
|
|
49
|
+
"@dzhechkov/skills-web3": "^0.2.9",
|
|
50
|
+
"@dzhechkov/skills-qe": "^0.3.14",
|
|
51
|
+
"@dzhechkov/skills-meta": "^0.9.55",
|
|
52
|
+
"@dzhechkov/skills-academic": "^0.3.7",
|
|
53
|
+
"@dzhechkov/skills-ecc": "^0.1.12",
|
|
54
|
+
"@dzhechkov/skills-news": "^0.1.16",
|
|
55
|
+
"@dzhechkov/skills-idea2prd": "^0.1.15",
|
|
56
|
+
"@dzhechkov/skills-pm": "^0.1.5",
|
|
57
|
+
"@dzhechkov/skills-reverse-engineering": "^0.3.4",
|
|
58
|
+
"@dzhechkov/skills-presentation-storyteller": "^0.1.14",
|
|
59
|
+
"@dzhechkov/skills-taste": "^0.1.5",
|
|
60
|
+
"@dzhechkov/skills-website-cloner": "^0.1.5"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^25.6.0",
|
|
@@ -72,10 +72,13 @@
|
|
|
72
72
|
},
|
|
73
73
|
"repository": {
|
|
74
74
|
"type": "git",
|
|
75
|
-
"url": "https://github.com/djd1m/dz-harness
|
|
75
|
+
"url": "git+https://github.com/djd1m/dz-harness.git",
|
|
76
76
|
"directory": "packages/@dzhechkov/harness-cli"
|
|
77
77
|
},
|
|
78
|
-
"homepage": "https://github.com/djd1m/dz-harness
|
|
78
|
+
"homepage": "https://github.com/djd1m/dz-harness/tree/main/packages/@dzhechkov/harness-cli#readme",
|
|
79
|
+
"bugs": {
|
|
80
|
+
"url": "https://github.com/djd1m/dz-harness/issues"
|
|
81
|
+
},
|
|
79
82
|
"scripts": {
|
|
80
83
|
"build": "tsc -p tsconfig.json",
|
|
81
84
|
"test": "vitest run",
|
package/sbom.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"hashes": [
|
|
26
26
|
{
|
|
27
27
|
"alg": "SHA-256",
|
|
28
|
-
"content": "
|
|
28
|
+
"content": "4a54061280f2c8a3173a07e43492a73a927fdb98dccf6c1acd933735e5b9aca5"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
},
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"hashes": [
|
|
126
126
|
{
|
|
127
127
|
"alg": "SHA-256",
|
|
128
|
-
"content": "
|
|
128
|
+
"content": "d02abab0b734f19ac7e75d7a28b25ae43cbfc025911128c5848f8e81ba2bf01f"
|
|
129
129
|
}
|
|
130
130
|
]
|
|
131
131
|
},
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"hashes": [
|
|
136
136
|
{
|
|
137
137
|
"alg": "SHA-256",
|
|
138
|
-
"content": "
|
|
138
|
+
"content": "3ac75ba951cac7ae985b2b7407ffb2645ef36d98f44897d9094b00f4db20517f"
|
|
139
139
|
}
|
|
140
140
|
]
|
|
141
141
|
},
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"hashes": [
|
|
146
146
|
{
|
|
147
147
|
"alg": "SHA-256",
|
|
148
|
-
"content": "
|
|
148
|
+
"content": "c7c308fbc3d596905c25760ba8fa337656f42de06d0565f73ad7c230979a514d"
|
|
149
149
|
}
|
|
150
150
|
]
|
|
151
151
|
},
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
"name": "dz:canonical-json-sha256-v2",
|
|
342
|
-
"value": "
|
|
342
|
+
"value": "2549a7e988b7a04071319dbbad8917d4231cd648720e70485a594ee9e3bd4aa1"
|
|
343
343
|
}
|
|
344
344
|
]
|
|
345
345
|
},
|
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
"hashes": [
|
|
370
370
|
{
|
|
371
371
|
"alg": "SHA-256",
|
|
372
|
-
"content": "
|
|
372
|
+
"content": "5ff3270b1b82fe28e15d09f37d8099468102abc926512303c7ef76e6c75ecf0d"
|
|
373
373
|
}
|
|
374
374
|
]
|
|
375
375
|
},
|
package/src/cli.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { execFile, execFileSync, execSync, spawn, spawnSync, type ChildProcess }
|
|
|
15
15
|
import { createHash, randomBytes } from 'node:crypto';
|
|
16
16
|
import { homedir, tmpdir } from 'node:os';
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
18
19
|
|
|
19
20
|
import {
|
|
20
21
|
createSkill,
|
|
@@ -283,6 +284,7 @@ import {
|
|
|
283
284
|
harvestStageOutcomes,
|
|
284
285
|
recommendModels,
|
|
285
286
|
planFeed,
|
|
287
|
+
unfedRuns,
|
|
286
288
|
GRADE_SUCCESS_FLOOR,
|
|
287
289
|
COST_LADDER,
|
|
288
290
|
splitScenarios,
|
|
@@ -609,7 +611,7 @@ Usage:
|
|
|
609
611
|
dz backlog ship <id> [<id>…] [--reason <t>] [--dry-run] [--project <dir>] [--json] (mark work DONE: new|enriched|in-progress → shipped, removing it from the roulette pool — run it after finishing a task; short id prefixes ok, ambiguous = loud error)
|
|
610
612
|
dz backlog drop <id> [<id>…] [--reason <t>] [--dry-run] [--project <dir>] [--json] (retire an idea: new|enriched|in-progress → dropped)
|
|
611
613
|
dz backlog edit <id> --text "<new>" | --append "<more>" [--dry-run] [--project <dir>] [--json] (rewrite ONE idea's text, preserving every other field; re-embeds the dedup vector, and on a failed re-embed MARKS the record embedStale so dedup refuses to trust it — previous text preserved in .dz/backlog/edits.jsonl)
|
|
612
|
-
dz routing recommend [--tier <t>] [--apply] [--json] (per-stage args.models suggestion from REAL telemetry — harness records + imported run-meta sidecars — printed WITH its basis
|
|
614
|
+
dz routing recommend [--tier <t>] [--apply] [--json] (per-stage args.models suggestion from REAL telemetry — harness records + imported run-meta sidecars — printed WITH its basis + current/STALE/UNFED store receipt; qe is FORCED cross-family of code; --apply feeds .dz/routing-outcomes.json idempotently by runId)
|
|
613
615
|
dz backlog reopen <id> [<id>…] [--reason <t>] [--dry-run] [--project <dir>] [--json] (back to the pool: shipped|dropped|in-progress → new)
|
|
614
616
|
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)
|
|
615
617
|
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)
|
|
@@ -7833,6 +7835,90 @@ function gatherReadmeCounts(root: string): { label: string; a: number; b: number
|
|
|
7833
7835
|
function gatherGuardFacts(op: string, root: string, text: string | undefined, storeCap: number): Record<string, unknown> {
|
|
7834
7836
|
const facts: Record<string, unknown> = { op };
|
|
7835
7837
|
if (op === 'publish') {
|
|
7838
|
+
// Advisory I/O: unreadable telemetry or fed state is absence of evidence, never a fabricated
|
|
7839
|
+
// stale finding and never a publish blocker.
|
|
7840
|
+
try {
|
|
7841
|
+
const routing = readRoutingTelemetry(root);
|
|
7842
|
+
facts['routingFreshness'] = { unfedRunIds: unfedRuns(routing.harvest.samples, routing.alreadyFed) };
|
|
7843
|
+
} catch { /* fail-open: routing-store-stale is SOFT and needs gathered evidence */ }
|
|
7844
|
+
// marketplace-parity: regenerate the two published showcase manifests from the LIVE registry,
|
|
7845
|
+
// but redirect every write to a unique temp root. Version is operator-owned, so pinning the
|
|
7846
|
+
// published value into generatePlugin makes the comparison composition-only by construction.
|
|
7847
|
+
try {
|
|
7848
|
+
const showcaseDir = join(root, '.claude-plugin');
|
|
7849
|
+
if (!existsSync(showcaseDir)) {
|
|
7850
|
+
facts['marketplaceParity'] = { applicable: false };
|
|
7851
|
+
} else {
|
|
7852
|
+
const pluginPath = join(showcaseDir, 'plugin.json');
|
|
7853
|
+
const marketplacePath = join(showcaseDir, 'marketplace.json');
|
|
7854
|
+
const hasPlugin = existsSync(pluginPath);
|
|
7855
|
+
const hasMarketplace = existsSync(marketplacePath);
|
|
7856
|
+
const manifestFailures: { file: 'plugin.json' | 'marketplace.json'; error: string }[] = [];
|
|
7857
|
+
const readManifest = (path: string, file: 'plugin.json' | 'marketplace.json'): unknown => {
|
|
7858
|
+
try {
|
|
7859
|
+
return JSON.parse(readFileSync(path, 'utf8')) as unknown;
|
|
7860
|
+
} catch (error) {
|
|
7861
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7862
|
+
manifestFailures.push({ file, error: message.replace(/\s+/g, ' ').slice(0, 240) });
|
|
7863
|
+
return undefined;
|
|
7864
|
+
}
|
|
7865
|
+
};
|
|
7866
|
+
const versionOf = (manifest: unknown): string | undefined => {
|
|
7867
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) return undefined;
|
|
7868
|
+
const version = (manifest as Record<string, unknown>)['version'];
|
|
7869
|
+
return typeof version === 'string' && version !== '' ? version : undefined;
|
|
7870
|
+
};
|
|
7871
|
+
const publishedPlugin = hasPlugin ? readManifest(pluginPath, 'plugin.json') : undefined;
|
|
7872
|
+
const publishedMarketplace = hasMarketplace ? readManifest(marketplacePath, 'marketplace.json') : undefined;
|
|
7873
|
+
const marketplaceRecord = publishedMarketplace && typeof publishedMarketplace === 'object' && !Array.isArray(publishedMarketplace)
|
|
7874
|
+
? publishedMarketplace as Record<string, unknown>
|
|
7875
|
+
: undefined;
|
|
7876
|
+
const marketplacePlugins = marketplaceRecord !== undefined && Array.isArray(marketplaceRecord['plugins'])
|
|
7877
|
+
? marketplaceRecord['plugins']
|
|
7878
|
+
: [];
|
|
7879
|
+
const publishedVersion = versionOf(publishedPlugin)
|
|
7880
|
+
?? versionOf(publishedMarketplace)
|
|
7881
|
+
?? versionOf(marketplacePlugins[0]);
|
|
7882
|
+
if (manifestFailures.length > 0) {
|
|
7883
|
+
facts['marketplaceParity'] = {
|
|
7884
|
+
applicable: true,
|
|
7885
|
+
manifestFailures,
|
|
7886
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7887
|
+
};
|
|
7888
|
+
} else if (hasPlugin !== hasMarketplace) {
|
|
7889
|
+
facts['marketplaceParity'] = {
|
|
7890
|
+
applicable: true,
|
|
7891
|
+
onlyOnePresent: true,
|
|
7892
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7893
|
+
};
|
|
7894
|
+
} else if (!hasPlugin) {
|
|
7895
|
+
facts['marketplaceParity'] = { applicable: false };
|
|
7896
|
+
} else {
|
|
7897
|
+
let scratch: string | undefined;
|
|
7898
|
+
try {
|
|
7899
|
+
const registry = buildRegistry(root);
|
|
7900
|
+
scratch = mkdtempSync(join(tmpdir(), 'dz-guard-marketplace-'));
|
|
7901
|
+
const generated = generatePlugin(scratch, registry, { version: publishedVersion });
|
|
7902
|
+
const freshPlugin = JSON.parse(readFileSync(generated.pluginJsonPath, 'utf8')) as unknown;
|
|
7903
|
+
const freshMarketplace = JSON.parse(readFileSync(generated.marketplaceJsonPath, 'utf8')) as unknown;
|
|
7904
|
+
facts['marketplaceParity'] = {
|
|
7905
|
+
applicable: true,
|
|
7906
|
+
diverged: !isDeepStrictEqual(publishedPlugin, freshPlugin)
|
|
7907
|
+
|| !isDeepStrictEqual(publishedMarketplace, freshMarketplace),
|
|
7908
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7909
|
+
};
|
|
7910
|
+
} catch {
|
|
7911
|
+
facts['marketplaceParity'] = {
|
|
7912
|
+
applicable: true,
|
|
7913
|
+
regenerateFailed: true,
|
|
7914
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7915
|
+
};
|
|
7916
|
+
} finally {
|
|
7917
|
+
if (scratch !== undefined) rmSync(scratch, { recursive: true, force: true });
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
7921
|
+
} catch { /* fail-open only on unexpected showcase-discovery failure; manifest read/parse errors are structured violations above */ }
|
|
7836
7922
|
// agents-md-policy-sync: fixed registry, no tree walk. The pure detector
|
|
7837
7923
|
// recomputes every expected hash from current source text; this gatherer
|
|
7838
7924
|
// only supplies bytes. Any unexpected gather failure omits the fact, and
|
|
@@ -14272,6 +14358,34 @@ async function cmdBacklog(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
14272
14358
|
return sub === undefined ? 0 : 1;
|
|
14273
14359
|
}
|
|
14274
14360
|
|
|
14361
|
+
/** Read the recommender's existing telemetry planes plus its idempotency receipt. One adapter is
|
|
14362
|
+
* shared by `recommend` and the publish advisory so their freshness definitions cannot drift. */
|
|
14363
|
+
function readRoutingTelemetry(repoRoot: string): { harvest: ReturnType<typeof harvestStageOutcomes>; alreadyFed: string[] } {
|
|
14364
|
+
const records: unknown[] = readHarnessRecords(repoRoot);
|
|
14365
|
+
for (const base of [join(repoRoot, 'features'), join(repoRoot, '.dz', 'loop-trace')]) {
|
|
14366
|
+
if (!existsSync(base)) continue;
|
|
14367
|
+
let names: string[] = [];
|
|
14368
|
+
try { names = readdirSync(base); } catch { continue; }
|
|
14369
|
+
for (const name of names) {
|
|
14370
|
+
const sidecar = join(base, name, 'run-meta.json');
|
|
14371
|
+
if (!existsSync(sidecar)) continue;
|
|
14372
|
+
try {
|
|
14373
|
+
const meta = JSON.parse(readFileSync(sidecar, 'utf-8')) as { runMeta?: { resolved?: boolean; records?: unknown[] } };
|
|
14374
|
+
if (meta.runMeta?.resolved === true && Array.isArray(meta.runMeta.records)) records.push(...meta.runMeta.records);
|
|
14375
|
+
} catch { /* an unreadable sidecar contributes no asserted record */ }
|
|
14376
|
+
}
|
|
14377
|
+
}
|
|
14378
|
+
let alreadyFed: string[] = [];
|
|
14379
|
+
const fedPath = join(repoRoot, '.dz', 'routing-fed.json');
|
|
14380
|
+
if (existsSync(fedPath)) {
|
|
14381
|
+
const parsed = JSON.parse(readFileSync(join(repoRoot, '.dz', 'routing-fed.json'), 'utf-8')) as unknown;
|
|
14382
|
+
if (!Array.isArray(parsed)) throw new Error('.dz/routing-fed.json must be a JSON array of runIds');
|
|
14383
|
+
if (parsed.some((v) => typeof v !== 'string')) throw new Error('.dz/routing-fed.json contains a non-string runId');
|
|
14384
|
+
alreadyFed = parsed as string[];
|
|
14385
|
+
}
|
|
14386
|
+
return { harvest: harvestStageOutcomes(records), alreadyFed };
|
|
14387
|
+
}
|
|
14388
|
+
|
|
14275
14389
|
/**
|
|
14276
14390
|
* `dz routing` — inspect the learned cost-optimal routing outcome store (feature learned-cost-routing). Shows
|
|
14277
14391
|
* what `args.models[stage]='auto-cost'` currently believes per (stage, complexity-tier, model): gated
|
|
@@ -14286,28 +14400,20 @@ function cmdRouting(options: Map<string, string>, flags: Set<string>, cwd: strin
|
|
|
14286
14400
|
// ── recommend — harvest real telemetry, print per-stage picks WITH THE BASIS, optionally feed the
|
|
14287
14401
|
// store (a9c3dd5c fn 3, ADR-001). Sources: live harness records + imported run-meta.json sidecars.
|
|
14288
14402
|
if (options.get('_positional_0') === 'recommend') {
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
try { names = readdirSync(base); } catch { continue; }
|
|
14295
|
-
for (const name of names) {
|
|
14296
|
-
const sidecar = join(base, name, 'run-meta.json');
|
|
14297
|
-
if (!existsSync(sidecar)) continue;
|
|
14298
|
-
try {
|
|
14299
|
-
const meta = JSON.parse(readFileSync(sidecar, 'utf-8')) as { runMeta?: { resolved?: boolean; records?: unknown[] } };
|
|
14300
|
-
if (meta.runMeta?.resolved === true && Array.isArray(meta.runMeta.records)) records.push(...meta.runMeta.records);
|
|
14301
|
-
} catch { /* an unreadable sidecar contributes nothing — counted below as noResult */ }
|
|
14302
|
-
}
|
|
14403
|
+
let routing: ReturnType<typeof readRoutingTelemetry>;
|
|
14404
|
+
try { routing = readRoutingTelemetry(repoRoot); }
|
|
14405
|
+
catch (e) {
|
|
14406
|
+
write(`dz routing recommend: cannot read routing freshness state — ${(e as Error).message}`);
|
|
14407
|
+
return 1;
|
|
14303
14408
|
}
|
|
14304
|
-
const harvest =
|
|
14305
|
-
|
|
14409
|
+
const harvest = routing.harvest;
|
|
14410
|
+
let rec = recommendModels(harvest, {
|
|
14411
|
+
...(options.get('tier') !== undefined ? { tier: options.get('tier')! } : {}),
|
|
14412
|
+
alreadyFed: routing.alreadyFed,
|
|
14413
|
+
});
|
|
14306
14414
|
if (flags.has('apply')) {
|
|
14307
14415
|
const fedPath = join(repoRoot, '.dz', 'routing-fed.json');
|
|
14308
|
-
|
|
14309
|
-
try { alreadyFed = JSON.parse(readFileSync(fedPath, 'utf-8')) as string[]; } catch { /* first apply */ }
|
|
14310
|
-
const plan = planFeed(harvest.samples, alreadyFed);
|
|
14416
|
+
const plan = planFeed(harvest.samples, routing.alreadyFed);
|
|
14311
14417
|
for (const sample of plan.toFeed) finalizeOutcome(repoRoot, sample.stage, sample.tier, sample.model, sample.success);
|
|
14312
14418
|
try {
|
|
14313
14419
|
mkdirSync(join(repoRoot, '.dz'), { recursive: true });
|
|
@@ -14316,6 +14422,10 @@ function cmdRouting(options: Map<string, string>, flags: Set<string>, cwd: strin
|
|
|
14316
14422
|
write(`dz routing recommend: fed ${plan.toFeed.length} sample(s) but could NOT persist the fed-set — a re-apply WILL double-count: ${(e as Error).message}`);
|
|
14317
14423
|
return 1;
|
|
14318
14424
|
}
|
|
14425
|
+
rec = recommendModels(harvest, {
|
|
14426
|
+
...(options.get('tier') !== undefined ? { tier: options.get('tier')! } : {}),
|
|
14427
|
+
alreadyFed: plan.fedAfter,
|
|
14428
|
+
});
|
|
14319
14429
|
write(`dz routing recommend --apply: fed ${plan.toFeed.length} sample(s) into .dz/routing-outcomes.json${plan.skippedRuns.length > 0 ? `; skipped ${plan.skippedRuns.length} already-fed run(s) (idempotent by runId)` : ''}`);
|
|
14320
14430
|
}
|
|
14321
14431
|
if (flags.has('json')) {
|
|
@@ -14327,6 +14437,9 @@ function cmdRouting(options: Map<string, string>, flags: Set<string>, cwd: strin
|
|
|
14327
14437
|
write(` ${s.stage.padEnd(14)} ${s.spec.padEnd(20)} ${s.insufficientData ? '[insufficient data — cold-start pick]' : '[quality bar met]'} ${s.pick.evidence}`);
|
|
14328
14438
|
}
|
|
14329
14439
|
write(` basis: ${rec.basis.rule}`);
|
|
14440
|
+
if (rec.basis.freshness === 'current') write(' store: current (0 unharvested runs)');
|
|
14441
|
+
else if (rec.basis.freshness === 'stale') write(` store: STALE — ${rec.basis.unfed.count} run(s) harvested but never fed; auto-cost is routing from an older snapshot — run \`dz routing recommend --apply\``);
|
|
14442
|
+
else write(' store: UNFED — auto-cost has no learned rows; picks below are cold-start');
|
|
14330
14443
|
write(` basis: skipped records — no result ${rec.basis.skipped.noResult}, no modelsUsed ${rec.basis.skipped.noModels} (predates model routing — history, not an error), no grade ${rec.basis.skipped.noGrade}, unknown model ${rec.basis.skipped.unknownModel}`);
|
|
14331
14444
|
write(` basis: ${rec.basis.crossFamilyNote}`);
|
|
14332
14445
|
return 0;
|