@dzhechkov/harness-cli 0.3.200 → 0.3.202
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 +72 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +154 -7
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +160 -7
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ point any tool at (a skill is just Markdown). Dedicated targets are on the [road
|
|
|
132
132
|
|
|
133
133
|
## User Journey — from install to mastery
|
|
134
134
|
|
|
135
|
-
All
|
|
135
|
+
All 41 commands mapped to a real workflow:
|
|
136
136
|
|
|
137
137
|
```
|
|
138
138
|
DISCOVER → INSTALL → USE → CREATE → MAINTAIN → SHARE
|
|
@@ -356,7 +356,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
|
|
|
356
356
|
|
|
357
357
|
> **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.
|
|
358
358
|
|
|
359
|
-
## All Commands (
|
|
359
|
+
## All Commands (41)
|
|
360
360
|
|
|
361
361
|
```
|
|
362
362
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
|
|
@@ -377,6 +377,7 @@ dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC me
|
|
|
377
377
|
dz teach --harmonize [--apply] [--threshold <0..1>] # alias of `dz vector harmonize`
|
|
378
378
|
dz statusline [--json] [--install] # compact Claude Code statusline: live self-learning pattern count + brain sources
|
|
379
379
|
dz usage [--json] [--project <dir>] | --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] # ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS
|
|
380
|
+
dz claim-check [paths...] [--json] [--fail-on high|medium|none] [--project <dir>] # enforce the Integrity Rule: flag untagged/overstated accuracy claims; default scan = READMEs + features' 08_qe_report.md; exit 1 only at/above --fail-on (default high)
|
|
380
381
|
dz brain list [--json] # the durable cross-project knowledge brain
|
|
381
382
|
dz brain query "<q>" [--source <slug>] [--limit <N>] [--any] [--rerank] [--json] # cross-source recall (--any = OR match; --rerank reorders on-point first). Auto-broadens: if strict all-terms match yields 0 hits, it retries once as OR and labels the result "broadened" (text note + broadened:true in --json) instead of returning empty. Explicit --any is OR from the start (never labeled broadened).
|
|
382
383
|
dz brain add [--source <slug>] [--project <dir>] [--from-slice <f>|--from-pack <p>|--from-kus <f> --slug <s> --kind <k> --license <spdx> [--override]] [--json] # grow the brain
|
|
@@ -400,7 +401,7 @@ dz create-skill --name <id> [--description <text>] [--tier 1|2|3] [--with-r
|
|
|
400
401
|
dz registry [search <query>] [--category <cat>]
|
|
401
402
|
dz benchmark <skill-dir> [--compare <dir>] [--all]
|
|
402
403
|
dz mcp-scan [path] [--json] (static agent-permission audit; exit 0/1/2 = clean/medium/high)
|
|
403
|
-
dz publish [--filter <name>] [--bump-only] (dry-run by default; pass --yes/--confirm to go live)
|
|
404
|
+
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)
|
|
404
405
|
dz auto-canonicalize --source <github-url> --pack <skills-pack>
|
|
405
406
|
dz sync-upstream [--package <dir>] [--list] [--all]
|
|
406
407
|
dz drift-check [--all] [--json] [--project <dir>] # CI gate: exit 1 on NEW shared-skill drift (baseline: .dz/drift-allowlist.json; --all incl .claude dogfood)
|
|
@@ -528,6 +529,74 @@ phase boundary and pre-emptively routes the remaining stages to Codex when `sess
|
|
|
528
529
|
dispatch dies (`codex:codex-rescue` is a Claude wrapper), so the switch must happen BEFORE — the
|
|
529
530
|
pre-emptive probe, not reactive detection, is the real defense.
|
|
530
531
|
|
|
532
|
+
### Claim check (`dz claim-check`)
|
|
533
|
+
|
|
534
|
+
**Why you want this.** Docs accumulate numbers nobody can reproduce — things like `99% accuracy`,
|
|
535
|
+
`10× faster`, `100% coverage` — and an AI agent writing your README is especially good at inventing
|
|
536
|
+
them. A rule like *"no fake claims; verify before claiming success"* enforced only by discipline
|
|
537
|
+
eventually loses. This makes it a runnable check with an exit code.
|
|
538
|
+
|
|
539
|
+
**What it does.** Every quantitative claim (coverage, test/skill/command counts, benchmarks) must be
|
|
540
|
+
tagged `MEASURED` / `CLAIMED` / `SYNTHETIC` / `ESTIMATED` / `UNVALIDATED` / `BASELINE`, and a
|
|
541
|
+
`MEASURED` claim must name its reproducer (`npm test`, a coverage report, `npm view`, a git ref).
|
|
542
|
+
A `100%` or `perfect` framing is always high severity — it is the claim most likely to be untrue.
|
|
543
|
+
(The backticks are load-bearing: this paragraph quotes the claims it forbids, and a backticked
|
|
544
|
+
literal reads as code, not as an assertion.)
|
|
545
|
+
|
|
546
|
+
So `coverage 92%` is flagged; `coverage 92% (MEASURED — npm test)` is not. `2136 tests` is flagged;
|
|
547
|
+
a shields.io badge URL is not (the URL is machinery, not prose). A number inside backticks still
|
|
548
|
+
counts: ``accuracy reached `0.95` `` is a claim.
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
dz claim-check # scans root README, every package README, features/*/08_qe_report.md
|
|
552
|
+
dz claim-check docs/perf.md --json # {"ok":false,"findings":[…],"scanned":[…]}
|
|
553
|
+
dz claim-check --fail-on medium # stricter gate for CI
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**Exit-code contract:** `0` when ok; `1` only when a finding at or above `--fail-on` (default `high`)
|
|
557
|
+
exists. `--json` always emits valid JSON, even on the failure path, and never throws on an
|
|
558
|
+
unreadable, binary, or missing file — those are skipped and reported in `scanned`.
|
|
559
|
+
|
|
560
|
+
`dz publish` runs the same check over each package's README. The gate defaults to `warn`: findings
|
|
561
|
+
are surfaced but publish status is **never** changed. Pass `--claim-check error` to fail a package
|
|
562
|
+
that carries a high-severity claim, or `--claim-check off` to disable it.
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
dz publish --filter harness-cli # warn (default): prints "⚠ claim-check: N finding(s) (M high)"
|
|
566
|
+
dz publish --filter harness-cli --claim-check error # a high finding fails THIS package only
|
|
567
|
+
dz publish --filter harness-cli --claim-check off # disable the gate entirely
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
#### From Claude Code — just ask in plain language
|
|
571
|
+
|
|
572
|
+
There is no slash command: `dz` is a CLI, and the agent runs it for you.
|
|
573
|
+
|
|
574
|
+
> "Проверь наши README на непроверяемые утверждения перед релизом"
|
|
575
|
+
>
|
|
576
|
+
> "Run dz claim-check and fix the high findings by tagging them MEASURED with a reproducer"
|
|
577
|
+
>
|
|
578
|
+
> "Before we publish, make sure no doc claims 100% of anything"
|
|
579
|
+
|
|
580
|
+
Claude runs `dz claim-check --json`, reads the findings, and edits the offending lines. Two habits
|
|
581
|
+
worth asking for explicitly: **do not silence a finding by deleting the number** (a claim you cannot
|
|
582
|
+
reproduce should be removed or downgraded, not hidden), and **do not weaken the rules** to make the
|
|
583
|
+
gate green.
|
|
584
|
+
|
|
585
|
+
#### In CI
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
dz claim-check --fail-on high || exit 1 # block a release on a "100%" claim
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Ported from rUv's [`ruview`](https://www.npmjs.com/package/@ruvnet/ruview) `src/guardrails.js` (MIT),
|
|
592
|
+
which was itself written after a documented AI-slop incident. The detection semantics are kept
|
|
593
|
+
verbatim — including a deliberately `\b`-free `100%` regex that looks like a bug and is not.
|
|
594
|
+
|
|
595
|
+
**Honest note:** run on this repo it reports hundreds of medium findings and a handful of high ones
|
|
596
|
+
(reproducer: `dz claim-check --json`, which prints the exact current counts — deliberately not frozen
|
|
597
|
+
into this README, since any doc edit moves them). That is the point of the tool, not a defect in it:
|
|
598
|
+
the high findings are real untagged perfect-score claims in our own documentation.
|
|
599
|
+
|
|
531
600
|
### Targets (10 platforms — 5 lossless + Copilot + AGENTS.md + Cursor + Gemini + Windsurf)
|
|
532
601
|
|
|
533
602
|
Five platforms natively support the [agentskills.io](https://agentskills.io) `SKILL.md` format:
|
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;AA8JH,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;AAyiHD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkH5E"}
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { execSync } from 'node:child_process';
|
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
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, recallHybrid, teachGuard, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, queryBookKnowledge, loadStorePatternsSync, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, } 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, recallHybrid, teachGuard, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, } 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
|
|
@@ -28,7 +28,7 @@ Usage:
|
|
|
28
28
|
dz workflow <task> [--dry-run]
|
|
29
29
|
dz install <npm-pkg> [--target <name>] [--project <dir>] [--force]
|
|
30
30
|
dz bundle [--preset <name> | --select id,id,...] [--out <dir>] [--skills-dir <dir>] [--force] (portable self-contained skill bundles for a generic/LangGraph consumer)
|
|
31
|
-
dz publish [--filter <name>] [--bump-only] (dry-run by default; pass --yes/--confirm/--no-dry-run to go live)
|
|
31
|
+
dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] (dry-run by default; pass --yes/--confirm/--no-dry-run to go live; claim-check gate default warn — surfaces README claim findings, never blocks; error fails an offending package)
|
|
32
32
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
|
|
33
33
|
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)
|
|
34
34
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
@@ -52,6 +52,7 @@ Usage:
|
|
|
52
52
|
dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
|
|
53
53
|
dz statusline --fa-record --slug <s> --step "<label>" [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state → 📐 panel segment)
|
|
54
54
|
dz usage [--json] [--project <dir>] | dz usage --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] [--project <dir>] (ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS; pct=null when limits unconfigured)
|
|
55
|
+
dz claim-check [paths...] [--json] [--fail-on high|medium|none] [--project <dir>] (enforce the Integrity Rule: flag untagged/overstated accuracy claims; default scan = root README.md + every discovered package's README.md + features/*/08_qe_report.md; exit 1 only at/above --fail-on, default high)
|
|
55
56
|
dz pretrain [--project <dir>]
|
|
56
57
|
dz recommend "<task description>"
|
|
57
58
|
dz compose <preset1+preset2+...> [--target <name>]
|
|
@@ -2599,11 +2600,12 @@ function cmdPublish(options, flags, cwd, write) {
|
|
|
2599
2600
|
// Reject unknown flags/options so a typo (e.g. `--dry-rum`) can NEVER be
|
|
2600
2601
|
// silently swallowed and flip the command into live-publish mode.
|
|
2601
2602
|
const allowedFlags = new Set(['dry-run', 'no-dry-run', 'yes', 'confirm', 'bump-only', 'help']);
|
|
2602
|
-
const allowedOptions = new Set(['filter']);
|
|
2603
|
+
const allowedOptions = new Set(['filter', 'claim-check']);
|
|
2604
|
+
const allowedHelp = ' allowed: --dry-run (default), --yes/--confirm/--no-dry-run (go live), --bump-only, --filter <substr>, --claim-check <off|warn|error>';
|
|
2603
2605
|
for (const flag of flags) {
|
|
2604
2606
|
if (!allowedFlags.has(flag)) {
|
|
2605
2607
|
write(`dz publish: unknown option --${flag}`);
|
|
2606
|
-
write(
|
|
2608
|
+
write(allowedHelp);
|
|
2607
2609
|
return 1;
|
|
2608
2610
|
}
|
|
2609
2611
|
}
|
|
@@ -2612,10 +2614,19 @@ function cmdPublish(options, flags, cwd, write) {
|
|
|
2612
2614
|
continue;
|
|
2613
2615
|
if (!allowedOptions.has(key)) {
|
|
2614
2616
|
write(`dz publish: unknown option --${key}`);
|
|
2615
|
-
write(
|
|
2617
|
+
write(allowedHelp);
|
|
2616
2618
|
return 1;
|
|
2617
2619
|
}
|
|
2618
2620
|
}
|
|
2621
|
+
// Pre-publish claim-check gate strictness: reject (never coerce) an invalid value. Default 'warn'
|
|
2622
|
+
// per ADR-001 — findings are SURFACED on every publish, but 'warn' never changes publish status,
|
|
2623
|
+
// so the success path is unchanged. 'off' disables the gate; 'error' fails an offending package.
|
|
2624
|
+
const claimCheckRaw = options.get('claim-check');
|
|
2625
|
+
if (claimCheckRaw !== undefined && !['off', 'warn', 'error'].includes(claimCheckRaw)) {
|
|
2626
|
+
write(`dz publish: invalid --claim-check '${claimCheckRaw}' (expected off|warn|error)`);
|
|
2627
|
+
return 1;
|
|
2628
|
+
}
|
|
2629
|
+
const claimCheckOpt = claimCheckRaw ?? 'warn';
|
|
2619
2630
|
const bumpOnly = flags.has('bump-only');
|
|
2620
2631
|
const filterStr = options.get('filter');
|
|
2621
2632
|
// SAFETY: trim + drop empty segments (mirrors --select at the top of cmdInit).
|
|
@@ -2657,13 +2668,17 @@ function cmdPublish(options, flags, cwd, write) {
|
|
|
2657
2668
|
}
|
|
2658
2669
|
write(`╚══════════════════════════════════════════════════════════════════════╝`);
|
|
2659
2670
|
}
|
|
2660
|
-
const report = publishPackages(cwd, { dryRun, filter, bumpOnly });
|
|
2661
|
-
write(`\ndz publish${dryRun ? ' --dry-run' : ''}${bumpOnly ? ' --bump-only' : ''}`);
|
|
2671
|
+
const report = publishPackages(cwd, { dryRun, filter, bumpOnly, claimGate: claimCheckOpt });
|
|
2672
|
+
write(`\ndz publish${dryRun ? ' --dry-run' : ''}${bumpOnly ? ' --bump-only' : ''}${claimCheckOpt !== 'warn' ? ` --claim-check ${claimCheckOpt}` : ''}`);
|
|
2662
2673
|
write(` Published: ${report.published} Skipped: ${report.skipped} Errors: ${report.errors}\n`);
|
|
2663
2674
|
for (const pkg of report.packages) {
|
|
2664
2675
|
const icon = pkg.status === 'published' ? '✓' : pkg.status === 'skipped' ? '○' : '✗';
|
|
2665
2676
|
const detail = pkg.error ? ` (${pkg.error.slice(0, 60)})` : '';
|
|
2666
2677
|
write(` ${icon} ${pkg.name.padEnd(35)} ${pkg.oldVersion} → ${pkg.newVersion} ${pkg.status}${detail}`);
|
|
2678
|
+
// Surface warn-mode findings that did not block the publish.
|
|
2679
|
+
if (pkg.claimCheck && pkg.claimCheck.findings > 0 && pkg.status !== 'error') {
|
|
2680
|
+
write(` ⚠ claim-check: ${pkg.claimCheck.findings} finding(s) (${pkg.claimCheck.high} high) in README.md`);
|
|
2681
|
+
}
|
|
2667
2682
|
}
|
|
2668
2683
|
return report.errors > 0 ? 1 : 0;
|
|
2669
2684
|
}
|
|
@@ -2819,6 +2834,136 @@ function cmdBenchmark(options, flags, cwd, write) {
|
|
|
2819
2834
|
write(`\nL0 only. For L1/L2 judge evaluation: /bto-test ${skillDir}`);
|
|
2820
2835
|
return score.passRate >= 80 ? 0 : 1;
|
|
2821
2836
|
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Exit-code contract for `dz claim-check` (named in the ADR, locked by tests):
|
|
2839
|
+
* exit 0 when no finding at/above `failOn` exists; exit 1 only when one does.
|
|
2840
|
+
* `--fail-on none` never exits non-zero. Severity order: high > medium > none.
|
|
2841
|
+
*/
|
|
2842
|
+
function computeClaimExit(findings, failOn) {
|
|
2843
|
+
const rank = { none: 0, medium: 1, high: 2 };
|
|
2844
|
+
if (failOn === 'none')
|
|
2845
|
+
return 0;
|
|
2846
|
+
return findings.some((f) => rank[f.severity] >= rank[failOn]) ? 1 : 0;
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Default scan set when no paths are given: the repo root README, every published
|
|
2850
|
+
* package README under packages/@dzhechkov, and every feature's 08_qe_report.md. Each
|
|
2851
|
+
* entry is guarded by existsSync; a missing packages/ or features/ dir is skipped, never fatal.
|
|
2852
|
+
*/
|
|
2853
|
+
function defaultClaimScanSet(root) {
|
|
2854
|
+
const set = [];
|
|
2855
|
+
const rootReadme = join(root, 'README.md');
|
|
2856
|
+
if (existsSync(rootReadme))
|
|
2857
|
+
set.push(rootReadme);
|
|
2858
|
+
try {
|
|
2859
|
+
for (const p of discoverPackages(root)) {
|
|
2860
|
+
const readme = join(p.dir, 'README.md');
|
|
2861
|
+
if (existsSync(readme))
|
|
2862
|
+
set.push(readme);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
catch { /* no packages/@dzhechkov dir — skip */ }
|
|
2866
|
+
try {
|
|
2867
|
+
const featuresDir = join(root, 'features');
|
|
2868
|
+
if (existsSync(featuresDir)) {
|
|
2869
|
+
for (const e of readdirSync(featuresDir, { withFileTypes: true })) {
|
|
2870
|
+
if (!e.isDirectory())
|
|
2871
|
+
continue;
|
|
2872
|
+
const qe = join(featuresDir, e.name, '08_qe_report.md');
|
|
2873
|
+
if (existsSync(qe))
|
|
2874
|
+
set.push(qe);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
catch { /* no features dir — skip */ }
|
|
2879
|
+
return set;
|
|
2880
|
+
}
|
|
2881
|
+
/** Cheap binary sniff: a NUL byte in the first 512 chars ⇒ skip (never scan binaries). */
|
|
2882
|
+
function looksBinaryText(text) {
|
|
2883
|
+
const n = Math.min(text.length, 512);
|
|
2884
|
+
for (let i = 0; i < n; i += 1)
|
|
2885
|
+
if (text.charCodeAt(i) === 0)
|
|
2886
|
+
return true;
|
|
2887
|
+
return false;
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* `dz claim-check [paths...] [--json] [--fail-on high|medium|none] [--project <dir>]`
|
|
2891
|
+
*
|
|
2892
|
+
* I/O adapter over the pure `claimCheck` engine: resolves the scan set, reads each file
|
|
2893
|
+
* never-throw (unreadable/binary/missing files are skipped and reported in `scanned`), merges
|
|
2894
|
+
* per-file findings (each enriched with its `file`), and applies the exit-code contract.
|
|
2895
|
+
* `--json` ALWAYS emits valid JSON `{ok, findings, scanned}`, even on the failure path.
|
|
2896
|
+
*/
|
|
2897
|
+
function cmdClaimCheck(options, _optionLists, flags, cwd, write) {
|
|
2898
|
+
// Reject (never silently coerce) an invalid --fail-on.
|
|
2899
|
+
const failOnRaw = options.get('fail-on') ?? 'high';
|
|
2900
|
+
if (!['high', 'medium', 'none'].includes(failOnRaw)) {
|
|
2901
|
+
write(`dz claim-check: invalid --fail-on '${failOnRaw}' (expected high|medium|none)`);
|
|
2902
|
+
return 1;
|
|
2903
|
+
}
|
|
2904
|
+
const failOn = failOnRaw;
|
|
2905
|
+
const root = resolve(cwd, options.get('project') ?? '.');
|
|
2906
|
+
// `--json <path>` is captured by parseArgs as the OPTION `json=<path>` (the boolean flag ate the
|
|
2907
|
+
// next token — the same gotcha cmdMcpScan recovers). Recover both: mark json, adopt the eaten
|
|
2908
|
+
// token as the first path. `--json` alone (at end / before another --flag) lands as a bare flag.
|
|
2909
|
+
let json = flags.has('json');
|
|
2910
|
+
const paths = [];
|
|
2911
|
+
const jsonConsumed = options.get('json');
|
|
2912
|
+
if (jsonConsumed !== undefined) {
|
|
2913
|
+
json = true;
|
|
2914
|
+
if (jsonConsumed !== 'true')
|
|
2915
|
+
paths.push(jsonConsumed);
|
|
2916
|
+
}
|
|
2917
|
+
// Variadic positional paths land as _positional_0, _positional_1, … (see parseArgs).
|
|
2918
|
+
for (let i = 0;; i += 1) {
|
|
2919
|
+
const p = options.get(`_positional_${i}`);
|
|
2920
|
+
if (p === undefined)
|
|
2921
|
+
break;
|
|
2922
|
+
paths.push(p);
|
|
2923
|
+
}
|
|
2924
|
+
const scanSet = paths.length > 0 ? paths.map((p) => resolve(root, p)) : defaultClaimScanSet(root);
|
|
2925
|
+
const findings = [];
|
|
2926
|
+
const scanned = [];
|
|
2927
|
+
for (const abs of scanSet) {
|
|
2928
|
+
// Show a repo-relative path for in-tree files; fall back to the absolute path for
|
|
2929
|
+
// anything outside root (avoids an ugly ../../.. chain for an explicit external path).
|
|
2930
|
+
const relRaw = relative(root, abs);
|
|
2931
|
+
const rel = relRaw && !relRaw.startsWith('..') ? relRaw : abs;
|
|
2932
|
+
let text;
|
|
2933
|
+
try {
|
|
2934
|
+
text = readFileSync(abs, 'utf-8');
|
|
2935
|
+
}
|
|
2936
|
+
catch (err) {
|
|
2937
|
+
scanned.push({ path: rel, status: 'skipped', reason: err instanceof Error ? err.message : 'not found' });
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
if (looksBinaryText(text)) {
|
|
2941
|
+
scanned.push({ path: rel, status: 'skipped', reason: 'binary' });
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
const result = claimCheck(text);
|
|
2945
|
+
for (const f of result.findings)
|
|
2946
|
+
findings.push({ ...f, file: rel });
|
|
2947
|
+
scanned.push({ path: rel, status: 'scanned', findings: result.findings.length });
|
|
2948
|
+
}
|
|
2949
|
+
const ok = findings.length === 0;
|
|
2950
|
+
if (json) {
|
|
2951
|
+
write(JSON.stringify({ ok, findings, scanned })); // ALWAYS valid JSON, pass or fail
|
|
2952
|
+
return computeClaimExit(findings, failOn);
|
|
2953
|
+
}
|
|
2954
|
+
// Human output.
|
|
2955
|
+
write(summarize({ ok, findings }));
|
|
2956
|
+
for (const f of findings) {
|
|
2957
|
+
write(` [${f.severity}] ${f.file}:${f.line} — ${f.reason}`);
|
|
2958
|
+
write(` ${f.excerpt}`);
|
|
2959
|
+
write(` ↳ ${f.suggestion}`);
|
|
2960
|
+
}
|
|
2961
|
+
const skipped = scanned.filter((s) => s.status === 'skipped');
|
|
2962
|
+
write(`\n ${scanned.length} file(s) in scan set, ${skipped.length} skipped.`);
|
|
2963
|
+
for (const s of skipped)
|
|
2964
|
+
write(` skipped ${s.path} (${s.reason})`);
|
|
2965
|
+
return computeClaimExit(findings, failOn);
|
|
2966
|
+
}
|
|
2822
2967
|
function cmdMcpScan(options, flags, cwd, write) {
|
|
2823
2968
|
// The arg parser captures `--boolFlag <next>` as an OPTION value, so a path
|
|
2824
2969
|
// typed AFTER a boolean flag (e.g. `dz mcp-scan --reconcile .`) lands as that
|
|
@@ -3410,6 +3555,8 @@ export async function runCli(argv, io = {}) {
|
|
|
3410
3555
|
return cmdStatusline(options, flags, cwd, write, readStdin);
|
|
3411
3556
|
case 'usage':
|
|
3412
3557
|
return cmdUsage(options, optionLists, flags, cwd, write);
|
|
3558
|
+
case 'claim-check':
|
|
3559
|
+
return cmdClaimCheck(options, optionLists, flags, cwd, write);
|
|
3413
3560
|
case 'setup':
|
|
3414
3561
|
return await cmdSetup(options, flags, cwd, write);
|
|
3415
3562
|
case 'pretrain':
|