@dzhechkov/harness-cli 0.8.15 → 0.8.17
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 +16 -16
- package/README.md +134 -13
- package/dist/boolean-flags.d.ts.map +1 -1
- package/dist/boolean-flags.js +4 -0
- package/dist/boolean-flags.js.map +1 -1
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +516 -42
- package/dist/cli.js.map +1 -1
- package/dist/known-flags.d.ts.map +1 -1
- package/dist/known-flags.js +14 -0
- package/dist/known-flags.js.map +1 -1
- package/package.json +10 -10
- package/sbom.json +15 -15
- package/src/boolean-flags.ts +4 -0
- package/src/cli.ts +448 -42
- package/src/known-flags.ts +14 -0
package/dist/cli.js
CHANGED
|
@@ -16,9 +16,11 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
16
16
|
import { homedir, hostname, tmpdir } from 'node:os';
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
18
|
import { isDeepStrictEqual } from 'node:util';
|
|
19
|
+
import { JOURNAL_KINDS, formatLine, parseLine, selectWindow, appendWitnessed } from '@dzhechkov/harness-core';
|
|
20
|
+
import { appendRunEvent, readRunRegistry, liveParents, liveness, probePid, settleDeadRuns, planRegistryArchive, planWorktreeCleanup, renderCleanupPlan, worktreeRemovalsToApply } from '@dzhechkov/harness-core';
|
|
19
21
|
import { createSkill, getSkillInfo, listSkillsDetailed, formatSkillLoadFailures, formatSkillApplyFailures, resolveTargetName, formatTargetProblem, formatTargetAliasNote, TARGET_NAMES_SORTED, runDoctor, runInit, discoverSkillIds, resolveSelection, formatSelectRefusal, runIntegrationsVerify, 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,
|
|
20
22
|
// dz workflow run (feature dz-workflow-run): the pure scheduler + the dispatch adapters.
|
|
21
|
-
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, recordLessonForms, normalizeLessonForms, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, renderFeatureAdrPhaseLine, statuslineData, countLearningStoreRowsReadonly, readStoreMark, writeStoreMark, resetStoreMark, checkStoreHealth, storeGuardPath, storeSnapshotPath, writeFeatureAdrState, writeFeatureAdrStateDetailed, CHECKPOINT_STAGES, estimateEta, extractStageSamples, formatEta, parseCheckpointLines, segmentRun, 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, patternIdentityOf, mergeLessonMatchedForms, SWARM_BRIEF_CONTRACT, checkSwarmBrief, visibleText, 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, classifyChainDefects, CHAINED_JOURNALS, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, appendTransition, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
|
|
23
|
+
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, recordLessonForms, normalizeLessonForms, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, renderFeatureAdrPhaseLine, statuslineData, countLearningStoreRowsReadonly, readStoreMark, writeStoreMark, resetStoreMark, checkStoreHealth, storeGuardPath, storeSnapshotPath, writeFeatureAdrState, writeFeatureAdrStateDetailed, CHECKPOINT_STAGES, estimateEta, extractStageSamples, formatEta, parseCheckpointLines, segmentRun, 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, patternIdentityOf, mergeLessonMatchedForms, SWARM_BRIEF_CONTRACT, checkSwarmBrief, visibleText, loadStoreRecords, findExactLesson, 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, classifyChainDefects, CHAINED_JOURNALS, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, appendTransition, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
|
|
22
24
|
// guard-promotion (feature guard-promotion, scout idea #1)
|
|
23
25
|
assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, recordPromotionRunEvidence, isLessonRuleContentAnchor, isOffsetIsoTimestamp, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listPackFiles, 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, resolveScanTailTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, runRetroTailScan, 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,
|
|
24
26
|
// Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
|
|
@@ -28,7 +30,7 @@ readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, readGo
|
|
|
28
30
|
// qe-bridge (feature qe-bridge-claude, ADR-001): the pure half of the reverse QE bridge.
|
|
29
31
|
KNOWN_CLAUDE, isSafeClaudeId, claudeProbeArgs, claudeReviewArgs, interpretClaudeProbe, modelFamily, buildBridgePrompt, parseBridgeOutput, buildBridgeFailureRecord, buildBridgeSignoffRecord, renderBridgeReport, isSafeSlug, hasUnsafePathChars, hasDotDotSegment, buildReqeBrief, settleReqeDebt, renderReqeList, REQE_SCOPE,
|
|
30
32
|
// Mutation gate (feature ha-mutation-gate) — break each named protection, run the suite, require red.
|
|
31
|
-
parseMutationRegistry, applyMutationToText, attributeBaselineRedness, countFailingTests, detectSuiteCompletionReceipt, detectSuiteReceiptMismatch, classifyBaseline, classifyRunFailure, classifyMutationOutcome, mutationGateExitCode, summarizeMutationResults, renderMutationReport, runWithOneInternalRetry, TRACE_BUNDLE_LEDGER_PATH, TRACE_BUNDLE_SCHEMA, TRACE_BUNDLE_RUN_META_FILE, buildBundle, serializeBundle, parseBundle, planImport, decideCheckpointWrite, amendmentSection, amendmentSectionCount, amendmentDeclarationAmbiguity, 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,
|
|
33
|
+
parseMutationRegistry, applyMutationToText, attributeBaselineRedness, countFailingTests, detectSuiteCompletionReceipt, detectSuiteReceiptMismatch, classifyBaseline, classifyRunFailure, classifyMutationOutcome, mutationGateExitCode, summarizeMutationResults, renderMutationReport, runWithOneInternalRetry, TRACE_BUNDLE_LEDGER_PATH, TRACE_BUNDLE_SCHEMA, TRACE_BUNDLE_RUN_META_FILE, buildBundle, serializeBundle, parseBundle, planImport, decideCheckpointWrite, amendmentSection, amendmentSectionCount, amendmentDeclarationAmbiguity, planSaysNoAmendments, parseAmendments, resolveAmendments, decideAmendmentOutcome, amendmentVerdictLine, amendmentsMissingFromPlan, AMENDMENT_VACUITY_NOTE, extractContractChecklist, readFeatureTier, 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,
|
|
32
34
|
// operator-profile (ADR-001): per-user 0600 store + marked block in ~/.claude/CLAUDE.md
|
|
33
35
|
renderProfileBlock, readProfile, writeProfile, syncProfileBlock, checkProfileDrift, parseRegister, registerOwnerWord, profileAgeDays, parseDomainList, domainListText, parseYesNo, REGISTERS, } from '@dzhechkov/harness-core';
|
|
34
36
|
import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
|
|
@@ -69,11 +71,14 @@ export const DZ_COMMANDS = [
|
|
|
69
71
|
'retro', 'feature-adr-setup', 'challenge', 'discrimination-check',
|
|
70
72
|
'mutation-gate', 'delivery-check', 'skills-verify', 'compounding', 'deadwood',
|
|
71
73
|
'epoch-replay', 'score', 'recap', 'cadence', 'qe-rounds', 'restart-advisor', 'tg-post',
|
|
72
|
-
'name-check', 'brief-check', 'provenance-check', 'feature-adr-record', 'amendment-check', 'contract-check',
|
|
74
|
+
'name-check', 'brief-check', 'provenance-check', 'journal', 'feature-adr-record', 'runs', 'runs-record', 'runs-clean', 'amendment-check', 'contract-check',
|
|
73
75
|
'feature-adr-checkpoint', 'profile', 'reqe', 'qe-bridge', 'backlog', 'routing',
|
|
74
76
|
'bto-optimize', 'dashboard', 'roam', 'import-ecc', 'chain',
|
|
75
77
|
];
|
|
76
78
|
const USAGE = `dz - DZ cross-platform harness CLI
|
|
79
|
+
dz runs [--settle] [--stall-minutes N] [--json] [--project <dir>] [--probe-pid <pid>] (run registry: live, stalled, orphaned, inconclusive or finished; PID probe prints true|false|unknown)
|
|
80
|
+
dz runs-record --event started|heartbeat|finished [--run-id <id>] [--kind <kind>] [--slug <slug>] [--pid <pid|host>] [--parent-run-id <id>] [--outcome <outcome>] [--reason <text>] [--project <dir>] [--json] (append run event; started can allocate a run ID; host resolves the Claude ancestor PID)
|
|
81
|
+
dz runs-clean [--apply] [--retention-days N] [--project <dir>] [--json] (plan cleanup of old clean merged worktrees and completed/dead registry events; apply explicitly)
|
|
77
82
|
|
|
78
83
|
Usage:
|
|
79
84
|
dz init --target <name> [--skills-dir <dir>] [--project <dir>] [--preset <name>] [--select id,id,...] [--force] [--enrich] [--allow-integrations <sha256:digest>] [--no-integrations] [--no-hooks] [--no-verify] (integration manifests require exact digest consent; --no-integrations = explicit skills-only)
|
|
@@ -128,6 +133,7 @@ Usage:
|
|
|
128
133
|
dz sbom [--pack <name>] [--out <file>] (CycloneDX software bill of materials for the workspace, or for one pack with --pack)
|
|
129
134
|
dz amendment-check --slug <slug> | --feature-dir <dir> | --all [--json] (the deterministic Step-8 amendment gate: every AM-N / AM-CP-N row must resolve to a test found INSIDE the file the row names (the challenge-panel prefix is part of the id: AM-CP-1 is never AM-1); 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, UNLESS the plan explicitly declares \"None\"/\"нет\", which is an answer and reports skip. --all is a CENSUS and always exits 0. Does NOT prove non-vacuity — that is dz discrimination-check)
|
|
130
135
|
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)
|
|
136
|
+
dz journal add --kind decision|verdict|run|error|block "<text>" [--ref <trace>] [--at <ISO>] [--quote <file>] [--commit-quote]; dz journal show [--day|--week] [--at <date>] [--kind <kind>] [--json] (UTC day files, witnessed append; quotes stay local unless explicitly staged)
|
|
131
137
|
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)
|
|
132
138
|
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)
|
|
133
139
|
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)
|
|
@@ -169,7 +175,7 @@ Usage:
|
|
|
169
175
|
dz brain init [--project <dir>] [--k <N>] (wire the grounding hook into .claude/settings.json — opt-in)
|
|
170
176
|
dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
|
|
171
177
|
dz store-guard [--status|--reset] [--yes] [--project <dir>] (show the monotonic external high-water mark; --reset is the only lowering path and requires confirmation or --yes)
|
|
172
|
-
dz statusline --fa-record --slug <s> --step "<label>" [--kind <feature-adr|loop>] [--tier <S|M|L|XL>] [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state + phase → 📐 SECOND-LINE phase panel; a plain "Step <n>"
|
|
178
|
+
dz statusline --fa-record --slug <s> --step "<label>" [--kind <feature-adr|loop>] [--tier <S|M|L|XL>] [--run-id <id>] [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state + phase → 📐 SECOND-LINE phase panel; the monotone guard absorbs a backwards plain "Step <n>" only within the same non-empty run id, while an absent/empty id retains legacy fresh-slot behavior — prefix the label with ⛔ or ⏸ to record a legitimate regression)
|
|
173
179
|
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)
|
|
174
180
|
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)
|
|
175
181
|
dz chain [--project <dir>] [--json] (verify EVERY hash-chained journal in ONE command: coverage is DERIVED from the CHAINED_JOURNALS registry, never typed, so a journal cannot be given a chain and checked by nobody. An ABSENT journal is NAMED absent, never omitted — omission and cleanliness are indistinguishable in a report. Statuses: ok | healed (defects the current unbroken run has outlived — verdicts over present records are sound) | unchained (present, no chained record yet — legal) | absent | broken | unreadable. Exit 1 on broken/unreadable: a verifier that reports damage and exits 0 is one no automation can act on)
|
|
@@ -2079,12 +2085,14 @@ function cmdStatuslineFaRecord(options, cwd, write, writeErr) {
|
|
|
2079
2085
|
return 1;
|
|
2080
2086
|
}
|
|
2081
2087
|
const mode = options.get('mode');
|
|
2088
|
+
const runId = options.get('run-id')?.trim();
|
|
2082
2089
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
2083
2090
|
const outcome = writeFeatureAdrStateDetailed(projectRoot, {
|
|
2084
2091
|
kind: kindRaw, slug, step, recalled, stored,
|
|
2085
2092
|
...(reinforced > 0 ? { reinforced } : {}),
|
|
2086
2093
|
...(mode !== undefined && mode.trim() !== '' ? { mode: mode.trim() } : {}),
|
|
2087
2094
|
...(tier !== undefined ? { tier } : {}),
|
|
2095
|
+
...(runId !== undefined && runId !== '' ? { runId } : {}),
|
|
2088
2096
|
});
|
|
2089
2097
|
const state = outcome.state;
|
|
2090
2098
|
if (state === undefined) {
|
|
@@ -2299,26 +2307,37 @@ function cmdStatusline(options, flags, cwd, write, readStdin, writeErr) {
|
|
|
2299
2307
|
}));
|
|
2300
2308
|
return 0;
|
|
2301
2309
|
}
|
|
2310
|
+
// ТЕКСТ ПАНЕЛИ — ПО-АНГЛИЙСКИ по просьбе владельца 2026-09-09: строка статуса узкая,
|
|
2311
|
+
// английские слова в ней короче русских при той же ясности. Комментарии остаются русскими.
|
|
2302
2312
|
const breakdown = data.patternBreakdown;
|
|
2303
2313
|
let line = breakdown === undefined
|
|
2304
2314
|
? `🎓 dz: ${data.patterns} patterns`
|
|
2305
|
-
: `🎓 dz: ${data.patterns} (${breakdown.active}
|
|
2306
|
-
? ` · ${breakdown.quarantined}
|
|
2307
|
-
: ''})${breakdown.tierDelta !== undefined ? ` ⚠
|
|
2315
|
+
: `🎓 dz: ${data.patterns} (${breakdown.active} active${breakdown.quarantined > 0
|
|
2316
|
+
? ` · ${breakdown.quarantined} quarantined${breakdown.attention ? ' ⚠' : ''}`
|
|
2317
|
+
: ''})${breakdown.tierDelta !== undefined ? ` ⚠ tiers Δ${breakdown.tierDelta}` : ''}`;
|
|
2318
|
+
// Показатель зеркала печатается и здесь: `dz statusline` — та же панель, и показатель,
|
|
2319
|
+
// живущий только во вспомогательном скрипте, для этой поверхности просто не существовал.
|
|
2320
|
+
if (data.patternMirror?.state === 'unavailable')
|
|
2321
|
+
line += ' (mirror unreadable ⚠)';
|
|
2322
|
+
else if (data.patternMirror?.state === 'different')
|
|
2323
|
+
line += ` (mirror ${data.patternMirror.vector} ⚠)`;
|
|
2308
2324
|
if (data.storeHealth?.verdict === 'collapsed') {
|
|
2309
|
-
line += ` ⛔
|
|
2325
|
+
line += ` ⛔ COLLAPSE: was ${data.storeHealth.previousMax ?? '?'} · dz store-guard --reset`;
|
|
2310
2326
|
}
|
|
2311
2327
|
else if (data.storeHealth?.verdict === 'cold-start-over-existing') {
|
|
2312
|
-
line += ` ⛔
|
|
2328
|
+
line += ` ⛔ STORE EMPTY: was ${data.storeHealth.previousMax ?? '?'} · restore from snapshots ${data.storeHealth.snapshotPath ?? ''}`.trimEnd();
|
|
2313
2329
|
}
|
|
2314
2330
|
else if (data.storeHealth?.verdict === 'unreadable') {
|
|
2315
|
-
line += ` ⛔
|
|
2331
|
+
line += ` ⛔ STORE UNREADABLE${data.storeHealth.unreadableFiles !== undefined && data.storeHealth.unreadableFiles.length > 0
|
|
2316
2332
|
? `: ${data.storeHealth.unreadableFiles.join(', ')}` : ''}`;
|
|
2317
2333
|
}
|
|
2318
2334
|
else if (data.storeHealth?.verdict === 'source-changed') {
|
|
2319
|
-
line += ' ⚠
|
|
2335
|
+
line += ' ⚠ store source changed';
|
|
2320
2336
|
}
|
|
2321
|
-
|
|
2337
|
+
// Рядом с числом источников — объём каждого через «/» (просьба владельца 2026-09-09):
|
|
2338
|
+
// четыре источника по 300 единиц и четыре по три — разные корпуса, а число одно и то же.
|
|
2339
|
+
const ku = data.brainKuCounts.length > 0 ? ` (${data.brainKuCounts.join('/')})` : '';
|
|
2340
|
+
line += `${data.usedPatterns !== undefined ? ` · ${data.usedPatterns} used` : ''} · 🧠 ${data.brainSources} sources${ku}`;
|
|
2322
2341
|
const branch = statuslineGitBranch(projectRoot);
|
|
2323
2342
|
if (branch !== undefined)
|
|
2324
2343
|
line += ` · ⎇ ${branch}`;
|
|
@@ -3174,9 +3193,14 @@ async function cmdStoreGuard(options, flags, cwd, write, writeErr, stdinText, in
|
|
|
3174
3193
|
return 1;
|
|
3175
3194
|
}
|
|
3176
3195
|
}
|
|
3177
|
-
async function runTeachGuardReinforcement(projectRoot, dzId, reward) {
|
|
3196
|
+
async function runTeachGuardReinforcement(projectRoot, dzId, reward, preserveQuarantine = false) {
|
|
3178
3197
|
const backend = resolveLearningBackend(projectRoot);
|
|
3179
|
-
backend.addSample({
|
|
3198
|
+
backend.addSample({
|
|
3199
|
+
dzId,
|
|
3200
|
+
kind: preserveQuarantine ? 'recall-hit' : 'reinforce',
|
|
3201
|
+
reward,
|
|
3202
|
+
ts: new Date().toISOString(),
|
|
3203
|
+
});
|
|
3180
3204
|
return backend.train();
|
|
3181
3205
|
}
|
|
3182
3206
|
async function cmdTeach(options, flags, cwd, write, writeErr = (line) => { console.error(line); }, interactive = false, guardRunner = teachGuard, reinforceRunner = runTeachGuardReinforcement) {
|
|
@@ -3425,6 +3449,15 @@ async function cmdTeach(options, flags, cwd, write, writeErr = (line) => { conso
|
|
|
3425
3449
|
}
|
|
3426
3450
|
}
|
|
3427
3451
|
}
|
|
3452
|
+
if (!flags.has('guard')) {
|
|
3453
|
+
const exact = findExactLesson(loadStoreRecords(storeRoot), pattern, domain);
|
|
3454
|
+
if (exact !== null) {
|
|
3455
|
+
await reinforceRunner(storeRoot, exact.id, Math.max(0, Math.min(1, reward)), exact.quarantined);
|
|
3456
|
+
write(`Reinforced existing: ${exact.id} (exact text)${exact.quarantined ? ' [quarantined]' : ''}`);
|
|
3457
|
+
write(` Total patterns: ${loadStorePatternsSync(storeRoot).length}`);
|
|
3458
|
+
return 0;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3428
3461
|
// Distill pattern into actionable rule (claude-smart inspired)
|
|
3429
3462
|
// Convert "what happened" into "what to do next time"
|
|
3430
3463
|
const isRule = pattern.toLowerCase().startsWith('always') || pattern.toLowerCase().startsWith('never') ||
|
|
@@ -6383,11 +6416,19 @@ function cmdPublish(options, flags, cwd, write) {
|
|
|
6383
6416
|
// A one-line preview inline; on error the FULL captured npm error prints below (truncating a publish
|
|
6384
6417
|
// failure to 60 chars is how a release stays undiagnosable — the reason lives past char 60).
|
|
6385
6418
|
const detail = pkg.error && pkg.status !== 'error' ? ` (${pkg.error.slice(0, 60)})` : '';
|
|
6386
|
-
|
|
6419
|
+
const receipt = pkg.status === 'published' && pkg.registryProbes !== undefined
|
|
6420
|
+
? ` (confirmed by registry after ${pkg.registryProbes} probes)`
|
|
6421
|
+
: '';
|
|
6422
|
+
write(` ${icon} ${pkg.name.padEnd(35)} ${pkg.oldVersion} → ${pkg.newVersion} ${pkg.status}${receipt}${detail}`);
|
|
6387
6423
|
if (pkg.status === 'error' && pkg.error) {
|
|
6388
6424
|
for (const line of pkg.error.split('\n'))
|
|
6389
6425
|
write(` ${line}`);
|
|
6390
6426
|
}
|
|
6427
|
+
// The rollback branch cannot tell "publish failed" from "publish landed, registry never answered":
|
|
6428
|
+
// both restore package.json. The operator can — one registry query — so say what to check.
|
|
6429
|
+
if (pkg.status === 'error' && pkg.error?.startsWith('registry did not confirm')) {
|
|
6430
|
+
write(` ↳ disk bump rolled back; if \`npm view ${pkg.name}@${pkg.newVersion} version\` answers, the publish DID land — re-apply the bump by hand`);
|
|
6431
|
+
}
|
|
6391
6432
|
// Surface warn-mode findings that did not block the publish.
|
|
6392
6433
|
if (pkg.claimCheck && pkg.claimCheck.findings > 0 && pkg.status !== 'error') {
|
|
6393
6434
|
write(` ⚠ claim-check: ${pkg.claimCheck.findings} finding(s) (${pkg.claimCheck.high} high) in README.md`);
|
|
@@ -11012,14 +11053,34 @@ function cmdMutationGate(options, flags, cwd, write, injectedRunner) {
|
|
|
11012
11053
|
return fail(`registry ${registryPath} is invalid:\n - ${parsed.errors.join('\n - ')}`);
|
|
11013
11054
|
}
|
|
11014
11055
|
let entries = parsed.registry.entries;
|
|
11056
|
+
let entryResults = parsed.entryResults;
|
|
11015
11057
|
const only = options.get('only');
|
|
11016
11058
|
if (only !== undefined) {
|
|
11017
11059
|
const ids = only.split(',').map((s) => s.trim()).filter(Boolean);
|
|
11018
|
-
const known = new Set(
|
|
11060
|
+
const known = new Set([
|
|
11061
|
+
...entries.map((entry) => entry.id),
|
|
11062
|
+
...entryResults.map((result) => result.id),
|
|
11063
|
+
]);
|
|
11019
11064
|
const unknown = ids.filter((id) => !known.has(id));
|
|
11020
11065
|
if (unknown.length > 0)
|
|
11021
11066
|
return fail(`--only names unknown entry id(s): ${unknown.join(', ')}`);
|
|
11022
11067
|
entries = entries.filter((e) => ids.includes(e.id));
|
|
11068
|
+
entryResults = entryResults.filter((result) => ids.includes(result.id));
|
|
11069
|
+
}
|
|
11070
|
+
if (entries.length === 0) {
|
|
11071
|
+
const scope = only === undefined ? 'registry' : 'selected registry entries';
|
|
11072
|
+
const error = `${scope} has no runnable entries after validation — nothing can be run; the registry is unusable`;
|
|
11073
|
+
const summary = summarizeMutationResults(entryResults);
|
|
11074
|
+
if (json) {
|
|
11075
|
+
write(JSON.stringify({ error, registryPath, results: entryResults, summary, exitCode: 2 }, null, 2));
|
|
11076
|
+
}
|
|
11077
|
+
else {
|
|
11078
|
+
write(`dz mutation-gate: ${error}`);
|
|
11079
|
+
for (const result of entryResults) {
|
|
11080
|
+
write(` - ${result.id}: ${result.verdict} — ${result.detail}`);
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
return 2;
|
|
11023
11084
|
}
|
|
11024
11085
|
const testCmdRaw = options.get('test-cmd') ?? parsed.registry.testCommand ?? 'npm test';
|
|
11025
11086
|
if (/[\0\n\r]/.test(testCmdRaw))
|
|
@@ -11055,7 +11116,7 @@ function cmdMutationGate(options, flags, cwd, write, injectedRunner) {
|
|
|
11055
11116
|
}
|
|
11056
11117
|
catch { /* not in a git repo */ }
|
|
11057
11118
|
let copyDir = join(scratchParent, 'pkg');
|
|
11058
|
-
const results = [];
|
|
11119
|
+
const results = [...entryResults];
|
|
11059
11120
|
const observations = [];
|
|
11060
11121
|
const warnings = [];
|
|
11061
11122
|
const internalRetries = [];
|
|
@@ -11187,10 +11248,10 @@ function cmdMutationGate(options, flags, cwd, write, injectedRunner) {
|
|
|
11187
11248
|
: undefined);
|
|
11188
11249
|
if (!baseline.ok) {
|
|
11189
11250
|
if (json) {
|
|
11190
|
-
write(JSON.stringify({ packageDir: pkgDir, registryPath, testCommand: testCmd, baseline, results
|
|
11251
|
+
write(JSON.stringify({ packageDir: pkgDir, registryPath, testCommand: testCmd, baseline, results, internalRetries, exitCode: 1 }, null, 2));
|
|
11191
11252
|
return 1;
|
|
11192
11253
|
}
|
|
11193
|
-
write(renderMutationReport(
|
|
11254
|
+
write(renderMutationReport(results, baseline, pkgDir));
|
|
11194
11255
|
return 1;
|
|
11195
11256
|
}
|
|
11196
11257
|
for (const entry of entries) {
|
|
@@ -13259,6 +13320,258 @@ function cmdLedgerBackfill(options, flags, cwd, write) {
|
|
|
13259
13320
|
write(` not filled: ${n} row(s) — ${reason}`);
|
|
13260
13321
|
return 0;
|
|
13261
13322
|
}
|
|
13323
|
+
const runRegistryIO = {
|
|
13324
|
+
append: (path, line) => withNamedLockSync(dirname(path), 'run-registry-archive', () => appendFileSync(path, line, 'utf8')),
|
|
13325
|
+
read: (path) => readFileSync(path, 'utf8'),
|
|
13326
|
+
mkdir: (dir) => { mkdirSync(dir, { recursive: true }); },
|
|
13327
|
+
};
|
|
13328
|
+
function cmdRuns(options, flags, cwd, write) {
|
|
13329
|
+
if (options.has('probe-pid')) {
|
|
13330
|
+
const result = probePid(Number(options.get('probe-pid')));
|
|
13331
|
+
write(result === null ? 'unknown' : String(result));
|
|
13332
|
+
return 0;
|
|
13333
|
+
}
|
|
13334
|
+
const root = resolve(options.get('project') ?? cwd);
|
|
13335
|
+
const minutes = Number(options.get('stall-minutes') ?? 120);
|
|
13336
|
+
if (!Number.isFinite(minutes) || minutes < 0) {
|
|
13337
|
+
write(flags.has('json') ? JSON.stringify({ status: 'refused', reason: 'stall-minutes must be a finite non-negative number' }) : 'stall-minutes must be a finite non-negative number');
|
|
13338
|
+
return 2;
|
|
13339
|
+
}
|
|
13340
|
+
const opts = { stallMs: minutes * 60_000 };
|
|
13341
|
+
let settled = [];
|
|
13342
|
+
if (flags.has('settle')) {
|
|
13343
|
+
settled = withNamedLockSync(join(root, '.dz', 'runs'), 'run-registry-archive', () => {
|
|
13344
|
+
const events = settleDeadRuns(readRunRegistry(root, runRegistryIO), Date.now(), probePid);
|
|
13345
|
+
// Already holding the shared archive/append lock across read-plan-append.
|
|
13346
|
+
const unlockedIO = { ...runRegistryIO, append: (path, line) => appendFileSync(path, line, 'utf8') };
|
|
13347
|
+
for (const event of events)
|
|
13348
|
+
appendRunEvent(root, event, unlockedIO);
|
|
13349
|
+
return events;
|
|
13350
|
+
});
|
|
13351
|
+
if (!flags.has('json')) {
|
|
13352
|
+
for (const event of settled)
|
|
13353
|
+
write(`settled ${event.runId} — died (${event.reason})`);
|
|
13354
|
+
if (!settled.length)
|
|
13355
|
+
write('nothing to settle');
|
|
13356
|
+
}
|
|
13357
|
+
}
|
|
13358
|
+
const registry = readRunRegistry(root, runRegistryIO);
|
|
13359
|
+
if (registry.status === 'missing') {
|
|
13360
|
+
write(flags.has('json') ? JSON.stringify(registry) : 'нет реестра: .dz/runs/registry.jsonl ещё не создан (ни одного прогона)');
|
|
13361
|
+
return 0;
|
|
13362
|
+
}
|
|
13363
|
+
const now = Date.now();
|
|
13364
|
+
const parents = liveParents(registry, now, probePid, opts);
|
|
13365
|
+
const rows = registry.runs.map((run, i) => {
|
|
13366
|
+
const parent = parents[i].liveness;
|
|
13367
|
+
const own = liveness(run, now, probePid, opts);
|
|
13368
|
+
const decision = registry.status !== 'readable' ? parent :
|
|
13369
|
+
run.finished ? { state: 'finished', reason: run.outcome } :
|
|
13370
|
+
parent.state !== 'live' && parent.state !== 'stalled' ? parent : own;
|
|
13371
|
+
return { ...run, state: decision.state, reason: decision.reason,
|
|
13372
|
+
parentLiveness: parent, heartbeatAgeMs: run.heartbeat ? now - Date.parse(run.heartbeat) : null };
|
|
13373
|
+
});
|
|
13374
|
+
if (flags.has('json'))
|
|
13375
|
+
write(JSON.stringify({ ...registry, runs: rows, ...(flags.has('settle') ? { settled } : {}) }));
|
|
13376
|
+
else {
|
|
13377
|
+
if (registry.status !== 'readable')
|
|
13378
|
+
write('inconclusive — ' + registry.reason);
|
|
13379
|
+
write('runId | kind | slug | pid | parent | liveness | heartbeat age');
|
|
13380
|
+
for (const row of rows)
|
|
13381
|
+
write([row.runId, row.kind, row.slug, row.pid, row.parentRunId ?? '—',
|
|
13382
|
+
row.state, row.heartbeatAgeMs === null ? '—' : `${row.heartbeatAgeMs}ms`].join(' | ') + ' — ' + row.reason);
|
|
13383
|
+
}
|
|
13384
|
+
return registry.status === 'readable' ? 0 : 1;
|
|
13385
|
+
}
|
|
13386
|
+
/** Git effects are injected; the core owns eligibility and the explicit apply boundary. */
|
|
13387
|
+
export function cmdRunsClean(options, flags, cwd, write, exec = execSync) {
|
|
13388
|
+
const root = resolve(options.get('project') ?? cwd);
|
|
13389
|
+
const json = flags.has('json');
|
|
13390
|
+
const messages = [];
|
|
13391
|
+
const say = (line) => { messages.push(line); if (!json)
|
|
13392
|
+
write(line); };
|
|
13393
|
+
try {
|
|
13394
|
+
const days = Number(options.get('retention-days') ?? 2);
|
|
13395
|
+
if (!Number.isFinite(days) || days < 0)
|
|
13396
|
+
throw new Error('retention-days must be a finite non-negative number');
|
|
13397
|
+
const now = Date.now(), retentionMs = days * 86400000;
|
|
13398
|
+
const git = (command, dir = root) => String(exec(command, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }));
|
|
13399
|
+
// Git C-quotes paths containing controls/non-ASCII bytes. Decode octal UTF-8 as well.
|
|
13400
|
+
const unquote = (value) => {
|
|
13401
|
+
if (!value.startsWith('"'))
|
|
13402
|
+
return value;
|
|
13403
|
+
const bytes = [];
|
|
13404
|
+
const body = value.slice(1, -1);
|
|
13405
|
+
const escapes = { a: '\x07', b: '\b', t: '\t', n: '\n', v: '\v', f: '\f', r: '\r', '"': '"', '\\': '\\' };
|
|
13406
|
+
for (let i = 0; i < body.length; i++) {
|
|
13407
|
+
if (body[i] === '\\') {
|
|
13408
|
+
const octal = /^[0-7]{1,3}/.exec(body.slice(i + 1));
|
|
13409
|
+
if (octal) {
|
|
13410
|
+
bytes.push(parseInt(octal[0], 8));
|
|
13411
|
+
i += octal[0].length;
|
|
13412
|
+
}
|
|
13413
|
+
else {
|
|
13414
|
+
i++;
|
|
13415
|
+
bytes.push(...Buffer.from(escapes[body[i]] ?? body[i]));
|
|
13416
|
+
}
|
|
13417
|
+
}
|
|
13418
|
+
else {
|
|
13419
|
+
const cp = String.fromCodePoint(body.codePointAt(i));
|
|
13420
|
+
bytes.push(...Buffer.from(cp));
|
|
13421
|
+
i += cp.length - 1;
|
|
13422
|
+
}
|
|
13423
|
+
}
|
|
13424
|
+
return Buffer.from(bytes).toString('utf8');
|
|
13425
|
+
};
|
|
13426
|
+
const list = () => {
|
|
13427
|
+
const text = git('git worktree list --porcelain');
|
|
13428
|
+
const trees = text.trimEnd().split(/\n\n+/).filter(Boolean).map(block => {
|
|
13429
|
+
const lines = block.split('\n');
|
|
13430
|
+
const path = lines.find(line => line.startsWith('worktree '))?.slice(9);
|
|
13431
|
+
const sha = lines.find(line => line.startsWith('HEAD '))?.slice(5);
|
|
13432
|
+
if (!path || !sha)
|
|
13433
|
+
throw new Error('unreadable worktree entry: ' + block);
|
|
13434
|
+
return { path: unquote(path), sha,
|
|
13435
|
+
branch: lines.find(line => line.startsWith('branch '))?.slice(7) ?? null,
|
|
13436
|
+
detached: lines.includes('detached') };
|
|
13437
|
+
});
|
|
13438
|
+
if (!trees.length)
|
|
13439
|
+
throw new Error('empty git worktree list');
|
|
13440
|
+
return trees;
|
|
13441
|
+
};
|
|
13442
|
+
const trees = list();
|
|
13443
|
+
const facts = trees.map((tree, index) => {
|
|
13444
|
+
// Any unreadable status aborts before removal; unknown cleanliness is never clean.
|
|
13445
|
+
const dirtyFiles = git('git status --short --untracked-files=all', tree.path).split('\n').filter(Boolean)
|
|
13446
|
+
.map(line => unquote(line.slice(3)));
|
|
13447
|
+
let merged = null;
|
|
13448
|
+
try {
|
|
13449
|
+
git(`git merge-base --is-ancestor ${shellQuote(tree.branch ?? tree.sha)} main`);
|
|
13450
|
+
merged = true;
|
|
13451
|
+
}
|
|
13452
|
+
catch (error) {
|
|
13453
|
+
if (error.status === 1)
|
|
13454
|
+
merged = false;
|
|
13455
|
+
}
|
|
13456
|
+
let lastCommitTs = null;
|
|
13457
|
+
try {
|
|
13458
|
+
const raw = git('git log -1 --format=%ct', tree.path).trim();
|
|
13459
|
+
if (/^\d+$/.test(raw) && Number.isFinite(Number(raw) * 1000))
|
|
13460
|
+
lastCommitTs = Number(raw) * 1000;
|
|
13461
|
+
}
|
|
13462
|
+
catch { /* unknown age keeps the worktree */ }
|
|
13463
|
+
return { path: tree.path, branch: tree.branch?.replace(/^refs\/heads\//, '') ?? null,
|
|
13464
|
+
detached: tree.detached, isMain: index === 0 || tree.branch === 'refs/heads/main', merged, dirtyFiles, lastCommitTs };
|
|
13465
|
+
});
|
|
13466
|
+
const plan = planWorktreeCleanup(facts, { now, retentionMs });
|
|
13467
|
+
for (const line of renderCleanupPlan(plan))
|
|
13468
|
+
say(line);
|
|
13469
|
+
let code = 0;
|
|
13470
|
+
for (const fact of worktreeRemovalsToApply(plan, flags.has('apply'))) {
|
|
13471
|
+
let failure;
|
|
13472
|
+
try {
|
|
13473
|
+
git(`git worktree remove ${shellQuote(fact.path)}`);
|
|
13474
|
+
}
|
|
13475
|
+
catch (error) {
|
|
13476
|
+
failure = String(error);
|
|
13477
|
+
}
|
|
13478
|
+
try {
|
|
13479
|
+
if (!list().some(tree => tree.path === fact.path))
|
|
13480
|
+
say(`removed ${fact.path} (confirmed)`);
|
|
13481
|
+
else {
|
|
13482
|
+
say(`NOT removed ${fact.path}` + (failure ? ` — ${failure}` : ''));
|
|
13483
|
+
code = 1;
|
|
13484
|
+
}
|
|
13485
|
+
}
|
|
13486
|
+
catch (error) {
|
|
13487
|
+
say(`NOT removed ${fact.path} — confirmation unavailable: ${String(error)}`);
|
|
13488
|
+
code = 1;
|
|
13489
|
+
}
|
|
13490
|
+
}
|
|
13491
|
+
const archiveRegistry = () => {
|
|
13492
|
+
const registry = readRunRegistry(root, runRegistryIO);
|
|
13493
|
+
if (registry.status === 'inconclusive')
|
|
13494
|
+
throw new Error(registry.reason);
|
|
13495
|
+
const archivePlan = planRegistryArchive(registry, { now, retentionMs, probe: probePid });
|
|
13496
|
+
if (flags.has('apply') && archivePlan.archive.length > 0) {
|
|
13497
|
+
const dir = join(root, '.dz', 'runs');
|
|
13498
|
+
const encode = (events) => events.map(ev => JSON.stringify(ev) + '\n').join('');
|
|
13499
|
+
const temp = join(dir, `registry.${randomBytes(8).toString('hex')}.tmp`);
|
|
13500
|
+
try {
|
|
13501
|
+
writeFileSync(temp, encode(archivePlan.keep), { flag: 'wx', mode: 0o600 });
|
|
13502
|
+
appendFileSync(join(dir, 'registry.archive.jsonl'), encode(archivePlan.archive));
|
|
13503
|
+
renameSync(temp, join(dir, 'registry.jsonl'));
|
|
13504
|
+
}
|
|
13505
|
+
finally {
|
|
13506
|
+
if (existsSync(temp))
|
|
13507
|
+
unlinkSync(temp);
|
|
13508
|
+
}
|
|
13509
|
+
}
|
|
13510
|
+
return archivePlan;
|
|
13511
|
+
};
|
|
13512
|
+
const archive = flags.has('apply')
|
|
13513
|
+
? withNamedLockSync(join(root, '.dz', 'runs'), 'run-registry-archive', archiveRegistry)
|
|
13514
|
+
: archiveRegistry();
|
|
13515
|
+
say(`${flags.has('apply') ? 'archived' : 'archive'} ${archive.archive.length} registry event(s)`);
|
|
13516
|
+
if (json)
|
|
13517
|
+
write(JSON.stringify({ ...plan, applied: flags.has('apply'), archive, messages, ok: code === 0 }));
|
|
13518
|
+
return code;
|
|
13519
|
+
}
|
|
13520
|
+
catch (error) {
|
|
13521
|
+
const reason = String(error);
|
|
13522
|
+
if (json)
|
|
13523
|
+
write(JSON.stringify({ ok: false, reason, messages }));
|
|
13524
|
+
else
|
|
13525
|
+
write('runs-clean refused: ' + reason);
|
|
13526
|
+
return 1;
|
|
13527
|
+
}
|
|
13528
|
+
}
|
|
13529
|
+
function cmdRunsRecord(options, flags, cwd, write) {
|
|
13530
|
+
try {
|
|
13531
|
+
const event = options.get('event');
|
|
13532
|
+
const runId = options.get('run-id') ?? (event === 'started' ? randomBytes(16).toString('hex') : '');
|
|
13533
|
+
const ev = { event, runId, ts: new Date().toISOString() };
|
|
13534
|
+
if (event === 'started') {
|
|
13535
|
+
const kind = options.get('kind');
|
|
13536
|
+
const slug = options.get('slug');
|
|
13537
|
+
Object.assign(ev, { ...(kind === undefined ? {} : { kind }), ...(slug === undefined ? {} : { slug }) });
|
|
13538
|
+
const rawPid = options.get('pid');
|
|
13539
|
+
if (rawPid === 'host') {
|
|
13540
|
+
// The shell agent is transient. Walk to the nearest Claude host, never guess from age.
|
|
13541
|
+
let candidate = process.ppid;
|
|
13542
|
+
const seen = new Set();
|
|
13543
|
+
while (candidate > 1 && !seen.has(candidate)) {
|
|
13544
|
+
seen.add(candidate);
|
|
13545
|
+
const row = execFileSync('ps', ['-p', String(candidate), '-o', 'ppid=', '-o', 'comm='], { encoding: 'utf8' }).trim();
|
|
13546
|
+
const match = /^(\d+)\s+(.+)$/.exec(row);
|
|
13547
|
+
if (!match)
|
|
13548
|
+
break;
|
|
13549
|
+
if (/^claude(?:$|[- ])/.test(basename(match[2]))) {
|
|
13550
|
+
ev.pid = candidate;
|
|
13551
|
+
break;
|
|
13552
|
+
}
|
|
13553
|
+
candidate = Number(match[1]);
|
|
13554
|
+
}
|
|
13555
|
+
if (ev.pid === undefined)
|
|
13556
|
+
throw new Error('Claude host PID unavailable; pass explicit --pid (Workflow args.runPid)');
|
|
13557
|
+
}
|
|
13558
|
+
else
|
|
13559
|
+
ev.pid = Number(rawPid);
|
|
13560
|
+
const parentRunId = options.get('parent-run-id');
|
|
13561
|
+
Object.assign(ev, { ...(parentRunId === undefined ? {} : { parentRunId }) });
|
|
13562
|
+
}
|
|
13563
|
+
const outcome = event === 'finished' ? options.get('outcome') : undefined;
|
|
13564
|
+
const reason = options.get('reason');
|
|
13565
|
+
Object.assign(ev, { ...(outcome === undefined ? {} : { outcome }), ...(reason === undefined ? {} : { reason }) });
|
|
13566
|
+
appendRunEvent(resolve(options.get('project') ?? cwd), ev, runRegistryIO);
|
|
13567
|
+
write(flags.has('json') ? JSON.stringify({ status: 'written', ...ev }) : `written ${event} ${runId}`);
|
|
13568
|
+
return 0;
|
|
13569
|
+
}
|
|
13570
|
+
catch (error) {
|
|
13571
|
+
write(flags.has('json') ? JSON.stringify({ status: 'refused', reason: String(error) }) : `refused: ${String(error)}`);
|
|
13572
|
+
return 2;
|
|
13573
|
+
}
|
|
13574
|
+
}
|
|
13262
13575
|
function cmdFeatureAdrRecord(options, flags, cwd, write) {
|
|
13263
13576
|
const json = flags.has('json');
|
|
13264
13577
|
// `--backfill` is a different verb on the same store: it fills the ledger's null cost fields from
|
|
@@ -13444,15 +13757,28 @@ function contractDirectoryConfined(repoRoot, absolute, artifact) {
|
|
|
13444
13757
|
catch {
|
|
13445
13758
|
return {
|
|
13446
13759
|
ok: false,
|
|
13760
|
+
reason: 'unreadable',
|
|
13447
13761
|
diagnostic: { code: 'repository-unreadable', message: 'repository root cannot be resolved', artifact: '.' },
|
|
13448
13762
|
};
|
|
13449
13763
|
}
|
|
13764
|
+
try {
|
|
13765
|
+
lstatSync(absolute);
|
|
13766
|
+
}
|
|
13767
|
+
catch (error) {
|
|
13768
|
+
const missing = error.code === 'ENOENT';
|
|
13769
|
+
return {
|
|
13770
|
+
ok: false,
|
|
13771
|
+
reason: missing ? 'missing' : 'unreadable',
|
|
13772
|
+
diagnostic: { code: 'adr-directory-unreadable', message: `required ADR directory cannot be resolved: ${artifact}`, artifact },
|
|
13773
|
+
};
|
|
13774
|
+
}
|
|
13450
13775
|
try {
|
|
13451
13776
|
targetReal = realpathSync(absolute);
|
|
13452
13777
|
}
|
|
13453
13778
|
catch {
|
|
13454
13779
|
return {
|
|
13455
13780
|
ok: false,
|
|
13781
|
+
reason: 'unreadable',
|
|
13456
13782
|
diagnostic: { code: 'adr-directory-unreadable', message: `required ADR directory cannot be resolved: ${artifact}`, artifact },
|
|
13457
13783
|
};
|
|
13458
13784
|
}
|
|
@@ -13460,6 +13786,7 @@ function contractDirectoryConfined(repoRoot, absolute, artifact) {
|
|
|
13460
13786
|
if (rel === '' || rel.startsWith(`..${sep}`) || rel === '..' || isAbsolute(rel)) {
|
|
13461
13787
|
return {
|
|
13462
13788
|
ok: false,
|
|
13789
|
+
reason: 'unreadable',
|
|
13463
13790
|
diagnostic: { code: 'artifact-outside-repository', message: `artifact resolves outside the repository: ${artifact}`, artifact },
|
|
13464
13791
|
};
|
|
13465
13792
|
}
|
|
@@ -13471,6 +13798,7 @@ function contractDirectoryConfined(repoRoot, absolute, artifact) {
|
|
|
13471
13798
|
catch {
|
|
13472
13799
|
return {
|
|
13473
13800
|
ok: false,
|
|
13801
|
+
reason: 'unreadable',
|
|
13474
13802
|
diagnostic: { code: 'adr-directory-unreadable', message: `required ADR directory is not readable: ${artifact}`, artifact },
|
|
13475
13803
|
};
|
|
13476
13804
|
}
|
|
@@ -13523,30 +13851,63 @@ function cmdContractCheck(options, flags, cwd, write, writeErr) {
|
|
|
13523
13851
|
return emitEarly('not-established', 2, [requirementsRead.diagnostic]);
|
|
13524
13852
|
const adrDirRel = `features/${slug}/03_adr`;
|
|
13525
13853
|
const adrDirectory = contractDirectoryConfined(repoRoot, join(featureDir, '03_adr'), adrDirRel);
|
|
13526
|
-
|
|
13527
|
-
return emitEarly('not-established', 2, [adrDirectory.diagnostic]);
|
|
13528
|
-
const adrDir = adrDirectory.realPath;
|
|
13854
|
+
const informationalDiagnostics = [];
|
|
13529
13855
|
let adrNames;
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13856
|
+
let adrDir = null;
|
|
13857
|
+
let adrsOptional;
|
|
13858
|
+
if (!adrDirectory.ok) {
|
|
13859
|
+
if (adrDirectory.reason !== 'missing')
|
|
13860
|
+
return emitEarly('not-established', 2, [adrDirectory.diagnostic]);
|
|
13861
|
+
const tier = readFeatureTier((rel) => {
|
|
13862
|
+
const assessment = contractReadConfined(repoRoot, join(repoRoot, rel), rel);
|
|
13863
|
+
return assessment.ok ? assessment.text : null;
|
|
13864
|
+
}, slug);
|
|
13865
|
+
if (tier === 'S') {
|
|
13866
|
+
adrNames = [];
|
|
13867
|
+
adrsOptional = true;
|
|
13868
|
+
informationalDiagnostics.push({
|
|
13869
|
+
code: 'adr-not-required-for-tier',
|
|
13870
|
+
message: 'ADR artifacts are not required for an S-tier feature',
|
|
13540
13871
|
artifact: adrDirRel,
|
|
13541
|
-
}
|
|
13872
|
+
});
|
|
13873
|
+
}
|
|
13874
|
+
else if (tier !== null) {
|
|
13875
|
+
return emitEarly('not-established', 2, [{
|
|
13876
|
+
code: 'adr-directory-missing',
|
|
13877
|
+
message: `required ADR directory is missing for ${tier}-tier feature: ${adrDirRel}`,
|
|
13878
|
+
artifact: adrDirRel,
|
|
13879
|
+
}]);
|
|
13880
|
+
}
|
|
13881
|
+
else {
|
|
13882
|
+
return emitEarly('not-established', 2, [{
|
|
13883
|
+
...adrDirectory.diagnostic,
|
|
13884
|
+
message: `${adrDirectory.diagnostic.message}; feature tier is unreadable`,
|
|
13885
|
+
}]);
|
|
13886
|
+
}
|
|
13542
13887
|
}
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13888
|
+
else {
|
|
13889
|
+
adrDir = adrDirectory.realPath;
|
|
13890
|
+
try {
|
|
13891
|
+
adrNames = readdirSync(adrDir, { withFileTypes: true })
|
|
13892
|
+
.filter((entry) => entry.name.endsWith('.md') && (entry.isFile() || entry.isSymbolicLink()))
|
|
13893
|
+
.map((entry) => entry.name)
|
|
13894
|
+
.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
13895
|
+
}
|
|
13896
|
+
catch {
|
|
13897
|
+
return emitEarly('not-established', 2, [{
|
|
13898
|
+
code: 'adr-directory-unreadable',
|
|
13899
|
+
message: `required ADR directory cannot be read: ${adrDirRel}`,
|
|
13900
|
+
artifact: adrDirRel,
|
|
13901
|
+
}]);
|
|
13902
|
+
}
|
|
13903
|
+
if (adrNames.length === 0) {
|
|
13904
|
+
return emitEarly('not-established', 2, [{
|
|
13905
|
+
code: 'adr-artifacts-missing',
|
|
13906
|
+
message: `no direct ADR Markdown artifacts exist under ${adrDirRel}`,
|
|
13907
|
+
artifact: adrDirRel,
|
|
13908
|
+
observed: 0,
|
|
13909
|
+
}]);
|
|
13910
|
+
}
|
|
13550
13911
|
}
|
|
13551
13912
|
const adrs = [];
|
|
13552
13913
|
const adrReadErrors = [];
|
|
@@ -13563,6 +13924,7 @@ function cmdContractCheck(options, flags, cwd, write, writeErr) {
|
|
|
13563
13924
|
const extracted = extractContractChecklist({
|
|
13564
13925
|
requirements: { path: requirementsRel, text: requirementsRead.text },
|
|
13565
13926
|
adrs,
|
|
13927
|
+
...(adrsOptional === true ? { adrsOptional: true } : {}),
|
|
13566
13928
|
});
|
|
13567
13929
|
if (!extracted.ok)
|
|
13568
13930
|
return emitEarly('fail', 1, extracted.diagnostics);
|
|
@@ -13606,7 +13968,7 @@ function cmdContractCheck(options, flags, cwd, write, writeErr) {
|
|
|
13606
13968
|
});
|
|
13607
13969
|
const verification = {
|
|
13608
13970
|
...rawVerification,
|
|
13609
|
-
diagnostics: rawVerification.diagnostics.map(withReportArtifact),
|
|
13971
|
+
diagnostics: [...informationalDiagnostics, ...rawVerification.diagnostics.map(withReportArtifact)],
|
|
13610
13972
|
items: rawVerification.items.map((item) => ({
|
|
13611
13973
|
...item,
|
|
13612
13974
|
diagnostics: item.diagnostics.map(withReportArtifact),
|
|
@@ -13635,7 +13997,7 @@ function cmdContractCheck(options, flags, cwd, write, writeErr) {
|
|
|
13635
13997
|
writeErr(contractDiagnosticLine(entry));
|
|
13636
13998
|
const summary = verification.outcome === 'pass'
|
|
13637
13999
|
? `${verification.counts.met} contract item(s) met`
|
|
13638
|
-
: `${
|
|
14000
|
+
: `${rawVerification.diagnostics.length} contract or evidence violation(s)`;
|
|
13639
14001
|
write(`contract-check: ${verification.outcome === 'pass' ? 'PASS' : 'FAIL'} — ${summary}`);
|
|
13640
14002
|
return verification.exitCode;
|
|
13641
14003
|
}
|
|
@@ -17189,6 +17551,110 @@ async function cmdProfile(options, flags, write, writeErr) {
|
|
|
17189
17551
|
}
|
|
17190
17552
|
return fail(2, `dz profile: unknown subcommand ${JSON.stringify(sub)} — accepted: init | show | set | sync`);
|
|
17191
17553
|
}
|
|
17554
|
+
function cmdJournal(options, flags, cwd, write, injectedIo) {
|
|
17555
|
+
try {
|
|
17556
|
+
const verb = options.get('_positional_0');
|
|
17557
|
+
if (verb !== 'add' && verb !== 'show')
|
|
17558
|
+
throw new Error('Ожидается journal add или journal show');
|
|
17559
|
+
const kindRaw = options.get('kind');
|
|
17560
|
+
if ((verb === 'add' || kindRaw !== undefined) && !JOURNAL_KINDS.includes(kindRaw)) {
|
|
17561
|
+
write(`Категория должна быть одной из: ${JOURNAL_KINDS.join(', ')}`);
|
|
17562
|
+
return 1;
|
|
17563
|
+
}
|
|
17564
|
+
if (flags.has('day') && flags.has('week'))
|
|
17565
|
+
throw new Error('Выберите --day или --week');
|
|
17566
|
+
const root = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
17567
|
+
const dir = join(root, 'docs', 'journal');
|
|
17568
|
+
// A journal symlink must never redirect an append or a quote outside this repository.
|
|
17569
|
+
for (const path of [join(root, 'docs'), dir, join(dir, 'quotes')]) {
|
|
17570
|
+
if (existsSync(path) && lstatSync(path).isSymbolicLink())
|
|
17571
|
+
throw new Error(`Символическая ссылка запрещена: ${path}`);
|
|
17572
|
+
}
|
|
17573
|
+
const at = options.get('at') ?? (verb === 'add' ? new Date().toISOString() : new Date().toISOString().slice(0, 10));
|
|
17574
|
+
let date = at;
|
|
17575
|
+
let time = '';
|
|
17576
|
+
if (verb === 'add') {
|
|
17577
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(at))
|
|
17578
|
+
throw new Error('--at ожидает ISO с часовым поясом');
|
|
17579
|
+
selectWindow([], at.slice(0, 10), false);
|
|
17580
|
+
const stamp = new Date(at).toISOString();
|
|
17581
|
+
date = stamp.slice(0, 10);
|
|
17582
|
+
time = stamp.slice(11, 16);
|
|
17583
|
+
}
|
|
17584
|
+
selectWindow([], date, false);
|
|
17585
|
+
if (verb === 'show') {
|
|
17586
|
+
const days = existsSync(dir) ? readdirSync(dir).filter(n => /^\d{4}-\d{2}-\d{2}\.md$/.test(n)).map(n => n.slice(0, 10)) : [];
|
|
17587
|
+
const rows = selectWindow(days, date, flags.has('week')).flatMap(day => {
|
|
17588
|
+
return readFileSync(join(dir, `${day}.md`), 'utf8').split('\n').filter(line => line.startsWith('- '))
|
|
17589
|
+
.map(raw => ({ day, ...parseLine(raw) }));
|
|
17590
|
+
}).filter(row => row.status === 'unparsed' || kindRaw === undefined || row.kind === kindRaw);
|
|
17591
|
+
if (flags.has('json'))
|
|
17592
|
+
write(JSON.stringify(rows));
|
|
17593
|
+
else
|
|
17594
|
+
for (const row of rows)
|
|
17595
|
+
write(row.status === 'unparsed' ? `[unparsed] ${row.raw}` : row.raw);
|
|
17596
|
+
return 0;
|
|
17597
|
+
}
|
|
17598
|
+
const text = options.get('_positional_1') ?? '';
|
|
17599
|
+
let ref = options.get('ref') ?? '';
|
|
17600
|
+
const event = { time, kind: kindRaw, text, ref };
|
|
17601
|
+
formatLine(event); // validate before any journal/quote write
|
|
17602
|
+
if (options.has('_positional_2'))
|
|
17603
|
+
throw new Error('Текст должен быть одним аргументом');
|
|
17604
|
+
if (flags.has('commit-quote') && !options.has('quote'))
|
|
17605
|
+
throw new Error('--commit-quote требует --quote');
|
|
17606
|
+
return withNamedLockSync(root, 'journal', () => {
|
|
17607
|
+
const path = join(dir, `${date}.md`);
|
|
17608
|
+
if (existsSync(path) && lstatSync(path).isSymbolicLink())
|
|
17609
|
+
throw new Error(`Символическая ссылка запрещена: ${path}`);
|
|
17610
|
+
const previous = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
17611
|
+
if (previous.split('\n').map(parseLine).some(row => row.status === 'parsed' && row.time === time && row.text === text.trim())) {
|
|
17612
|
+
write('уже записано');
|
|
17613
|
+
return 0;
|
|
17614
|
+
}
|
|
17615
|
+
const quote = options.get('quote');
|
|
17616
|
+
if (quote !== undefined) {
|
|
17617
|
+
const content = readFileSync(resolve(cwd, quote), 'utf8');
|
|
17618
|
+
const quotes = join(dir, 'quotes');
|
|
17619
|
+
const exclude = execFileSync('git', ['rev-parse', '--git-path', 'info/exclude'], { cwd: root, encoding: 'utf8' }).trim();
|
|
17620
|
+
const excludePath = resolve(root, exclude);
|
|
17621
|
+
const ignored = existsSync(excludePath) ? readFileSync(excludePath, 'utf8') : '';
|
|
17622
|
+
if (!ignored.split('\n').includes('/docs/journal/quotes/')) {
|
|
17623
|
+
mkdirSync(dirname(excludePath), { recursive: true });
|
|
17624
|
+
appendFileSync(excludePath, `${ignored.endsWith('\n') || !ignored ? '' : '\n'}/docs/journal/quotes/\n`);
|
|
17625
|
+
}
|
|
17626
|
+
mkdirSync(quotes, { recursive: true });
|
|
17627
|
+
const tracked = new Set(execFileSync('git', ['ls-files', '-z', '--', 'docs/journal/quotes/'], { cwd: root, encoding: 'utf8' }).split('\0'));
|
|
17628
|
+
let n = 1;
|
|
17629
|
+
while (existsSync(join(quotes, `${date}-${n}.md`)) || tracked.has(`docs/journal/quotes/${date}-${n}.md`))
|
|
17630
|
+
n++;
|
|
17631
|
+
const quoteRef = `docs/journal/quotes/${date}-${n}.md`;
|
|
17632
|
+
const quotePath = join(root, quoteRef);
|
|
17633
|
+
writeFileSync(quotePath, content, { flag: 'wx' });
|
|
17634
|
+
if (readFileSync(quotePath, 'utf8') !== content)
|
|
17635
|
+
throw new Error('Цитата не засвидетельствована');
|
|
17636
|
+
if (flags.has('commit-quote')) {
|
|
17637
|
+
execFileSync('git', ['add', '-f', '--', quoteRef], { cwd: root, stdio: 'pipe' });
|
|
17638
|
+
}
|
|
17639
|
+
ref = ref ? `${ref}; ${quoteRef}` : quoteRef;
|
|
17640
|
+
}
|
|
17641
|
+
mkdirSync(dir, { recursive: true });
|
|
17642
|
+
if (!existsSync(path))
|
|
17643
|
+
writeFileSync(path, `# Журнал действий — ${date}\n\n## Решения · вердикты · запуски и падения · ошибки · блокировки (UTC)\n\n`, { flag: 'wx' });
|
|
17644
|
+
else if (previous && !previous.endsWith('\n'))
|
|
17645
|
+
appendFileSync(path, '\n');
|
|
17646
|
+
const fs = injectedIo ?? { read: p => readFileSync(p, 'utf8'), append: (p, s) => appendFileSync(p, s) };
|
|
17647
|
+
const line = formatLine({ ...event, ref });
|
|
17648
|
+
appendWitnessed(fs, path, line);
|
|
17649
|
+
write(`записано и засвидетельствовано: ${line}`);
|
|
17650
|
+
return 0;
|
|
17651
|
+
});
|
|
17652
|
+
}
|
|
17653
|
+
catch (error) {
|
|
17654
|
+
write(`dz journal: ${error instanceof Error ? error.message : String(error)}`);
|
|
17655
|
+
return 1;
|
|
17656
|
+
}
|
|
17657
|
+
}
|
|
17192
17658
|
export async function runCli(argv, io = {}) {
|
|
17193
17659
|
const cwd = io.cwd ?? process.cwd();
|
|
17194
17660
|
const write = io.write ?? ((line) => { console.log(line); });
|
|
@@ -17439,8 +17905,16 @@ export async function runCli(argv, io = {}) {
|
|
|
17439
17905
|
return cmdBriefCheck(options, flags, cwd, write);
|
|
17440
17906
|
case 'provenance-check':
|
|
17441
17907
|
return cmdProvenanceCheck(options, flags, cwd, write);
|
|
17908
|
+
case 'journal':
|
|
17909
|
+
return cmdJournal(options, flags, cwd, write, io.journalIo);
|
|
17442
17910
|
case 'feature-adr-record':
|
|
17443
17911
|
return cmdFeatureAdrRecord(options, flags, cwd, write);
|
|
17912
|
+
case 'runs':
|
|
17913
|
+
return cmdRuns(options, flags, cwd, write);
|
|
17914
|
+
case 'runs-clean':
|
|
17915
|
+
return cmdRunsClean(options, flags, cwd, write);
|
|
17916
|
+
case 'runs-record':
|
|
17917
|
+
return cmdRunsRecord(options, flags, cwd, write);
|
|
17444
17918
|
case 'amendment-check':
|
|
17445
17919
|
return cmdAmendmentCheck(options, flags, cwd, write);
|
|
17446
17920
|
case 'contract-check':
|