@dzhechkov/harness-cli 0.8.2 → 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 +20 -20
- package/README.md +127 -14
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +897 -38
- package/dist/cli.js.map +1 -1
- package/dist/core-compat.d.ts +1 -1
- package/dist/core-compat.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/known-flags.d.ts.map +1 -1
- package/dist/known-flags.js +3 -0
- package/dist/known-flags.js.map +1 -1
- package/package.json +24 -21
- package/sbom.json +19 -19
- package/src/cli.ts +905 -25
- package/src/core-compat.ts +1 -1
- package/src/index.ts +1 -1
- package/src/known-flags.ts +3 -0
package/dist/cli.js
CHANGED
|
@@ -14,11 +14,12 @@ import { execFile, execFileSync, execSync, spawn, spawnSync } from 'node:child_p
|
|
|
14
14
|
import { createHash, randomBytes } from 'node:crypto';
|
|
15
15
|
import { homedir, tmpdir } from 'node:os';
|
|
16
16
|
import { createRequire } from 'node:module';
|
|
17
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
17
18
|
import { createSkill, getSkillInfo, listSkillsDetailed, formatSkillLoadFailures, formatSkillApplyFailures, resolveTargetName, formatTargetProblem, formatTargetAliasNote, TARGET_NAMES_SORTED, runDoctor, runInit, resolvePackageSkillRoots, PACKAGE_SKILL_LAYOUTS, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, discoverVerifiablePackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, runSyncAgentsPolicy, runSyncCodexHooks, resolveCodexHome, withNamedLockSync,
|
|
18
19
|
// dz workflow run (feature dz-workflow-run): the pure scheduler + the dispatch adapters.
|
|
19
|
-
TRACE_RUNID_RE, WF_RUN_OWNER_HOST, preflight, runWorkflow, makeClaudePDispatcher, makeCodexExecDispatcher, NamedLockTimeoutError, NamedLockCompromisedError, POLICY_SOURCES, detectPolicyDrift, hasPolicyFence, TARGET_NAMES, buildParityMatrix, downgradeForStaleEvidence, findStaleTranscriptEvidence, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_TEMPLATES_RETIRED_MESSAGE, parsePlan, isParseErrors, validatePlan, normalizePlan, planDigest, toTraceProjection, renderPlan, mergeRender, lint, lintExitCode, LOOP_BLOBS, parseTrace, assembleTimeline, runInvariants, deriveAttestation, stampAttestation, corroborate, NOT_WITNESSED, renderTimelineHtml, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveCostLedger, planLedgerBackfill, listCostLedgerRuns, resolveLedgerRunId, AMBIGUOUS, stampCheckpointLine, LEDGER_FILL_SOURCE, renderCostLedger, verifyCostLedgerReport, writeCostLedgerJsonl, COST_LEDGER_SCOPE, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, parseWeeklyResetAnchor, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, bookKbPath, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
|
|
20
|
+
TRACE_RUNID_RE, WF_RUN_OWNER_HOST, preflight, runWorkflow, makeClaudePDispatcher, makeCodexExecDispatcher, NamedLockTimeoutError, NamedLockCompromisedError, POLICY_SOURCES, detectPolicyDrift, hasPolicyFence, TARGET_NAMES, buildParityMatrix, downgradeForStaleEvidence, findStaleTranscriptEvidence, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_TEMPLATES_RETIRED_MESSAGE, parsePlan, isParseErrors, validatePlan, normalizePlan, planDigest, toTraceProjection, renderPlan, mergeRender, lint, lintExitCode, LOOP_BLOBS, parseTrace, assembleTimeline, runInvariants, deriveAttestation, stampAttestation, corroborate, NOT_WITNESSED, renderTimelineHtml, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveCostLedger, planLedgerBackfill, listCostLedgerRuns, resolveLedgerRunId, AMBIGUOUS, stampCheckpointLine, LEDGER_FILL_SOURCE, renderCostLedger, verifyCostLedgerReport, writeCostLedgerJsonl, COST_LEDGER_SCOPE, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, parseWeeklyResetAnchor, claimCheck, summarize, BUNDLED_SLOP_REGISTRY_URL, DEFAULT_SLOP_CONFIG, parseSlopRegistry, slopLint, validateSlopLintConfig, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, bookKbPath, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
|
|
20
21
|
// guard-promotion (feature guard-promotion, scout idea #1)
|
|
21
|
-
assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, classifyExecutionEvidence, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, harvestStageOutcomes, recommendModels, planFeed, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, declaredPluginSurface, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport, banditStats, narrowBanditReport, renderBanditHealth,
|
|
22
|
+
assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, classifyExecutionEvidence, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, harvestStageOutcomes, recommendModels, planFeed, unfedRuns, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, declaredPluginSurface, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport, buildDeadwoodReport, compactCmdUsageIfNeeded, measureCmdUsageDepthDays, recordCommandInvocation, resolveCmdUsageRoot, renderDeadwoodReport, CMD_USAGE_LOG_RELATIVE, banditStats, narrowBanditReport, renderBanditHealth,
|
|
22
23
|
// Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
|
|
23
24
|
replayableInstances, buildWorkOrder, buildJudgePrompts, unblindJudgments, verifyWorkOrder, isValidMargin, DIGEST_HONEST_SCOPE, scoreEpochReplay, generateMockOutcomes, renderEpochReplayResult, renderWorkOrderSummary, renderJudgePromptsSummary, WORK_ORDER_KIND, DEFAULT_MOCK_N, DEFAULT_MOCK_SEED, scoreRun, readQeGrade, recapWindow, decideHorizon, withinWindow, buildRecap, renderRecap, parseSourceManifest, tgPostHtmlIssues, tgVisibleLength, decideTgSend, TG_TEXT_LIMIT, countRecallEventsForRun, unknownFlagNotice, mirrorWriterExplanation, appendRecallUsage, closenessLine, anyAboveFloor, decideNameCheck, renderNameCheck, exportedNamesIn, dispatchedCommandsIn, decideSourceProvenance, renderSourceProvenance, REFUSED_HORIZONS, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
|
|
24
25
|
// Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
|
|
@@ -26,11 +27,28 @@ readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, readGo
|
|
|
26
27
|
// qe-bridge (feature qe-bridge-claude, ADR-001): the pure half of the reverse QE bridge.
|
|
27
28
|
KNOWN_CLAUDE, isSafeClaudeId, claudeProbeArgs, claudeReviewArgs, interpretClaudeProbe, modelFamily, buildBridgePrompt, parseBridgeOutput, buildBridgeFailureRecord, buildBridgeSignoffRecord, renderBridgeReport, isSafeSlug, hasUnsafePathChars, hasDotDotSegment, buildReqeBrief, settleReqeDebt, renderReqeList, REQE_SCOPE,
|
|
28
29
|
// Mutation gate (feature ha-mutation-gate) — break each named protection, run the suite, require red.
|
|
29
|
-
parseMutationRegistry, applyMutationToText, countFailingTests, detectSuiteCompletionReceipt, detectSuiteReceiptMismatch, classifyBaseline, classifyRunFailure, classifyMutationOutcome, mutationGateExitCode, summarizeMutationResults, renderMutationReport, TRACE_BUNDLE_LEDGER_PATH, TRACE_BUNDLE_SCHEMA, TRACE_BUNDLE_RUN_META_FILE, buildBundle, serializeBundle, parseBundle, planImport, decideCheckpointWrite, amendmentSection, planSaysNoAmendments, parseAmendments, resolveAmendments, decideAmendmentOutcome, amendmentVerdictLine, amendmentsMissingFromPlan, AMENDMENT_VACUITY_NOTE, decideSignableSet, signableSetLine, decideRecordWrite, decideReadBack, recordVerdictLine, buildCadenceReport, tgVisibleSha256, CADENCE_WINDOW_DAYS, readQeRounds, QE_ROUNDS_DEFAULT_CEILING, describeStoreLocation, storeLocationLine, resolveTeachTarget, teachReasonPhrase, readTeachToConfig, TeachTargetError, mergeStoreHits, sameStore, globalStoreRoot, storeCountLabel,
|
|
30
|
+
parseMutationRegistry, applyMutationToText, countFailingTests, detectSuiteCompletionReceipt, detectSuiteReceiptMismatch, classifyBaseline, classifyRunFailure, classifyMutationOutcome, mutationGateExitCode, summarizeMutationResults, renderMutationReport, TRACE_BUNDLE_LEDGER_PATH, TRACE_BUNDLE_SCHEMA, TRACE_BUNDLE_RUN_META_FILE, buildBundle, serializeBundle, parseBundle, planImport, decideCheckpointWrite, amendmentSection, planSaysNoAmendments, parseAmendments, resolveAmendments, decideAmendmentOutcome, amendmentVerdictLine, amendmentsMissingFromPlan, AMENDMENT_VACUITY_NOTE, extractContractChecklist, parseContractVerdictReport, verifyContractVerdicts, decideSignableSet, signableSetLine, decideRecordWrite, decideReadBack, recordVerdictLine, buildCadenceReport, tgVisibleSha256, CADENCE_WINDOW_DAYS, readQeRounds, QE_ROUNDS_DEFAULT_CEILING, adviseRestart, describeStoreLocation, storeLocationLine, resolveTeachTarget, teachReasonPhrase, readTeachToConfig, TeachTargetError, mergeStoreHits, sameStore, globalStoreRoot, storeCountLabel,
|
|
30
31
|
// operator-profile (ADR-001): per-user 0600 store + marked block in ~/.claude/CLAUDE.md
|
|
31
32
|
renderProfileBlock, readProfile, writeProfile, syncProfileBlock, checkProfileDrift, parseRegister, registerOwnerWord, profileAgeDays, parseDomainList, domainListText, parseYesNo, REGISTERS, } from '@dzhechkov/harness-core';
|
|
32
33
|
import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
|
|
33
34
|
import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
|
|
35
|
+
/** Literal command inventory, pinned against the main dispatch switch by a layer-1 test. */
|
|
36
|
+
export const DZ_COMMANDS = [
|
|
37
|
+
'init', 'verify', 'sync', 'update', 'list', 'create-skill', 'info', 'scout',
|
|
38
|
+
'workflow', 'workflow-lint', 'workflow-trace', 'migrate', 'doctor', 'install',
|
|
39
|
+
'bundle', 'teach', 'consolidate', 'recall', 'vector', 'brain', 'statusline',
|
|
40
|
+
'usage', 'claim-check', 'lint', 'sign', 'sbom', 'guard', 'verify-pack', 'setup',
|
|
41
|
+
'pretrain', 'compose', 'diff', 'recommend', 'upgrade', 'auto-canonicalize',
|
|
42
|
+
'publish', 'release', 'parity', 'registry', 'benchmark', 'mcp-scan',
|
|
43
|
+
'sync-upstream', 'drift-check', 'hooks-sync', 'agents-sync', 'sync-canonical',
|
|
44
|
+
'plugin', 'downloads', 'stats', 'architecture', 'project-skills', 'mr-rakes',
|
|
45
|
+
'retro', 'feature-adr-setup', 'challenge', 'discrimination-check',
|
|
46
|
+
'mutation-gate', 'delivery-check', 'skills-verify', 'compounding', 'deadwood',
|
|
47
|
+
'epoch-replay', 'score', 'recap', 'cadence', 'qe-rounds', 'restart-advisor', 'tg-post',
|
|
48
|
+
'name-check', 'provenance-check', 'feature-adr-record', 'amendment-check', 'contract-check',
|
|
49
|
+
'feature-adr-checkpoint', 'profile', 'reqe', 'qe-bridge', 'backlog', 'routing',
|
|
50
|
+
'bto-optimize', 'dashboard', 'roam', 'import-ecc',
|
|
51
|
+
];
|
|
34
52
|
const USAGE = `dz - DZ cross-platform harness CLI
|
|
35
53
|
|
|
36
54
|
Usage:
|
|
@@ -64,6 +82,7 @@ Usage:
|
|
|
64
82
|
dz challenge --plan <plan.md> [--author <model>] (the deterministic cartridge behind the challenge-panel adversarial plan-gate (R6): assembles the wide brief — plan + architecture/vision.md + testing.md + map.json + degradations.md — and prints the C1-C8 adversary prompt naming the cross-family reviewer to dispatch. exit 0 brief printed / 1 plan missing or empty)
|
|
65
83
|
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)
|
|
66
84
|
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)
|
|
85
|
+
dz deadwood [--weeks <n>] [--json] (advisory zero-usage candidates for human deprecation review; safety-excluded surfaces carry reasons; never deletes or deprecates anything; shallow history says INSUFFICIENT_DATA)
|
|
67
86
|
dz epoch-replay --mock [--n <N>] [--effect <-1..1>] [--tie-rate <0..1>] [--seed <N>] [--slice <name>] [--json] ($0 synthetic run — exercises the verdict math, NOT evidence)
|
|
68
87
|
dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--out <file>] (cold-vs-warm work order: instances + PRE-REGISTERED blind A/B assignment; the runner never calls a model)
|
|
69
88
|
dz epoch-replay --judge <filled-work-order.json> [--out <file>] (blind judge prompts from the filled plans)
|
|
@@ -72,6 +91,7 @@ Usage:
|
|
|
72
91
|
dz recap [--day|--week|--month] [--at <ISO date>] [--project <dir>] [--json] (what was done over a window, from records only: deliveries with the grade an independent review STATED — a report naming two grades is reported ambiguous, never guessed — registry publishes, gate verdicts, knowledge reuse. --quarter/--half-year/--year are RECOGNISED and REFUSED with the real span in days: there is one complete quarter and the longest record is 174 days. Every section carries its own data-start date, and "the source was not read" never prints as zero. Contaminated measures — commit count, lines, tokens, learning-event volume, inventory counts, lesson count — are not computed, and the report says so. exit 0 reported / 2 refused)
|
|
73
92
|
dz cadence [--window day|week|month|quarter|halfyear|year] [--json] (the WHAT-SHIPPED aggregator: graded-shipment cadence by ISO week + npm-publish cadence (recap cache) + guard repeat decay on the FIXED rule set + recall reuse; a window deeper than 2× the record is REFUSED with the depth named (ADR: a cadence from one point is scale forgery); exit 0 report / 2 refused-window / 1 usage)
|
|
74
93
|
dz qe-rounds (--slug <feature> | --feature-dir <abs>) [--ceiling <n>] [--project <dir>] [--json] (how many Step-8 review rounds has this feature ALREADY had? Reads what dz qe-bridge already wrote — signoff-<runId>.json and failed-*.json under features/<slug>/.fa-state/qe-bridge — and writes nothing itself, so it can answer for runs already past. A round is a runId, not a file; an attempt with no verdict is counted SEPARATELY and never merged; an unreadable record is NAMED and the count is declared a LOWER BOUND. ONE directory, never a union across checkouts. exit 0 under the ceiling / 1 at-or-over — owner decides, the command does not judge whether the rounds were warranted / 2 NOT ESTABLISHED, which is never "zero rounds")
|
|
94
|
+
dz restart-advisor --slug <s> [--threshold C|D] [--rounds N] [--json] (read-only advisory decision over features/<slug>/.fa-state/checkpoints.jsonl and .dz/fa-training/<slug>/qe.jsonl. Defaults: threshold D, rounds 2 — both origins are printed. Equal sources corroborate; conflicts, torn/unreadable evidence, gaps, and unsafe paths are NOT ESTABLISHED. RECOMMENDATION ONLY: autoAction=false; never invokes feature-adr, deletes a stage, or writes advisor state. exit 0 established recommendation/no-recommendation / 2 NOT ESTABLISHED or invalid input / 1 unexpected runtime failure)
|
|
75
95
|
dz tg-post --draft <file.html> [--manifest <sources.json>] [--channel <@name|id>] [--send --yes] [--night] [--preview] [--json] (the sender for an APPROVED channel post, per the accepted genai-tweets-channel ADRs: HTML mode only — never MarkdownV2; link preview OFF by default (x.com previews in Telegram are broken); the 00:00-06:00 MSK quiet window refuses without an explicit --night. DEFAULT IS A DRY-RUN: it validates the draft (tag balance, allowed tags, bare &/<, the 4096 visible-character limit with the overshoot counted) and runs the provenance gate over --manifest IN-PROCESS — a draft with no manifest is refused as unchecked, and anything but ALLOWED refuses. A real send needs --send --yes, stating ADR-004's manual-publishing decision out loud each time. The token comes from TELEGRAM_BOT_TOKEN or telegram.tokenFile in .dz/config.json and is never printed. exit 0 sent or clean dry-run / 1 refused or Telegram error / 2 usage)
|
|
76
96
|
dz name-check [--command <n>] [--module <basename>] [--export <a,b>] [--project <dir>] [--json] (is this name free, BEFORE a line of code? Scans workspace SOURCE — never dist, because a stale build answers 'free' confidently. Checks a dz command name against the dispatcher AND the help block, a module basename against every package's src/, and exported identifiers against every declaration in the workspace. exit 0 all free / 1 at least one taken, naming where / 2 nothing asked or the scan did not run — an empty sweep is never a clean bill. Honest limit, printed on the passing path: it reads declarations, so a re-export under a different name stays the build's job)
|
|
77
97
|
dz provenance-check --manifest <sources.json> [--project <dir>] [--json] (nothing goes out citing a source that may not leave this machine. Checks PROVENANCE, not words: every claim names its source, and only a KNOWN kind that resolves safely is cleared. Repo paths go through 'git -C <root> check-ignore' over the RESOLVED path — a symlink into an ignored directory is REFUSED (git classifies the string and never dereferences, MEASURED), and the verdict does not change with your working directory. Store records must be named in the git-TRACKED provenance-public.json, so declaring one public is a reviewable commit rather than a field inside an ignored store. An undeclared kind is refused, never inferred from the path's shape. exit 0 allowed / 1 blocked / 3 NOT ESTABLISHED — an empty manifest, an unreadable one, or an oracle that did not run is never a pass. It proves what was CITED: it cannot see a paraphrase with no citation, nor confidential text pasted by hand into an allowed file)
|
|
@@ -81,6 +101,7 @@ Usage:
|
|
|
81
101
|
dz architecture [--check --slug <s> --desc <text>] [--project <dir>] [--revise] (the live product map + vision: --check is the soft Step-0 сверка of a new feature against them, reporting {signal,confidence} rather than blocking)
|
|
82
102
|
dz sbom [--pack <name>] [--out <file>] (CycloneDX software bill of materials for the workspace, or for one pack with --pack)
|
|
83
103
|
dz amendment-check --slug <slug> | --feature-dir <dir> | --all [--json] (the deterministic Step-8 amendment gate: every AM-N row must resolve to a test found INSIDE the file the row names; the PLAN is authoritative when it carries rows, and an ideation amendment the plan drops is a failure. exit 0 pass/skip, 1 fail, 3 NOT-ESTABLISHED — a section that parsed ZERO rows is never a pass. --all is a CENSUS and always exits 0. Does NOT prove non-vacuity — that is dz discrimination-check)
|
|
104
|
+
dz contract-check --slug <s> [--json] (read-only retrospective feature contract gate: extracts canonical AC-N + ADR Confirmation items, requires one artifact-anchored met|unmet|not-testable verdict per CC-N, and rejects A/B with unmet. exit 0 pass / 1 readable contract or verdict violation / 2 invalid invocation or unreadable/not-established artifacts)
|
|
84
105
|
dz feature-adr-record --kind ledger|training-pair --stage <s> [--slug <s>] [--row|--pair <json>] [--mark <n>] [--once] [--json] (the witnessed writer for the run-cost ledger and training pairs: the payload arrives as an ARGUMENT, never as shell; a malformed or wrong-kind payload is REFUSED before any write; the timestamp is stamped before serialising; the append is verified by re-reading the tail. exit 0 written|duplicate|skipped, 2 refused, 3 not-verified — a record failure is never blocking)
|
|
85
106
|
dz feature-adr-checkpoint (--slug <feature> | --feature-dir <abs>) --stage <s> --input-hash <h> --result <json> [--artifact a,b] [--json] (record a pipeline stage ONLY after measuring its artifacts on disk; refuses a null result, an absent artifact, or a stage that declares none — the subagent runs a COMMAND instead of hand-writing durable state)
|
|
86
107
|
dz profile [init|show|set|sync] [--json] (WHO the assistant is talking to — per-user store at ~/.dz/profile.json (0600, NEVER in a project), delivered as a marked block in ~/.claude/CLAUDE.md so it loads in EVERY project, dz installed or not. init = five questions (language, register, deep/weak domains as comma lists — "networking (CCIE; NSX)" keeps the parenthetical as the note, Enter skips — teaches y/n with one re-ask, never a silent default); show ALWAYS prints the store path + age + drift verdict + the rendered block; set register|language|teaches <v> or set deep|weak add|rm <tag> [note] — register accepts the owner's own words (профи / профи лайт / просто), an unknown value is REFUSED naming the accepted set; sync re-writes the block (runs automatically after init/set; foreign content byte-for-byte, timestamped backup before every modifying write). The register changes FORM, never FACTS, and governs dialogue only — never ADRs/commits/QE reports; both rules are baked into the rendered block at every level. exit 0 done / 1 no profile or failed / 2 refused input)
|
|
@@ -95,7 +116,7 @@ Usage:
|
|
|
95
116
|
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)
|
|
96
117
|
dz backlog drop <id> [<id>…] [--reason <t>] [--dry-run] [--project <dir>] [--json] (retire an idea: new|enriched|in-progress → dropped)
|
|
97
118
|
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)
|
|
98
|
-
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
|
|
119
|
+
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)
|
|
99
120
|
dz backlog reopen <id> [<id>…] [--reason <t>] [--dry-run] [--project <dir>] [--json] (back to the pool: shipped|dropped|in-progress → new)
|
|
100
121
|
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)
|
|
101
122
|
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)
|
|
@@ -125,6 +146,7 @@ Usage:
|
|
|
125
146
|
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)
|
|
126
147
|
dz usage --by-stage [--run <runId> | --slug <slug>] [--epsilon <0..1>] [--write <file.jsonl>] [--json] (per-stage cost ledger for ONE feature-adr run + the reconciliation invariant: accounted + unaccounted = run total; verdict BALANCED | DEFECT | INSUFFICIENT_DATA; local transcript ESTIMATES — catches ATTRIBUTION errors, not pricing errors)
|
|
127
148
|
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 + docs/**/*.md (historical feature artifacts are NOT scanned — pass paths explicitly); exit 1 only at/above --fail-on, default high)
|
|
149
|
+
dz lint [paths...] [--json] [--config <file>] [--registry <file>] [--project <dir>] (advisory EN/RU prose-style lint; findings exit 0, incomplete input/policy exits 1, usage exits 2)
|
|
128
150
|
dz pretrain [--project <dir>]
|
|
129
151
|
dz recommend "<task description>"
|
|
130
152
|
dz compose <preset1+preset2+...> [--target <name>]
|
|
@@ -2178,6 +2200,111 @@ function cmdQeRounds(options, flags, cwd, write) {
|
|
|
2178
2200
|
write(' under the ceiling — another round is within the documented budget');
|
|
2179
2201
|
return 0;
|
|
2180
2202
|
}
|
|
2203
|
+
/** Read one fixed advisor evidence path without following a symlink or escaping the project root. */
|
|
2204
|
+
function readRestartAdvisorEvidence(root, relativePath) {
|
|
2205
|
+
const contained = containedUnderRoot(root, relativePath);
|
|
2206
|
+
if (!contained.ok) {
|
|
2207
|
+
return { text: null, diagnostic: `${relativePath}: ${contained.why}` };
|
|
2208
|
+
}
|
|
2209
|
+
let stat;
|
|
2210
|
+
try {
|
|
2211
|
+
stat = lstatSync(contained.path);
|
|
2212
|
+
}
|
|
2213
|
+
catch (error) {
|
|
2214
|
+
if (error.code === 'ENOENT')
|
|
2215
|
+
return { text: null, diagnostic: null };
|
|
2216
|
+
return { text: null, diagnostic: `${relativePath}: evidence cannot be inspected (${error.message})` };
|
|
2217
|
+
}
|
|
2218
|
+
if (stat.isSymbolicLink()) {
|
|
2219
|
+
return { text: null, diagnostic: `${relativePath}: symlink evidence is refused` };
|
|
2220
|
+
}
|
|
2221
|
+
if (!stat.isFile()) {
|
|
2222
|
+
return { text: null, diagnostic: `${relativePath}: evidence exists but is not a regular file` };
|
|
2223
|
+
}
|
|
2224
|
+
try {
|
|
2225
|
+
return { text: readFileSync(contained.path, 'utf-8'), diagnostic: null };
|
|
2226
|
+
}
|
|
2227
|
+
catch (error) {
|
|
2228
|
+
return { text: null, diagnostic: `${relativePath}: evidence cannot be read (${error.message})` };
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
/** Manual restart recommendation only. I/O ends here; the core remains deterministic and pure. */
|
|
2232
|
+
function cmdRestartAdvisor(options, flags, cwd, write) {
|
|
2233
|
+
const json = flags.has('json');
|
|
2234
|
+
const rawSlug = options.get('slug') ?? '';
|
|
2235
|
+
const slug = rawSlug.trim();
|
|
2236
|
+
const thresholdRaw = options.get('threshold');
|
|
2237
|
+
const roundsRaw = options.get('rounds');
|
|
2238
|
+
const thresholdOrigin = thresholdRaw === undefined ? 'default' : 'flag';
|
|
2239
|
+
const roundsOrigin = roundsRaw === undefined ? 'default' : 'flag';
|
|
2240
|
+
const emit = (advice) => {
|
|
2241
|
+
if (json) {
|
|
2242
|
+
write(JSON.stringify(advice));
|
|
2243
|
+
}
|
|
2244
|
+
else {
|
|
2245
|
+
write(`dz restart-advisor: ${advice.recommendation} — advisory only; autoAction=false`);
|
|
2246
|
+
write(` policy: threshold ${String(advice.policy.threshold)} (${advice.policy.thresholdOrigin}), rounds ${String(advice.policy.rounds)} (${advice.policy.roundsOrigin})`);
|
|
2247
|
+
write(` source: ${advice.sourcePath ?? 'none'}${advice.corroborated ? ' (corroborated by both stores)' : ''}`);
|
|
2248
|
+
for (const diagnostic of advice.diagnostics)
|
|
2249
|
+
write(` diagnostic: ${JSON.stringify(diagnostic)}`);
|
|
2250
|
+
if (advice.diagnosticsSummary.truncated) {
|
|
2251
|
+
write(` diagnostics: ${advice.diagnosticsSummary.returned}/${advice.diagnosticsSummary.total} shown`);
|
|
2252
|
+
}
|
|
2253
|
+
write(advice.decisionLogLine);
|
|
2254
|
+
}
|
|
2255
|
+
return advice.recommendation === 'RESTART_CODE_STAGE'
|
|
2256
|
+
|| advice.recommendation === 'NO_RESTART_RECOMMENDATION'
|
|
2257
|
+
? 0
|
|
2258
|
+
: 2;
|
|
2259
|
+
};
|
|
2260
|
+
const inputErrors = [];
|
|
2261
|
+
for (const flag of flags) {
|
|
2262
|
+
if (flag !== 'json' && flag !== 'help')
|
|
2263
|
+
inputErrors.push(`--${flag} requires a value or is not supported`);
|
|
2264
|
+
}
|
|
2265
|
+
for (const key of options.keys()) {
|
|
2266
|
+
if (key !== 'slug' && key !== 'threshold' && key !== 'rounds') {
|
|
2267
|
+
inputErrors.push(key.startsWith('_positional_')
|
|
2268
|
+
? `unexpected argument ${JSON.stringify(options.get(key))}`
|
|
2269
|
+
: `unsupported option --${key}`);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
if (!isSafeSlug(slug)) {
|
|
2273
|
+
inputErrors.push(`--slug ${JSON.stringify(rawSlug)} must be one kebab-case path segment (max 40 characters)`);
|
|
2274
|
+
}
|
|
2275
|
+
const threshold = thresholdRaw ?? 'D';
|
|
2276
|
+
if (threshold !== 'C' && threshold !== 'D') {
|
|
2277
|
+
inputErrors.push(`--threshold ${JSON.stringify(thresholdRaw)} must be exactly C or D`);
|
|
2278
|
+
}
|
|
2279
|
+
const roundsValue = roundsRaw === undefined ? 2 : Number(roundsRaw);
|
|
2280
|
+
if (!Number.isFinite(roundsValue) || !Number.isInteger(roundsValue) || roundsValue < 1) {
|
|
2281
|
+
inputErrors.push(`--rounds ${JSON.stringify(roundsRaw)} must be a positive integer`);
|
|
2282
|
+
}
|
|
2283
|
+
if (inputErrors.length > 0) {
|
|
2284
|
+
return emit(adviseRestart({ slug, inputErrors }, {
|
|
2285
|
+
thresholdOrigin,
|
|
2286
|
+
roundsOrigin,
|
|
2287
|
+
}));
|
|
2288
|
+
}
|
|
2289
|
+
const root = resolve(cwd);
|
|
2290
|
+
const checkpointPath = `features/${slug}/.fa-state/checkpoints.jsonl`;
|
|
2291
|
+
const trainingPath = `.dz/fa-training/${slug}/qe.jsonl`;
|
|
2292
|
+
const checkpoints = readRestartAdvisorEvidence(root, checkpointPath);
|
|
2293
|
+
const trainingPairs = readRestartAdvisorEvidence(root, trainingPath);
|
|
2294
|
+
const readDiagnostics = [checkpoints.diagnostic, trainingPairs.diagnostic]
|
|
2295
|
+
.filter((entry) => entry !== null);
|
|
2296
|
+
return emit(adviseRestart({
|
|
2297
|
+
slug,
|
|
2298
|
+
checkpointsJsonl: checkpoints.text,
|
|
2299
|
+
trainingPairsJsonl: trainingPairs.text,
|
|
2300
|
+
readDiagnostics,
|
|
2301
|
+
}, {
|
|
2302
|
+
threshold: threshold,
|
|
2303
|
+
rounds: roundsValue,
|
|
2304
|
+
thresholdOrigin,
|
|
2305
|
+
roundsOrigin,
|
|
2306
|
+
}));
|
|
2307
|
+
}
|
|
2181
2308
|
function cmdCadence(options, flags, cwd, write) {
|
|
2182
2309
|
const root = resolve(cwd, options.get('project') ?? '.');
|
|
2183
2310
|
const windowRaw = (options.get('window') ?? 'week').trim();
|
|
@@ -5918,6 +6045,253 @@ function cmdBenchmark(options, flags, cwd, write) {
|
|
|
5918
6045
|
write(`\nL0 only. For L1/L2 judge evaluation: /bto-test ${skillDir}`);
|
|
5919
6046
|
return score.passRate >= 80 ? 0 : 1;
|
|
5920
6047
|
}
|
|
6048
|
+
const SLOP_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
6049
|
+
const SLOP_COURSE_TEXT_KEYS = new Set([
|
|
6050
|
+
'back', 'courseDescription', 'courseTitle', 'description', 'explanation', 'front', 'instruction',
|
|
6051
|
+
'keyConcept', 'keyConcepts', 'note', 'options', 'question', 'reflection', 'shortTitle', 'strengths',
|
|
6052
|
+
'theory', 'title', 'weaknesses', 'wrapup',
|
|
6053
|
+
]);
|
|
6054
|
+
function slopDisplayPath(root, absolute) {
|
|
6055
|
+
const rel = relative(root, absolute);
|
|
6056
|
+
return rel !== '' && !rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel) ? rel : absolute;
|
|
6057
|
+
}
|
|
6058
|
+
function slopWalk(dir, depth = 0) {
|
|
6059
|
+
if (depth > 16)
|
|
6060
|
+
return [];
|
|
6061
|
+
const out = [];
|
|
6062
|
+
let entries;
|
|
6063
|
+
try {
|
|
6064
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
6065
|
+
}
|
|
6066
|
+
catch {
|
|
6067
|
+
return out;
|
|
6068
|
+
}
|
|
6069
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
6070
|
+
for (const entry of entries) {
|
|
6071
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build')
|
|
6072
|
+
continue;
|
|
6073
|
+
if (entry.isSymbolicLink())
|
|
6074
|
+
continue;
|
|
6075
|
+
const full = join(dir, entry.name);
|
|
6076
|
+
if (entry.isDirectory())
|
|
6077
|
+
out.push(...slopWalk(full, depth + 1));
|
|
6078
|
+
else if (entry.isFile() && (['.md', '.mdx'].includes(extname(entry.name).toLowerCase()) || entry.name === 'course.json'))
|
|
6079
|
+
out.push(full);
|
|
6080
|
+
}
|
|
6081
|
+
return out;
|
|
6082
|
+
}
|
|
6083
|
+
function defaultSlopScanSet(root) {
|
|
6084
|
+
const paths = [];
|
|
6085
|
+
const readme = join(root, 'README.md');
|
|
6086
|
+
if (existsSync(readme))
|
|
6087
|
+
paths.push(readme);
|
|
6088
|
+
try {
|
|
6089
|
+
for (const pkg of discoverPackages(root)) {
|
|
6090
|
+
const packageReadme = join(pkg.dir, 'README.md');
|
|
6091
|
+
if (existsSync(packageReadme))
|
|
6092
|
+
paths.push(packageReadme);
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
catch { /* a foreign project need not be a dz workspace */ }
|
|
6096
|
+
paths.push(...slopWalk(join(root, 'packages', '@dzhechkov', 'sitedoc', 'src', 'content'))
|
|
6097
|
+
.filter((path) => ['.md', '.mdx'].includes(extname(path).toLowerCase())));
|
|
6098
|
+
try {
|
|
6099
|
+
const featureDir = join(root, 'features');
|
|
6100
|
+
for (const entry of readdirSync(featureDir, { withFileTypes: true })) {
|
|
6101
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
6102
|
+
continue;
|
|
6103
|
+
const course = join(featureDir, entry.name, 'course.json');
|
|
6104
|
+
if (existsSync(course))
|
|
6105
|
+
paths.push(course);
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
catch { /* no features directory */ }
|
|
6109
|
+
return [...new Set(paths.map((path) => resolve(path)))].sort((a, b) => {
|
|
6110
|
+
const left = slopDisplayPath(root, a);
|
|
6111
|
+
const right = slopDisplayPath(root, b);
|
|
6112
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
6113
|
+
});
|
|
6114
|
+
}
|
|
6115
|
+
function decodeSlopUtf8(path) {
|
|
6116
|
+
const stat = lstatSync(path);
|
|
6117
|
+
if (!stat.isFile())
|
|
6118
|
+
throw new Error('not a regular file');
|
|
6119
|
+
if (stat.size > SLOP_MAX_FILE_BYTES)
|
|
6120
|
+
throw new Error(`file exceeds ${SLOP_MAX_FILE_BYTES} byte limit`);
|
|
6121
|
+
const bytes = readFileSync(path);
|
|
6122
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
6123
|
+
}
|
|
6124
|
+
function projectCourseJson(text) {
|
|
6125
|
+
let value;
|
|
6126
|
+
try {
|
|
6127
|
+
value = JSON.parse(text);
|
|
6128
|
+
}
|
|
6129
|
+
catch {
|
|
6130
|
+
return null;
|
|
6131
|
+
}
|
|
6132
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
6133
|
+
return null;
|
|
6134
|
+
const root = value;
|
|
6135
|
+
if (typeof root.language !== 'string' || typeof root.courseTitle !== 'string' ||
|
|
6136
|
+
!Array.isArray(root.topics) || !Array.isArray(root.sections))
|
|
6137
|
+
return null;
|
|
6138
|
+
const output = [];
|
|
6139
|
+
const visit = (current, key = '') => {
|
|
6140
|
+
if (typeof current === 'string') {
|
|
6141
|
+
if (SLOP_COURSE_TEXT_KEYS.has(key) && current.trim() !== '')
|
|
6142
|
+
output.push(current);
|
|
6143
|
+
return;
|
|
6144
|
+
}
|
|
6145
|
+
if (Array.isArray(current)) {
|
|
6146
|
+
for (const item of current)
|
|
6147
|
+
visit(item, key);
|
|
6148
|
+
return;
|
|
6149
|
+
}
|
|
6150
|
+
if (current === null || typeof current !== 'object')
|
|
6151
|
+
return;
|
|
6152
|
+
for (const [childKey, child] of Object.entries(current))
|
|
6153
|
+
visit(child, childKey);
|
|
6154
|
+
};
|
|
6155
|
+
visit(root);
|
|
6156
|
+
return output.join('\n\n');
|
|
6157
|
+
}
|
|
6158
|
+
function slopLoadJson(path, validate) {
|
|
6159
|
+
const decoded = decodeSlopUtf8(path);
|
|
6160
|
+
let parsed;
|
|
6161
|
+
try {
|
|
6162
|
+
parsed = JSON.parse(decoded);
|
|
6163
|
+
}
|
|
6164
|
+
catch (error) {
|
|
6165
|
+
throw new Error(`invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
6166
|
+
}
|
|
6167
|
+
const result = validate(parsed);
|
|
6168
|
+
if (!result.ok)
|
|
6169
|
+
throw new Error(result.errors.map((error) => `${error.field}=${JSON.stringify(error.value)}: ${error.reason}`).join('; '));
|
|
6170
|
+
return result.value;
|
|
6171
|
+
}
|
|
6172
|
+
/**
|
|
6173
|
+
* Read-only adapter over the pure slopLint core. Style findings are advisory by construction:
|
|
6174
|
+
* only usage errors return 2 and incomplete policy/input evidence returns 1.
|
|
6175
|
+
*/
|
|
6176
|
+
function cmdLint(options, flags, cwd, write) {
|
|
6177
|
+
const json = flags.has('json');
|
|
6178
|
+
const root = resolve(cwd, options.get('project') ?? '.');
|
|
6179
|
+
const usageError = (message) => {
|
|
6180
|
+
if (json)
|
|
6181
|
+
write(JSON.stringify({ schema: 'dz-slop-lint/1', advisory: true, ok: false, status: 'incomplete', findings: [], scanned: [], skipped: [], errors: [{ message }] }));
|
|
6182
|
+
else
|
|
6183
|
+
write(`dz lint: ${message}`);
|
|
6184
|
+
return 2;
|
|
6185
|
+
};
|
|
6186
|
+
if (flags.has('config'))
|
|
6187
|
+
return usageError('--config requires a file');
|
|
6188
|
+
if (flags.has('registry'))
|
|
6189
|
+
return usageError('--registry requires a file');
|
|
6190
|
+
let config = DEFAULT_SLOP_CONFIG;
|
|
6191
|
+
let registry;
|
|
6192
|
+
const setupErrors = [];
|
|
6193
|
+
try {
|
|
6194
|
+
if (options.has('config'))
|
|
6195
|
+
config = slopLoadJson(resolve(root, options.get('config')), validateSlopLintConfig);
|
|
6196
|
+
}
|
|
6197
|
+
catch (error) {
|
|
6198
|
+
const path = options.get('config');
|
|
6199
|
+
setupErrors.push({ ...(path === undefined ? {} : { path }), message: `config: ${error instanceof Error ? error.message : String(error)}` });
|
|
6200
|
+
}
|
|
6201
|
+
try {
|
|
6202
|
+
const registryPath = options.has('registry') ? resolve(root, options.get('registry')) : fileURLToPath(BUNDLED_SLOP_REGISTRY_URL);
|
|
6203
|
+
registry = slopLoadJson(registryPath, parseSlopRegistry);
|
|
6204
|
+
}
|
|
6205
|
+
catch (error) {
|
|
6206
|
+
const path = options.get('registry');
|
|
6207
|
+
setupErrors.push({ ...(path === undefined ? {} : { path }), message: `registry: ${error instanceof Error ? error.message : String(error)}` });
|
|
6208
|
+
registry = { schema: 'dz-slop-registry/1', metadata: {}, markers: [], adjectives: [] };
|
|
6209
|
+
}
|
|
6210
|
+
const requested = [];
|
|
6211
|
+
for (let index = 0;; index += 1) {
|
|
6212
|
+
const value = options.get(`_positional_${index}`);
|
|
6213
|
+
if (value === undefined)
|
|
6214
|
+
break;
|
|
6215
|
+
requested.push(resolve(root, value));
|
|
6216
|
+
}
|
|
6217
|
+
const explicit = requested.length > 0;
|
|
6218
|
+
const candidates = explicit ? requested.flatMap((path) => {
|
|
6219
|
+
try {
|
|
6220
|
+
const stat = lstatSync(path);
|
|
6221
|
+
if (stat.isSymbolicLink())
|
|
6222
|
+
return [path];
|
|
6223
|
+
return stat.isDirectory() ? slopWalk(path) : [path];
|
|
6224
|
+
}
|
|
6225
|
+
catch {
|
|
6226
|
+
return [path];
|
|
6227
|
+
}
|
|
6228
|
+
}) : defaultSlopScanSet(root);
|
|
6229
|
+
const scanSet = [...new Set(candidates.map((path) => resolve(path)))].sort((a, b) => {
|
|
6230
|
+
const left = slopDisplayPath(root, a);
|
|
6231
|
+
const right = slopDisplayPath(root, b);
|
|
6232
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
6233
|
+
});
|
|
6234
|
+
const findings = [];
|
|
6235
|
+
const scanned = [];
|
|
6236
|
+
const errors = [...setupErrors];
|
|
6237
|
+
if (setupErrors.length === 0) {
|
|
6238
|
+
for (const path of scanSet) {
|
|
6239
|
+
const display = slopDisplayPath(root, path);
|
|
6240
|
+
let source;
|
|
6241
|
+
try {
|
|
6242
|
+
source = decodeSlopUtf8(path);
|
|
6243
|
+
}
|
|
6244
|
+
catch (error) {
|
|
6245
|
+
const message = error instanceof TypeError ? `invalid UTF-8: ${error.message}` : error instanceof Error ? error.message : String(error);
|
|
6246
|
+
scanned.push({ path: display, status: 'skipped', reason: message });
|
|
6247
|
+
errors.push({ path: display, message });
|
|
6248
|
+
continue;
|
|
6249
|
+
}
|
|
6250
|
+
if (basename(path) === 'course.json') {
|
|
6251
|
+
const projected = projectCourseJson(source);
|
|
6252
|
+
if (projected === null) {
|
|
6253
|
+
const message = 'unsupported course.json shape';
|
|
6254
|
+
scanned.push({ path: display, status: 'skipped', reason: message });
|
|
6255
|
+
if (explicit)
|
|
6256
|
+
errors.push({ path: display, message });
|
|
6257
|
+
continue;
|
|
6258
|
+
}
|
|
6259
|
+
source = projected;
|
|
6260
|
+
}
|
|
6261
|
+
else if (!['.md', '.mdx'].includes(extname(path).toLowerCase())) {
|
|
6262
|
+
const message = 'unsupported input type; expected Markdown, MDX, or recognized course.json';
|
|
6263
|
+
scanned.push({ path: display, status: 'skipped', reason: message });
|
|
6264
|
+
errors.push({ path: display, message });
|
|
6265
|
+
continue;
|
|
6266
|
+
}
|
|
6267
|
+
const result = slopLint(source, { config, registry });
|
|
6268
|
+
if (result.paragraphCount === 0)
|
|
6269
|
+
errors.push({ path: display, message: 'no analyzable prose' });
|
|
6270
|
+
for (const diagnostic of result.diagnostics)
|
|
6271
|
+
errors.push({ path: display, message: `${diagnostic.code} at line ${diagnostic.line}: ${diagnostic.message}` });
|
|
6272
|
+
for (const finding of result.findings)
|
|
6273
|
+
findings.push({ ...finding, file: display });
|
|
6274
|
+
scanned.push({ path: display, status: 'scanned', paragraphs: result.paragraphCount, findings: result.findings.length });
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
// A valid empty directory/default scope is a clean advisory no-op. Explicit missing paths stay
|
|
6278
|
+
// in scanSet and fail above, while a supported file containing no prose remains incomplete.
|
|
6279
|
+
const skipped = scanned.filter((row) => row.status === 'skipped');
|
|
6280
|
+
const status = errors.length > 0 ? 'incomplete' : findings.length > 0 ? 'findings' : 'clean';
|
|
6281
|
+
const report = {
|
|
6282
|
+
schema: 'dz-slop-lint/1', advisory: true, ok: errors.length === 0, status, findings, scanned, skipped, errors,
|
|
6283
|
+
};
|
|
6284
|
+
if (json)
|
|
6285
|
+
write(JSON.stringify(report));
|
|
6286
|
+
else {
|
|
6287
|
+
write(`dz lint: ${findings.length} finding(s), ${scanned.filter((row) => row.status === 'scanned').length} file(s) scanned — ${status}`);
|
|
6288
|
+
for (const finding of findings)
|
|
6289
|
+
write(` [${finding.ruleId}] ${finding.file}:${finding.lineStart}:${finding.columnStart} — ${finding.excerpt}`);
|
|
6290
|
+
for (const error of errors)
|
|
6291
|
+
write(` incomplete${error.path ? ` ${error.path}` : ''}: ${error.message}`);
|
|
6292
|
+
}
|
|
6293
|
+
return errors.length > 0 ? 1 : 0;
|
|
6294
|
+
}
|
|
5921
6295
|
/**
|
|
5922
6296
|
* Exit-code contract for `dz claim-check` (named in the ADR, locked by tests):
|
|
5923
6297
|
* exit 0 when no finding at/above `failOn` exists; exit 1 only when one does.
|
|
@@ -6851,6 +7225,101 @@ function gatherReadmeCounts(root) {
|
|
|
6851
7225
|
function gatherGuardFacts(op, root, text, storeCap) {
|
|
6852
7226
|
const facts = { op };
|
|
6853
7227
|
if (op === 'publish') {
|
|
7228
|
+
// Advisory I/O: unreadable telemetry or fed state is absence of evidence, never a fabricated
|
|
7229
|
+
// stale finding and never a publish blocker.
|
|
7230
|
+
try {
|
|
7231
|
+
const routing = readRoutingTelemetry(root);
|
|
7232
|
+
facts['routingFreshness'] = { unfedRunIds: unfedRuns(routing.harvest.samples, routing.alreadyFed) };
|
|
7233
|
+
}
|
|
7234
|
+
catch { /* fail-open: routing-store-stale is SOFT and needs gathered evidence */ }
|
|
7235
|
+
// marketplace-parity: regenerate the two published showcase manifests from the LIVE registry,
|
|
7236
|
+
// but redirect every write to a unique temp root. Version is operator-owned, so pinning the
|
|
7237
|
+
// published value into generatePlugin makes the comparison composition-only by construction.
|
|
7238
|
+
try {
|
|
7239
|
+
const showcaseDir = join(root, '.claude-plugin');
|
|
7240
|
+
if (!existsSync(showcaseDir)) {
|
|
7241
|
+
facts['marketplaceParity'] = { applicable: false };
|
|
7242
|
+
}
|
|
7243
|
+
else {
|
|
7244
|
+
const pluginPath = join(showcaseDir, 'plugin.json');
|
|
7245
|
+
const marketplacePath = join(showcaseDir, 'marketplace.json');
|
|
7246
|
+
const hasPlugin = existsSync(pluginPath);
|
|
7247
|
+
const hasMarketplace = existsSync(marketplacePath);
|
|
7248
|
+
const manifestFailures = [];
|
|
7249
|
+
const readManifest = (path, file) => {
|
|
7250
|
+
try {
|
|
7251
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
7252
|
+
}
|
|
7253
|
+
catch (error) {
|
|
7254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7255
|
+
manifestFailures.push({ file, error: message.replace(/\s+/g, ' ').slice(0, 240) });
|
|
7256
|
+
return undefined;
|
|
7257
|
+
}
|
|
7258
|
+
};
|
|
7259
|
+
const versionOf = (manifest) => {
|
|
7260
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
7261
|
+
return undefined;
|
|
7262
|
+
const version = manifest['version'];
|
|
7263
|
+
return typeof version === 'string' && version !== '' ? version : undefined;
|
|
7264
|
+
};
|
|
7265
|
+
const publishedPlugin = hasPlugin ? readManifest(pluginPath, 'plugin.json') : undefined;
|
|
7266
|
+
const publishedMarketplace = hasMarketplace ? readManifest(marketplacePath, 'marketplace.json') : undefined;
|
|
7267
|
+
const marketplaceRecord = publishedMarketplace && typeof publishedMarketplace === 'object' && !Array.isArray(publishedMarketplace)
|
|
7268
|
+
? publishedMarketplace
|
|
7269
|
+
: undefined;
|
|
7270
|
+
const marketplacePlugins = marketplaceRecord !== undefined && Array.isArray(marketplaceRecord['plugins'])
|
|
7271
|
+
? marketplaceRecord['plugins']
|
|
7272
|
+
: [];
|
|
7273
|
+
const publishedVersion = versionOf(publishedPlugin)
|
|
7274
|
+
?? versionOf(publishedMarketplace)
|
|
7275
|
+
?? versionOf(marketplacePlugins[0]);
|
|
7276
|
+
if (manifestFailures.length > 0) {
|
|
7277
|
+
facts['marketplaceParity'] = {
|
|
7278
|
+
applicable: true,
|
|
7279
|
+
manifestFailures,
|
|
7280
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7281
|
+
};
|
|
7282
|
+
}
|
|
7283
|
+
else if (hasPlugin !== hasMarketplace) {
|
|
7284
|
+
facts['marketplaceParity'] = {
|
|
7285
|
+
applicable: true,
|
|
7286
|
+
onlyOnePresent: true,
|
|
7287
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7288
|
+
};
|
|
7289
|
+
}
|
|
7290
|
+
else if (!hasPlugin) {
|
|
7291
|
+
facts['marketplaceParity'] = { applicable: false };
|
|
7292
|
+
}
|
|
7293
|
+
else {
|
|
7294
|
+
let scratch;
|
|
7295
|
+
try {
|
|
7296
|
+
const registry = buildRegistry(root);
|
|
7297
|
+
scratch = mkdtempSync(join(tmpdir(), 'dz-guard-marketplace-'));
|
|
7298
|
+
const generated = generatePlugin(scratch, registry, { version: publishedVersion });
|
|
7299
|
+
const freshPlugin = JSON.parse(readFileSync(generated.pluginJsonPath, 'utf8'));
|
|
7300
|
+
const freshMarketplace = JSON.parse(readFileSync(generated.marketplaceJsonPath, 'utf8'));
|
|
7301
|
+
facts['marketplaceParity'] = {
|
|
7302
|
+
applicable: true,
|
|
7303
|
+
diverged: !isDeepStrictEqual(publishedPlugin, freshPlugin)
|
|
7304
|
+
|| !isDeepStrictEqual(publishedMarketplace, freshMarketplace),
|
|
7305
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7306
|
+
};
|
|
7307
|
+
}
|
|
7308
|
+
catch {
|
|
7309
|
+
facts['marketplaceParity'] = {
|
|
7310
|
+
applicable: true,
|
|
7311
|
+
regenerateFailed: true,
|
|
7312
|
+
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
7313
|
+
};
|
|
7314
|
+
}
|
|
7315
|
+
finally {
|
|
7316
|
+
if (scratch !== undefined)
|
|
7317
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
catch { /* fail-open only on unexpected showcase-discovery failure; manifest read/parse errors are structured violations above */ }
|
|
6854
7323
|
// agents-md-policy-sync: fixed registry, no tree walk. The pure detector
|
|
6855
7324
|
// recomputes every expected hash from current source text; this gatherer
|
|
6856
7325
|
// only supplies bytes. Any unexpected gather failure omits the fact, and
|
|
@@ -10843,6 +11312,262 @@ function cmdFeatureAdrRecord(options, flags, cwd, write) {
|
|
|
10843
11312
|
}
|
|
10844
11313
|
return emit(decideReadBack(lineToWrite, lastLine), { target });
|
|
10845
11314
|
}
|
|
11315
|
+
function contractRepoRoot(cwd) {
|
|
11316
|
+
let root = cwd;
|
|
11317
|
+
try {
|
|
11318
|
+
root = execSync('git rev-parse --show-toplevel', {
|
|
11319
|
+
cwd,
|
|
11320
|
+
encoding: 'utf-8',
|
|
11321
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
11322
|
+
}).trim() || cwd;
|
|
11323
|
+
}
|
|
11324
|
+
catch { /* temporary repository fixtures intentionally use cwd as their root */ }
|
|
11325
|
+
return resolve(root);
|
|
11326
|
+
}
|
|
11327
|
+
function contractReadConfined(repoRoot, absolute, artifact) {
|
|
11328
|
+
let rootReal;
|
|
11329
|
+
let targetReal;
|
|
11330
|
+
try {
|
|
11331
|
+
rootReal = realpathSync(repoRoot);
|
|
11332
|
+
}
|
|
11333
|
+
catch {
|
|
11334
|
+
return {
|
|
11335
|
+
ok: false,
|
|
11336
|
+
diagnostic: { code: 'repository-unreadable', message: 'repository root cannot be resolved', artifact: '.' },
|
|
11337
|
+
};
|
|
11338
|
+
}
|
|
11339
|
+
try {
|
|
11340
|
+
targetReal = realpathSync(absolute);
|
|
11341
|
+
}
|
|
11342
|
+
catch {
|
|
11343
|
+
return {
|
|
11344
|
+
ok: false,
|
|
11345
|
+
diagnostic: { code: 'artifact-unreadable', message: `required artifact cannot be resolved or read: ${artifact}`, artifact },
|
|
11346
|
+
};
|
|
11347
|
+
}
|
|
11348
|
+
const rel = relative(rootReal, targetReal);
|
|
11349
|
+
if (rel === '' || rel.startsWith(`..${sep}`) || rel === '..' || isAbsolute(rel)) {
|
|
11350
|
+
return {
|
|
11351
|
+
ok: false,
|
|
11352
|
+
diagnostic: { code: 'artifact-outside-repository', message: `artifact resolves outside the repository: ${artifact}`, artifact },
|
|
11353
|
+
};
|
|
11354
|
+
}
|
|
11355
|
+
try {
|
|
11356
|
+
if (!statSync(targetReal).isFile())
|
|
11357
|
+
throw new Error('not a regular file');
|
|
11358
|
+
return { ok: true, text: readFileSync(targetReal, 'utf-8'), realPath: targetReal };
|
|
11359
|
+
}
|
|
11360
|
+
catch {
|
|
11361
|
+
return {
|
|
11362
|
+
ok: false,
|
|
11363
|
+
diagnostic: { code: 'artifact-unreadable', message: `required artifact cannot be read as a file: ${artifact}`, artifact },
|
|
11364
|
+
};
|
|
11365
|
+
}
|
|
11366
|
+
}
|
|
11367
|
+
function contractDirectoryConfined(repoRoot, absolute, artifact) {
|
|
11368
|
+
let rootReal;
|
|
11369
|
+
let targetReal;
|
|
11370
|
+
try {
|
|
11371
|
+
rootReal = realpathSync(repoRoot);
|
|
11372
|
+
}
|
|
11373
|
+
catch {
|
|
11374
|
+
return {
|
|
11375
|
+
ok: false,
|
|
11376
|
+
diagnostic: { code: 'repository-unreadable', message: 'repository root cannot be resolved', artifact: '.' },
|
|
11377
|
+
};
|
|
11378
|
+
}
|
|
11379
|
+
try {
|
|
11380
|
+
targetReal = realpathSync(absolute);
|
|
11381
|
+
}
|
|
11382
|
+
catch {
|
|
11383
|
+
return {
|
|
11384
|
+
ok: false,
|
|
11385
|
+
diagnostic: { code: 'adr-directory-unreadable', message: `required ADR directory cannot be resolved: ${artifact}`, artifact },
|
|
11386
|
+
};
|
|
11387
|
+
}
|
|
11388
|
+
const rel = relative(rootReal, targetReal);
|
|
11389
|
+
if (rel === '' || rel.startsWith(`..${sep}`) || rel === '..' || isAbsolute(rel)) {
|
|
11390
|
+
return {
|
|
11391
|
+
ok: false,
|
|
11392
|
+
diagnostic: { code: 'artifact-outside-repository', message: `artifact resolves outside the repository: ${artifact}`, artifact },
|
|
11393
|
+
};
|
|
11394
|
+
}
|
|
11395
|
+
try {
|
|
11396
|
+
if (!statSync(targetReal).isDirectory())
|
|
11397
|
+
throw new Error('not a directory');
|
|
11398
|
+
return { ok: true, realPath: targetReal };
|
|
11399
|
+
}
|
|
11400
|
+
catch {
|
|
11401
|
+
return {
|
|
11402
|
+
ok: false,
|
|
11403
|
+
diagnostic: { code: 'adr-directory-unreadable', message: `required ADR directory is not readable: ${artifact}`, artifact },
|
|
11404
|
+
};
|
|
11405
|
+
}
|
|
11406
|
+
}
|
|
11407
|
+
function contractDiagnosticLine(entry) {
|
|
11408
|
+
const where = [entry.artifact, entry.contractId ?? entry.sourceId].filter((part) => part !== undefined).join(' · ');
|
|
11409
|
+
return ` [${entry.code}]${where === '' ? '' : ` ${where} —`} ${entry.message}`;
|
|
11410
|
+
}
|
|
11411
|
+
function cmdContractCheck(options, flags, cwd, write, writeErr) {
|
|
11412
|
+
const json = flags.has('json');
|
|
11413
|
+
const emitEarly = (outcome, exitCode, diagnostics) => {
|
|
11414
|
+
if (json) {
|
|
11415
|
+
write(JSON.stringify({ outcome, exitCode, diagnostics }));
|
|
11416
|
+
}
|
|
11417
|
+
else {
|
|
11418
|
+
for (const entry of diagnostics)
|
|
11419
|
+
writeErr(contractDiagnosticLine(entry));
|
|
11420
|
+
write(`contract-check: ${outcome === 'fail' ? 'FAIL' : 'NOT-ESTABLISHED'} — ${diagnostics[0]?.message ?? 'no trustworthy verdict'}`);
|
|
11421
|
+
}
|
|
11422
|
+
return exitCode;
|
|
11423
|
+
};
|
|
11424
|
+
for (const flag of flags) {
|
|
11425
|
+
if (flag !== 'json' && flag !== 'help') {
|
|
11426
|
+
return emitEarly('not-established', 2, [{ code: 'usage-invalid', message: `unknown option --${flag}`, observed: `--${flag}` }]);
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
for (const key of options.keys()) {
|
|
11430
|
+
if (key !== 'slug') {
|
|
11431
|
+
return emitEarly('not-established', 2, [{
|
|
11432
|
+
code: 'usage-invalid',
|
|
11433
|
+
message: key.startsWith('_positional_') ? `unexpected argument ${JSON.stringify(options.get(key))}` : `unknown option --${key}`,
|
|
11434
|
+
observed: key.startsWith('_positional_') ? options.get(key) ?? '' : `--${key}`,
|
|
11435
|
+
}]);
|
|
11436
|
+
}
|
|
11437
|
+
}
|
|
11438
|
+
const slug = (options.get('slug') ?? '').trim();
|
|
11439
|
+
if (!isSafeSlug(slug)) {
|
|
11440
|
+
return emitEarly('not-established', 2, [{
|
|
11441
|
+
code: 'slug-invalid',
|
|
11442
|
+
message: 'a kebab-case --slug <feature> is required (one path segment, max 40 characters)',
|
|
11443
|
+
observed: slug,
|
|
11444
|
+
}]);
|
|
11445
|
+
}
|
|
11446
|
+
const repoRoot = contractRepoRoot(cwd);
|
|
11447
|
+
const featureDir = join(repoRoot, 'features', slug);
|
|
11448
|
+
const requirementsRel = `features/${slug}/01_requirements.md`;
|
|
11449
|
+
const reportRel = `features/${slug}/08_qe_report.md`;
|
|
11450
|
+
const requirementsRead = contractReadConfined(repoRoot, join(featureDir, '01_requirements.md'), requirementsRel);
|
|
11451
|
+
if (!requirementsRead.ok)
|
|
11452
|
+
return emitEarly('not-established', 2, [requirementsRead.diagnostic]);
|
|
11453
|
+
const adrDirRel = `features/${slug}/03_adr`;
|
|
11454
|
+
const adrDirectory = contractDirectoryConfined(repoRoot, join(featureDir, '03_adr'), adrDirRel);
|
|
11455
|
+
if (!adrDirectory.ok)
|
|
11456
|
+
return emitEarly('not-established', 2, [adrDirectory.diagnostic]);
|
|
11457
|
+
const adrDir = adrDirectory.realPath;
|
|
11458
|
+
let adrNames;
|
|
11459
|
+
try {
|
|
11460
|
+
adrNames = readdirSync(adrDir, { withFileTypes: true })
|
|
11461
|
+
.filter((entry) => entry.name.endsWith('.md') && (entry.isFile() || entry.isSymbolicLink()))
|
|
11462
|
+
.map((entry) => entry.name)
|
|
11463
|
+
.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
11464
|
+
}
|
|
11465
|
+
catch {
|
|
11466
|
+
return emitEarly('not-established', 2, [{
|
|
11467
|
+
code: 'adr-directory-unreadable',
|
|
11468
|
+
message: `required ADR directory cannot be read: ${adrDirRel}`,
|
|
11469
|
+
artifact: adrDirRel,
|
|
11470
|
+
}]);
|
|
11471
|
+
}
|
|
11472
|
+
if (adrNames.length === 0) {
|
|
11473
|
+
return emitEarly('not-established', 2, [{
|
|
11474
|
+
code: 'adr-artifacts-missing',
|
|
11475
|
+
message: `no direct ADR Markdown artifacts exist under ${adrDirRel}`,
|
|
11476
|
+
artifact: adrDirRel,
|
|
11477
|
+
observed: 0,
|
|
11478
|
+
}]);
|
|
11479
|
+
}
|
|
11480
|
+
const adrs = [];
|
|
11481
|
+
const adrReadErrors = [];
|
|
11482
|
+
for (const name of adrNames) {
|
|
11483
|
+
const rel = `features/${slug}/03_adr/${name}`;
|
|
11484
|
+
const read = contractReadConfined(repoRoot, join(adrDir, name), rel);
|
|
11485
|
+
if (read.ok)
|
|
11486
|
+
adrs.push({ path: rel, text: read.text });
|
|
11487
|
+
else
|
|
11488
|
+
adrReadErrors.push(read.diagnostic);
|
|
11489
|
+
}
|
|
11490
|
+
if (adrReadErrors.length > 0)
|
|
11491
|
+
return emitEarly('not-established', 2, adrReadErrors);
|
|
11492
|
+
const extracted = extractContractChecklist({
|
|
11493
|
+
requirements: { path: requirementsRel, text: requirementsRead.text },
|
|
11494
|
+
adrs,
|
|
11495
|
+
});
|
|
11496
|
+
if (!extracted.ok)
|
|
11497
|
+
return emitEarly('fail', 1, extracted.diagnostics);
|
|
11498
|
+
const reportRead = contractReadConfined(repoRoot, join(featureDir, '08_qe_report.md'), reportRel);
|
|
11499
|
+
if (!reportRead.ok)
|
|
11500
|
+
return emitEarly('not-established', 2, [reportRead.diagnostic]);
|
|
11501
|
+
const parsed = parseContractVerdictReport(reportRead.text);
|
|
11502
|
+
if (!parsed.ok) {
|
|
11503
|
+
const diagnostics = parsed.diagnostics.map((entry) => ({
|
|
11504
|
+
...entry,
|
|
11505
|
+
artifact: entry.artifact ?? reportRel,
|
|
11506
|
+
}));
|
|
11507
|
+
return emitEarly(parsed.established ? 'fail' : 'not-established', parsed.established ? 1 : 2, diagnostics);
|
|
11508
|
+
}
|
|
11509
|
+
const evidenceCache = new Map();
|
|
11510
|
+
const reader = {
|
|
11511
|
+
reportArtifact: reportRel,
|
|
11512
|
+
read(artifact) {
|
|
11513
|
+
const cached = evidenceCache.get(artifact);
|
|
11514
|
+
if (cached !== undefined)
|
|
11515
|
+
return cached;
|
|
11516
|
+
const disk = contractReadConfined(repoRoot, join(repoRoot, artifact), artifact);
|
|
11517
|
+
let result;
|
|
11518
|
+
if (!disk.ok) {
|
|
11519
|
+
result = { ok: false, code: `evidence-${disk.diagnostic.code}`, detail: disk.diagnostic.message };
|
|
11520
|
+
}
|
|
11521
|
+
else if (disk.realPath === reportRead.realPath) {
|
|
11522
|
+
result = { ok: false, code: 'evidence-self-citation', detail: `${artifact} resolves to the QE verdict payload itself` };
|
|
11523
|
+
}
|
|
11524
|
+
else {
|
|
11525
|
+
result = { ok: true, text: disk.text };
|
|
11526
|
+
}
|
|
11527
|
+
evidenceCache.set(artifact, result);
|
|
11528
|
+
return result;
|
|
11529
|
+
},
|
|
11530
|
+
};
|
|
11531
|
+
const rawVerification = verifyContractVerdicts(extracted.checklist, parsed.report, reader);
|
|
11532
|
+
const withReportArtifact = (entry) => ({
|
|
11533
|
+
...entry,
|
|
11534
|
+
artifact: entry.artifact ?? reportRel,
|
|
11535
|
+
});
|
|
11536
|
+
const verification = {
|
|
11537
|
+
...rawVerification,
|
|
11538
|
+
diagnostics: rawVerification.diagnostics.map(withReportArtifact),
|
|
11539
|
+
items: rawVerification.items.map((item) => ({
|
|
11540
|
+
...item,
|
|
11541
|
+
diagnostics: item.diagnostics.map(withReportArtifact),
|
|
11542
|
+
})),
|
|
11543
|
+
};
|
|
11544
|
+
if (json) {
|
|
11545
|
+
write(JSON.stringify({
|
|
11546
|
+
contract: extracted.checklist,
|
|
11547
|
+
report: parsed.report,
|
|
11548
|
+
items: verification.items,
|
|
11549
|
+
diagnostics: verification.diagnostics,
|
|
11550
|
+
counts: verification.counts,
|
|
11551
|
+
overallGrade: verification.overallGrade,
|
|
11552
|
+
outcome: verification.outcome,
|
|
11553
|
+
exitCode: verification.exitCode,
|
|
11554
|
+
}));
|
|
11555
|
+
return verification.exitCode;
|
|
11556
|
+
}
|
|
11557
|
+
for (const item of verification.items) {
|
|
11558
|
+
const reason = item.reason === undefined ? '' : ` — ${item.reason}`;
|
|
11559
|
+
write(` ${item.id}: ${item.verdict ?? 'missing'} · evidence ${item.evidence}${reason}`);
|
|
11560
|
+
}
|
|
11561
|
+
write(` counts: contract=${verification.counts.contractItems} verdict=${verification.counts.verdictItems} met=${verification.counts.met} unmet=${verification.counts.unmet} not-testable=${verification.counts.notTestable} invalid-evidence=${verification.counts.invalidEvidence}`);
|
|
11562
|
+
write(` overall grade: ${verification.overallGrade}`);
|
|
11563
|
+
for (const entry of verification.diagnostics)
|
|
11564
|
+
writeErr(contractDiagnosticLine(entry));
|
|
11565
|
+
const summary = verification.outcome === 'pass'
|
|
11566
|
+
? `${verification.counts.met} contract item(s) met`
|
|
11567
|
+
: `${verification.diagnostics.length} contract or evidence violation(s)`;
|
|
11568
|
+
write(`contract-check: ${verification.outcome === 'pass' ? 'PASS' : 'FAIL'} — ${summary}`);
|
|
11569
|
+
return verification.exitCode;
|
|
11570
|
+
}
|
|
10846
11571
|
function cmdAmendmentCheck(options, flags, cwd, write) {
|
|
10847
11572
|
const json = flags.has('json');
|
|
10848
11573
|
const readOr = (abs) => {
|
|
@@ -12010,6 +12735,85 @@ function readRecallUsageEvents(root) {
|
|
|
12010
12735
|
}
|
|
12011
12736
|
return usage;
|
|
12012
12737
|
}
|
|
12738
|
+
function readOptionalText(path) {
|
|
12739
|
+
try {
|
|
12740
|
+
return readFileSync(path, 'utf8');
|
|
12741
|
+
}
|
|
12742
|
+
catch {
|
|
12743
|
+
return '';
|
|
12744
|
+
}
|
|
12745
|
+
}
|
|
12746
|
+
function deadwoodAllowlistText() {
|
|
12747
|
+
const require = createRequire(import.meta.url);
|
|
12748
|
+
const corePackage = require.resolve('@dzhechkov/harness-core/package.json');
|
|
12749
|
+
return readFileSync(join(dirname(corePackage), 'src', 'deadwood-allowlist.json'), 'utf8');
|
|
12750
|
+
}
|
|
12751
|
+
function cmdUsageDepthDays(root, now) {
|
|
12752
|
+
const text = readOptionalText(join(root, CMD_USAGE_LOG_RELATIVE));
|
|
12753
|
+
if (text === '')
|
|
12754
|
+
return null;
|
|
12755
|
+
return measureCmdUsageDepthDays(text, now);
|
|
12756
|
+
}
|
|
12757
|
+
/** Canonical deadwood candidates keyed to every alternate top-level dispatch token. */
|
|
12758
|
+
const DEADWOOD_COMMAND_ALIASES = {
|
|
12759
|
+
sync: ['update'],
|
|
12760
|
+
};
|
|
12761
|
+
function deadwoodInventory(root) {
|
|
12762
|
+
const aliasTokens = new Set(Object.values(DEADWOOD_COMMAND_ALIASES).flat());
|
|
12763
|
+
const inventory = DZ_COMMANDS
|
|
12764
|
+
.filter((surface) => !aliasTokens.has(surface))
|
|
12765
|
+
.map((surface) => ({
|
|
12766
|
+
surface,
|
|
12767
|
+
kind: 'command',
|
|
12768
|
+
...(DEADWOOD_COMMAND_ALIASES[surface] === undefined
|
|
12769
|
+
? {}
|
|
12770
|
+
: { aliases: DEADWOOD_COMMAND_ALIASES[surface] }),
|
|
12771
|
+
}));
|
|
12772
|
+
for (const rule of DEFAULT_RULES)
|
|
12773
|
+
inventory.push({ surface: rule.id, kind: 'rule' });
|
|
12774
|
+
const skillDir = resolve(root, '.claude/skills');
|
|
12775
|
+
const { skills } = listSkillsDetailed(skillDir);
|
|
12776
|
+
for (const skill of skills)
|
|
12777
|
+
inventory.push({ surface: skill.id, kind: 'skill' });
|
|
12778
|
+
return inventory;
|
|
12779
|
+
}
|
|
12780
|
+
/** `dz deadwood` is a read-only advisory report; findings never affect the exit code. */
|
|
12781
|
+
function cmdDeadwood(options, flags, cwd, write, writeErr) {
|
|
12782
|
+
if (flags.has('weeks')) {
|
|
12783
|
+
writeErr('dz deadwood: --weeks requires an integer value');
|
|
12784
|
+
return 1;
|
|
12785
|
+
}
|
|
12786
|
+
const rawWeeks = options.get('weeks') ?? '8';
|
|
12787
|
+
const weeks = Number(rawWeeks);
|
|
12788
|
+
if (!Number.isInteger(weeks) || weeks <= 0 || weeks > 520) {
|
|
12789
|
+
writeErr(`dz deadwood: --weeks must be an integer from 1 to 520 (received ${JSON.stringify(rawWeeks)})`);
|
|
12790
|
+
return 1;
|
|
12791
|
+
}
|
|
12792
|
+
const root = resolve(cwd);
|
|
12793
|
+
try {
|
|
12794
|
+
// Observe integrity before maintenance: compaction may discard malformed lines, but this run
|
|
12795
|
+
// still has to report that they existed rather than laundering the count to zero.
|
|
12796
|
+
const cmdUsageText = readOptionalText(join(resolveCmdUsageRoot(root), CMD_USAGE_LOG_RELATIVE));
|
|
12797
|
+
const report = buildDeadwoodReport({
|
|
12798
|
+
cmdUsageText,
|
|
12799
|
+
guardAuditText: readOptionalText(join(root, '.dz', 'guard-audit.jsonl')),
|
|
12800
|
+
inventory: deadwoodInventory(root),
|
|
12801
|
+
allowlistText: deadwoodAllowlistText(),
|
|
12802
|
+
weeks,
|
|
12803
|
+
now: new Date(),
|
|
12804
|
+
});
|
|
12805
|
+
compactCmdUsageIfNeeded(root);
|
|
12806
|
+
if (flags.has('json'))
|
|
12807
|
+
write(JSON.stringify({ ...report, exitCode: 0 }, null, 2));
|
|
12808
|
+
else
|
|
12809
|
+
write(renderDeadwoodReport(report, 'text'));
|
|
12810
|
+
return 0;
|
|
12811
|
+
}
|
|
12812
|
+
catch (error) {
|
|
12813
|
+
writeErr(`dz deadwood: ${error instanceof Error ? error.message : String(error)}`);
|
|
12814
|
+
return 1;
|
|
12815
|
+
}
|
|
12816
|
+
}
|
|
12013
12817
|
/**
|
|
12014
12818
|
* `dz compounding` — does the learning loop actually PAY? (feature compounding, scout C2.)
|
|
12015
12819
|
* Gathers the facts (store rows, apply-leg usage log, guard audit) and hands them to the PURE
|
|
@@ -12085,7 +12889,15 @@ function cmdCompounding(options, flags, cwd, write) {
|
|
|
12085
12889
|
/* absent log — reported by its own gate above, not invented here */
|
|
12086
12890
|
}
|
|
12087
12891
|
}
|
|
12088
|
-
const
|
|
12892
|
+
const now = new Date();
|
|
12893
|
+
const report = assembleCompoundingReport({
|
|
12894
|
+
lessons,
|
|
12895
|
+
usage,
|
|
12896
|
+
guard,
|
|
12897
|
+
nowTs: now.toISOString(),
|
|
12898
|
+
evidenceLogs,
|
|
12899
|
+
cmdUsageDepthDays: cmdUsageDepthDays(root, now),
|
|
12900
|
+
});
|
|
12089
12901
|
// lesson-bandit-rerank §11: the payoff axis joins THIS report rather than growing a private
|
|
12090
12902
|
// dashboard — the `rewardEvents : exposureEvents` row asks exactly the question this command
|
|
12091
12903
|
// already asks of the reinforcement loop (is the apply leg alive, or is it a write-only log?).
|
|
@@ -13300,6 +14112,44 @@ async function cmdBacklog(options, flags, cwd, write) {
|
|
|
13300
14112
|
write(' harmonize [--apply][--threshold 0-1] batch semantic dedup of the backlog');
|
|
13301
14113
|
return sub === undefined ? 0 : 1;
|
|
13302
14114
|
}
|
|
14115
|
+
/** Read the recommender's existing telemetry planes plus its idempotency receipt. One adapter is
|
|
14116
|
+
* shared by `recommend` and the publish advisory so their freshness definitions cannot drift. */
|
|
14117
|
+
function readRoutingTelemetry(repoRoot) {
|
|
14118
|
+
const records = readHarnessRecords(repoRoot);
|
|
14119
|
+
for (const base of [join(repoRoot, 'features'), join(repoRoot, '.dz', 'loop-trace')]) {
|
|
14120
|
+
if (!existsSync(base))
|
|
14121
|
+
continue;
|
|
14122
|
+
let names = [];
|
|
14123
|
+
try {
|
|
14124
|
+
names = readdirSync(base);
|
|
14125
|
+
}
|
|
14126
|
+
catch {
|
|
14127
|
+
continue;
|
|
14128
|
+
}
|
|
14129
|
+
for (const name of names) {
|
|
14130
|
+
const sidecar = join(base, name, 'run-meta.json');
|
|
14131
|
+
if (!existsSync(sidecar))
|
|
14132
|
+
continue;
|
|
14133
|
+
try {
|
|
14134
|
+
const meta = JSON.parse(readFileSync(sidecar, 'utf-8'));
|
|
14135
|
+
if (meta.runMeta?.resolved === true && Array.isArray(meta.runMeta.records))
|
|
14136
|
+
records.push(...meta.runMeta.records);
|
|
14137
|
+
}
|
|
14138
|
+
catch { /* an unreadable sidecar contributes no asserted record */ }
|
|
14139
|
+
}
|
|
14140
|
+
}
|
|
14141
|
+
let alreadyFed = [];
|
|
14142
|
+
const fedPath = join(repoRoot, '.dz', 'routing-fed.json');
|
|
14143
|
+
if (existsSync(fedPath)) {
|
|
14144
|
+
const parsed = JSON.parse(readFileSync(join(repoRoot, '.dz', 'routing-fed.json'), 'utf-8'));
|
|
14145
|
+
if (!Array.isArray(parsed))
|
|
14146
|
+
throw new Error('.dz/routing-fed.json must be a JSON array of runIds');
|
|
14147
|
+
if (parsed.some((v) => typeof v !== 'string'))
|
|
14148
|
+
throw new Error('.dz/routing-fed.json contains a non-string runId');
|
|
14149
|
+
alreadyFed = parsed;
|
|
14150
|
+
}
|
|
14151
|
+
return { harvest: harvestStageOutcomes(records), alreadyFed };
|
|
14152
|
+
}
|
|
13303
14153
|
/**
|
|
13304
14154
|
* `dz routing` — inspect the learned cost-optimal routing outcome store (feature learned-cost-routing). Shows
|
|
13305
14155
|
* what `args.models[stage]='auto-cost'` currently believes per (stage, complexity-tier, model): gated
|
|
@@ -13316,40 +14166,22 @@ function cmdRouting(options, flags, cwd, write) {
|
|
|
13316
14166
|
// ── recommend — harvest real telemetry, print per-stage picks WITH THE BASIS, optionally feed the
|
|
13317
14167
|
// store (a9c3dd5c fn 3, ADR-001). Sources: live harness records + imported run-meta.json sidecars.
|
|
13318
14168
|
if (options.get('_positional_0') === 'recommend') {
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
if (!existsSync(base))
|
|
13323
|
-
continue;
|
|
13324
|
-
let names = [];
|
|
13325
|
-
try {
|
|
13326
|
-
names = readdirSync(base);
|
|
13327
|
-
}
|
|
13328
|
-
catch {
|
|
13329
|
-
continue;
|
|
13330
|
-
}
|
|
13331
|
-
for (const name of names) {
|
|
13332
|
-
const sidecar = join(base, name, 'run-meta.json');
|
|
13333
|
-
if (!existsSync(sidecar))
|
|
13334
|
-
continue;
|
|
13335
|
-
try {
|
|
13336
|
-
const meta = JSON.parse(readFileSync(sidecar, 'utf-8'));
|
|
13337
|
-
if (meta.runMeta?.resolved === true && Array.isArray(meta.runMeta.records))
|
|
13338
|
-
records.push(...meta.runMeta.records);
|
|
13339
|
-
}
|
|
13340
|
-
catch { /* an unreadable sidecar contributes nothing — counted below as noResult */ }
|
|
13341
|
-
}
|
|
14169
|
+
let routing;
|
|
14170
|
+
try {
|
|
14171
|
+
routing = readRoutingTelemetry(repoRoot);
|
|
13342
14172
|
}
|
|
13343
|
-
|
|
13344
|
-
|
|
14173
|
+
catch (e) {
|
|
14174
|
+
write(`dz routing recommend: cannot read routing freshness state — ${e.message}`);
|
|
14175
|
+
return 1;
|
|
14176
|
+
}
|
|
14177
|
+
const harvest = routing.harvest;
|
|
14178
|
+
let rec = recommendModels(harvest, {
|
|
14179
|
+
...(options.get('tier') !== undefined ? { tier: options.get('tier') } : {}),
|
|
14180
|
+
alreadyFed: routing.alreadyFed,
|
|
14181
|
+
});
|
|
13345
14182
|
if (flags.has('apply')) {
|
|
13346
14183
|
const fedPath = join(repoRoot, '.dz', 'routing-fed.json');
|
|
13347
|
-
|
|
13348
|
-
try {
|
|
13349
|
-
alreadyFed = JSON.parse(readFileSync(fedPath, 'utf-8'));
|
|
13350
|
-
}
|
|
13351
|
-
catch { /* first apply */ }
|
|
13352
|
-
const plan = planFeed(harvest.samples, alreadyFed);
|
|
14184
|
+
const plan = planFeed(harvest.samples, routing.alreadyFed);
|
|
13353
14185
|
for (const sample of plan.toFeed)
|
|
13354
14186
|
finalizeOutcome(repoRoot, sample.stage, sample.tier, sample.model, sample.success);
|
|
13355
14187
|
try {
|
|
@@ -13360,6 +14192,10 @@ function cmdRouting(options, flags, cwd, write) {
|
|
|
13360
14192
|
write(`dz routing recommend: fed ${plan.toFeed.length} sample(s) but could NOT persist the fed-set — a re-apply WILL double-count: ${e.message}`);
|
|
13361
14193
|
return 1;
|
|
13362
14194
|
}
|
|
14195
|
+
rec = recommendModels(harvest, {
|
|
14196
|
+
...(options.get('tier') !== undefined ? { tier: options.get('tier') } : {}),
|
|
14197
|
+
alreadyFed: plan.fedAfter,
|
|
14198
|
+
});
|
|
13363
14199
|
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)` : ''}`);
|
|
13364
14200
|
}
|
|
13365
14201
|
if (flags.has('json')) {
|
|
@@ -13371,6 +14207,12 @@ function cmdRouting(options, flags, cwd, write) {
|
|
|
13371
14207
|
write(` ${s.stage.padEnd(14)} ${s.spec.padEnd(20)} ${s.insufficientData ? '[insufficient data — cold-start pick]' : '[quality bar met]'} ${s.pick.evidence}`);
|
|
13372
14208
|
}
|
|
13373
14209
|
write(` basis: ${rec.basis.rule}`);
|
|
14210
|
+
if (rec.basis.freshness === 'current')
|
|
14211
|
+
write(' store: current (0 unharvested runs)');
|
|
14212
|
+
else if (rec.basis.freshness === 'stale')
|
|
14213
|
+
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\``);
|
|
14214
|
+
else
|
|
14215
|
+
write(' store: UNFED — auto-cost has no learned rows; picks below are cold-start');
|
|
13374
14216
|
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}`);
|
|
13375
14217
|
write(` basis: ${rec.basis.crossFamilyNote}`);
|
|
13376
14218
|
return 0;
|
|
@@ -14008,8 +14850,10 @@ export async function runCli(argv, io = {}) {
|
|
|
14008
14850
|
// reads appear nowhere in help, and static extraction over the dispatch table lost `--week` from
|
|
14009
14851
|
// `dz recap` — a refusal built on either list would reject working commands, which is a worse
|
|
14010
14852
|
// failure than the one being fixed. Goes to STDERR so a `--json` consumer's stdout stays clean.
|
|
14011
|
-
|
|
14012
|
-
|
|
14853
|
+
if (command !== 'contract-check') {
|
|
14854
|
+
for (const notice of unknownFlagNotice([...flags, ...options.keys()].filter((k) => !k.startsWith('_positional_')), KNOWN_CLI_FLAGS)) {
|
|
14855
|
+
writeErr(notice.line);
|
|
14856
|
+
}
|
|
14013
14857
|
}
|
|
14014
14858
|
// ── `dz --version` / `dz -v` / `dz version` — PRE-DISPATCH, before the help branch ──
|
|
14015
14859
|
//
|
|
@@ -14035,6 +14879,13 @@ export async function runCli(argv, io = {}) {
|
|
|
14035
14879
|
write(USAGE);
|
|
14036
14880
|
return 0;
|
|
14037
14881
|
}
|
|
14882
|
+
// Only registered command identifiers are telemetry. An unknown first argv token may be a path,
|
|
14883
|
+
// typo, or secret-like value; persisting it would violate the command-name-only privacy boundary.
|
|
14884
|
+
// `contract-check` has an explicit byte-for-byte read-only contract: even the advisory command
|
|
14885
|
+
// usage ledger would mutate the repository being audited and invalidate its own safety proof.
|
|
14886
|
+
if (command !== 'contract-check') {
|
|
14887
|
+
recordCommandInvocation(cwd, DZ_COMMANDS.includes(command) ? command : '', new Date());
|
|
14888
|
+
}
|
|
14038
14889
|
try {
|
|
14039
14890
|
switch (command) {
|
|
14040
14891
|
case 'init':
|
|
@@ -14085,6 +14936,8 @@ export async function runCli(argv, io = {}) {
|
|
|
14085
14936
|
return cmdUsage(options, optionLists, flags, cwd, write);
|
|
14086
14937
|
case 'claim-check':
|
|
14087
14938
|
return cmdClaimCheck(options, optionLists, flags, cwd, write);
|
|
14939
|
+
case 'lint':
|
|
14940
|
+
return cmdLint(options, flags, cwd, write);
|
|
14088
14941
|
case 'sign':
|
|
14089
14942
|
return cmdSign(options, flags, cwd, write);
|
|
14090
14943
|
case 'sbom':
|
|
@@ -14157,6 +15010,8 @@ export async function runCli(argv, io = {}) {
|
|
|
14157
15010
|
return cmdSkillsVerify(options, flags, cwd, write);
|
|
14158
15011
|
case 'compounding':
|
|
14159
15012
|
return cmdCompounding(options, flags, cwd, write);
|
|
15013
|
+
case 'deadwood':
|
|
15014
|
+
return cmdDeadwood(options, flags, cwd, write, writeErr);
|
|
14160
15015
|
case 'epoch-replay':
|
|
14161
15016
|
return cmdEpochReplay(options, flags, cwd, write);
|
|
14162
15017
|
case 'score':
|
|
@@ -14167,6 +15022,8 @@ export async function runCli(argv, io = {}) {
|
|
|
14167
15022
|
return cmdCadence(options, flags, cwd, write);
|
|
14168
15023
|
case 'qe-rounds':
|
|
14169
15024
|
return cmdQeRounds(options, flags, cwd, write);
|
|
15025
|
+
case 'restart-advisor':
|
|
15026
|
+
return cmdRestartAdvisor(options, flags, cwd, write);
|
|
14170
15027
|
case 'tg-post':
|
|
14171
15028
|
return cmdTgPost(options, flags, cwd, write);
|
|
14172
15029
|
case 'name-check':
|
|
@@ -14177,6 +15034,8 @@ export async function runCli(argv, io = {}) {
|
|
|
14177
15034
|
return cmdFeatureAdrRecord(options, flags, cwd, write);
|
|
14178
15035
|
case 'amendment-check':
|
|
14179
15036
|
return cmdAmendmentCheck(options, flags, cwd, write);
|
|
15037
|
+
case 'contract-check':
|
|
15038
|
+
return cmdContractCheck(options, flags, cwd, write, writeErr);
|
|
14180
15039
|
case 'feature-adr-checkpoint':
|
|
14181
15040
|
return cmdFeatureAdrCheckpoint(options, flags, cwd, write);
|
|
14182
15041
|
case 'profile':
|