@dzhechkov/harness-cli 0.3.225 → 0.3.227
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/README.md +36 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +161 -3
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +130 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ The **`dz`** CLI — the main entry point to the DZ Harness Hub. Install AI skil
|
|
|
6
6
|
|
|
7
7
|
> **`dz` is a package manager + cross-compiler for your AI agent harness.** Write a skill once in one canonical form; `dz` installs it into any agent's harness, holds it to a quality bar, and lets the harness learn over time.
|
|
8
8
|
|
|
9
|
-
**The problem.** You accumulate ~
|
|
9
|
+
**The problem.** You accumulate ~179 skills (design-thinking, QE, devops, web3, MCP, academic…). Five pains follow:
|
|
10
10
|
|
|
11
11
|
1. **Every agent wants a different layout.** Claude Code reads `.claude/skills/`, Codex `.codex/`, OpenCode/Hermes/OpenClaude their own. Hand-maintaining N copies is sync hell.
|
|
12
12
|
2. **Skills arrive from many upstream repos** — they must be *canonicalized* (brought to one form) and kept in sync without losing provenance.
|
|
@@ -147,9 +147,9 @@ dz help # see all commands
|
|
|
147
147
|
dz pretrain # analyze project files → recommend by tech stack
|
|
148
148
|
dz recommend "build API and deploy to K8s" # keyword match → skills + toolkits
|
|
149
149
|
dz recommend "work on this project" # generic? → auto-runs pretrain → recommends by stack
|
|
150
|
-
dz stats #
|
|
150
|
+
dz stats # 46 packages, 179 skills, 10 targets, 14 presets
|
|
151
151
|
dz dashboard # visual panel — packages, adapters, skill packs
|
|
152
|
-
dz registry # browse all
|
|
152
|
+
dz registry # browse all 179 skills by category
|
|
153
153
|
dz registry search kubernetes # find specific skills
|
|
154
154
|
dz registry --category devops # filter by domain
|
|
155
155
|
dz downloads # npm weekly download stats
|
|
@@ -355,7 +355,7 @@ the release job gets a perfectly attested malicious package.
|
|
|
355
355
|
A ready-to-install workflow is at `features/publish-provenance/07_code_changes/publish.yml`; copy it to
|
|
356
356
|
`.github/workflows/` and add an `NPM_TOKEN` secret.
|
|
357
357
|
|
|
358
|
-
### Skill Packs (
|
|
358
|
+
### Skill Packs (23 packs · 179 skills)
|
|
359
359
|
|
|
360
360
|
Each pack is an npm package — click through for the **full per-skill documentation** (what each skill does + how to trigger it). Install a whole pack with `dz install <pkg>`, or pick skills with `dz init --select` / a `--preset`.
|
|
361
361
|
|
|
@@ -365,6 +365,7 @@ Each pack is an npm package — click through for the **full per-skill documenta
|
|
|
365
365
|
| [@dzhechkov/skills-mcp](https://www.npmjs.com/package/@dzhechkov/skills-mcp) | 16 | MCP-server integrations — search, git/GitLab, Google Workspace, Notion, AgentDB memory |
|
|
366
366
|
| [@dzhechkov/skills-web3](https://www.npmjs.com/package/@dzhechkov/skills-web3) | 12 | On-chain / DeFi — wallets, swaps, bridges, ENS, agent identity (ERC-8004) |
|
|
367
367
|
| [@dzhechkov/skills-qe](https://www.npmjs.com/package/@dzhechkov/skills-qe) | 20 | Quality engineering — test-gen, coverage, chaos, defect intelligence, QCSD swarms |
|
|
368
|
+
| [@dzhechkov/skills-reasoning](https://www.npmjs.com/package/@dzhechkov/skills-reasoning) | 4 | Generic reasoning & code-quality — investigate (root-cause), solid (SOLID/TDD), karpathy-guidelines, agents-md-creator |
|
|
368
369
|
| [@dzhechkov/skills-ecc](https://www.npmjs.com/package/@dzhechkov/skills-ecc) | 20 | Claude-Code engineering craft — agent architecture, autonomous loops, framework patterns |
|
|
369
370
|
| [@dzhechkov/skills-meta](https://www.npmjs.com/package/@dzhechkov/skills-meta) | 18 | Dev-process meta skills — explore, feature-adr, design-thinking, audit, skill-advisor |
|
|
370
371
|
| [@dzhechkov/skills-academic](https://www.npmjs.com/package/@dzhechkov/skills-academic) | 5 | Thesis-defense toolkit — dissertation review, questions, doc-check, defense eval |
|
|
@@ -388,7 +389,8 @@ Each pack is an npm package — click through for the **full per-skill documenta
|
|
|
388
389
|
| `health` | 8 | Medical AI (diagnostics, drugs, labs, clinical decisions) |
|
|
389
390
|
| `keysarium` | 9 | Full research toolkit (feature-adr, presentation, reverse-eng) |
|
|
390
391
|
| `p-replicator` | 10 | AI product development (/replicate, SPARC PRD, pipeline-forge) |
|
|
391
|
-
| `feature-adr` |
|
|
392
|
+
| `feature-adr` | 8 | Feature pipeline (feature-adr, explore, frontend-design, code-critic, code-impl, system-grill) |
|
|
393
|
+
| `reasoning` | 4 | Generic reasoning & code-quality (investigate, solid, karpathy-guidelines, agents-md-creator) — stack-neutral, zero coupling |
|
|
392
394
|
| `devops` | 30 | DevOps skills (terraform, kubernetes, c4-architecture, incident-response, problem-management, risk-assessment, ...) |
|
|
393
395
|
| `web3` | 12 | Web3/DeFi (quicknode, zerion, symbiosis, bankr, veil, neynar, ...) |
|
|
394
396
|
| `mcp` | 16 | MCP servers (agentdb, brave-search, gmail, gitlab, comfyui, notion, ...) |
|
|
@@ -431,7 +433,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
|
|
|
431
433
|
|
|
432
434
|
> **A skill and its npx toolkit are not duplicates — they're a graduation.** Several skills (e.g. `feature-adr`, `design-thinking`) exist BOTH as a skill inside a `dz` preset AND as a standalone `npx` package. The preset's SKILL.md is **fully functional on its own** (the whole methodology — modules + references — travels with it, and it auto-activates by description), and it's the only way to compile that capability to the **non-Claude platforms** (Codex/OpenCode/Hermes/OpenClaude) via `dz`. The npx package adds **project-level runtime governance** around the same skill: a slash command, governance rules, a context shard, and (for feature-adr) reward-learning + `/harvest`. So: pick the **skill/preset** for a working capability across platforms; pick the **npx toolkit** when you want it as a governed, command-driven fixture of one project.
|
|
433
435
|
|
|
434
|
-
## All Commands (
|
|
436
|
+
## All Commands (52)
|
|
435
437
|
|
|
436
438
|
```
|
|
437
439
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
|
|
@@ -485,6 +487,7 @@ dz feature-adr-setup [--plan] [--from-spec <f>] [--apply] # guided project onb
|
|
|
485
487
|
dz challenge --plan <plan.md> [--json] [--context-only] [--author <model>] # adversarial plan-gate (behind the `challenge-panel` skill): assemble a WIDE context pack (plan + vision + testing + map + degradations) + the fixed C1-C8 "break it" brief for a FRESH adversary (≠ plan author); advisory, never blocks
|
|
486
488
|
dz routing [--stage <s>] [--json] # inspect the learned cost-optimal routing store: what `args.models.<stage>='auto-cost'` believes per (stage, complexity-tier, model) — gated attempts/successes/rate (feeds feature-adr model selection)
|
|
487
489
|
dz bto-optimize --split|--plan|--select|--scope-check|--diff [--json] # deterministic engine behind /bto-optimize: hold-out split + hard-capped budget + no-regress-on-holdout winner selection (defeats judge-gaming); prose-only, diff-confirmed, never auto-writes
|
|
490
|
+
dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runner <cmd>] [--json] # §42 test-discrimination gate for feature-adr Step-8: run the ADR's property test in an isolated git worktree at pre-feature base — it MUST go red without the fix; a green is a false green (HIGH finding, advisory, never auto-aborts)
|
|
488
491
|
dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] (dry-run by default; pass --yes/--confirm to go live; claim-check gate defaults to warn — surfaces README claim findings, never blocks)
|
|
489
492
|
dz auto-canonicalize --source <github-url> --pack <skills-pack>
|
|
490
493
|
dz sync-upstream [--package <dir>] [--list] [--all]
|
|
@@ -810,7 +813,7 @@ dz scout --since 2026-05-01 # only recent repos
|
|
|
810
813
|
**Radar mode** (`dz scout`) scans **11 sources** in parallel (GitHub + npm + HN + MCP Registry + Glama + OSSInsight + Smithery + Semantic Scholar + arXiv + ECC + AgentBox):
|
|
811
814
|
1. **Detects skill format** — SKILL.md, plugin.json, .claude/skills/, .claude-plugin/, MCP manifests
|
|
812
815
|
2. **Scores relevance** — format (40%) + stars (30%) + recency (20%) + novelty (10%)
|
|
813
|
-
3. **Compares against our
|
|
816
|
+
3. **Compares against our 46 packages** — finds skills we don't have
|
|
814
817
|
4. **Recommends** — integrate (score ≥70) / monitor (40-69 + ≥50 stars) / skip
|
|
815
818
|
|
|
816
819
|
**Deep analyst mode** (`dz scout --deep`) goes further for top-scored repos:
|
|
@@ -932,7 +935,7 @@ Compares installed skills with canonical source, reports which need `dz init --f
|
|
|
932
935
|
### dz downloads — npm weekly download stats
|
|
933
936
|
|
|
934
937
|
```bash
|
|
935
|
-
dz downloads # fetch weekly downloads for all
|
|
938
|
+
dz downloads # fetch weekly downloads for all 46 packages
|
|
936
939
|
```
|
|
937
940
|
|
|
938
941
|
### dz benchmark — L0 quality gate
|
|
@@ -982,7 +985,7 @@ Scans a GitHub repo for SKILL.md files, generates `dz create-skill` commands.
|
|
|
982
985
|
### dz registry — searchable skill index
|
|
983
986
|
|
|
984
987
|
```bash
|
|
985
|
-
dz registry # visual panel:
|
|
988
|
+
dz registry # visual panel: 179 skills in 11 categories
|
|
986
989
|
dz registry search security # fuzzy search
|
|
987
990
|
dz registry --category mcp # filter by category
|
|
988
991
|
```
|
|
@@ -1556,6 +1559,30 @@ Verify: after a session, `.dz/agentdb.db` grows and `.dz/sessions.jsonl` stays e
|
|
|
1556
1559
|
|
|
1557
1560
|
Bridge: with the agentdb backend enabled, `dz recall` itself is the bridge — it merges lexical FTS5 with vector similarity over the same store (see *Semantic recall* below). `dz recall --all --json` stays the portable SHARING form.
|
|
1558
1561
|
|
|
1562
|
+
### `dz discrimination-check` — when a test is green but you're not sure it actually catches anything
|
|
1563
|
+
|
|
1564
|
+
feature-adr Step-8 asserts the ADR's load-bearing safety property **has** a test. But a green test can be a
|
|
1565
|
+
false green — it may never exercise the property, so it would stay green even if the property regressed. The
|
|
1566
|
+
`§42` gate (learned from rUv's `cve-bench/evaluate.mjs`) proves the test **discriminates**: it runs the property
|
|
1567
|
+
test in a throwaway git worktree at the pre-feature base (no fix) and requires it to go **red**.
|
|
1568
|
+
```bash
|
|
1569
|
+
# the Step-7 feature diff is uncommitted mid-pipeline, so HEAD is the pre-feature base
|
|
1570
|
+
dz discrimination-check --test packages/x/test/auth-property.test.ts --base HEAD --json
|
|
1571
|
+
```
|
|
1572
|
+
```
|
|
1573
|
+
discrimination-check @ HEAD — verdict: NON_DISCRIMINATING
|
|
1574
|
+
✗ packages/x/test/auth-property.test.ts: NON_DISCRIMINATING
|
|
1575
|
+
|
|
1576
|
+
[high] non-discriminating property test (false green)
|
|
1577
|
+
1 property test(s) PASS at pre-feature base — they do not exercise the ADR safety property … (Advisory — the pipeline continues; the owner decides.)
|
|
1578
|
+
```
|
|
1579
|
+
Verdicts: `DISCRIMINATES` (red by assertion — good) · `DISCRIMINATES_VIA_ERROR` (couldn't load at base, e.g. a
|
|
1580
|
+
brand-new module — inferred) · `NON_DISCRIMINATING` (green at base — a false green, HIGH finding) ·
|
|
1581
|
+
`CANNOT_ISOLATE` (no runnable property test — folds into "property untested"). It **never auto-aborts** (dz's
|
|
1582
|
+
rule: a false gate kills trust) — exit 0 on any verdict, exit 2 only on a usage/setup error. The worktree +
|
|
1583
|
+
sanitation live in tested CLI code; base ref, paths, name filter, and runner are all injection-checked, and the
|
|
1584
|
+
worktree is always removed. Step-8 runs this on the ADR Confirmation's `Required automated check` automatically.
|
|
1585
|
+
|
|
1559
1586
|
### Semantic recall (vector tier)
|
|
1560
1587
|
|
|
1561
1588
|
`dz recall` is **hybrid** when the vector tier is available and **exactly the old lexical command** when it is not — enabling it never changes behavior for projects that skip it.
|
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;AA2OH,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;CACzB;AA+oJD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwI5E"}
|
package/dist/cli.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmdirSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
|
-
import { homedir } from 'node:os';
|
|
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, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, 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, decideProvenance, isInsideTree, signManifest, verifyManifest, assertKeyOutsideTree, decidePublishGate, 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, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, } from '@dzhechkov/harness-core';
|
|
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, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, 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, decideProvenance, isInsideTree, signManifest, verifyManifest, assertKeyOutsideTree, decidePublishGate, 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, } from '@dzhechkov/harness-core';
|
|
13
13
|
import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
|
|
14
14
|
import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
|
|
15
15
|
const USAGE = `dz - DZ cross-platform harness CLI
|
|
@@ -4249,6 +4249,162 @@ function cmdChallenge(options, flags, cwd, write) {
|
|
|
4249
4249
|
write(`\n── dispatch (panel ≠ plan author) ──\n${adversary.model}: ${adversary.note}`);
|
|
4250
4250
|
return 0;
|
|
4251
4251
|
}
|
|
4252
|
+
/**
|
|
4253
|
+
* `dz discrimination-check` — the §42 test-discrimination gate (feature learned from cve-bench/evaluate.mjs).
|
|
4254
|
+
* feature-adr Step-8 already asserts the ADR safety property HAS a test; this asserts that test DISCRIMINATES:
|
|
4255
|
+
* run the property test(s) in an isolated git worktree at the pre-feature base (default HEAD, since the feature
|
|
4256
|
+
* diff is uncommitted mid-pipeline). They MUST go red without the feature diff — a green is a false green.
|
|
4257
|
+
*
|
|
4258
|
+
* --test <a.test.ts[,b.test.ts]> the property test file(s) mapped from the ADR Confirmation (comma list)
|
|
4259
|
+
* --base <ref> the base ref to fail against (default HEAD)
|
|
4260
|
+
* --name '<filter>' optional -t test-name filter applied to every target
|
|
4261
|
+
* --runner '<cmd>' test runner (default `npx vitest run`)
|
|
4262
|
+
* --json machine-readable {plan, results, verdict, finding}
|
|
4263
|
+
*
|
|
4264
|
+
* NEVER auto-aborts: a non-discriminating (false-green) test is reported as a HIGH finding for the owner to
|
|
4265
|
+
* decide (dz's rule — a false gate kills trust). Exit code is 0 on a clean run regardless of verdict; 2 only on
|
|
4266
|
+
* a usage/setup error, so a caller distinguishes "gate ran" from "gate could not run".
|
|
4267
|
+
*/
|
|
4268
|
+
function cmdDiscriminationCheck(options, flags, cwd, write) {
|
|
4269
|
+
let repoRoot = cwd;
|
|
4270
|
+
try {
|
|
4271
|
+
repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim() || cwd;
|
|
4272
|
+
}
|
|
4273
|
+
catch { /* not git */ }
|
|
4274
|
+
const testArg = options.get('test');
|
|
4275
|
+
if (testArg === undefined || testArg.trim() === '') {
|
|
4276
|
+
write('dz discrimination-check: pass --test <property-test.ts[,...]> (the test(s) the ADR Confirmation names).');
|
|
4277
|
+
return 2;
|
|
4278
|
+
}
|
|
4279
|
+
const nameFilter = options.get('name');
|
|
4280
|
+
const propertyTests = testArg.split(',').map((s) => s.trim()).filter(Boolean).map((file) => nameFilter !== undefined && nameFilter.trim() !== '' ? { file, name: nameFilter.trim() } : { file });
|
|
4281
|
+
const baseRef = options.get('base') ?? 'HEAD';
|
|
4282
|
+
const runnerOpt = options.get('runner');
|
|
4283
|
+
const plan = planDiscriminationCheck(runnerOpt !== undefined ? { baseRef, propertyTests, runner: runnerOpt } : { baseRef, propertyTests });
|
|
4284
|
+
if (!plan.runnable) {
|
|
4285
|
+
// No safe target to run → this is the existing "property untested" finding (empty propertyTests classify).
|
|
4286
|
+
const result = classifyDiscrimination({ propertyTests: [], results: [] });
|
|
4287
|
+
if (flags.has('json')) {
|
|
4288
|
+
write(JSON.stringify({ plan, results: [], ...result }, null, 2));
|
|
4289
|
+
return 0;
|
|
4290
|
+
}
|
|
4291
|
+
write(`discrimination-check: NOT RUN (${plan.reason ?? 'unknown'})`);
|
|
4292
|
+
if (plan.rejected.length)
|
|
4293
|
+
write(' rejected: ' + plan.rejected.map((r) => `${r.file} (${r.reason})`).join(', '));
|
|
4294
|
+
write(` → ${result.finding?.detail ?? 'no property test to check'}`);
|
|
4295
|
+
return 0;
|
|
4296
|
+
}
|
|
4297
|
+
// Execute the plan in a temp worktree WE own; substitute {{WORKTREE}} and always clean up.
|
|
4298
|
+
// `git worktree add` must CREATE the path, so compute a fresh non-existent one (do NOT mkdtemp it).
|
|
4299
|
+
const worktree = join(mkdtempSync(join(tmpdir(), 'dz-disc-')), 'wt');
|
|
4300
|
+
const results = [];
|
|
4301
|
+
try {
|
|
4302
|
+
// 1) add the detached worktree at base (git creates `worktree`; its parent already exists).
|
|
4303
|
+
const addCmd = plan.commands[0].replace(/\{\{WORKTREE\}\}/g, worktree);
|
|
4304
|
+
execSync(addCmd, { cwd: repoRoot, stdio: 'pipe', encoding: 'utf-8' });
|
|
4305
|
+
// 1b) a fresh worktree has NO node_modules — without this, every test fails to load (runner + deps
|
|
4306
|
+
// unresolvable) and the gate collapses to always-VIA_ERROR, blind to false greens. Absolute-path
|
|
4307
|
+
// symlinks point back at the main checkout's already-installed trees, robust across pnpm's layout.
|
|
4308
|
+
const linkNodeModules = (relDir) => {
|
|
4309
|
+
const srcNm = join(repoRoot, relDir, 'node_modules');
|
|
4310
|
+
if (!existsSync(srcNm))
|
|
4311
|
+
return;
|
|
4312
|
+
const dstNm = join(worktree, relDir, 'node_modules');
|
|
4313
|
+
if (existsSync(dstNm))
|
|
4314
|
+
return;
|
|
4315
|
+
try {
|
|
4316
|
+
mkdirSync(dirname(dstNm), { recursive: true });
|
|
4317
|
+
symlinkSync(srcNm, dstNm, 'dir');
|
|
4318
|
+
}
|
|
4319
|
+
catch { /* best effort */ }
|
|
4320
|
+
};
|
|
4321
|
+
linkNodeModules('.'); // root (hoisted deps + .bin)
|
|
4322
|
+
const pkgDirs = new Set();
|
|
4323
|
+
for (const t of plan.targets) {
|
|
4324
|
+
let d = dirname(t.file);
|
|
4325
|
+
while (d && d !== '.' && d !== sep) {
|
|
4326
|
+
if (existsSync(join(repoRoot, d, 'package.json'))) {
|
|
4327
|
+
pkgDirs.add(d);
|
|
4328
|
+
break;
|
|
4329
|
+
}
|
|
4330
|
+
d = dirname(d);
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
for (const d of pkgDirs)
|
|
4334
|
+
linkNodeModules(d);
|
|
4335
|
+
// 2) copy each property test into the base worktree, then 3) run it and record pass/fail/error per target.
|
|
4336
|
+
for (const t of plan.targets) {
|
|
4337
|
+
try {
|
|
4338
|
+
const src = resolve(repoRoot, t.file);
|
|
4339
|
+
// containment guard (defense in depth beyond planDiscriminationCheck's path sanitation).
|
|
4340
|
+
if (!resolve(src).startsWith(resolve(repoRoot) + sep)) {
|
|
4341
|
+
results.push(nameFor(t, 'error'));
|
|
4342
|
+
continue;
|
|
4343
|
+
}
|
|
4344
|
+
const dst = join(worktree, t.file);
|
|
4345
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
4346
|
+
writeFileSync(dst, readFileSync(src));
|
|
4347
|
+
}
|
|
4348
|
+
catch {
|
|
4349
|
+
results.push(nameFor(t, 'error'));
|
|
4350
|
+
continue;
|
|
4351
|
+
}
|
|
4352
|
+
const runner = (runnerOpt !== undefined && plan.commands.some((c) => c.includes(runnerOpt))) ? runnerOpt : 'npx vitest run';
|
|
4353
|
+
// t.file + t.name already passed the engine's strict sanitation (no quotes/metacharacters/leading-dash);
|
|
4354
|
+
// still quote + `--` so a path can never be read as a runner option or split a word.
|
|
4355
|
+
const nameArg = t.name ? ` -t '${t.name}'` : '';
|
|
4356
|
+
try {
|
|
4357
|
+
execSync(`${runner}${nameArg} -- '${t.file}'`, { cwd: worktree, stdio: 'pipe', encoding: 'utf-8' });
|
|
4358
|
+
results.push(nameFor(t, 'pass')); // exit 0 → test PASSED at base → false green
|
|
4359
|
+
}
|
|
4360
|
+
catch (e) {
|
|
4361
|
+
// vitest exits non-zero on failure AND on load/compile error. Distinguish: a load error usually names
|
|
4362
|
+
// "Cannot find module"/"Failed to load"/"No test files"; otherwise treat as an assertion failure (red).
|
|
4363
|
+
const out = String(e.stdout ?? '') + String(e.stderr ?? '');
|
|
4364
|
+
const isLoadError = /cannot find module|failed to load|no test (files )?found|error: cannot|transform failed|esbuild/i.test(out);
|
|
4365
|
+
results.push(nameFor(t, isLoadError ? 'error' : 'fail'));
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
catch (e) {
|
|
4370
|
+
if (flags.has('json')) {
|
|
4371
|
+
write(JSON.stringify({ plan, error: 'worktree-setup-failed', detail: String(e.message).slice(0, 300) }, null, 2));
|
|
4372
|
+
}
|
|
4373
|
+
else
|
|
4374
|
+
write(`discrimination-check: could not create worktree at ${baseRef}: ${String(e.message).slice(0, 200)}`);
|
|
4375
|
+
return 2;
|
|
4376
|
+
}
|
|
4377
|
+
finally {
|
|
4378
|
+
try {
|
|
4379
|
+
execSync(`git worktree remove --force ${worktree}`, { cwd: repoRoot, stdio: 'pipe' });
|
|
4380
|
+
}
|
|
4381
|
+
catch { /* fall through to rm */ }
|
|
4382
|
+
// remove the whole mkdtemp parent (worktree is `<mkdtemp>/wt`), so nothing leaks under tmp even on error.
|
|
4383
|
+
try {
|
|
4384
|
+
rmSync(dirname(worktree), { recursive: true, force: true });
|
|
4385
|
+
}
|
|
4386
|
+
catch { /* best effort */ }
|
|
4387
|
+
try {
|
|
4388
|
+
execSync('git worktree prune', { cwd: repoRoot, stdio: 'pipe' });
|
|
4389
|
+
}
|
|
4390
|
+
catch { /* best effort */ }
|
|
4391
|
+
}
|
|
4392
|
+
const result = classifyDiscrimination({ propertyTests, results });
|
|
4393
|
+
if (flags.has('json')) {
|
|
4394
|
+
write(JSON.stringify({ plan, results, ...result }, null, 2));
|
|
4395
|
+
return 0;
|
|
4396
|
+
}
|
|
4397
|
+
write(`discrimination-check @ ${baseRef} — verdict: ${result.aggregate}`);
|
|
4398
|
+
for (const p of result.perTest)
|
|
4399
|
+
write(` ${p.verdict === 'NON_DISCRIMINATING' ? '✗' : '✓'} ${p.file}${p.name ? ` (${p.name})` : ''}: ${p.verdict}`);
|
|
4400
|
+
if (result.finding)
|
|
4401
|
+
write(`\n [${result.finding.severity}] ${result.finding.title}\n ${result.finding.detail}`);
|
|
4402
|
+
return 0;
|
|
4403
|
+
}
|
|
4404
|
+
/** small helper: build a result row, omitting `name` when absent (exactOptionalPropertyTypes). */
|
|
4405
|
+
function nameFor(t, outcome) {
|
|
4406
|
+
return t.name !== undefined ? { file: t.file, name: t.name, outcome } : { file: t.file, outcome };
|
|
4407
|
+
}
|
|
4252
4408
|
/**
|
|
4253
4409
|
* `dz routing` — inspect the learned cost-optimal routing outcome store (feature learned-cost-routing). Shows
|
|
4254
4410
|
* what `args.models[stage]='auto-cost'` currently believes per (stage, complexity-tier, model): gated
|
|
@@ -4676,6 +4832,8 @@ export async function runCli(argv, io = {}) {
|
|
|
4676
4832
|
return cmdFeatureAdrSetup(options, flags, cwd, write);
|
|
4677
4833
|
case 'challenge':
|
|
4678
4834
|
return cmdChallenge(options, flags, cwd, write);
|
|
4835
|
+
case 'discrimination-check':
|
|
4836
|
+
return cmdDiscriminationCheck(options, flags, cwd, write);
|
|
4679
4837
|
case 'routing':
|
|
4680
4838
|
return cmdRouting(options, flags, cwd, write);
|
|
4681
4839
|
case 'bto-optimize':
|