@dzhechkov/harness-cli 0.8.20 → 0.8.22
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 +12 -12
- package/README.md +61 -21
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +306 -25
- package/dist/cli.js.map +1 -1
- package/dist/known-flags.d.ts.map +1 -1
- package/dist/known-flags.js +1 -0
- package/dist/known-flags.js.map +1 -1
- package/package.json +10 -9
- package/sbom.json +11 -11
- package/src/cli.ts +322 -27
- package/src/known-flags.ts +1 -0
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import { createSkill, getSkillInfo, listSkillsDetailed, formatSkillLoadFailures,
|
|
|
22
22
|
// dz workflow run (feature dz-workflow-run): the pure scheduler + the dispatch adapters.
|
|
23
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,
|
|
24
24
|
// guard-promotion (feature guard-promotion, scout idea #1)
|
|
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,
|
|
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, hashPackBytes, rewriteWorkspaceSpecs, 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,
|
|
26
26
|
// Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
|
|
27
27
|
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, scoreReceiptToAggregateRow, readScoreAggregateRows, dedupeScoreAggregateRows, buildScoreAggregateReport, renderScoreAggregateReport, 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,
|
|
28
28
|
// Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
|
|
@@ -104,7 +104,7 @@ Usage:
|
|
|
104
104
|
dz upgrade [--target <name>] [--pubkey <path>] [--require-signing] (a TAMPERED pack aborts the upgrade)
|
|
105
105
|
dz sign --pack <dir> --key <path-outside-repo> (Ed25519 manifest + CycloneDX SBOM for a pack)
|
|
106
106
|
dz verify-pack --pack <dir> [--pubkey <path>] (signature check; fail-closed; key from the repo, never the pack)
|
|
107
|
-
dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] [--require-signing] [--provenance|--no-provenance] (dry-run by default; pass --yes/--confirm/--no-dry-run to go live; claim-check gate default warn — surfaces README claim findings, never blocks; error fails an offending package)
|
|
107
|
+
dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] [--mirror-cmd <cmd>|--no-mirror] [--require-signing] [--provenance|--no-provenance] (dry-run by default; pass --yes/--confirm/--no-dry-run to go live; claim-check gate default warn — surfaces README claim findings, never blocks; error fails an offending package)
|
|
108
108
|
dz release [--filter <name>] [--tag] [--publish] [--json] [--dry-run] [--no-issue] (VERIFIED release: 4 HARD gates in FRONT of dz publish — full package test suites, audit >=high, node --check of every dist/bin file, bin smoke-boot via "node <bin> --help" — any red gate STOPS the release (exit 1) + best-effort gh issue; all green ⇒ re-sign reminder, then prints the ready dz publish command (or chains with --publish); never duplicates publish's own gates)
|
|
109
109
|
dz parity [--target <name>] [--json] (the honest feature×target map, COMPUTED from the capability model — which harness feature is full / manual / absent on each of the ${TARGET_NAMES.length} targets, and via which form)
|
|
110
110
|
dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] (portable Step-10 Delivery Gate: prints the 4-plane review brief + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked)
|
|
@@ -2331,6 +2331,9 @@ function cmdStatusline(options, flags, cwd, write, readStdin, writeErr) {
|
|
|
2331
2331
|
line += ` ⛔ STORE UNREADABLE${data.storeHealth.unreadableFiles !== undefined && data.storeHealth.unreadableFiles.length > 0
|
|
2332
2332
|
? `: ${data.storeHealth.unreadableFiles.join(', ')}` : ''}`;
|
|
2333
2333
|
}
|
|
2334
|
+
else if (data.storeHealth?.verdict === 'busy') {
|
|
2335
|
+
line += ' ⏳ STORE BUSY';
|
|
2336
|
+
}
|
|
2334
2337
|
else if (data.storeHealth?.verdict === 'source-changed') {
|
|
2335
2338
|
line += ' ⚠ store source changed';
|
|
2336
2339
|
}
|
|
@@ -2940,9 +2943,9 @@ function learningStoreLine(storeRoot, projectOption, target, verb) {
|
|
|
2940
2943
|
const reason = teachReasonPhrase(target.reason);
|
|
2941
2944
|
return storeLocationLine(describeStoreLocation(storeRoot, projectOption, target.store === 'global' ? 'global' : undefined), verb) + (reason ? ' [' + reason + ']' : '');
|
|
2942
2945
|
}
|
|
2943
|
-
function inspectLearningStore(projectRoot) {
|
|
2946
|
+
function inspectLearningStore(projectRoot, countOptions) {
|
|
2944
2947
|
const mark = readStoreMark(projectRoot);
|
|
2945
|
-
const rows = countLearningStoreRowsReadonly(projectRoot);
|
|
2948
|
+
const rows = countLearningStoreRowsReadonly(projectRoot, countOptions);
|
|
2946
2949
|
return { mark, rows, health: checkStoreHealth({ projectRoot, ...rows, mark }) };
|
|
2947
2950
|
}
|
|
2948
2951
|
function shellQuote(value) {
|
|
@@ -3014,7 +3017,8 @@ function refreshLearningStoreMark(projectRoot, writeErr, command, options = {})
|
|
|
3014
3017
|
const rows = countLearningStoreRowsReadonly(projectRoot);
|
|
3015
3018
|
const counts = observedRows(rows);
|
|
3016
3019
|
if (counts === undefined) {
|
|
3017
|
-
|
|
3020
|
+
const busy = rows.lexicalRows === 'busy' || rows.vectorRows === 'busy';
|
|
3021
|
+
writeErr(`⚠ dz store guard: ${options.reader ? 'reader observation' : 'store operation'} completed but the external mark was not updated — ${busy ? 'store busy; health not measured this run' : 'a store tier is unreadable'}`);
|
|
3018
3022
|
return;
|
|
3019
3023
|
}
|
|
3020
3024
|
writeStoreMark(projectRoot, {
|
|
@@ -3045,7 +3049,7 @@ function storeGuardResetReminder(projectRoot, writeErr, command) {
|
|
|
3045
3049
|
function allowLearningStoreWrite(projectRoot, flags, writeErr, command) {
|
|
3046
3050
|
let inspection;
|
|
3047
3051
|
try {
|
|
3048
|
-
inspection = inspectLearningStore(projectRoot);
|
|
3052
|
+
inspection = inspectLearningStore(projectRoot, { busyTimeoutMs: 250, attempts: 3 });
|
|
3049
3053
|
}
|
|
3050
3054
|
catch (error) {
|
|
3051
3055
|
const health = {
|
|
@@ -3063,6 +3067,10 @@ function allowLearningStoreWrite(projectRoot, flags, writeErr, command) {
|
|
|
3063
3067
|
// own row had landed.
|
|
3064
3068
|
return true;
|
|
3065
3069
|
}
|
|
3070
|
+
if (inspection.health.verdict === 'busy') {
|
|
3071
|
+
writeErr(`dz store guard: NOT MEASURED — ${inspection.health.reason}`);
|
|
3072
|
+
return true;
|
|
3073
|
+
}
|
|
3066
3074
|
if (inspection.health.verdict === 'source-changed') {
|
|
3067
3075
|
const counts = observedRows(inspection.rows);
|
|
3068
3076
|
if (counts === undefined)
|
|
@@ -3105,6 +3113,8 @@ function warnLearningStoreRead(projectRoot, writeErr, command) {
|
|
|
3105
3113
|
writeErr(line);
|
|
3106
3114
|
return;
|
|
3107
3115
|
}
|
|
3116
|
+
if (health.verdict === 'busy')
|
|
3117
|
+
return;
|
|
3108
3118
|
const counts = observedRows(rows);
|
|
3109
3119
|
if (counts !== undefined && counts.lexicalRows + counts.vectorRows > 0
|
|
3110
3120
|
&& (mark === undefined || mark.lexicalLast !== counts.lexicalRows || mark.vectorLast !== counts.vectorRows
|
|
@@ -6140,7 +6150,33 @@ function cmdSbom(options, flags, cwd, write) {
|
|
|
6140
6150
|
write(out);
|
|
6141
6151
|
return 0;
|
|
6142
6152
|
}
|
|
6143
|
-
function
|
|
6153
|
+
function mirrorCommandFromConfig(cwd) {
|
|
6154
|
+
const configPath = join(cwd, '.dz', 'config.json');
|
|
6155
|
+
if (!existsSync(configPath))
|
|
6156
|
+
return {};
|
|
6157
|
+
try {
|
|
6158
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
6159
|
+
const command = typeof config.publish?.mirrorCommand === 'string' ? config.publish.mirrorCommand.trim() : '';
|
|
6160
|
+
return command === '' ? {} : { command };
|
|
6161
|
+
}
|
|
6162
|
+
catch (error) {
|
|
6163
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6164
|
+
return { warning: `.dz/config.json unreadable — ${reason}` };
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
function mirrorShellToken(value) {
|
|
6168
|
+
if (value === '')
|
|
6169
|
+
return "''";
|
|
6170
|
+
if (!/^[A-Za-z0-9@/.,+_-]+$/.test(value))
|
|
6171
|
+
throw new Error('published package/version list is not shell-safe');
|
|
6172
|
+
return value;
|
|
6173
|
+
}
|
|
6174
|
+
function mirrorFailureMessage(error) {
|
|
6175
|
+
if (error instanceof Error && error.message.trim() !== '')
|
|
6176
|
+
return error.message.trim().split(/\r?\n/, 1)[0] ?? 'mirror command failed';
|
|
6177
|
+
return String(error);
|
|
6178
|
+
}
|
|
6179
|
+
function cmdPublish(options, flags, cwd, writeOutput, mirrorRunner) {
|
|
6144
6180
|
const json = flags.has('json');
|
|
6145
6181
|
// Under --json stdout carries exactly one JSON document, so every human line — guard notes, refusals,
|
|
6146
6182
|
// progress — goes to stderr instead of being dropped: a refusal that prints nothing is the silent
|
|
@@ -6148,9 +6184,9 @@ function cmdPublish(options, flags, cwd, writeOutput) {
|
|
|
6148
6184
|
const write = json ? (line) => { process.stderr.write(`${line}\n`); } : writeOutput;
|
|
6149
6185
|
// Reject unknown flags/options so a typo (e.g. `--dry-rum`) can NEVER be
|
|
6150
6186
|
// silently swallowed and flip the command into live-publish mode.
|
|
6151
|
-
const allowedFlags = new Set(['dry-run', 'no-dry-run', 'yes', 'confirm', 'bump-only', 'help', 'require-signing', 'provenance', 'no-provenance', 'json']);
|
|
6152
|
-
const allowedOptions = new Set(['filter', 'claim-check', 'no-guard', 'sign-key']);
|
|
6153
|
-
const allowedHelp = ' allowed: --dry-run (default), --yes/--confirm/--no-dry-run (go live), --bump-only, --filter <substr>, --claim-check <off|warn|error>, --no-guard "<reason>" (skip the guard pre-flight; logged)';
|
|
6187
|
+
const allowedFlags = new Set(['dry-run', 'no-dry-run', 'yes', 'confirm', 'bump-only', 'help', 'require-signing', 'provenance', 'no-provenance', 'json', 'no-mirror']);
|
|
6188
|
+
const allowedOptions = new Set(['filter', 'claim-check', 'no-guard', 'sign-key', 'mirror-cmd']);
|
|
6189
|
+
const allowedHelp = ' allowed: --dry-run (default), --yes/--confirm/--no-dry-run (go live), --bump-only, --filter <substr>, --claim-check <off|warn|error>, --mirror-cmd <cmd>, --no-mirror, --no-guard "<reason>" (skip the guard pre-flight; logged)';
|
|
6154
6190
|
for (const flag of flags) {
|
|
6155
6191
|
if (!allowedFlags.has(flag)) {
|
|
6156
6192
|
write(`dz publish: unknown option --${flag}`);
|
|
@@ -6332,7 +6368,7 @@ function cmdPublish(options, flags, cwd, writeOutput) {
|
|
|
6332
6368
|
// longer exist. Default to the same path `dz sign --init` writes, so the ordinary operator needs no
|
|
6333
6369
|
// new flag; `--sign-key` overrides it.
|
|
6334
6370
|
const signKey = (options.get('sign-key') ?? join(homedir(), '.dz', 'keys', 'dz.key')).trim();
|
|
6335
|
-
const
|
|
6371
|
+
const publishReport = publishPackages(cwd, {
|
|
6336
6372
|
provenance,
|
|
6337
6373
|
dryRun,
|
|
6338
6374
|
filter,
|
|
@@ -6414,9 +6450,82 @@ function cmdPublish(options, flags, cwd, writeOutput) {
|
|
|
6414
6450
|
}
|
|
6415
6451
|
},
|
|
6416
6452
|
});
|
|
6453
|
+
const configMirror = mirrorCommandFromConfig(cwd);
|
|
6454
|
+
const configuredCommand = (options.get('mirror-cmd') ?? configMirror.command ?? '').trim();
|
|
6455
|
+
const publishedVersions = publishReport.packages
|
|
6456
|
+
.filter((pkg) => pkg.status === 'published')
|
|
6457
|
+
.map((pkg) => `${pkg.name}@${pkg.newVersion}`);
|
|
6458
|
+
const expected = publishedVersions.join(',');
|
|
6459
|
+
const fullMirrorCommand = configuredCommand === ''
|
|
6460
|
+
? ''
|
|
6461
|
+
: `${configuredCommand} --expect ${mirrorShellToken(expected)} --json`;
|
|
6462
|
+
let mirror;
|
|
6463
|
+
// This exact conjunction is the Step-7/8 mutation anchor: an epilogue is eligible only after a
|
|
6464
|
+
// live sweep that actually landed at least one package. Other explicit skip states are handled
|
|
6465
|
+
// before command resolution so each reason remains distinguishable in text and JSON.
|
|
6466
|
+
const mirrorEligible = !dryRun && publishReport.published >= 1;
|
|
6467
|
+
if (bumpOnly) {
|
|
6468
|
+
mirror = { status: 'skipped', command: fullMirrorCommand, reason: 'bump-only' };
|
|
6469
|
+
}
|
|
6470
|
+
else if (!mirrorEligible) {
|
|
6471
|
+
mirror = {
|
|
6472
|
+
status: 'skipped',
|
|
6473
|
+
command: fullMirrorCommand,
|
|
6474
|
+
reason: dryRun ? 'dry-run' : 'published=0',
|
|
6475
|
+
};
|
|
6476
|
+
}
|
|
6477
|
+
else if (flags.has('no-mirror')) {
|
|
6478
|
+
mirror = { status: 'skipped', command: fullMirrorCommand, reason: '--no-mirror' };
|
|
6479
|
+
}
|
|
6480
|
+
else if (configMirror.warning !== undefined && options.get('mirror-cmd') === undefined) {
|
|
6481
|
+
mirror = { status: 'not-configured', command: '', warning: configMirror.warning };
|
|
6482
|
+
}
|
|
6483
|
+
else if (configuredCommand === '') {
|
|
6484
|
+
mirror = { status: 'not-configured', command: '' };
|
|
6485
|
+
}
|
|
6486
|
+
else {
|
|
6487
|
+
const runMirror = mirrorRunner
|
|
6488
|
+
?? ((command, runnerOptions) => execSync(command, {
|
|
6489
|
+
cwd: runnerOptions.cwd,
|
|
6490
|
+
env: runnerOptions.env,
|
|
6491
|
+
encoding: 'utf8',
|
|
6492
|
+
stdio: 'pipe',
|
|
6493
|
+
}));
|
|
6494
|
+
try {
|
|
6495
|
+
const stdout = runMirror(fullMirrorCommand, {
|
|
6496
|
+
cwd,
|
|
6497
|
+
env: { ...process.env, DZ_PUBLISHED: expected },
|
|
6498
|
+
});
|
|
6499
|
+
const parsed = JSON.parse(stdout);
|
|
6500
|
+
if (parsed.ok !== true)
|
|
6501
|
+
throw new Error(typeof parsed.error === 'string' ? parsed.error : 'mirror command returned ok:false');
|
|
6502
|
+
if (typeof parsed.commit !== 'string' || parsed.commit === '')
|
|
6503
|
+
throw new Error('mirror command returned no commit');
|
|
6504
|
+
if (typeof parsed.receipt?.manifestUrl !== 'string'
|
|
6505
|
+
|| typeof parsed.receipt.confirmedAt !== 'string'
|
|
6506
|
+
|| typeof parsed.receipt.waitedMs !== 'number') {
|
|
6507
|
+
throw new Error('mirror command returned no live-manifest receipt');
|
|
6508
|
+
}
|
|
6509
|
+
mirror = {
|
|
6510
|
+
status: 'confirmed',
|
|
6511
|
+
command: fullMirrorCommand,
|
|
6512
|
+
commit: parsed.commit,
|
|
6513
|
+
receipt: {
|
|
6514
|
+
manifestUrl: parsed.receipt.manifestUrl,
|
|
6515
|
+
confirmedAt: parsed.receipt.confirmedAt,
|
|
6516
|
+
waitedMs: parsed.receipt.waitedMs,
|
|
6517
|
+
},
|
|
6518
|
+
};
|
|
6519
|
+
}
|
|
6520
|
+
catch (error) {
|
|
6521
|
+
mirror = { status: 'unconfirmed', command: fullMirrorCommand, error: mirrorFailureMessage(error) };
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
const report = { ...publishReport, mirror };
|
|
6525
|
+
const exitCode = report.errors > 0 ? 1 : mirror.status === 'unconfirmed' ? 3 : 0;
|
|
6417
6526
|
if (json) {
|
|
6418
6527
|
writeOutput(JSON.stringify(report));
|
|
6419
|
-
return
|
|
6528
|
+
return exitCode;
|
|
6420
6529
|
}
|
|
6421
6530
|
write(`\ndz publish${dryRun ? ' --dry-run' : ''}${bumpOnly ? ' --bump-only' : ''}${claimCheckOpt !== 'warn' ? ` --claim-check ${claimCheckOpt}` : ''}`);
|
|
6422
6531
|
write(` Published: ${report.published} Skipped: ${report.skipped} Errors: ${report.errors}\n`);
|
|
@@ -6459,7 +6568,28 @@ function cmdPublish(options, flags, cwd, writeOutput) {
|
|
|
6459
6568
|
write(` · ${item}`);
|
|
6460
6569
|
}
|
|
6461
6570
|
}
|
|
6462
|
-
|
|
6571
|
+
for (const warning of report.warnings ?? [])
|
|
6572
|
+
write(` ⚠ warning: ${warning}`);
|
|
6573
|
+
for (const path of report.releaseLineSynced ?? [])
|
|
6574
|
+
write(` ↳ release line synced: ${path}`);
|
|
6575
|
+
if (mirror.status === 'confirmed') {
|
|
6576
|
+
write(` ✓ mirror: confirmed — ${mirror.commit} (${mirror.receipt?.manifestUrl})`);
|
|
6577
|
+
}
|
|
6578
|
+
else if (mirror.status === 'not-configured') {
|
|
6579
|
+
if (mirror.warning !== undefined)
|
|
6580
|
+
write(` ⚠ mirror: ${mirror.warning}`);
|
|
6581
|
+
else
|
|
6582
|
+
write(' ℹ mirror: not configured');
|
|
6583
|
+
}
|
|
6584
|
+
else if (mirror.status === 'skipped') {
|
|
6585
|
+
write(` ℹ mirror: skipped (${mirror.reason})`);
|
|
6586
|
+
}
|
|
6587
|
+
else {
|
|
6588
|
+
write(` ✗ mirror: unconfirmed — ${mirror.error}`);
|
|
6589
|
+
const rerun = mirror.command || 'configure publish.mirrorCommand, then run it';
|
|
6590
|
+
write(`dz publish: published, mirror NOT confirmed — ${mirror.error}; re-run: ${rerun}`);
|
|
6591
|
+
}
|
|
6592
|
+
return exitCode;
|
|
6463
6593
|
}
|
|
6464
6594
|
/* ------------------------------------------------------------------ */
|
|
6465
6595
|
/* dz parity — the honest feature×target map (target-parity-matrix, */
|
|
@@ -8936,9 +9066,10 @@ function gatherGuardFacts(op, root, text, storeCap, publishFilter) {
|
|
|
8936
9066
|
const out = volumeGitText(root, ['ls-files', 'packages/@dzhechkov/*/package.json']);
|
|
8937
9067
|
for (const rel of out.split('\n').map((s) => s.trim()).filter(Boolean)) {
|
|
8938
9068
|
try {
|
|
8939
|
-
const
|
|
9069
|
+
const packageJsonText = readFileSync(join(root, rel), 'utf8');
|
|
9070
|
+
const m = JSON.parse(packageJsonText);
|
|
8940
9071
|
manifests.push(m);
|
|
8941
|
-
located.push({ dir: rel.replace(/\/package\.json$/, ''), m });
|
|
9072
|
+
located.push({ dir: rel.replace(/\/package\.json$/, ''), m, text: packageJsonText });
|
|
8942
9073
|
}
|
|
8943
9074
|
catch { /* skip unreadable */ }
|
|
8944
9075
|
}
|
|
@@ -8948,6 +9079,45 @@ function gatherGuardFacts(op, root, text, storeCap, publishFilter) {
|
|
|
8948
9079
|
for (const m of manifests)
|
|
8949
9080
|
if (m.name && typeof m.version === 'string')
|
|
8950
9081
|
versionByName.set(m.name, m.version);
|
|
9082
|
+
// release-line-in-sync: filesystem access belongs in the CLI gatherer. The core evaluator receives
|
|
9083
|
+
// only immutable evidence, so evaluateGuard(facts) stays deterministic and independent of cwd.
|
|
9084
|
+
const coreManifestPath = join(root, 'packages', '@dzhechkov', 'harness-core', 'package.json');
|
|
9085
|
+
const cliManifestPath = join(root, 'packages', '@dzhechkov', 'harness-cli', 'package.json');
|
|
9086
|
+
if (existsSync(coreManifestPath) || existsSync(cliManifestPath)) {
|
|
9087
|
+
const readText = (path) => {
|
|
9088
|
+
try {
|
|
9089
|
+
return readFileSync(path, 'utf8');
|
|
9090
|
+
}
|
|
9091
|
+
catch {
|
|
9092
|
+
return null;
|
|
9093
|
+
}
|
|
9094
|
+
};
|
|
9095
|
+
const readVersion = (path) => {
|
|
9096
|
+
const raw = readText(path);
|
|
9097
|
+
if (raw === null)
|
|
9098
|
+
return null;
|
|
9099
|
+
try {
|
|
9100
|
+
const parsed = JSON.parse(raw);
|
|
9101
|
+
return typeof parsed.version === 'string' && /^\d+\.\d+\.\d+$/.test(parsed.version)
|
|
9102
|
+
? parsed.version
|
|
9103
|
+
: null;
|
|
9104
|
+
}
|
|
9105
|
+
catch {
|
|
9106
|
+
return null;
|
|
9107
|
+
}
|
|
9108
|
+
};
|
|
9109
|
+
facts['releaseLines'] = {
|
|
9110
|
+
coreVersion: readVersion(coreManifestPath),
|
|
9111
|
+
cliVersion: readVersion(cliManifestPath),
|
|
9112
|
+
readmes: [
|
|
9113
|
+
{ path: 'README.md', text: readText(join(root, 'README.md')) },
|
|
9114
|
+
{
|
|
9115
|
+
path: 'packages/@dzhechkov/harness-cli/README.md',
|
|
9116
|
+
text: readText(join(root, 'packages', '@dzhechkov', 'harness-cli', 'README.md')),
|
|
9117
|
+
},
|
|
9118
|
+
],
|
|
9119
|
+
};
|
|
9120
|
+
}
|
|
8951
9121
|
publishPackageRoots.push(...located
|
|
8952
9122
|
.filter(({ dir, m }) => m.private !== true && (publishFilter === undefined
|
|
8953
9123
|
|| publishFilter.length === 0
|
|
@@ -9142,25 +9312,136 @@ function gatherGuardFacts(op, root, text, storeCap, publishFilter) {
|
|
|
9142
9312
|
}
|
|
9143
9313
|
catch { /* нечитаемо — правило молчит */ }
|
|
9144
9314
|
facts['counts'] = gatherReadmeCounts(root);
|
|
9145
|
-
//
|
|
9146
|
-
//
|
|
9315
|
+
// One package-scoped WORKING-TREE diff feeds both readme-first and signature-fresh. A second
|
|
9316
|
+
// porcelain call could observe a different tree and let the two publish guards disagree.
|
|
9317
|
+
let changedPackageFiles;
|
|
9318
|
+
// readme-first: from the WORKING-TREE diff (publishes happen pre-commit here), per package: did
|
|
9319
|
+
// the version FIELD change from HEAD without its README.md changing? A package.json edit alone
|
|
9320
|
+
// says nothing about version; repository/metadata-only edits must not manufacture a bump.
|
|
9147
9321
|
try {
|
|
9148
|
-
const status = execSync('git status --porcelain -- "packages/@dzhechkov/"', { cwd: root, encoding: 'utf-8' });
|
|
9322
|
+
const status = execSync('git status --porcelain -uall -- "packages/@dzhechkov/"', { cwd: root, encoding: 'utf-8' });
|
|
9323
|
+
changedPackageFiles = status
|
|
9324
|
+
.split('\n')
|
|
9325
|
+
.map((line) => line.slice(3).trim())
|
|
9326
|
+
.map((path) => (path.includes(' -> ') ? path.split(' -> ')[1].trim() : path))
|
|
9327
|
+
.filter(Boolean);
|
|
9149
9328
|
const perPack = new Map();
|
|
9150
|
-
for (const
|
|
9151
|
-
const m =
|
|
9329
|
+
for (const rel of changedPackageFiles) {
|
|
9330
|
+
const m = rel.match(/^packages\/@dzhechkov\/([^/]+)\/(.+)$/);
|
|
9152
9331
|
if (!m || !m[1] || !m[2])
|
|
9153
9332
|
continue;
|
|
9154
|
-
const e = perPack.get(m[1]) ?? {
|
|
9155
|
-
if (m[2] === 'package.json')
|
|
9156
|
-
e.pkgJson = true;
|
|
9333
|
+
const e = perPack.get(m[1]) ?? { readme: false };
|
|
9157
9334
|
if (m[2] === 'README.md')
|
|
9158
9335
|
e.readme = true;
|
|
9159
9336
|
perPack.set(m[1], e);
|
|
9160
9337
|
}
|
|
9161
|
-
|
|
9338
|
+
const readVersion = (raw) => {
|
|
9339
|
+
const version = JSON.parse(raw).version;
|
|
9340
|
+
if (typeof version !== 'string')
|
|
9341
|
+
throw new Error('package version is unreadable');
|
|
9342
|
+
return version;
|
|
9343
|
+
};
|
|
9344
|
+
facts['readmeFirst'] = [...perPack.entries()].map(([name, e]) => {
|
|
9345
|
+
const packagePath = `packages/@dzhechkov/${name}/package.json`;
|
|
9346
|
+
let versionBumped = false;
|
|
9347
|
+
let versionUnknown = false;
|
|
9348
|
+
try {
|
|
9349
|
+
const treeVersion = readVersion(readFileSync(join(root, packagePath), 'utf8'));
|
|
9350
|
+
try {
|
|
9351
|
+
const headVersion = readVersion(execFileSync('git', ['show', `HEAD:${packagePath}`], {
|
|
9352
|
+
cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
9353
|
+
}));
|
|
9354
|
+
versionBumped = treeVersion !== headVersion;
|
|
9355
|
+
}
|
|
9356
|
+
catch {
|
|
9357
|
+
try {
|
|
9358
|
+
const trackedAtHead = execFileSync('git', ['ls-tree', '--name-only', 'HEAD', '--', packagePath], {
|
|
9359
|
+
cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
9360
|
+
}).trim() === packagePath;
|
|
9361
|
+
if (trackedAtHead)
|
|
9362
|
+
versionUnknown = true;
|
|
9363
|
+
else
|
|
9364
|
+
versionBumped = true;
|
|
9365
|
+
}
|
|
9366
|
+
catch {
|
|
9367
|
+
versionUnknown = true;
|
|
9368
|
+
}
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
9371
|
+
catch {
|
|
9372
|
+
versionUnknown = true;
|
|
9373
|
+
}
|
|
9374
|
+
return {
|
|
9375
|
+
name: '@dzhechkov/' + name,
|
|
9376
|
+
versionBumped,
|
|
9377
|
+
readmeChanged: e.readme,
|
|
9378
|
+
...(versionUnknown ? { versionUnknown: true } : {}),
|
|
9379
|
+
};
|
|
9380
|
+
});
|
|
9162
9381
|
}
|
|
9163
9382
|
catch { /* not a git repo — rule skips */ }
|
|
9383
|
+
// signature-fresh: verify only signed packs whose non-signature files changed in the same
|
|
9384
|
+
// porcelain listing above. Manifest/SBOM churn is the remedy, not another reason to verify.
|
|
9385
|
+
// The trust key and every byte read here stay in the CLI; the guard core receives facts only.
|
|
9386
|
+
const trustRootPath = join(root, TRUST_ROOT_REL);
|
|
9387
|
+
if (changedPackageFiles !== undefined && existsSync(trustRootPath)) {
|
|
9388
|
+
try {
|
|
9389
|
+
const trustRootPem = readFileSync(trustRootPath, 'utf8');
|
|
9390
|
+
const signedPacks = [];
|
|
9391
|
+
for (const { dir, m, text: packageJsonText } of located) {
|
|
9392
|
+
const manifestPath = join(root, dir, MANIFEST_NAME);
|
|
9393
|
+
if (!existsSync(manifestPath))
|
|
9394
|
+
continue;
|
|
9395
|
+
const prefix = dir + '/';
|
|
9396
|
+
const changedPackFiles = new Set(changedPackageFiles
|
|
9397
|
+
.filter((rel) => rel.startsWith(prefix))
|
|
9398
|
+
.map((rel) => rel.slice(prefix.length))
|
|
9399
|
+
.filter((packRel) => packRel !== MANIFEST_NAME && packRel !== SBOM_NAME));
|
|
9400
|
+
const changed = changedPackFiles.size > 0;
|
|
9401
|
+
let ok = null;
|
|
9402
|
+
let failures = [];
|
|
9403
|
+
let note;
|
|
9404
|
+
if (changed) {
|
|
9405
|
+
try {
|
|
9406
|
+
const signed = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
9407
|
+
const verified = verifyManifest(join(root, dir), signed, trustRootPem);
|
|
9408
|
+
// A source checkout contains dev files outside package.json#files. They are absent
|
|
9409
|
+
// from the tarball by design, so an unchanged "present but not signed" source file
|
|
9410
|
+
// is not evidence about signature freshness. A changed/new unsigned file stays loud.
|
|
9411
|
+
const relevantFailures = verified.failures.filter((failure) => failure.reason !== 'present in the pack but not signed' || changedPackFiles.has(failure.path));
|
|
9412
|
+
ok = verified.ok || relevantFailures.length === 0;
|
|
9413
|
+
if (!ok && relevantFailures.length > 0 && relevantFailures.every(({ path }) => path === 'package.json')) {
|
|
9414
|
+
const signedPackageHash = signed.manifest?.files?.find(({ path }) => path === 'package.json')?.sha256;
|
|
9415
|
+
if (typeof signedPackageHash === 'string') {
|
|
9416
|
+
const rewritten = rewriteWorkspaceSpecs(packageJsonText, versionByName);
|
|
9417
|
+
// MEASURED 2026-09-10 on keysarium/skills-meta/core: pnpm also removes only
|
|
9418
|
+
// prepublishOnly from the packed scripts table. Keep that I/O-adapter concern
|
|
9419
|
+
// outside the workspace-only core helper.
|
|
9420
|
+
const packedShape = JSON.parse(rewritten);
|
|
9421
|
+
const scripts = packedShape['scripts'];
|
|
9422
|
+
if (scripts !== null && typeof scripts === 'object' && !Array.isArray(scripts)) {
|
|
9423
|
+
delete scripts['prepublishOnly'];
|
|
9424
|
+
}
|
|
9425
|
+
const rewrittenHash = hashPackBytes('package.json', Buffer.from(JSON.stringify(packedShape, null, 2) + '\n'), signed.manifest?.version);
|
|
9426
|
+
if (rewrittenHash === signedPackageHash) {
|
|
9427
|
+
ok = true;
|
|
9428
|
+
note = 'package.json matches after workspace rewrite';
|
|
9429
|
+
}
|
|
9430
|
+
}
|
|
9431
|
+
}
|
|
9432
|
+
failures = relevantFailures.slice(0, 3).map((failure) => `${failure.path}: ${failure.reason}`);
|
|
9433
|
+
}
|
|
9434
|
+
catch (error) {
|
|
9435
|
+
ok = false;
|
|
9436
|
+
failures = [`${MANIFEST_NAME}: ${error instanceof Error ? error.message : String(error)}`];
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
signedPacks.push({ name: m.name ?? dir, dir, changed, ok, failures, ...(note !== undefined ? { note } : {}) });
|
|
9440
|
+
}
|
|
9441
|
+
facts['signedPacks'] = signedPacks;
|
|
9442
|
+
}
|
|
9443
|
+
catch { /* unreadable trust root — absence of evidence, so the rule is not established */ }
|
|
9444
|
+
}
|
|
9164
9445
|
// review-round: the same WORKING-TREE diff, asked a different question — does a package that
|
|
9165
9446
|
// bumps its version and changes SOURCE bring a GRADED QE report with it? Scoped to source so a
|
|
9166
9447
|
// docs-only republish is never blocked (ADR-001, features/publish-needs-a-review). A throw here
|
|
@@ -17835,7 +18116,7 @@ export async function runCli(argv, io = {}) {
|
|
|
17835
18116
|
case 'auto-canonicalize':
|
|
17836
18117
|
return await cmdAutoCanonicalize(options, cwd, write);
|
|
17837
18118
|
case 'publish':
|
|
17838
|
-
return cmdPublish(options, flags, cwd, write);
|
|
18119
|
+
return cmdPublish(options, flags, cwd, write, io.publishMirrorRunner);
|
|
17839
18120
|
case 'release':
|
|
17840
18121
|
return cmdRelease(options, flags, cwd, write, io.releaseRunner);
|
|
17841
18122
|
case 'parity':
|