@dzhechkov/harness-cli 0.8.6 → 0.8.9
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 +25 -25
- package/README.md +106 -9
- package/dist/boolean-flags.d.ts.map +1 -1
- package/dist/boolean-flags.js +1 -0
- package/dist/boolean-flags.js.map +1 -1
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1062 -98
- package/dist/cli.js.map +1 -1
- package/dist/core-compat.d.ts +1 -1
- package/dist/core-compat.js +4 -4
- package/dist/core-compat.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/known-flags.d.ts.map +1 -1
- package/dist/known-flags.js +10 -0
- package/dist/known-flags.js.map +1 -1
- package/package.json +8 -8
- package/sbom.json +24 -24
- package/src/boolean-flags.ts +1 -0
- package/src/cli.ts +1113 -100
- package/src/core-compat.ts +4 -4
- package/src/index.ts +1 -1
- package/src/known-flags.ts +10 -0
package/dist/cli.js
CHANGED
|
@@ -15,11 +15,11 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
15
15
|
import { homedir, tmpdir } from 'node:os';
|
|
16
16
|
import { createRequire } from 'node:module';
|
|
17
17
|
import { isDeepStrictEqual } from 'node:util';
|
|
18
|
-
import { createSkill, getSkillInfo, listSkillsDetailed, formatSkillLoadFailures, formatSkillApplyFailures, resolveTargetName, formatTargetProblem, formatTargetAliasNote, TARGET_NAMES_SORTED, runDoctor, runInit, resolvePackageSkillRoots, PACKAGE_SKILL_LAYOUTS, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, discoverVerifiablePackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, runSyncAgentsPolicy, runSyncCodexHooks, resolveCodexHome, withNamedLockSync,
|
|
18
|
+
import { createSkill, getSkillInfo, listSkillsDetailed, formatSkillLoadFailures, formatSkillApplyFailures, resolveTargetName, formatTargetProblem, formatTargetAliasNote, TARGET_NAMES_SORTED, runDoctor, runInit, discoverSkillIds, 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,
|
|
19
19
|
// dz workflow run (feature dz-workflow-run): the pure scheduler + the dispatch adapters.
|
|
20
|
-
TRACE_RUNID_RE, WF_RUN_OWNER_HOST, preflight, runWorkflow, makeClaudePDispatcher, makeCodexExecDispatcher, NamedLockTimeoutError, NamedLockCompromisedError, POLICY_SOURCES, detectPolicyDrift, hasPolicyFence, TARGET_NAMES, buildParityMatrix, downgradeForStaleEvidence, findStaleTranscriptEvidence, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_TEMPLATES_RETIRED_MESSAGE, parsePlan, isParseErrors, validatePlan, normalizePlan, planDigest, toTraceProjection, renderPlan, mergeRender, lint, lintExitCode, LOOP_BLOBS, parseTrace, assembleTimeline, runInvariants, deriveAttestation, stampAttestation, corroborate, NOT_WITNESSED, renderTimelineHtml, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveCostLedger, planLedgerBackfill, listCostLedgerRuns, resolveLedgerRunId, AMBIGUOUS, stampCheckpointLine, LEDGER_FILL_SOURCE, renderCostLedger, verifyCostLedgerReport, writeCostLedgerJsonl, COST_LEDGER_SCOPE, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, parseWeeklyResetAnchor, claimCheck, summarize, BUNDLED_SLOP_REGISTRY_URL, DEFAULT_SLOP_CONFIG, parseSlopRegistry, slopLint, validateSlopLintConfig, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, bookKbPath, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
|
|
20
|
+
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, statuslineData, writeFeatureAdrState, 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, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, bookKbPath, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
|
|
21
21
|
// guard-promotion (feature guard-promotion, scout idea #1)
|
|
22
|
-
assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, classifyExecutionEvidence, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, harvestStageOutcomes, recommendModels, planFeed, unfedRuns, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, declaredPluginSurface, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport, buildDeadwoodReport, compactCmdUsageIfNeeded, measureCmdUsageDepthDays, recordCommandInvocation, resolveCmdUsageRoot, renderDeadwoodReport, CMD_USAGE_LOG_RELATIVE, banditStats, narrowBanditReport, renderBanditHealth,
|
|
22
|
+
assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, recordPromotionRunEvidence, isLessonRuleContentAnchor, isOffsetIsoTimestamp, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, classifyExecutionEvidence, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, harvestStageOutcomes, recommendModels, planFeed, unfedRuns, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, declaredPluginSurface, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport, buildDeadwoodReport, compactCmdUsageIfNeeded, measureCmdUsageDepthDays, recordCommandInvocation, resolveCmdUsageRoot, renderDeadwoodReport, CMD_USAGE_LOG_RELATIVE, banditStats, narrowBanditReport, renderBanditHealth,
|
|
23
23
|
// Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
|
|
24
24
|
replayableInstances, buildWorkOrder, buildJudgePrompts, unblindJudgments, verifyWorkOrder, isValidMargin, DIGEST_HONEST_SCOPE, scoreEpochReplay, generateMockOutcomes, renderEpochReplayResult, renderWorkOrderSummary, renderJudgePromptsSummary, WORK_ORDER_KIND, DEFAULT_MOCK_N, DEFAULT_MOCK_SEED, scoreRun, readQeGrade, recapWindow, decideHorizon, withinWindow, buildRecap, renderRecap, parseSourceManifest, tgPostHtmlIssues, tgVisibleLength, decideTgSend, TG_TEXT_LIMIT, countRecallEventsForRun, unknownFlagNotice, mirrorWriterExplanation, appendRecallUsage, closenessLine, anyAboveFloor, decideNameCheck, renderNameCheck, exportedNamesIn, dispatchedCommandsIn, decideSourceProvenance, renderSourceProvenance, REFUSED_HORIZONS, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
|
|
25
25
|
// Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
|
|
@@ -40,7 +40,7 @@ export const DZ_COMMANDS = [
|
|
|
40
40
|
'usage', 'claim-check', 'lint', 'sign', 'sbom', 'guard', 'verify-pack', 'setup',
|
|
41
41
|
'pretrain', 'compose', 'diff', 'recommend', 'upgrade', 'auto-canonicalize',
|
|
42
42
|
'publish', 'release', 'parity', 'registry', 'benchmark', 'mcp-scan',
|
|
43
|
-
'sync-upstream', 'drift-check', 'hooks-sync', 'agents-sync', 'sync-canonical',
|
|
43
|
+
'sync-upstream', 'drift-check', 'hooks-sync', 'integrations-verify', 'agents-sync', 'sync-canonical',
|
|
44
44
|
'plugin', 'downloads', 'stats', 'architecture', 'project-skills', 'mr-rakes',
|
|
45
45
|
'retro', 'feature-adr-setup', 'challenge', 'discrimination-check',
|
|
46
46
|
'mutation-gate', 'delivery-check', 'skills-verify', 'compounding', 'deadwood',
|
|
@@ -52,7 +52,7 @@ export const DZ_COMMANDS = [
|
|
|
52
52
|
const USAGE = `dz - DZ cross-platform harness CLI
|
|
53
53
|
|
|
54
54
|
Usage:
|
|
55
|
-
dz init --target <name> [--skills-dir <dir>] [--project <dir>] [--preset <name>] [--select id,id,...] [--force] [--enrich] [--no-hooks] [--no-verify] (
|
|
55
|
+
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)
|
|
56
56
|
dz verify [--skills-dir <dir>] [--target <name>]
|
|
57
57
|
dz sync [--canonical <dir>] [--project <dir>] [--dry-run] [--force]
|
|
58
58
|
dz update (alias of sync)
|
|
@@ -81,7 +81,7 @@ Usage:
|
|
|
81
81
|
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)
|
|
82
82
|
dz challenge --plan <plan.md> [--author <model>] (the deterministic cartridge behind the challenge-panel adversarial plan-gate (R6): assembles the wide brief — plan + architecture/vision.md + testing.md + map.json + degradations.md — and prints the C1-C8 adversary prompt naming the cross-family reviewer to dispatch. exit 0 brief printed / 1 plan missing or empty)
|
|
83
83
|
dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--json] (does .claude/skills/ actually REGISTER? --static = instant layout scan for CI; default reads the authoritative system/init listing from a real session. exit 0 pass / 1 fail / 2 inconclusive — never a false pass)
|
|
84
|
-
dz compounding [--project <dir>] [--json] (honest learning-loop payoff report: pool
|
|
84
|
+
dz compounding [--project <dir>] [--json] (honest learning-loop payoff report: pool/replay/guard instrumentation + monthly eligible→attempted→accepted→executions; unavailable is NOT MEASURED, and only a named empty stage after a non-empty predecessor for 3 measured months is a funnel finding)
|
|
85
85
|
dz deadwood [--weeks <n>] [--json] (advisory zero-usage candidates for human deprecation review; safety-excluded surfaces carry reasons; never deletes or deprecates anything; shallow history says INSUFFICIENT_DATA)
|
|
86
86
|
dz epoch-replay --mock [--n <N>] [--effect <-1..1>] [--tie-rate <0..1>] [--seed <N>] [--slice <name>] [--json] ($0 synthetic run — exercises the verdict math, NOT evidence)
|
|
87
87
|
dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--out <file>] (cold-vs-warm work order: instances + PRE-REGISTERED blind A/B assignment; the runner never calls a model)
|
|
@@ -122,7 +122,7 @@ Usage:
|
|
|
122
122
|
dz backlog jira <id> [--project <dir>] [--json] (draft a Jira issue via the configurable adapter (backlog.jira.adapter: jira-mcp|copilot-mcp|none); none writes an auditable jira-outbox/<id>.json stub)
|
|
123
123
|
dz backlog harmonize [--apply] [--threshold <0-1>] [--project <dir>] [--json] (batch semantic dedup of the backlog ideas; --dry-run default, --apply snapshots first)
|
|
124
124
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--no-verify] [--install-driver] [--force] [--enrich] (--target codex ALSO installs + LIVE-verifies the codex hooks; an unverified hook exits non-zero WITHOUT aborting the rest of setup)
|
|
125
|
-
dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] (--project pins the learned store to <dir>/.dz
|
|
125
|
+
dz teach "<pattern>" [--class-form "<template with :slot>"] [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] (class form is optional; rejection never blocks the specific write; --project pins the learned store to <dir>/.dz)
|
|
126
126
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
127
127
|
dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] [--prune-quarantine [--apply]] (both prunes: DRY-RUN by default; --apply snapshots then deletes; prune-quarantine = expired unproven lessons ONLY, never coupled to noise)
|
|
128
128
|
dz recall "<query>" [--limit <N>] [--domain <name>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] | dz recall --promote <dzId>[,<dzId>] [--apply] (--domain <name> BOOSTS lessons of that domain without dropping foreign ones — a shared store keeps its cross-domain transfers; forget/promote: dry-run default; forget snapshots before removing; promote lifts lesson-quarantine)
|
|
@@ -161,6 +161,7 @@ Usage:
|
|
|
161
161
|
dz drift-check [--json] [--project <dir>] (CI gate: exit 1 if any shared skill drifted between its monorepo copies)
|
|
162
162
|
dz agents-sync [--project <dir>] [--check] [--json] (sync/verify the always-on policy fence in root AGENTS.md; exit 0 synced/written, 1 drift, 3 inconclusive)
|
|
163
163
|
dz hooks-sync --target codex [--check] [--verify] [--remove] [--json] (install/verify the dz veto + recall hooks in $CODEX_HOME/hooks.json; exit 0 armed+trusted, 1 not armed/drift, 3 inconclusive)
|
|
164
|
+
dz integrations-verify --target <name> --component <mcp|hooks> [--project <dir>] [--json] (non-executing exact-version registration probe; exit 0 observed / 1 refused)
|
|
164
165
|
dz sync-canonical <skill> [--check] [--from <dir>] [--auto] [--project <dir>] (heal every copy from skills-meta/<skill> or --from; no canonical + --check = compare copies to each other (exit 1 on drift); no canonical + write = refuse unless --auto (LOUD, picks most-complete copy); --check writes nothing)
|
|
165
166
|
dz plugin [--version <ver>]
|
|
166
167
|
dz downloads
|
|
@@ -272,8 +273,8 @@ async function installSkills(opts) {
|
|
|
272
273
|
// route them through one aggregation.
|
|
273
274
|
if (target === 'agents-md' || target === 'gemini') {
|
|
274
275
|
const report = target === 'gemini'
|
|
275
|
-
? runInitGeminiMd({ skillsDirs, projectRoot, ...(select !== undefined ? { select } : {}) })
|
|
276
|
-
: runInitAgentsMd({ skillsDirs, projectRoot, ...(select !== undefined ? { select } : {}) });
|
|
276
|
+
? runInitGeminiMd({ skillsDirs, projectRoot, ...(select !== undefined ? { select } : {}), ...(opts.noHooks !== undefined ? { noHooks: opts.noHooks } : {}), ...(opts.noIntegrations !== undefined ? { noIntegrations: opts.noIntegrations } : {}) })
|
|
277
|
+
: runInitAgentsMd({ skillsDirs, projectRoot, ...(select !== undefined ? { select } : {}), ...(opts.noHooks !== undefined ? { noHooks: opts.noHooks } : {}), ...(opts.noIntegrations !== undefined ? { noIntegrations: opts.noIntegrations } : {}) });
|
|
277
278
|
const results = report.skills.map((s) => ({
|
|
278
279
|
id: s.id,
|
|
279
280
|
written: s.written.length,
|
|
@@ -285,12 +286,20 @@ async function installSkills(opts) {
|
|
|
285
286
|
written += s.written;
|
|
286
287
|
skipped += s.skipped;
|
|
287
288
|
}
|
|
288
|
-
return { results, dirsSearched: skillsDirs.length, written, skipped, missing: [...report.missing], failures: [...report.failures], applyFailures: [...report.applyFailures] };
|
|
289
|
+
return { results, dirsSearched: skillsDirs.length, written, skipped, missing: [...report.missing], failures: [...report.failures], applyFailures: [...report.applyFailures], integrations: [...report.integrations], ...(report.integrationDigest !== undefined ? { integrationDigest: report.integrationDigest } : {}) };
|
|
289
290
|
}
|
|
290
291
|
const results = [];
|
|
291
292
|
const failures = [];
|
|
292
293
|
const applyFailures = [];
|
|
293
|
-
|
|
294
|
+
let integrations = [];
|
|
295
|
+
let integrationDigest;
|
|
296
|
+
const integrationManifestSources = skillsDirs.flatMap((skillsDir) => {
|
|
297
|
+
const discovered = discoverSkillIds(skillsDir);
|
|
298
|
+
return discovered
|
|
299
|
+
.filter((id) => select === undefined || select.includes(id))
|
|
300
|
+
.map((skillId) => ({ skillId, skillDir: skillsDir }));
|
|
301
|
+
});
|
|
302
|
+
for (const [dirIndex, skillsDir] of skillsDirs.entries()) {
|
|
294
303
|
const r = await runInit({
|
|
295
304
|
target,
|
|
296
305
|
skillsDir,
|
|
@@ -298,12 +307,27 @@ async function installSkills(opts) {
|
|
|
298
307
|
force,
|
|
299
308
|
enrich,
|
|
300
309
|
...(select !== undefined ? { select } : {}),
|
|
310
|
+
...(opts.noHooks !== undefined ? { noHooks: opts.noHooks } : {}),
|
|
311
|
+
...(dirIndex === 0
|
|
312
|
+
? {
|
|
313
|
+
integrationManifestSources,
|
|
314
|
+
...(opts.noIntegrations !== undefined ? { noIntegrations: opts.noIntegrations } : {}),
|
|
315
|
+
}
|
|
316
|
+
: { noIntegrations: true }),
|
|
317
|
+
...(opts.noVerify !== undefined ? { noVerify: opts.noVerify } : {}),
|
|
318
|
+
...(opts.allowIntegrations !== undefined ? { allowIntegrations: opts.allowIntegrations } : {}),
|
|
301
319
|
});
|
|
302
320
|
for (const skill of r.skills) {
|
|
303
321
|
results.push({ id: skill.id, written: skill.written.length, skipped: skill.skipped.length });
|
|
304
322
|
}
|
|
305
323
|
failures.push(...r.failures);
|
|
306
324
|
applyFailures.push(...r.applyFailures);
|
|
325
|
+
if (r.integrations.some((row) => row.status !== 'not-requested'))
|
|
326
|
+
integrations = [...r.integrations];
|
|
327
|
+
else if (integrations.length === 0)
|
|
328
|
+
integrations = [...r.integrations];
|
|
329
|
+
if (r.integrationDigest !== undefined)
|
|
330
|
+
integrationDigest = r.integrationDigest;
|
|
307
331
|
}
|
|
308
332
|
let written = 0;
|
|
309
333
|
let skipped = 0;
|
|
@@ -313,7 +337,7 @@ async function installSkills(opts) {
|
|
|
313
337
|
}
|
|
314
338
|
const installed = new Set(results.map((s) => s.id));
|
|
315
339
|
const missing = select !== undefined ? [...select].filter((id) => !installed.has(id)) : [];
|
|
316
|
-
return { results, dirsSearched: skillsDirs.length, written, skipped, missing, failures, applyFailures };
|
|
340
|
+
return { results, dirsSearched: skillsDirs.length, written, skipped, missing, failures, applyFailures, integrations, ...(integrationDigest !== undefined ? { integrationDigest } : {}) };
|
|
317
341
|
}
|
|
318
342
|
/** Warn about preset/select ids that weren't found in any installed pack. */
|
|
319
343
|
function writeMissingSkillsHint(write, missing, presetName) {
|
|
@@ -376,17 +400,15 @@ async function cmdInit(options, flags, cwd, write, writeErr) {
|
|
|
376
400
|
select,
|
|
377
401
|
force: flags.has('force'),
|
|
378
402
|
enrich: flags.has('enrich'),
|
|
403
|
+
noHooks: flags.has('no-hooks'),
|
|
404
|
+
noIntegrations: flags.has('no-integrations'),
|
|
405
|
+
noVerify: flags.has('no-verify'),
|
|
406
|
+
...(options.get('allow-integrations') !== undefined ? { allowIntegrations: options.get('allow-integrations') } : {}),
|
|
379
407
|
});
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
writeMissingSkillsHint(write, r.missing, presetName);
|
|
385
|
-
// Codex-targeted init DELIVERS the hooks and verifies them (ADR-001 §8). Skills alone are not the
|
|
386
|
-
// target's harness: the veto + recall legs are what `--target codex` promises.
|
|
387
|
-
// `--no-hooks` is the documented escape for "skills only" (the same flag `dz setup` already
|
|
388
|
-
// carries): hook delivery writes USER-GLOBAL config, so a command that only wants skills compiled
|
|
389
|
-
// must be able to say so — and every test that is about skills says it.
|
|
408
|
+
// Codex keeps its established user-registry writer, but its result is normalized into the same
|
|
409
|
+
// two-outcome contract before JSON/human rendering. A write without a live ready observation is
|
|
410
|
+
// a refusal with applied=true, never a second success channel.
|
|
411
|
+
let integrationOutcomes = r.integrations;
|
|
390
412
|
let codexHooksOk = true;
|
|
391
413
|
if (target === 'codex' && !flags.has('no-hooks')) {
|
|
392
414
|
const delivery = deliverCodexHooks({ project: projectRoot, verify: !flags.has('no-verify') }, undefined, 'dz init');
|
|
@@ -395,7 +417,30 @@ async function cmdInit(options, flags, cwd, write, writeErr) {
|
|
|
395
417
|
write(line);
|
|
396
418
|
for (const line of delivery.stderr)
|
|
397
419
|
writeErr(line);
|
|
420
|
+
const hookIndex = integrationOutcomes.findIndex((row) => row.component === 'hooks' && row.status !== 'not-requested');
|
|
421
|
+
if (hookIndex !== -1) {
|
|
422
|
+
const hook = normalizeCodexHookOutcome(integrationOutcomes[hookIndex], delivery, flags.has('no-verify'));
|
|
423
|
+
integrationOutcomes = integrationOutcomes.map((row, index) => index === hookIndex ? hook : row);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (flags.has('json')) {
|
|
427
|
+
write(JSON.stringify({ target, skills: { count: r.results.length, written: r.written, skipped: r.skipped }, integrations: integrationOutcomes, integrationDigest: r.integrationDigest ?? null }));
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
write(`dz init --target ${target}: ${r.results.length} skill(s), ${r.written} file(s) written, ${r.skipped} skipped`);
|
|
431
|
+
if (r.dirsSearched > 1)
|
|
432
|
+
write(` (searched ${r.dirsSearched} skill directories)`);
|
|
433
|
+
for (const outcome of integrationOutcomes) {
|
|
434
|
+
const label = outcome.component === 'mcp' ? 'MCP' : 'Hooks';
|
|
435
|
+
const detail = outcome.status === 'refused'
|
|
436
|
+
? `${outcome.reasonCode ?? 'LIVE_PROBE_FAILED'}${outcome.remediation ? ` — ${outcome.remediation}` : ''}`
|
|
437
|
+
: outcome.status === 'emitted'
|
|
438
|
+
? `${outcome.carrier?.path ?? 'registered'}${outcome.registrations.some((row) => row.approval === 'pending') ? '; Pending approval; ready=false' : ''}`
|
|
439
|
+
: 'explicitly not requested';
|
|
440
|
+
write(`${label}: ${outcome.status.toUpperCase()} (${detail})`);
|
|
441
|
+
}
|
|
398
442
|
}
|
|
443
|
+
writeMissingSkillsHint(write, r.missing, presetName);
|
|
399
444
|
// Skip-and-collect must not become skip-and-SILENCE: a skill that failed to load is
|
|
400
445
|
// named on stderr and the command exits 1 (it exited 1 before too — by throwing).
|
|
401
446
|
if (r.failures.length > 0 || r.applyFailures.length > 0) {
|
|
@@ -418,7 +463,38 @@ async function cmdInit(options, flags, cwd, write, writeErr) {
|
|
|
418
463
|
writeErr(line);
|
|
419
464
|
return 1;
|
|
420
465
|
}
|
|
421
|
-
|
|
466
|
+
const integrationOk = !integrationOutcomes.some((row) => row.status === 'refused');
|
|
467
|
+
return codexHooksOk && integrationOk ? 0 : 1;
|
|
468
|
+
}
|
|
469
|
+
function cmdIntegrationsVerify(options, flags, cwd, write, writeErr) {
|
|
470
|
+
const targetInput = options.get('target');
|
|
471
|
+
const component = options.get('component');
|
|
472
|
+
if (targetInput === undefined || (component !== 'mcp' && component !== 'hooks')) {
|
|
473
|
+
writeErr('dz integrations-verify: requires --target <name> --component <mcp|hooks>');
|
|
474
|
+
return 1;
|
|
475
|
+
}
|
|
476
|
+
const resolution = resolveTargetName(targetInput);
|
|
477
|
+
if (resolution.kind === 'unknown') {
|
|
478
|
+
for (const line of formatTargetProblem('dz integrations-verify', resolution))
|
|
479
|
+
writeErr(line);
|
|
480
|
+
return 1;
|
|
481
|
+
}
|
|
482
|
+
if (resolution.via === 'alias')
|
|
483
|
+
writeErr(formatTargetAliasNote('dz integrations-verify', targetInput, resolution.target));
|
|
484
|
+
const result = runIntegrationsVerify({
|
|
485
|
+
target: resolution.target,
|
|
486
|
+
component,
|
|
487
|
+
projectRoot: resolve(cwd, options.get('project') ?? '.'),
|
|
488
|
+
});
|
|
489
|
+
if (flags.has('json'))
|
|
490
|
+
write(JSON.stringify(result));
|
|
491
|
+
else if (result.ok) {
|
|
492
|
+
write(`dz integrations-verify: ${resolution.target}/${component} registered (runtime ${result.runtimeVersion ?? 'unknown'}; ready=${result.registrations.every((row) => row.ready === true)})`);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
writeErr(`dz integrations-verify: ${resolution.target}/${component} REFUSED (${result.reasonCode ?? 'LIVE_PROBE_FAILED'}) — ${result.remediation ?? 'no qualifying receipt'}`);
|
|
496
|
+
}
|
|
497
|
+
return result.ok ? 0 : 1;
|
|
422
498
|
}
|
|
423
499
|
async function cmdVerify(options, cwd, write, writeErr) {
|
|
424
500
|
const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
|
|
@@ -1870,6 +1946,150 @@ function cmdStatuslineFaRecord(options, cwd, write) {
|
|
|
1870
1946
|
write(`dz statusline: recorded /feature-adr learning state for "${slug}" (${step}) — 🎓 ${state.pool} pool · ↑${state.recalled} used · +${state.stored} new · ↻${state.reinforced ?? 0} reinforced`);
|
|
1871
1947
|
return 0;
|
|
1872
1948
|
}
|
|
1949
|
+
function statuslineEtaStage(step) {
|
|
1950
|
+
const match = step.match(/\bStep\s*-?\s*(0|[1-9](?:\.5)?)(?:\b|\s)/i);
|
|
1951
|
+
if (match === null)
|
|
1952
|
+
return undefined;
|
|
1953
|
+
const value = Number(match[1]);
|
|
1954
|
+
if (value === 0)
|
|
1955
|
+
return 'router';
|
|
1956
|
+
if (value > 0 && value < 6)
|
|
1957
|
+
return 'design';
|
|
1958
|
+
if (value === 6)
|
|
1959
|
+
return 'plan';
|
|
1960
|
+
if (value === 7)
|
|
1961
|
+
return 'code';
|
|
1962
|
+
if (value === 8)
|
|
1963
|
+
return 'qe';
|
|
1964
|
+
if (value === 9)
|
|
1965
|
+
return 'fleet';
|
|
1966
|
+
return undefined;
|
|
1967
|
+
}
|
|
1968
|
+
function routerMetadata(router) {
|
|
1969
|
+
const result = router?.result;
|
|
1970
|
+
if (result === null || typeof result !== 'object')
|
|
1971
|
+
return {};
|
|
1972
|
+
const record = result;
|
|
1973
|
+
const tier = typeof record['tier'] === 'string' && record['tier'].length > 0 ? record['tier'] : undefined;
|
|
1974
|
+
const activeSteps = Array.isArray(record['activeSteps'])
|
|
1975
|
+
? record['activeSteps'].filter((value) => typeof value === 'number' && Number.isFinite(value))
|
|
1976
|
+
: undefined;
|
|
1977
|
+
return {
|
|
1978
|
+
...(tier !== undefined ? { tier } : {}),
|
|
1979
|
+
...(activeSteps !== undefined ? { activeSteps } : {}),
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
function activeCheckpointStages(tier, activeSteps) {
|
|
1983
|
+
const stages = new Set();
|
|
1984
|
+
if (activeSteps !== undefined) {
|
|
1985
|
+
for (const step of activeSteps) {
|
|
1986
|
+
if (step === 0)
|
|
1987
|
+
stages.add('router');
|
|
1988
|
+
else if (step > 0 && step < 6)
|
|
1989
|
+
stages.add('design');
|
|
1990
|
+
else if (step === 6)
|
|
1991
|
+
stages.add('plan');
|
|
1992
|
+
else if (step === 7)
|
|
1993
|
+
stages.add('code');
|
|
1994
|
+
else if (step === 8)
|
|
1995
|
+
stages.add('qe');
|
|
1996
|
+
else if (step === 9)
|
|
1997
|
+
stages.add('fleet');
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
else if (tier !== undefined) {
|
|
2001
|
+
for (const stage of CHECKPOINT_STAGES) {
|
|
2002
|
+
if (stage !== 'fleet' || tier === 'L' || tier === 'XL')
|
|
2003
|
+
stages.add(stage);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
return CHECKPOINT_STAGES.filter((stage) => stages.has(stage));
|
|
2007
|
+
}
|
|
2008
|
+
/** All checkpoint I/O for ETA lives in this CLI-only wrapper and degrades per file. */
|
|
2009
|
+
function readEtaCorpus(projectRoot, currentSlug) {
|
|
2010
|
+
const trace = process.env['DZ_ETA_TRACE'];
|
|
2011
|
+
if (trace !== undefined && trace.length > 0) {
|
|
2012
|
+
try {
|
|
2013
|
+
appendFileSync(trace, `${JSON.stringify({ projectRoot, currentSlug, ts: new Date().toISOString() })}\n`);
|
|
2014
|
+
}
|
|
2015
|
+
catch { /* a test/debug receipt can never take down the statusline */ }
|
|
2016
|
+
}
|
|
2017
|
+
const samples = [];
|
|
2018
|
+
let currentSegments = [];
|
|
2019
|
+
let hasCurrentCheckpoints = false;
|
|
2020
|
+
let entries = [];
|
|
2021
|
+
try {
|
|
2022
|
+
entries = readdirSync(join(projectRoot, 'features'), { withFileTypes: true });
|
|
2023
|
+
}
|
|
2024
|
+
catch {
|
|
2025
|
+
return {
|
|
2026
|
+
history: [], currentRunSamples: [], currentTier: undefined, activeStages: [],
|
|
2027
|
+
lastCheckpointTsMs: undefined, hasCurrentCheckpoints: false,
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
for (const entry of entries) {
|
|
2031
|
+
if (!entry.isDirectory())
|
|
2032
|
+
continue;
|
|
2033
|
+
const checkpointPath = join(projectRoot, 'features', entry.name, '.fa-state', 'checkpoints.jsonl');
|
|
2034
|
+
try {
|
|
2035
|
+
const text = readFileSync(checkpointPath, 'utf8');
|
|
2036
|
+
const observations = parseCheckpointLines(text, entry.name);
|
|
2037
|
+
const segments = segmentRun(observations);
|
|
2038
|
+
samples.push(...extractStageSamples(segments));
|
|
2039
|
+
if (entry.name === currentSlug) {
|
|
2040
|
+
hasCurrentCheckpoints = observations.length > 0;
|
|
2041
|
+
currentSegments = segments;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
catch {
|
|
2045
|
+
// Missing, unreadable, or racing append: this run contributes no evidence.
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
const lastSegment = currentSegments.at(-1);
|
|
2049
|
+
const currentRouter = lastSegment?.router;
|
|
2050
|
+
const invocationSegments = currentRouter === undefined
|
|
2051
|
+
? []
|
|
2052
|
+
: currentSegments.filter((segment) => segment.router === currentRouter);
|
|
2053
|
+
const invocationRunIds = new Set(invocationSegments.map((segment) => segment.runId));
|
|
2054
|
+
const currentRunSamples = extractStageSamples(invocationSegments);
|
|
2055
|
+
const metadata = routerMetadata(currentRouter);
|
|
2056
|
+
const currentTimestamps = invocationSegments
|
|
2057
|
+
.flatMap((segment) => segment.observations)
|
|
2058
|
+
.flatMap((observation) => observation.tsMs === undefined ? [] : [observation.tsMs]);
|
|
2059
|
+
return {
|
|
2060
|
+
history: samples.filter((sample) => !invocationRunIds.has(sample.runId)),
|
|
2061
|
+
currentRunSamples,
|
|
2062
|
+
currentTier: metadata.tier,
|
|
2063
|
+
activeStages: activeCheckpointStages(metadata.tier, metadata.activeSteps),
|
|
2064
|
+
lastCheckpointTsMs: currentTimestamps.length > 0 ? Math.max(...currentTimestamps) : undefined,
|
|
2065
|
+
hasCurrentCheckpoints,
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
function statuslineEta(projectRoot, state, nowMs) {
|
|
2069
|
+
const currentStage = statuslineEtaStage(state.step);
|
|
2070
|
+
if (currentStage === undefined)
|
|
2071
|
+
return undefined;
|
|
2072
|
+
const corpus = readEtaCorpus(projectRoot, state.slug);
|
|
2073
|
+
const currentIndex = CHECKPOINT_STAGES.indexOf(currentStage);
|
|
2074
|
+
const remainingStages = corpus.activeStages.filter((stage) => CHECKPOINT_STAGES.indexOf(stage) >= currentIndex && stage !== 'router');
|
|
2075
|
+
// Preserve honest absence as a machine-readable union member. The text formatter intentionally
|
|
2076
|
+
// omits both variants, but `--json` must still distinguish no file from a file with no tier.
|
|
2077
|
+
const stagesForEstimate = remainingStages.length > 0
|
|
2078
|
+
? remainingStages
|
|
2079
|
+
: (currentStage === 'router' ? [] : [currentStage]);
|
|
2080
|
+
if (stagesForEstimate.length === 0 && corpus.hasCurrentCheckpoints && corpus.currentTier !== undefined)
|
|
2081
|
+
return undefined;
|
|
2082
|
+
return estimateEta({
|
|
2083
|
+
samples: corpus.history,
|
|
2084
|
+
currentTier: corpus.currentTier,
|
|
2085
|
+
currentStage,
|
|
2086
|
+
remainingStages: stagesForEstimate,
|
|
2087
|
+
currentRunSamples: corpus.currentRunSamples,
|
|
2088
|
+
nowMs,
|
|
2089
|
+
...(corpus.lastCheckpointTsMs !== undefined ? { lastCheckpointTsMs: corpus.lastCheckpointTsMs } : {}),
|
|
2090
|
+
hasCurrentCheckpoints: corpus.hasCurrentCheckpoints,
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
1873
2093
|
/**
|
|
1874
2094
|
* `dz statusline` — render dz's OWN self-learning counts as one compact, emoji-tagged line for
|
|
1875
2095
|
* Claude Code's status bar (modeled on agentic-qe's "🎓 12 patterns"). Claude Code pipes a JSON
|
|
@@ -1889,8 +2109,23 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
|
|
|
1889
2109
|
try {
|
|
1890
2110
|
const projectRoot = statuslineProjectRoot(readStdin(), options, cwd);
|
|
1891
2111
|
const data = statuslineData(projectRoot);
|
|
2112
|
+
const fa = data.featureAdr;
|
|
2113
|
+
let eta;
|
|
2114
|
+
let etaFragment;
|
|
2115
|
+
if (fa !== undefined && fa.kind !== 'loop') {
|
|
2116
|
+
try {
|
|
2117
|
+
eta = statuslineEta(projectRoot, fa, Date.now());
|
|
2118
|
+
if (eta !== undefined)
|
|
2119
|
+
etaFragment = formatEta(eta);
|
|
2120
|
+
}
|
|
2121
|
+
catch {
|
|
2122
|
+
// ETA is advisory: any corpus/resource failure omits only this fragment.
|
|
2123
|
+
eta = undefined;
|
|
2124
|
+
etaFragment = undefined;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1892
2127
|
if (flags.has('json')) {
|
|
1893
|
-
write(JSON.stringify(data));
|
|
2128
|
+
write(JSON.stringify({ ...data, ...(eta !== undefined ? { eta } : {}) }));
|
|
1894
2129
|
return 0;
|
|
1895
2130
|
}
|
|
1896
2131
|
let line = `🎓 dz: ${data.patterns} patterns${data.usedPatterns !== undefined ? ` · ${data.usedPatterns} used` : ''} · 🧠 ${data.brainSources} sources`;
|
|
@@ -1900,14 +2135,13 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
|
|
|
1900
2135
|
if (data.consolidatedAgeH !== undefined)
|
|
1901
2136
|
line += ` · ⟳ ${data.consolidatedAgeH}h`;
|
|
1902
2137
|
// Live /feature-adr run in flight → PREPEND the pipeline learning segment to the base dz line.
|
|
1903
|
-
const fa = data.featureAdr;
|
|
1904
2138
|
if (fa !== undefined) {
|
|
1905
2139
|
// The producer marker reached data and arbitration in a previous round but not this label, so the bar asserted a pipeline that was not running.
|
|
1906
2140
|
if (fa.kind === 'loop') {
|
|
1907
2141
|
line = `🔁 loop ${fa.step} · ${line}`;
|
|
1908
2142
|
}
|
|
1909
2143
|
else {
|
|
1910
|
-
line = `📐 feature-adr ${fa.step} · 🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ↻${fa.reinforced ?? 0} reinforced · ${line}`;
|
|
2144
|
+
line = `📐 feature-adr ${fa.step} · ${etaFragment !== undefined ? `${etaFragment} · ` : ''}🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ↻${fa.reinforced ?? 0} reinforced · ${line}`;
|
|
1911
2145
|
}
|
|
1912
2146
|
}
|
|
1913
2147
|
write(line);
|
|
@@ -2494,7 +2728,12 @@ function learningStoreLine(storeRoot, projectOption, target, verb) {
|
|
|
2494
2728
|
const reason = teachReasonPhrase(target.reason);
|
|
2495
2729
|
return storeLocationLine(describeStoreLocation(storeRoot, projectOption, target.store === 'global' ? 'global' : undefined), verb) + (reason ? ' [' + reason + ']' : '');
|
|
2496
2730
|
}
|
|
2497
|
-
async function
|
|
2731
|
+
async function runTeachGuardReinforcement(projectRoot, dzId, reward) {
|
|
2732
|
+
const backend = resolveLearningBackend(projectRoot);
|
|
2733
|
+
backend.addSample({ dzId, kind: 'reinforce', reward, ts: new Date().toISOString() });
|
|
2734
|
+
return backend.train();
|
|
2735
|
+
}
|
|
2736
|
+
async function cmdTeach(options, flags, cwd, write, writeErr = (line) => { console.error(line); }, interactive = false, guardRunner = teachGuard, reinforceRunner = runTeachGuardReinforcement) {
|
|
2498
2737
|
// WHICH store this lesson belongs to, and WHO decided (teach-chooses-its-store).
|
|
2499
2738
|
// `--to` → `DZ_LEARN` → `.dz/config.json` learning.teachTo → project. The owner asked for a
|
|
2500
2739
|
// per-session choice; for a CLI every invocation is a fresh process, so the only honest session
|
|
@@ -2585,26 +2824,39 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
2585
2824
|
write('dz teach --from-json: expected a JSON array (produced by `dz recall --all --json`)');
|
|
2586
2825
|
return 1;
|
|
2587
2826
|
}
|
|
2588
|
-
const
|
|
2827
|
+
const importedKey = (p) => p.lessonForm !== undefined && p.lessonPairId !== undefined
|
|
2828
|
+
? `${p.pattern}\u0000${p.lessonForm}\u0000${p.lessonPairId}`
|
|
2829
|
+
: `legacy\u0000${p.pattern}`;
|
|
2830
|
+
const existing = new Set(loadStorePatternsSync(storeRoot).map(importedKey));
|
|
2589
2831
|
let imported = 0;
|
|
2590
2832
|
let skipped = 0;
|
|
2591
2833
|
const importedRecs = [];
|
|
2592
2834
|
for (const item of parsed) {
|
|
2593
2835
|
const p = item;
|
|
2594
|
-
if (!p || typeof p
|
|
2836
|
+
if (!p || typeof p !== 'object') {
|
|
2595
2837
|
skipped += 1;
|
|
2596
2838
|
continue;
|
|
2597
2839
|
}
|
|
2598
|
-
const
|
|
2599
|
-
|
|
2840
|
+
const pair = (p.lessonForm === 'specific' || p.lessonForm === 'class')
|
|
2841
|
+
&& typeof p.lessonPairId === 'string' && p.lessonPairId !== ''
|
|
2842
|
+
? { lessonForm: p.lessonForm, lessonPairId: p.lessonPairId }
|
|
2843
|
+
: {};
|
|
2844
|
+
const candidate = {
|
|
2845
|
+
pattern: typeof p.pattern === 'string' ? p.pattern : '',
|
|
2600
2846
|
type: (typeof p.type === 'string' ? p.type : 'lesson-learned'),
|
|
2601
2847
|
reward: typeof p.reward === 'number' ? Math.max(0, Math.min(1, p.reward)) : 0.8,
|
|
2602
2848
|
domain: typeof p.domain === 'string' ? p.domain : 'general',
|
|
2603
2849
|
ts: typeof p.ts === 'string' ? p.ts : new Date().toISOString(),
|
|
2604
2850
|
source: 'dz-teach-import',
|
|
2851
|
+
...pair,
|
|
2605
2852
|
};
|
|
2853
|
+
if (candidate.pattern.trim() === '' || existing.has(importedKey(candidate))) {
|
|
2854
|
+
skipped += 1;
|
|
2855
|
+
continue;
|
|
2856
|
+
}
|
|
2857
|
+
const rec = candidate;
|
|
2606
2858
|
await recordPattern(storeRoot, rec);
|
|
2607
|
-
existing.add(
|
|
2859
|
+
existing.add(importedKey(rec));
|
|
2608
2860
|
importedRecs.push(rec);
|
|
2609
2861
|
imported += 1;
|
|
2610
2862
|
}
|
|
@@ -2687,23 +2939,32 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
2687
2939
|
}
|
|
2688
2940
|
const reward = parseFloat(options.get('reward') ?? '0.8');
|
|
2689
2941
|
const domain = options.get('domain') ?? 'general';
|
|
2942
|
+
const classWasRequested = options.has('class-form') || flags.has('class-form');
|
|
2943
|
+
const lessonForms = normalizeLessonForms(pattern, options.get('class-form'));
|
|
2944
|
+
let guardedExisting;
|
|
2690
2945
|
if (flags.has('guard')) {
|
|
2691
|
-
const verdict = await
|
|
2946
|
+
const verdict = await guardRunner(storeRoot, pattern, { reward: Math.max(0, Math.min(1, reward)) });
|
|
2692
2947
|
if (verdict.action === 'reinforce') {
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2948
|
+
if (lessonForms.classForm !== undefined) {
|
|
2949
|
+
const existing = loadStoreRecords(storeRoot).find((record) => record.id === verdict.dzId);
|
|
2950
|
+
if (existing !== undefined) {
|
|
2951
|
+
guardedExisting = { dzId: verdict.dzId, cosine: verdict.cosine, pattern: recordToPattern(existing) };
|
|
2952
|
+
}
|
|
2953
|
+
else {
|
|
2954
|
+
write(`dz teach --guard: matched pattern ${verdict.dzId} was not found in the lexical store — teaching the lesson normally`);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
else {
|
|
2958
|
+
const trained = await reinforceRunner(storeRoot, verdict.dzId, Math.max(0, Math.min(1, reward)));
|
|
2959
|
+
if (trained.flushed > 0) {
|
|
2960
|
+
write(`↳ reinforced existing pattern ${verdict.dzId} (cos=${verdict.cosine.toFixed(2)}) — not re-added`);
|
|
2961
|
+
const clearedQ = clearAgentdbQuarantine(storeRoot, [verdict.dzId]);
|
|
2962
|
+
if (clearedQ.cleared > 0)
|
|
2963
|
+
write(' ↳ promoted out of quarantine (mirror updated)');
|
|
2964
|
+
return 0;
|
|
2965
|
+
}
|
|
2966
|
+
write(`dz teach --guard: reinforce of ${verdict.dzId} did not flush (backend off or write failure) — teaching the lesson normally instead`);
|
|
2705
2967
|
}
|
|
2706
|
-
write(`dz teach --guard: reinforce of ${verdict.dzId} did not flush (backend off or write failure) — teaching the lesson normally instead`);
|
|
2707
2968
|
}
|
|
2708
2969
|
}
|
|
2709
2970
|
// Distill pattern into actionable rule (claude-smart inspired)
|
|
@@ -2716,8 +2977,8 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
2716
2977
|
// Typed through the shared schema (harness-core owns PatternRecord) so the
|
|
2717
2978
|
// write side and the read side (recommend's loadPatterns) can never drift —
|
|
2718
2979
|
// a field rename here is a compile error, not a silently re-muted loop (audit #2).
|
|
2719
|
-
const entry = {
|
|
2720
|
-
pattern,
|
|
2980
|
+
const entry = guardedExisting?.pattern ?? {
|
|
2981
|
+
pattern: lessonForms.specific,
|
|
2721
2982
|
type: type,
|
|
2722
2983
|
reward: Math.max(0, Math.min(1, reward)),
|
|
2723
2984
|
domain,
|
|
@@ -2729,9 +2990,47 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
2729
2990
|
// total count. The lossy `npx agentdb add` dual-write was removed in Tier-1 (audit #6).
|
|
2730
2991
|
// lesson-quarantine (opt-in): a fresh lesson is a HYPOTHESIS until it earns promotion.
|
|
2731
2992
|
const quarantineOn = readMemoryLearningConfig(storeRoot).quarantine;
|
|
2732
|
-
const
|
|
2733
|
-
|
|
2734
|
-
|
|
2993
|
+
const stored = await recordLessonForms(storeRoot, entry, lessonForms.classForm, quarantineOn ? { quarantine: true } : {});
|
|
2994
|
+
let recordsToMirror = stored.records;
|
|
2995
|
+
let commandFailed = stored.class === 'failed';
|
|
2996
|
+
let reinforced = false;
|
|
2997
|
+
if (guardedExisting !== undefined) {
|
|
2998
|
+
let reinforceId = guardedExisting.dzId;
|
|
2999
|
+
if (stored.class === 'stored') {
|
|
3000
|
+
const specificRow = stored.records.find((row) => row.lessonForm === 'specific');
|
|
3001
|
+
if (specificRow !== undefined)
|
|
3002
|
+
reinforceId = patternRecordId(specificRow);
|
|
3003
|
+
if (reinforceId !== guardedExisting.dzId) {
|
|
3004
|
+
const removed = removePatternsByIds(storeRoot, new Set([guardedExisting.dzId]));
|
|
3005
|
+
if (removed.error !== undefined || removed.removed === 0) {
|
|
3006
|
+
commandFailed = true;
|
|
3007
|
+
writeErr(`dz teach --guard: class pair stored, but old pattern cleanup failed${removed.error === undefined ? '' : ` — ${removed.error}`}`);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
else if (stored.class === 'failed') {
|
|
3012
|
+
const partialSpecific = stored.records.find((row) => row.lessonForm === 'specific');
|
|
3013
|
+
if (partialSpecific !== undefined && patternRecordId(partialSpecific) !== guardedExisting.dzId) {
|
|
3014
|
+
const rolledBack = removePatternsByIds(storeRoot, new Set([patternRecordId(partialSpecific)]));
|
|
3015
|
+
if (rolledBack.error !== undefined)
|
|
3016
|
+
writeErr(`dz teach --guard: partial enrichment rollback failed — ${rolledBack.error}`);
|
|
3017
|
+
}
|
|
3018
|
+
recordsToMirror = [];
|
|
3019
|
+
reinforceId = guardedExisting.dzId;
|
|
3020
|
+
}
|
|
3021
|
+
if (!commandFailed) {
|
|
3022
|
+
const trained = await reinforceRunner(storeRoot, reinforceId, Math.max(0, Math.min(1, reward)));
|
|
3023
|
+
reinforced = trained.flushed > 0;
|
|
3024
|
+
if (reinforced) {
|
|
3025
|
+
const clearedQ = clearAgentdbQuarantine(storeRoot, [reinforceId]);
|
|
3026
|
+
if (clearedQ.cleared > 0)
|
|
3027
|
+
write(' ↳ promoted out of quarantine (mirror updated)');
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
const count = guardedExisting === undefined ? stored.count : loadStorePatternsSync(storeRoot).length;
|
|
3032
|
+
write(`${guardedExisting !== undefined && stored.class === 'stored' ? 'Enriched' : 'Learned'}: "${pattern.slice(0, 60)}${pattern.length > 60 ? '...' : ''}"`);
|
|
3033
|
+
write(` Domain: ${entry.domain} Reward: ${entry.reward} Backend: memory (@dzhechkov/memory)`);
|
|
2735
3034
|
write(` Total patterns: ${count}`);
|
|
2736
3035
|
// WHERE the write landed. MEASURED before this line existed: teach printed the pattern, the
|
|
2737
3036
|
// domain, the reward and the backend — and not one word about the path, so a store written to
|
|
@@ -2741,6 +3040,24 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
2741
3040
|
// sees a path, cannot tell what chose it, and has no reason to question it. `default` adds
|
|
2742
3041
|
// nothing, so the line stays byte-identical for everyone who set nothing.
|
|
2743
3042
|
write(storeLine('written'));
|
|
3043
|
+
if (stored.class === 'stored')
|
|
3044
|
+
write(' Class form: stored separately and linked to the specific lesson');
|
|
3045
|
+
if (classWasRequested && stored.class === 'absent')
|
|
3046
|
+
write(' Class form: skipped; the specific lesson was saved');
|
|
3047
|
+
if (stored.class === 'rejected')
|
|
3048
|
+
writeErr(`dz teach: class form rejected — ${stored.reason ?? 'invalid class form'}; the specific lesson was saved`);
|
|
3049
|
+
if (stored.class === 'failed')
|
|
3050
|
+
writeErr(`dz teach: specific stored; class failed — ${stored.reason ?? 'unknown storage failure'}`);
|
|
3051
|
+
if (guardedExisting !== undefined && stored.class === 'stored') {
|
|
3052
|
+
write(commandFailed
|
|
3053
|
+
? ' Guard: class enrichment stored; reinforcement skipped because old-pattern cleanup failed'
|
|
3054
|
+
: reinforced
|
|
3055
|
+
? ` Guard: enriched existing pattern (cos=${guardedExisting.cosine.toFixed(2)}), then reinforced the linked specific form`
|
|
3056
|
+
: ' Guard: class enrichment stored; reinforcement did not flush');
|
|
3057
|
+
}
|
|
3058
|
+
if (!classWasRequested && interactive) {
|
|
3059
|
+
write(' Rule of one place or of a class? Optional: add --class-form when a future reader needs the why for structure, interfaces, or maintainability; skip when scope, risk, time, and cost are low or standards, policy, or documentation already cover it.');
|
|
3060
|
+
}
|
|
2744
3061
|
// ADVICE, not a gate. Someone putting medical lessons in a shared store owns both
|
|
2745
3062
|
// directories and this binary; refusing would be defending a user against themselves,
|
|
2746
3063
|
// which this design does not attempt. Making the choice INFORMED is the part that is
|
|
@@ -2759,8 +3076,8 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
2759
3076
|
write(' ⚠ quarantined: excluded from auto-inject, damped in recall — promote by confirming it (dz teach --reinforce "<text>") or dz recall --promote <dzId> --apply');
|
|
2760
3077
|
}
|
|
2761
3078
|
// The lexical write above is durable — the vector mirror is strictly best-effort (I-3).
|
|
2762
|
-
await emitMirrorQ(storeRoot,
|
|
2763
|
-
return 0;
|
|
3079
|
+
await emitMirrorQ(storeRoot, recordsToMirror, 'dz-teach', quarantineOn);
|
|
3080
|
+
return commandFailed ? 1 : 0;
|
|
2764
3081
|
}
|
|
2765
3082
|
async function cmdConsolidate(options, flags, cwd, write) {
|
|
2766
3083
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
@@ -3120,7 +3437,7 @@ async function cmdRecallPromote(options, flags, projectRoot, write) {
|
|
|
3120
3437
|
write(` not found: ${res.notFound.join(', ')}`);
|
|
3121
3438
|
return 0;
|
|
3122
3439
|
}
|
|
3123
|
-
async function cmdRecall(options, flags, cwd, write) {
|
|
3440
|
+
async function cmdRecall(options, flags, cwd, write, writeErr, classMatcher) {
|
|
3124
3441
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
3125
3442
|
const asJson = flags.has('json');
|
|
3126
3443
|
const all = flags.has('all');
|
|
@@ -3141,7 +3458,15 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3141
3458
|
// review could not make correct: a tag set by the writer is decidable, prose is not.
|
|
3142
3459
|
const allPatterns = loadStorePatternsSync(projectRoot);
|
|
3143
3460
|
const holdout = applyExportHoldout(allPatterns, heldOutAfterOptIn(options.get('include-domain')));
|
|
3144
|
-
const
|
|
3461
|
+
const classByPair = new Map(holdout.exported
|
|
3462
|
+
.filter((row) => row.lessonForm === 'class' && row.lessonPairId !== undefined)
|
|
3463
|
+
.map((row) => [row.lessonPairId, row.pattern]));
|
|
3464
|
+
const patterns = holdout.exported.map((row) => {
|
|
3465
|
+
const classForm = row.lessonForm === 'specific' && row.lessonPairId !== undefined
|
|
3466
|
+
? classByPair.get(row.lessonPairId)
|
|
3467
|
+
: undefined;
|
|
3468
|
+
return classForm === undefined ? row : { ...row, classForm };
|
|
3469
|
+
});
|
|
3145
3470
|
const holdoutNote = renderHoldoutNote(holdout);
|
|
3146
3471
|
// The opt-in is honoured without argument — and named out loud. A flag that silently
|
|
3147
3472
|
// includes medical lessons in a portable export is a flag whose consequence the user
|
|
@@ -3171,7 +3496,15 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3171
3496
|
const rawStats = storeStats(projectRoot);
|
|
3172
3497
|
const topHoldout = applyExportHoldout(rawStats.topUses, heldOutAfterOptIn(options.get('include-domain')));
|
|
3173
3498
|
const perDomain = Object.fromEntries(Object.entries(rawStats.perDomain).filter(([d]) => !holdout.domains.includes(canonicalDomainKey(d))));
|
|
3174
|
-
const
|
|
3499
|
+
const generalizedPairs = new Set(patterns.flatMap((p) => p.lessonPairId === undefined ? [] : [p.lessonPairId]));
|
|
3500
|
+
const unpairedLessons = patterns.filter((p) => p.lessonPairId === undefined).length;
|
|
3501
|
+
const stats = {
|
|
3502
|
+
...rawStats,
|
|
3503
|
+
topUses: topHoldout.exported,
|
|
3504
|
+
perDomain,
|
|
3505
|
+
generalized: generalizedPairs.size,
|
|
3506
|
+
logicalLessons: generalizedPairs.size + unpairedLessons,
|
|
3507
|
+
};
|
|
3175
3508
|
const backendStats = resolveLearningBackend(projectRoot).getStats();
|
|
3176
3509
|
if (asJson) {
|
|
3177
3510
|
write(JSON.stringify({ patterns, stats, learning: backendStats, withheld: holdout.withheld.length, withheldDomains: holdout.domains }));
|
|
@@ -3185,6 +3518,7 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3185
3518
|
write(` backend: ${backendStats.backend}${backendStats.advisory !== undefined ? ` (${backendStats.advisory})` : ''}`);
|
|
3186
3519
|
write(` domains: ${Object.entries(stats.perDomain).map(([k, v]) => `${k}=${v}`).join(', ') || 'none'}`);
|
|
3187
3520
|
write(` exact-dup groups: ${stats.exactDupGroups}`);
|
|
3521
|
+
write(` generalized: ${stats.generalized} of ${stats.logicalLessons} lessons`);
|
|
3188
3522
|
write(` re-teach trend: ${stats.teachEvents} teach event(s), ${stats.reinforceEvents} reinforce event(s)`);
|
|
3189
3523
|
write(' top uses:');
|
|
3190
3524
|
for (const row of stats.topUses)
|
|
@@ -3312,6 +3646,10 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3312
3646
|
? 'lexical'
|
|
3313
3647
|
: 'hybrid';
|
|
3314
3648
|
const wantedDomain = options.get('domain');
|
|
3649
|
+
const classRecallOptions = {
|
|
3650
|
+
onClassDegraded: writeErr,
|
|
3651
|
+
...(classMatcher === undefined ? {} : { classMatcher }),
|
|
3652
|
+
};
|
|
3315
3653
|
// OVER-FETCH before boosting (Codex QE #5): the boost used to run on hits ALREADY
|
|
3316
3654
|
// truncated to `limit`, so an exact-domain lesson sitting at rank limit+1 could
|
|
3317
3655
|
// never receive its promised lift — the feature was weakest in exactly the case it
|
|
@@ -3334,8 +3672,8 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3334
3672
|
// second occurrence. Foreign stdout is routed to stderr for the duration of the engine call — our
|
|
3335
3673
|
// own output is written after it returns.
|
|
3336
3674
|
const result = asJson
|
|
3337
|
-
? await withForeignStdoutOnStderr(() => recallHybrid(projectRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) }))
|
|
3338
|
-
: await recallHybrid(projectRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) });
|
|
3675
|
+
? await withForeignStdoutOnStderr(() => recallHybrid(projectRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...classRecallOptions, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) }))
|
|
3676
|
+
: await recallHybrid(projectRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...classRecallOptions, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) });
|
|
3339
3677
|
if (mode === 'semantic' && result.vectorEngine === 'none') {
|
|
3340
3678
|
// --semantic is an explicit ask — degrading it silently would be dishonest (FR-3).
|
|
3341
3679
|
const why = result.vectorReason ?? 'no vector engine available — run: dz setup --memory agentdb';
|
|
@@ -3369,13 +3707,27 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3369
3707
|
let globalHits = [];
|
|
3370
3708
|
if (readGlobal) {
|
|
3371
3709
|
const g = asJson
|
|
3372
|
-
? await withForeignStdoutOnStderr(() => recallHybrid(globalRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) }))
|
|
3373
|
-
: await recallHybrid(globalRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) });
|
|
3710
|
+
? await withForeignStdoutOnStderr(() => recallHybrid(globalRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...classRecallOptions, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) }))
|
|
3711
|
+
: await recallHybrid(globalRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...classRecallOptions, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) });
|
|
3374
3712
|
globalHits = g.hits;
|
|
3375
3713
|
}
|
|
3714
|
+
const projectHits = boost ? boost.hits : result.hits;
|
|
3715
|
+
// TWO different keys on purpose, and they must not be unified. The cross-store MERGE key is the
|
|
3716
|
+
// lesson TEXT: the same lesson taught into the project store and the global store is one lesson
|
|
3717
|
+
// shown once, labelled `both`, and those two records legitimately differ in timestamp, reward and
|
|
3718
|
+
// domain. The matchedForm LOOKUP key may be narrower. Folding the merge key into
|
|
3719
|
+
// patternIdentityOf (which includes ts) made two `dz teach` calls of identical text stop
|
|
3720
|
+
// collapsing — it reddened the pre-existing P3 case in recall-reads-both-stores.test.ts.
|
|
3721
|
+
const mergeKey = (hit) => hit.pattern.pattern;
|
|
3722
|
+
const globalByIdentity = new Map(globalHits.map((hit) => [mergeKey(hit), hit]));
|
|
3376
3723
|
const merged = readGlobal
|
|
3377
|
-
? mergeStoreHits(
|
|
3378
|
-
|
|
3724
|
+
? mergeStoreHits(projectHits, globalHits, mergeKey).map((hit) => {
|
|
3725
|
+
if (hit.origin !== 'both')
|
|
3726
|
+
return hit;
|
|
3727
|
+
const matchedForm = mergeLessonMatchedForms(hit.matchedForm, globalByIdentity.get(mergeKey(hit))?.matchedForm);
|
|
3728
|
+
return matchedForm === undefined ? hit : { ...hit, matchedForm };
|
|
3729
|
+
})
|
|
3730
|
+
: projectHits;
|
|
3379
3731
|
const hits = merged.slice(0, limit);
|
|
3380
3732
|
// Computed ONCE, honoured by EVERY return path. It used to live only on the text tail, so the two
|
|
3381
3733
|
// paths that return earlier — `--json` and the zero-hits branch — still reported success. That
|
|
@@ -3422,6 +3774,7 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3422
3774
|
// version emitted the number while its own comment promised null (found by independent review).
|
|
3423
3775
|
write(JSON.stringify(hits.map((h) => ({
|
|
3424
3776
|
...h.pattern,
|
|
3777
|
+
...(h.matchedForm === undefined ? {} : { matchedForm: h.matchedForm }),
|
|
3425
3778
|
relevance: boost === null && 'score' in h && typeof h.score === 'number' ? h.score : null,
|
|
3426
3779
|
// The TRUE cosine, as a second companion key — the design named it and the first ship missed
|
|
3427
3780
|
// it, so a scripted consumer STILL could not threshold (found while recalibrating the floors:
|
|
@@ -3488,6 +3841,10 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3488
3841
|
// guessing which second one answered.
|
|
3489
3842
|
write(` store (read): ${join(globalRoot, '.dz')} [cross-project]`);
|
|
3490
3843
|
}
|
|
3844
|
+
const classMatches = hits.filter((h) => h.matchedForm === 'class' || h.matchedForm === 'both').length;
|
|
3845
|
+
if (hits.some((h) => h.pattern.classForm !== undefined)) {
|
|
3846
|
+
write(` class-form matches: ${classMatches} of ${hits.length}`);
|
|
3847
|
+
}
|
|
3491
3848
|
let sawQuarantined = false;
|
|
3492
3849
|
for (const h of hits) {
|
|
3493
3850
|
const backendTag = vectorOn ? ` ⟨${h.backend}⟩` : '';
|
|
@@ -3513,9 +3870,23 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
3513
3870
|
// 160, not 80: at 80 characters the evidence a reader needs to judge relevance sits in the
|
|
3514
3871
|
// hidden remainder, and the cosine then appears to describe the visible fragment rather than
|
|
3515
3872
|
// the whole lesson. `--full` prints it all, still on one line.
|
|
3516
|
-
const
|
|
3517
|
-
const
|
|
3518
|
-
|
|
3873
|
+
const specificText = oneLine(h.pattern.pattern);
|
|
3874
|
+
const classText = h.pattern.classForm === undefined ? undefined : oneLine(h.pattern.classForm);
|
|
3875
|
+
let shown = specificText;
|
|
3876
|
+
if (classText !== undefined) {
|
|
3877
|
+
const suffix = ` [match: ${h.matchedForm ?? 'specific'}]`;
|
|
3878
|
+
if (flags.has('full')) {
|
|
3879
|
+
shown = `specific: ${specificText} · class: ${classText}${suffix}`;
|
|
3880
|
+
}
|
|
3881
|
+
else {
|
|
3882
|
+
const contentWidth = Math.max(2, 160 - 'specific: '.length - ' · class: '.length - suffix.length);
|
|
3883
|
+
const specificWidth = Math.floor(contentWidth / 2);
|
|
3884
|
+
shown = `specific: ${specificText.slice(0, specificWidth)} · class: ${classText.slice(0, contentWidth - specificWidth)}${suffix}`;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
else if (!flags.has('full')) {
|
|
3888
|
+
shown = shown.slice(0, 160);
|
|
3889
|
+
}
|
|
3519
3890
|
// WHICH store this hit came from. A merged list that does not say re-creates the fragmentation
|
|
3520
3891
|
// blindness the store-location line just removed, one level down: the reader would see more
|
|
3521
3892
|
// results and have no way to tell whether the global store is even connected.
|
|
@@ -4524,7 +4895,13 @@ async function cmdSetup(options, flags, cwd, write, writeErr) {
|
|
|
4524
4895
|
? selectArg.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
|
|
4525
4896
|
: getPreset(preset)?.skills;
|
|
4526
4897
|
const install = select !== undefined && select.length > 0
|
|
4527
|
-
? await installSkills({
|
|
4898
|
+
? await installSkills({
|
|
4899
|
+
target, projectRoot, cwd, explicitSkillsDir: options.get('skills-dir'), select,
|
|
4900
|
+
force: flags.has('force'), enrich: flags.has('enrich'), noHooks: flags.has('no-hooks'),
|
|
4901
|
+
noIntegrations: flags.has('no-integrations'),
|
|
4902
|
+
noVerify: flags.has('no-verify'),
|
|
4903
|
+
...(options.get('allow-integrations') !== undefined ? { allowIntegrations: options.get('allow-integrations') } : {}),
|
|
4904
|
+
})
|
|
4528
4905
|
: undefined;
|
|
4529
4906
|
if (install) {
|
|
4530
4907
|
write(`║ ${String(install.results.length).padStart(2)} skill(s), ${String(install.written).padStart(3)} file(s) written${' '.repeat(15)}║`);
|
|
@@ -4534,6 +4911,7 @@ async function cmdSetup(options, flags, cwd, write, writeErr) {
|
|
|
4534
4911
|
}
|
|
4535
4912
|
// Step 5 (ADR-001 §8): DELIVER the codex hooks and verify them live. Non-aborting — the rest of
|
|
4536
4913
|
// setup has already run and the summary still prints; only the exit code carries the failure.
|
|
4914
|
+
let setupIntegrationOutcomes = install?.integrations ?? [];
|
|
4537
4915
|
let codexHooksOk = true;
|
|
4538
4916
|
if (target === 'codex' && !flags.has('no-hooks')) {
|
|
4539
4917
|
write(`║ 5. Delivering codex hooks (live verify)... ║`);
|
|
@@ -4543,6 +4921,11 @@ async function cmdSetup(options, flags, cwd, write, writeErr) {
|
|
|
4543
4921
|
write(line);
|
|
4544
4922
|
for (const line of delivery.stderr)
|
|
4545
4923
|
writeErr(line);
|
|
4924
|
+
const hookIndex = setupIntegrationOutcomes.findIndex((row) => row.component === 'hooks' && row.status !== 'not-requested');
|
|
4925
|
+
if (hookIndex !== -1) {
|
|
4926
|
+
const hook = normalizeCodexHookOutcome(setupIntegrationOutcomes[hookIndex], delivery, flags.has('no-verify'));
|
|
4927
|
+
setupIntegrationOutcomes = setupIntegrationOutcomes.map((row, index) => index === hookIndex ? hook : row);
|
|
4928
|
+
}
|
|
4546
4929
|
}
|
|
4547
4930
|
write(`╠══════════════════════════════════════════════════════╣`);
|
|
4548
4931
|
write(`║ Setup: ${String(setupResult.completed).padStart(2)} done, ${String(setupResult.skipped).padStart(2)} skipped ║`);
|
|
@@ -4560,6 +4943,9 @@ async function cmdSetup(options, flags, cwd, write, writeErr) {
|
|
|
4560
4943
|
if (install) {
|
|
4561
4944
|
write(`dz setup: ${install.results.length} skill(s), ${install.written} file(s) written, ${install.skipped} skipped`
|
|
4562
4945
|
+ (install.dirsSearched > 1 ? ` (searched ${install.dirsSearched} skill dirs)` : ''));
|
|
4946
|
+
for (const outcome of setupIntegrationOutcomes) {
|
|
4947
|
+
write(`dz setup integration ${outcome.component}: ${outcome.status.toUpperCase()}${outcome.reasonCode ? ` (${outcome.reasonCode})` : ''}`);
|
|
4948
|
+
}
|
|
4563
4949
|
writeMissingSkillsHint(write, install.missing, selectArg !== undefined ? undefined : preset);
|
|
4564
4950
|
}
|
|
4565
4951
|
// A hook that was written but never witnessed firing is NOT a completed setup (ADR-002 §5): the
|
|
@@ -4574,7 +4960,8 @@ async function cmdSetup(options, flags, cwd, write, writeErr) {
|
|
|
4574
4960
|
if (erroredSteps.length > 0) {
|
|
4575
4961
|
write(`\n✗ setup reported ${erroredSteps.length} failed step(s): ${erroredSteps.join(', ')} — exit 1`);
|
|
4576
4962
|
}
|
|
4577
|
-
|
|
4963
|
+
const integrationsOk = !setupIntegrationOutcomes.some((row) => row.status === 'refused');
|
|
4964
|
+
return codexHooksOk && erroredSteps.length === 0 && integrationsOk ? 0 : 1;
|
|
4578
4965
|
}
|
|
4579
4966
|
function cmdPretrain(options, cwd, write) {
|
|
4580
4967
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
@@ -5228,6 +5615,9 @@ function cmdPublish(options, flags, cwd, write) {
|
|
|
5228
5615
|
write(`dz publish: ⚠ guard warn — ${v.rule}: ${v.detail}`);
|
|
5229
5616
|
else
|
|
5230
5617
|
write('dz publish: ✓ guard pre-flight passed');
|
|
5618
|
+
for (const observation of guardResult.observations ?? []) {
|
|
5619
|
+
write(`dz publish: ℹ guard observation — ${observation.rule} ${observation.scope}: ${observation.detail} [${observation.status}]`);
|
|
5620
|
+
}
|
|
5231
5621
|
for (const n of guardResult.notes ?? [])
|
|
5232
5622
|
write(`dz publish: ℹ guard note — ${n}`); // FN-7: on the record, never blocking
|
|
5233
5623
|
}
|
|
@@ -6904,6 +7294,26 @@ export function codexHooksSyncOptions(input) {
|
|
|
6904
7294
|
verify: input.verify !== false,
|
|
6905
7295
|
};
|
|
6906
7296
|
}
|
|
7297
|
+
/** Map the retained Codex hook writer's one live verdict into the common integration contract. */
|
|
7298
|
+
export function normalizeCodexHookOutcome(base, delivery, noVerify = false) {
|
|
7299
|
+
if (delivery.report.ready) {
|
|
7300
|
+
return {
|
|
7301
|
+
target: 'codex', component: 'hooks', status: 'emitted',
|
|
7302
|
+
registrations: [{ id: 'dz-codex-hooks', scope: 'user', registered: delivery.report.installed, approval: delivery.report.trust === 'trusted' ? 'approved' : 'unknown', ready: true }],
|
|
7303
|
+
carrier: { scope: 'user', path: '$CODEX_HOME/hooks.json' },
|
|
7304
|
+
...(delivery.report.codexVersion !== null ? { runtimeVersion: delivery.report.codexVersion } : {}),
|
|
7305
|
+
evidenceVersion: 'codex-hooks-live-v1',
|
|
7306
|
+
};
|
|
7307
|
+
}
|
|
7308
|
+
return {
|
|
7309
|
+
...base,
|
|
7310
|
+
target: 'codex', component: 'hooks', status: 'refused',
|
|
7311
|
+
registrations: [{ id: 'dz-codex-hooks', scope: 'user', registered: delivery.report.installed, approval: delivery.report.trust === 'trusted' ? 'approved' : 'unknown', ready: false }],
|
|
7312
|
+
reasonCode: 'CURRENT_LIVE_CHECK_FAILED',
|
|
7313
|
+
remediation: noVerify ? '--no-verify cannot establish ready; rerun with live verification' : 'approve the managed hooks and rerun the live verification',
|
|
7314
|
+
applied: delivery.report.written || delivery.report.installed,
|
|
7315
|
+
};
|
|
7316
|
+
}
|
|
6907
7317
|
/**
|
|
6908
7318
|
* What the user is told about a sync report — the ONE place the success word can be printed.
|
|
6909
7319
|
*
|
|
@@ -7221,6 +7631,430 @@ function gatherReadmeCounts(root) {
|
|
|
7221
7631
|
sitePair('packages/@dzhechkov/sitedoc/src/content/docs/index.mdx', /^\s*tagline: "[^"]*?(\d+) commands/m, 'commands (docs-site index hero tagline vs cli All Commands)');
|
|
7222
7632
|
return pairs;
|
|
7223
7633
|
}
|
|
7634
|
+
const MAX_VOLUME_FILES_PER_SCOPE = 512;
|
|
7635
|
+
const MAX_VOLUME_BYTES_PER_SCOPE = 32 * 1024 * 1024;
|
|
7636
|
+
const CYRILLIC_CHARACTER = /\p{Script=Cyrillic}/u;
|
|
7637
|
+
function volumePathInside(root, candidate) {
|
|
7638
|
+
return candidate === root || candidate.startsWith(root + sep);
|
|
7639
|
+
}
|
|
7640
|
+
function cyrillicUtf8Bytes(buffer) {
|
|
7641
|
+
let bytes = 0;
|
|
7642
|
+
for (const character of buffer.toString('utf8')) {
|
|
7643
|
+
if (CYRILLIC_CHARACTER.test(character))
|
|
7644
|
+
bytes += Buffer.byteLength(character, 'utf8');
|
|
7645
|
+
}
|
|
7646
|
+
return bytes;
|
|
7647
|
+
}
|
|
7648
|
+
function gatherTemplateVolumeTarget(packageDir, target) {
|
|
7649
|
+
const templateRoot = join(packageDir, 'templates', '.claude');
|
|
7650
|
+
if (!existsSync(templateRoot))
|
|
7651
|
+
return undefined;
|
|
7652
|
+
const files = [];
|
|
7653
|
+
const realFiles = new Set();
|
|
7654
|
+
let totalBytes = 0;
|
|
7655
|
+
let failure;
|
|
7656
|
+
let packageReal;
|
|
7657
|
+
let templateReal;
|
|
7658
|
+
try {
|
|
7659
|
+
packageReal = realpathSync(packageDir);
|
|
7660
|
+
templateReal = realpathSync(templateRoot);
|
|
7661
|
+
if (!volumePathInside(packageReal, templateReal)) {
|
|
7662
|
+
failure = { reason: 'template-root-escape', detail: templateRoot };
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7665
|
+
catch (error) {
|
|
7666
|
+
return {
|
|
7667
|
+
target,
|
|
7668
|
+
files,
|
|
7669
|
+
collection: { complete: false, reason: 'template-root-unreadable', detail: error instanceof Error ? error.message : String(error) },
|
|
7670
|
+
};
|
|
7671
|
+
}
|
|
7672
|
+
const fail = (reason, detail) => {
|
|
7673
|
+
failure ??= { reason, detail: detail.replace(/\s+/g, ' ').slice(0, 240) };
|
|
7674
|
+
};
|
|
7675
|
+
const addFile = (absolute, kind) => {
|
|
7676
|
+
if (failure !== undefined)
|
|
7677
|
+
return;
|
|
7678
|
+
if (files.length >= MAX_VOLUME_FILES_PER_SCOPE) {
|
|
7679
|
+
fail('template-file-cap-exceeded', `${MAX_VOLUME_FILES_PER_SCOPE} files`);
|
|
7680
|
+
return;
|
|
7681
|
+
}
|
|
7682
|
+
try {
|
|
7683
|
+
const stat = lstatSync(absolute);
|
|
7684
|
+
if (!stat.isFile()) {
|
|
7685
|
+
fail('template-input-not-regular', absolute);
|
|
7686
|
+
return;
|
|
7687
|
+
}
|
|
7688
|
+
const real = realpathSync(absolute);
|
|
7689
|
+
if (!volumePathInside(templateReal, real)) {
|
|
7690
|
+
fail('template-input-escape', absolute);
|
|
7691
|
+
return;
|
|
7692
|
+
}
|
|
7693
|
+
if (realFiles.has(real)) {
|
|
7694
|
+
fail('template-input-duplicate', absolute);
|
|
7695
|
+
return;
|
|
7696
|
+
}
|
|
7697
|
+
if (stat.size <= 0 || stat.size > MAX_VOLUME_BYTES_PER_SCOPE - totalBytes) {
|
|
7698
|
+
fail(stat.size <= 0 ? 'template-input-empty' : 'template-byte-cap-exceeded', absolute);
|
|
7699
|
+
return;
|
|
7700
|
+
}
|
|
7701
|
+
const content = readFileSync(absolute);
|
|
7702
|
+
if (content.byteLength <= 0 || content.byteLength > MAX_VOLUME_BYTES_PER_SCOPE - totalBytes) {
|
|
7703
|
+
fail(content.byteLength <= 0 ? 'template-input-empty' : 'template-byte-cap-exceeded', absolute);
|
|
7704
|
+
return;
|
|
7705
|
+
}
|
|
7706
|
+
realFiles.add(real);
|
|
7707
|
+
totalBytes += content.byteLength;
|
|
7708
|
+
files.push({
|
|
7709
|
+
path: relative(packageDir, absolute).split(sep).join('/'),
|
|
7710
|
+
kind,
|
|
7711
|
+
bytes: content.byteLength,
|
|
7712
|
+
cyrillicUtf8Bytes: cyrillicUtf8Bytes(content),
|
|
7713
|
+
});
|
|
7714
|
+
}
|
|
7715
|
+
catch (error) {
|
|
7716
|
+
fail('template-input-unreadable', `${absolute}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7717
|
+
}
|
|
7718
|
+
};
|
|
7719
|
+
const scanFlat = (name) => {
|
|
7720
|
+
const dir = join(templateRoot, name);
|
|
7721
|
+
if (!existsSync(dir) || failure !== undefined)
|
|
7722
|
+
return;
|
|
7723
|
+
try {
|
|
7724
|
+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
7725
|
+
if (!entry.name.endsWith('.md'))
|
|
7726
|
+
continue;
|
|
7727
|
+
addFile(join(dir, entry.name), name);
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
catch (error) {
|
|
7731
|
+
fail('template-directory-unreadable', `${dir}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7732
|
+
}
|
|
7733
|
+
};
|
|
7734
|
+
const scanSkills = (dir) => {
|
|
7735
|
+
if (!existsSync(dir) || failure !== undefined)
|
|
7736
|
+
return;
|
|
7737
|
+
let entries;
|
|
7738
|
+
try {
|
|
7739
|
+
entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
7740
|
+
}
|
|
7741
|
+
catch (error) {
|
|
7742
|
+
fail('template-directory-unreadable', `${dir}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7743
|
+
return;
|
|
7744
|
+
}
|
|
7745
|
+
for (const entry of entries) {
|
|
7746
|
+
if (failure !== undefined)
|
|
7747
|
+
return;
|
|
7748
|
+
const absolute = join(dir, entry.name);
|
|
7749
|
+
if (entry.isSymbolicLink()) {
|
|
7750
|
+
fail('template-input-not-regular', absolute);
|
|
7751
|
+
return;
|
|
7752
|
+
}
|
|
7753
|
+
if (entry.isDirectory())
|
|
7754
|
+
scanSkills(absolute);
|
|
7755
|
+
else if (entry.name === 'SKILL.md')
|
|
7756
|
+
addFile(absolute, 'skills');
|
|
7757
|
+
}
|
|
7758
|
+
};
|
|
7759
|
+
scanFlat('rules');
|
|
7760
|
+
scanFlat('commands');
|
|
7761
|
+
scanSkills(join(templateRoot, 'skills'));
|
|
7762
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
7763
|
+
return {
|
|
7764
|
+
target,
|
|
7765
|
+
files,
|
|
7766
|
+
collection: failure === undefined
|
|
7767
|
+
? { complete: true }
|
|
7768
|
+
: { complete: false, reason: failure.reason, detail: failure.detail },
|
|
7769
|
+
};
|
|
7770
|
+
}
|
|
7771
|
+
function volumeGitText(root, args, allowedStatuses = [0]) {
|
|
7772
|
+
const result = spawnSync('git', [...args], {
|
|
7773
|
+
cwd: root,
|
|
7774
|
+
encoding: 'utf8',
|
|
7775
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
7776
|
+
});
|
|
7777
|
+
if (result.status === null || !allowedStatuses.includes(result.status)) {
|
|
7778
|
+
throw result.error ?? new Error(`git ${args[0] ?? ''} exited ${String(result.status)}`);
|
|
7779
|
+
}
|
|
7780
|
+
return result.stdout ?? '';
|
|
7781
|
+
}
|
|
7782
|
+
function parseGuardStatusPaths(root) {
|
|
7783
|
+
const text = volumeGitText(root, ['status', '--porcelain', '-uall']);
|
|
7784
|
+
return text.split('\n').flatMap((line) => {
|
|
7785
|
+
if (line.length < 4)
|
|
7786
|
+
return [];
|
|
7787
|
+
const code = line.slice(0, 2);
|
|
7788
|
+
const raw = line.slice(3).trim();
|
|
7789
|
+
const path = (raw.includes(' -> ') ? raw.split(' -> ')[1] : raw)?.trim();
|
|
7790
|
+
return path ? [{ code, path }] : [];
|
|
7791
|
+
});
|
|
7792
|
+
}
|
|
7793
|
+
function parseFeatureTier(text) {
|
|
7794
|
+
const match = text.match(/^##\s+Tier:\s*(S|M|L|XL)\s*$/m);
|
|
7795
|
+
return match?.[1];
|
|
7796
|
+
}
|
|
7797
|
+
function parseFeatureActiveSteps(text) {
|
|
7798
|
+
const section = text.match(/^## Active steps[^\n]*\n([\s\S]*?)(?=^## |(?![\s\S]))/mi)?.[1];
|
|
7799
|
+
if (section === undefined)
|
|
7800
|
+
return undefined;
|
|
7801
|
+
const steps = [...section.matchAll(/\b(?:10|[0-9](?:\.5)?)\b/g)].map((match) => match[0]);
|
|
7802
|
+
return [...new Set(steps)];
|
|
7803
|
+
}
|
|
7804
|
+
function featureLifecycle(root, slug, assessment) {
|
|
7805
|
+
if (assessment !== undefined && /^Lifecycle:\s*complete\s*$/mi.test(assessment))
|
|
7806
|
+
return { phase: 'complete' };
|
|
7807
|
+
try {
|
|
7808
|
+
const state = JSON.parse(readFileSync(join(root, '.dz', 'feature-adr', 'learning-state', `${slug}.json`), 'utf8'));
|
|
7809
|
+
const match = typeof state.step === 'string' ? state.step.match(/\bStep\s+(\d+(?:\.5)?)/i) : null;
|
|
7810
|
+
if (!match?.[1])
|
|
7811
|
+
return undefined;
|
|
7812
|
+
const current = Number(match[1]);
|
|
7813
|
+
if (!Number.isFinite(current))
|
|
7814
|
+
return undefined;
|
|
7815
|
+
return { phase: 'in-progress', completedThroughStep: current <= 0 ? 0 : Math.ceil(current) - 1 };
|
|
7816
|
+
}
|
|
7817
|
+
catch {
|
|
7818
|
+
return undefined;
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
function gatherFeatureVolumeFact(root, slug) {
|
|
7822
|
+
const featureDir = join(root, 'features', slug);
|
|
7823
|
+
const artifacts = [];
|
|
7824
|
+
const contents = new Map();
|
|
7825
|
+
let totalBytes = 0;
|
|
7826
|
+
let failure;
|
|
7827
|
+
let rootReal;
|
|
7828
|
+
let featureReal;
|
|
7829
|
+
try {
|
|
7830
|
+
rootReal = realpathSync(root);
|
|
7831
|
+
featureReal = realpathSync(featureDir);
|
|
7832
|
+
if (!volumePathInside(rootReal, featureReal))
|
|
7833
|
+
failure = { reason: 'feature-root-escape', detail: featureDir };
|
|
7834
|
+
}
|
|
7835
|
+
catch (error) {
|
|
7836
|
+
return { fact: {
|
|
7837
|
+
slug,
|
|
7838
|
+
artifacts,
|
|
7839
|
+
collection: { complete: false, reason: 'feature-root-unreadable', detail: error instanceof Error ? error.message : String(error) },
|
|
7840
|
+
} };
|
|
7841
|
+
}
|
|
7842
|
+
const fail = (reason, detail) => {
|
|
7843
|
+
failure ??= { reason, detail: detail.replace(/\s+/g, ' ').slice(0, 240) };
|
|
7844
|
+
};
|
|
7845
|
+
const addArtifact = (absolute) => {
|
|
7846
|
+
if (failure !== undefined)
|
|
7847
|
+
return;
|
|
7848
|
+
if (artifacts.length >= MAX_VOLUME_FILES_PER_SCOPE) {
|
|
7849
|
+
fail('feature-file-cap-exceeded', `${MAX_VOLUME_FILES_PER_SCOPE} files`);
|
|
7850
|
+
return;
|
|
7851
|
+
}
|
|
7852
|
+
try {
|
|
7853
|
+
const stat = lstatSync(absolute);
|
|
7854
|
+
if (!stat.isFile()) {
|
|
7855
|
+
fail('feature-input-not-regular', absolute);
|
|
7856
|
+
return;
|
|
7857
|
+
}
|
|
7858
|
+
const real = realpathSync(absolute);
|
|
7859
|
+
if (!volumePathInside(featureReal, real)) {
|
|
7860
|
+
fail('feature-input-escape', absolute);
|
|
7861
|
+
return;
|
|
7862
|
+
}
|
|
7863
|
+
if (stat.size <= 0 || stat.size > MAX_VOLUME_BYTES_PER_SCOPE - totalBytes) {
|
|
7864
|
+
fail(stat.size <= 0 ? 'feature-input-empty' : 'feature-byte-cap-exceeded', absolute);
|
|
7865
|
+
return;
|
|
7866
|
+
}
|
|
7867
|
+
const content = readFileSync(absolute);
|
|
7868
|
+
if (content.byteLength <= 0 || content.byteLength > MAX_VOLUME_BYTES_PER_SCOPE - totalBytes) {
|
|
7869
|
+
fail(content.byteLength <= 0 ? 'feature-input-empty' : 'feature-byte-cap-exceeded', absolute);
|
|
7870
|
+
return;
|
|
7871
|
+
}
|
|
7872
|
+
const path = relative(featureDir, absolute).split(sep).join('/');
|
|
7873
|
+
totalBytes += content.byteLength;
|
|
7874
|
+
contents.set(path, content);
|
|
7875
|
+
artifacts.push({ path, bytes: content.byteLength });
|
|
7876
|
+
}
|
|
7877
|
+
catch (error) {
|
|
7878
|
+
fail('feature-input-unreadable', `${absolute}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7879
|
+
}
|
|
7880
|
+
};
|
|
7881
|
+
const scanNested = (dir) => {
|
|
7882
|
+
if (!existsSync(dir) || failure !== undefined)
|
|
7883
|
+
return;
|
|
7884
|
+
let entries;
|
|
7885
|
+
try {
|
|
7886
|
+
entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
7887
|
+
}
|
|
7888
|
+
catch (error) {
|
|
7889
|
+
fail('feature-directory-unreadable', `${dir}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7890
|
+
return;
|
|
7891
|
+
}
|
|
7892
|
+
for (const entry of entries) {
|
|
7893
|
+
if (failure !== undefined)
|
|
7894
|
+
return;
|
|
7895
|
+
const absolute = join(dir, entry.name);
|
|
7896
|
+
if (entry.isSymbolicLink()) {
|
|
7897
|
+
fail('feature-input-not-regular', absolute);
|
|
7898
|
+
return;
|
|
7899
|
+
}
|
|
7900
|
+
if (entry.isDirectory())
|
|
7901
|
+
scanNested(absolute);
|
|
7902
|
+
else
|
|
7903
|
+
addArtifact(absolute);
|
|
7904
|
+
}
|
|
7905
|
+
};
|
|
7906
|
+
try {
|
|
7907
|
+
const entries = readdirSync(featureDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
7908
|
+
for (const entry of entries) {
|
|
7909
|
+
if (failure !== undefined)
|
|
7910
|
+
break;
|
|
7911
|
+
const absolute = join(featureDir, entry.name);
|
|
7912
|
+
if (entry.name === '03_adr' || entry.name === '07_code_changes') {
|
|
7913
|
+
if (entry.isSymbolicLink() || !entry.isDirectory())
|
|
7914
|
+
fail('feature-input-not-regular', absolute);
|
|
7915
|
+
else
|
|
7916
|
+
scanNested(absolute);
|
|
7917
|
+
}
|
|
7918
|
+
else if (entry.name === 'README.md' || /^0[0-9](?:[_.-][^/]*)?\.md$/.test(entry.name)) {
|
|
7919
|
+
addArtifact(absolute);
|
|
7920
|
+
}
|
|
7921
|
+
}
|
|
7922
|
+
}
|
|
7923
|
+
catch (error) {
|
|
7924
|
+
fail('feature-directory-unreadable', `${featureDir}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7925
|
+
}
|
|
7926
|
+
artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
7927
|
+
const assessment = contents.get('00_complexity_assessment.md')?.toString('utf8');
|
|
7928
|
+
const tier = assessment !== undefined ? parseFeatureTier(assessment) : undefined;
|
|
7929
|
+
const activeSteps = assessment !== undefined ? parseFeatureActiveSteps(assessment) : undefined;
|
|
7930
|
+
const lifecycle = featureLifecycle(root, slug, assessment);
|
|
7931
|
+
const fact = {
|
|
7932
|
+
slug,
|
|
7933
|
+
...(tier !== undefined ? { tier } : {}),
|
|
7934
|
+
...(activeSteps !== undefined ? { activeSteps } : {}),
|
|
7935
|
+
namedConsumers: [],
|
|
7936
|
+
...(lifecycle !== undefined ? { lifecycle } : {}),
|
|
7937
|
+
artifacts,
|
|
7938
|
+
collection: failure === undefined
|
|
7939
|
+
? { complete: true }
|
|
7940
|
+
: { complete: false, reason: failure.reason, detail: failure.detail },
|
|
7941
|
+
};
|
|
7942
|
+
const manifestText = contents.get('07_code_changes/change_manifest.md')?.toString('utf8');
|
|
7943
|
+
return { fact, ...(manifestText !== undefined ? { manifestText } : {}) };
|
|
7944
|
+
}
|
|
7945
|
+
function manifestPaths(text, slug) {
|
|
7946
|
+
const paths = new Set();
|
|
7947
|
+
for (const line of text.split('\n')) {
|
|
7948
|
+
const match = line.match(/^\s*-\s+`?([^`]+?)`?(?:\s+[—-]\s+.*)?$/);
|
|
7949
|
+
if (!match?.[1])
|
|
7950
|
+
continue;
|
|
7951
|
+
const path = match[1].trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
7952
|
+
if (path === '' || path.startsWith('/') || path.split('/').some((part) => part === '..'))
|
|
7953
|
+
continue;
|
|
7954
|
+
if (path.startsWith(`features/${slug}/`))
|
|
7955
|
+
continue;
|
|
7956
|
+
paths.add(path);
|
|
7957
|
+
}
|
|
7958
|
+
return [...paths].sort();
|
|
7959
|
+
}
|
|
7960
|
+
function attributableDiff(root, slug, feature, status, changedSlugs) {
|
|
7961
|
+
if (changedSlugs.length !== 1 || changedSlugs[0] !== slug) {
|
|
7962
|
+
return { attributable: false, reason: changedSlugs.length > 1 ? 'ambiguous-feature-attribution' : 'feature-not-attributable' };
|
|
7963
|
+
}
|
|
7964
|
+
if (feature.manifestText === undefined)
|
|
7965
|
+
return { attributable: false, reason: 'change-manifest-unavailable' };
|
|
7966
|
+
const listed = manifestPaths(feature.manifestText, slug);
|
|
7967
|
+
const changedByPath = new Map(status.map((item) => [item.path, item.code]));
|
|
7968
|
+
const changed = listed.filter((path) => changedByPath.has(path));
|
|
7969
|
+
if (changed.length === 0)
|
|
7970
|
+
return { attributable: true, bytes: 0, base: 'HEAD', head: 'working-tree', method: 'git-unified-diff-bytes/v1', excludedFeaturePath: `features/${slug}/**` };
|
|
7971
|
+
const tracked = [];
|
|
7972
|
+
const untracked = [];
|
|
7973
|
+
for (const path of changed) {
|
|
7974
|
+
const absolute = resolve(root, path);
|
|
7975
|
+
if (!volumePathInside(root, absolute))
|
|
7976
|
+
return { attributable: false, reason: 'manifest-path-escape' };
|
|
7977
|
+
if (changedByPath.get(path) === '??') {
|
|
7978
|
+
try {
|
|
7979
|
+
const stat = lstatSync(absolute);
|
|
7980
|
+
const real = realpathSync(absolute);
|
|
7981
|
+
if (!stat.isFile() || !volumePathInside(root, real) || stat.size > MAX_VOLUME_BYTES_PER_SCOPE) {
|
|
7982
|
+
return { attributable: false, reason: 'untracked-diff-input-refused' };
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
catch {
|
|
7986
|
+
return { attributable: false, reason: 'untracked-diff-input-unreadable' };
|
|
7987
|
+
}
|
|
7988
|
+
untracked.push(path);
|
|
7989
|
+
}
|
|
7990
|
+
else {
|
|
7991
|
+
tracked.push(path);
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
let bytes = 0;
|
|
7995
|
+
try {
|
|
7996
|
+
if (tracked.length > 0) {
|
|
7997
|
+
const diff = volumeGitText(root, ['diff', '--binary', '--no-ext-diff', 'HEAD', '--', ...tracked]);
|
|
7998
|
+
bytes += Buffer.byteLength(diff, 'utf8');
|
|
7999
|
+
}
|
|
8000
|
+
for (const path of untracked) {
|
|
8001
|
+
const diff = volumeGitText(root, ['diff', '--no-index', '--binary', '--', '/dev/null', resolve(root, path)], [0, 1]);
|
|
8002
|
+
bytes += Buffer.byteLength(diff, 'utf8');
|
|
8003
|
+
if (bytes > MAX_VOLUME_BYTES_PER_SCOPE)
|
|
8004
|
+
return { attributable: false, reason: 'diff-byte-cap-exceeded' };
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
catch {
|
|
8008
|
+
return { attributable: false, reason: 'git-diff-failed' };
|
|
8009
|
+
}
|
|
8010
|
+
return {
|
|
8011
|
+
attributable: true,
|
|
8012
|
+
bytes,
|
|
8013
|
+
base: 'HEAD',
|
|
8014
|
+
head: 'working-tree',
|
|
8015
|
+
method: 'git-unified-diff-bytes/v1',
|
|
8016
|
+
excludedFeaturePath: `features/${slug}/**`,
|
|
8017
|
+
};
|
|
8018
|
+
}
|
|
8019
|
+
function gatherVolumeShadowFacts(root, packages) {
|
|
8020
|
+
const templates = packages
|
|
8021
|
+
.filter((item) => !item.privateFlag)
|
|
8022
|
+
.sort((a, b) => a.name.localeCompare(b.name) || a.dir.localeCompare(b.dir))
|
|
8023
|
+
.flatMap((item) => {
|
|
8024
|
+
const fact = gatherTemplateVolumeTarget(join(root, item.dir), item.name);
|
|
8025
|
+
return fact === undefined ? [] : [fact];
|
|
8026
|
+
});
|
|
8027
|
+
let status;
|
|
8028
|
+
try {
|
|
8029
|
+
status = parseGuardStatusPaths(root);
|
|
8030
|
+
}
|
|
8031
|
+
catch {
|
|
8032
|
+
return templates.length > 0 ? { templates } : {};
|
|
8033
|
+
}
|
|
8034
|
+
const slugs = [...new Set(status.flatMap((item) => {
|
|
8035
|
+
const match = item.path.match(/^features\/([^/]+)\//);
|
|
8036
|
+
return match?.[1] ? [match[1]] : [];
|
|
8037
|
+
}))].sort();
|
|
8038
|
+
const gathered = slugs.slice(0, 32).map((slug) => gatherFeatureVolumeFact(root, slug));
|
|
8039
|
+
const measuredFeatures = gathered.map((feature) => ({
|
|
8040
|
+
...feature.fact,
|
|
8041
|
+
diff: attributableDiff(root, feature.fact.slug, feature, status, slugs),
|
|
8042
|
+
}));
|
|
8043
|
+
const cappedFeatures = slugs.slice(32).map((slug) => ({
|
|
8044
|
+
slug,
|
|
8045
|
+
artifacts: [],
|
|
8046
|
+
collection: {
|
|
8047
|
+
complete: false,
|
|
8048
|
+
reason: 'feature-scope-cap-exceeded',
|
|
8049
|
+
detail: 'only the first 32 lexicographically sorted changed feature scopes were traversed',
|
|
8050
|
+
},
|
|
8051
|
+
}));
|
|
8052
|
+
const features = [...measuredFeatures, ...cappedFeatures];
|
|
8053
|
+
return {
|
|
8054
|
+
...(templates.length > 0 ? { templates } : {}),
|
|
8055
|
+
...(features.length > 0 ? { features } : {}),
|
|
8056
|
+
};
|
|
8057
|
+
}
|
|
7224
8058
|
/** Gather the facts one op needs. All I/O is best-effort — a missing signal skips its rule, never crashes. */
|
|
7225
8059
|
function gatherGuardFacts(op, root, text, storeCap) {
|
|
7226
8060
|
const facts = { op };
|
|
@@ -7355,7 +8189,7 @@ function gatherGuardFacts(op, root, text, storeCap) {
|
|
|
7355
8189
|
const manifests = [];
|
|
7356
8190
|
const located = [];
|
|
7357
8191
|
try {
|
|
7358
|
-
const out =
|
|
8192
|
+
const out = volumeGitText(root, ['ls-files', 'packages/@dzhechkov/*/package.json']);
|
|
7359
8193
|
for (const rel of out.split('\n').map((s) => s.trim()).filter(Boolean)) {
|
|
7360
8194
|
try {
|
|
7361
8195
|
const m = JSON.parse(readFileSync(join(root, rel), 'utf8'));
|
|
@@ -7384,6 +8218,11 @@ function gatherGuardFacts(op, root, text, storeCap) {
|
|
|
7384
8218
|
packages.push({ name: m.name ?? '(unnamed)', deps });
|
|
7385
8219
|
}
|
|
7386
8220
|
facts['packages'] = packages;
|
|
8221
|
+
facts['volume'] = gatherVolumeShadowFacts(root, located.map(({ dir, m }) => ({
|
|
8222
|
+
dir,
|
|
8223
|
+
name: m.name ?? dir,
|
|
8224
|
+
privateFlag: m.private === true,
|
|
8225
|
+
})));
|
|
7387
8226
|
// licence-hold (ADR-001, hermes-claude-adaptation): for each pack DECLARING a hold via a
|
|
7388
8227
|
// `licenseHold` field, hand the raw evidence to the pure checker. Best-effort: an unreadable
|
|
7389
8228
|
// LICENSE reads as absent (null), which the checker treats as a violation for a publishable
|
|
@@ -7661,6 +8500,10 @@ function runGuardEvaluation(root, op, text, overrideReason) {
|
|
|
7661
8500
|
catch { /* audit is best-effort, never blocks the verdict */ }
|
|
7662
8501
|
return result;
|
|
7663
8502
|
}
|
|
8503
|
+
function renderGuardObservation(observation) {
|
|
8504
|
+
const tag = observation.status === 'unknown' ? 'note' : 'observe';
|
|
8505
|
+
return ` [${tag}] ${observation.rule} ${observation.scope}: ${observation.detail} [${observation.status}]`;
|
|
8506
|
+
}
|
|
7664
8507
|
/**
|
|
7665
8508
|
* The tail facts of an append-only log, read from its END — O(1) in the file size, which is what
|
|
7666
8509
|
* lets the chain be extended on every append without a full-file scan (FR-2). Anything unreadable
|
|
@@ -8035,8 +8878,9 @@ function cmdGuardPromote(options, flags, root, write) {
|
|
|
8035
8878
|
...next,
|
|
8036
8879
|
entries: Object.fromEntries(Object.entries(next.entries).map(([k, v]) => (applied.includes(k) ? [k, { ...v, appliedTs: nowTs }] : [k, v]))),
|
|
8037
8880
|
};
|
|
8881
|
+
const withEvidence = recordPromotionRunEvidence(withApplied, report, nowTs);
|
|
8038
8882
|
try {
|
|
8039
|
-
writeJsonAtomic(join(root, PROMOTION_STATE_FILE),
|
|
8883
|
+
writeJsonAtomic(join(root, PROMOTION_STATE_FILE), withEvidence);
|
|
8040
8884
|
}
|
|
8041
8885
|
catch { /* best-effort */ }
|
|
8042
8886
|
}
|
|
@@ -8145,6 +8989,8 @@ function cmdGuard(options, flags, cwd, write) {
|
|
|
8145
8989
|
}
|
|
8146
8990
|
const glyph = result.verdict === 'block' ? '✗' : result.verdict === 'warn' ? '⚠' : '✓';
|
|
8147
8991
|
write(`dz guard (${op}): ${glyph} ${result.verdict.toUpperCase()} [checked: ${result.checked.join(', ') || 'no rules for this op'}]`);
|
|
8992
|
+
for (const observation of result.observations ?? [])
|
|
8993
|
+
write(renderGuardObservation(observation));
|
|
8148
8994
|
for (const v of result.violations)
|
|
8149
8995
|
write(` [${v.severity === 'hard' ? 'BLOCK' : 'warn'}] ${v.rule}: ${v.detail}`);
|
|
8150
8996
|
for (const n of result.notes ?? [])
|
|
@@ -12743,6 +13589,134 @@ function readOptionalText(path) {
|
|
|
12743
13589
|
return '';
|
|
12744
13590
|
}
|
|
12745
13591
|
}
|
|
13592
|
+
function localEvidenceReadReason(resource, error) {
|
|
13593
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
13594
|
+
return code === 'ENOENT' ? `${resource}-missing` : `${resource}-unreadable`;
|
|
13595
|
+
}
|
|
13596
|
+
function unavailablePromotionEvidence(reason) {
|
|
13597
|
+
return { source: { status: 'not-measured', reason }, acceptances: [], truncatedPeriods: [], acceptanceHistoryComplete: false };
|
|
13598
|
+
}
|
|
13599
|
+
function readPromotionRunEvidence(root) {
|
|
13600
|
+
let raw;
|
|
13601
|
+
try {
|
|
13602
|
+
raw = JSON.parse(readFileSync(join(root, PROMOTION_STATE_FILE), 'utf-8'));
|
|
13603
|
+
}
|
|
13604
|
+
catch (error) {
|
|
13605
|
+
const reason = error instanceof SyntaxError
|
|
13606
|
+
? 'promotion-journal-malformed'
|
|
13607
|
+
: localEvidenceReadReason('promotion-journal', error);
|
|
13608
|
+
return unavailablePromotionEvidence(reason);
|
|
13609
|
+
}
|
|
13610
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
13611
|
+
return unavailablePromotionEvidence('promotion-journal-malformed');
|
|
13612
|
+
}
|
|
13613
|
+
const record = raw;
|
|
13614
|
+
if (record['version'] !== 1 || !Object.hasOwn(record, 'runs')) {
|
|
13615
|
+
return unavailablePromotionEvidence(record['version'] === 1 ? 'promotion-history-not-recorded' : 'promotion-journal-malformed');
|
|
13616
|
+
}
|
|
13617
|
+
if (!Array.isArray(record['runs'])) {
|
|
13618
|
+
return unavailablePromotionEvidence('promotion-history-malformed');
|
|
13619
|
+
}
|
|
13620
|
+
const state = normalizePromotionState(raw);
|
|
13621
|
+
if (state.runs === undefined || state.runs.length !== record['runs'].length) {
|
|
13622
|
+
return unavailablePromotionEvidence('promotion-history-malformed');
|
|
13623
|
+
}
|
|
13624
|
+
if ((Object.hasOwn(record, 'acceptances') && (!Array.isArray(record['acceptances']) || state.acceptances?.length !== record['acceptances'].length)) ||
|
|
13625
|
+
(Object.hasOwn(record, 'truncatedRunPeriods') && (!Array.isArray(record['truncatedRunPeriods']) || state.truncatedRunPeriods?.length !== record['truncatedRunPeriods'].length)) ||
|
|
13626
|
+
(Object.hasOwn(record, 'acceptanceHistoryComplete') && typeof record['acceptanceHistoryComplete'] !== 'boolean')) {
|
|
13627
|
+
return unavailablePromotionEvidence('promotion-history-malformed');
|
|
13628
|
+
}
|
|
13629
|
+
const derivedAcceptances = state.runs.flatMap((run) => run.candidates
|
|
13630
|
+
.filter((candidate) => candidate.verdict === 'promote' && candidate.ruleContentAnchor !== null)
|
|
13631
|
+
.map((candidate) => ({ ruleContentAnchor: candidate.ruleContentAnchor, acceptedTs: run.ts })));
|
|
13632
|
+
const claimedAcceptanceComplete = state.acceptanceHistoryComplete ??
|
|
13633
|
+
((state.truncatedRunPeriods?.length ?? 0) === 0 && state.runs.every((run) => run.complete === true));
|
|
13634
|
+
if (claimedAcceptanceComplete && state.acceptances !== undefined && derivedAcceptances.some((derived) => !state.acceptances.some((stored) => stored.ruleContentAnchor === derived.ruleContentAnchor &&
|
|
13635
|
+
Date.parse(stored.acceptedTs) <= Date.parse(derived.acceptedTs)))) {
|
|
13636
|
+
return unavailablePromotionEvidence('promotion-history-malformed');
|
|
13637
|
+
}
|
|
13638
|
+
const acceptancesByAnchor = new Map();
|
|
13639
|
+
for (const acceptance of [...(state.acceptances ?? []), ...derivedAcceptances]) {
|
|
13640
|
+
const prior = acceptancesByAnchor.get(acceptance.ruleContentAnchor);
|
|
13641
|
+
if (prior === undefined || Date.parse(acceptance.acceptedTs) < Date.parse(prior.acceptedTs)) {
|
|
13642
|
+
acceptancesByAnchor.set(acceptance.ruleContentAnchor, acceptance);
|
|
13643
|
+
}
|
|
13644
|
+
}
|
|
13645
|
+
return {
|
|
13646
|
+
source: { status: 'measured', rows: state.runs },
|
|
13647
|
+
acceptances: [...acceptancesByAnchor.values()],
|
|
13648
|
+
truncatedPeriods: state.truncatedRunPeriods ?? [],
|
|
13649
|
+
acceptanceHistoryComplete: claimedAcceptanceComplete,
|
|
13650
|
+
};
|
|
13651
|
+
}
|
|
13652
|
+
function readGuardAuditEvidence(root) {
|
|
13653
|
+
let text;
|
|
13654
|
+
try {
|
|
13655
|
+
text = readFileSync(join(root, '.dz', 'guard-audit.jsonl'), 'utf-8');
|
|
13656
|
+
}
|
|
13657
|
+
catch (error) {
|
|
13658
|
+
return {
|
|
13659
|
+
source: { status: 'not-measured', reason: localEvidenceReadReason('guard-audit', error) },
|
|
13660
|
+
rows: [],
|
|
13661
|
+
text: null,
|
|
13662
|
+
};
|
|
13663
|
+
}
|
|
13664
|
+
const rows = [];
|
|
13665
|
+
let malformed = false;
|
|
13666
|
+
for (const line of text.split('\n')) {
|
|
13667
|
+
if (line.trim() === '')
|
|
13668
|
+
continue;
|
|
13669
|
+
try {
|
|
13670
|
+
const raw = JSON.parse(line);
|
|
13671
|
+
if (!isOffsetIsoTimestamp(raw['ts']) ||
|
|
13672
|
+
!['publish', 'teach', 'consolidate', 'reindex'].includes(String(raw['op'])) ||
|
|
13673
|
+
!['pass', 'warn', 'block'].includes(String(raw['verdict']))) {
|
|
13674
|
+
malformed = true;
|
|
13675
|
+
continue;
|
|
13676
|
+
}
|
|
13677
|
+
const violations = [];
|
|
13678
|
+
if (!Array.isArray(raw['violations'])) {
|
|
13679
|
+
malformed = true;
|
|
13680
|
+
continue;
|
|
13681
|
+
}
|
|
13682
|
+
for (const item of raw['violations']) {
|
|
13683
|
+
const rule = item && typeof item === 'object' ? item.rule : undefined;
|
|
13684
|
+
if (typeof rule !== 'string' || rule === '' || rule.length > 200) {
|
|
13685
|
+
malformed = true;
|
|
13686
|
+
continue;
|
|
13687
|
+
}
|
|
13688
|
+
const anchor = item.contentAnchor;
|
|
13689
|
+
if (anchor !== undefined && !isLessonRuleContentAnchor(anchor)) {
|
|
13690
|
+
malformed = true;
|
|
13691
|
+
continue;
|
|
13692
|
+
}
|
|
13693
|
+
violations.push({ rule, ...(typeof anchor === 'string' ? { contentAnchor: anchor } : {}) });
|
|
13694
|
+
}
|
|
13695
|
+
const verdict = raw['verdict'];
|
|
13696
|
+
if ((verdict === 'pass') !== (violations.length === 0))
|
|
13697
|
+
malformed = true;
|
|
13698
|
+
rows.push({
|
|
13699
|
+
ts: raw['ts'],
|
|
13700
|
+
op: raw['op'],
|
|
13701
|
+
verdict,
|
|
13702
|
+
rules: violations.map((item) => item.rule),
|
|
13703
|
+
violations,
|
|
13704
|
+
});
|
|
13705
|
+
}
|
|
13706
|
+
catch {
|
|
13707
|
+
malformed = true;
|
|
13708
|
+
}
|
|
13709
|
+
}
|
|
13710
|
+
return {
|
|
13711
|
+
source: malformed
|
|
13712
|
+
? { status: 'not-measured', reason: 'guard-audit-malformed' }
|
|
13713
|
+
: !verifyEventChainText(text).ok
|
|
13714
|
+
? { status: 'not-measured', reason: 'guard-audit-chain-corrupt' }
|
|
13715
|
+
: { status: 'measured', rows },
|
|
13716
|
+
rows,
|
|
13717
|
+
text,
|
|
13718
|
+
};
|
|
13719
|
+
}
|
|
12746
13720
|
function deadwoodAllowlistText() {
|
|
12747
13721
|
const require = createRequire(import.meta.url);
|
|
12748
13722
|
const corePackage = require.resolve('@dzhechkov/harness-core/package.json');
|
|
@@ -12823,7 +13797,7 @@ function cmdCompounding(options, flags, cwd, write) {
|
|
|
12823
13797
|
const json = flags.has('json');
|
|
12824
13798
|
if (flags.has('help')) {
|
|
12825
13799
|
write('dz compounding [--project <dir>] [--json] — honest learning-loop payoff report');
|
|
12826
|
-
write(' pool payoff
|
|
13800
|
+
write(' pool payoff · guard trajectory · replay readiness · instrumentation · monthly eligible→attempted→accepted→executions funnel');
|
|
12827
13801
|
return 0;
|
|
12828
13802
|
}
|
|
12829
13803
|
for (const flag of flags) {
|
|
@@ -12852,36 +13826,14 @@ function cmdCompounding(options, flags, cwd, write) {
|
|
|
12852
13826
|
}));
|
|
12853
13827
|
// apply-leg usage events (read records only; aggregate rows carry no query by construction)
|
|
12854
13828
|
const usage = readRecallUsageEvents(root);
|
|
12855
|
-
|
|
12856
|
-
const
|
|
12857
|
-
|
|
12858
|
-
const text = readFileSync(join(root, '.dz', 'guard-audit.jsonl'), 'utf-8');
|
|
12859
|
-
for (const line of text.split('\n')) {
|
|
12860
|
-
if (line.trim() === '')
|
|
12861
|
-
continue;
|
|
12862
|
-
try {
|
|
12863
|
-
const o = JSON.parse(line);
|
|
12864
|
-
if (typeof o.ts === 'string' && typeof o.verdict === 'string') {
|
|
12865
|
-
guard.push({
|
|
12866
|
-
ts: o.ts,
|
|
12867
|
-
verdict: o.verdict,
|
|
12868
|
-
rules: Array.isArray(o.violations) ? o.violations.map((v) => (typeof v?.rule === 'string' ? v.rule : '')).filter(Boolean) : [],
|
|
12869
|
-
});
|
|
12870
|
-
}
|
|
12871
|
-
}
|
|
12872
|
-
catch {
|
|
12873
|
-
/* skip */
|
|
12874
|
-
}
|
|
12875
|
-
}
|
|
12876
|
-
}
|
|
12877
|
-
catch {
|
|
12878
|
-
/* no audit yet */
|
|
12879
|
-
}
|
|
13829
|
+
const promotionEvidence = readPromotionRunEvidence(root);
|
|
13830
|
+
const guardEvidence = readGuardAuditEvidence(root);
|
|
13831
|
+
const guard = [...guardEvidence.rows];
|
|
12880
13832
|
// The evidence logs themselves, verbatim: the report verifies their hash chains (feature
|
|
12881
13833
|
// event-chain). Handing over the TEXT rather than a pre-computed verdict keeps one definition of
|
|
12882
13834
|
// "the chain is intact" — a second copy here is how a gate and its report start disagreeing.
|
|
12883
13835
|
const evidenceLogs = [];
|
|
12884
|
-
for (const rel of ['.dz/recall-usage.jsonl'
|
|
13836
|
+
for (const rel of ['.dz/recall-usage.jsonl']) {
|
|
12885
13837
|
try {
|
|
12886
13838
|
evidenceLogs.push({ log: rel, text: readFileSync(join(root, ...rel.split('/')), 'utf-8') });
|
|
12887
13839
|
}
|
|
@@ -12889,6 +13841,9 @@ function cmdCompounding(options, flags, cwd, write) {
|
|
|
12889
13841
|
/* absent log — reported by its own gate above, not invented here */
|
|
12890
13842
|
}
|
|
12891
13843
|
}
|
|
13844
|
+
if (guardEvidence.text !== null) {
|
|
13845
|
+
evidenceLogs.push({ log: '.dz/guard-audit.jsonl', text: guardEvidence.text });
|
|
13846
|
+
}
|
|
12892
13847
|
const now = new Date();
|
|
12893
13848
|
const report = assembleCompoundingReport({
|
|
12894
13849
|
lessons,
|
|
@@ -12897,6 +13852,13 @@ function cmdCompounding(options, flags, cwd, write) {
|
|
|
12897
13852
|
nowTs: now.toISOString(),
|
|
12898
13853
|
evidenceLogs,
|
|
12899
13854
|
cmdUsageDepthDays: cmdUsageDepthDays(root, now),
|
|
13855
|
+
lessonToRule: {
|
|
13856
|
+
promotionRuns: promotionEvidence.source,
|
|
13857
|
+
guardAudits: guardEvidence.source,
|
|
13858
|
+
promotionAcceptances: promotionEvidence.acceptances,
|
|
13859
|
+
truncatedPromotionPeriods: promotionEvidence.truncatedPeriods,
|
|
13860
|
+
acceptanceHistoryComplete: promotionEvidence.acceptanceHistoryComplete,
|
|
13861
|
+
},
|
|
12900
13862
|
});
|
|
12901
13863
|
// lesson-bandit-rerank §11: the payoff axis joins THIS report rather than growing a private
|
|
12902
13864
|
// dashboard — the `rewardEvents : exposureEvents` row asks exactly the question this command
|
|
@@ -14921,11 +15883,11 @@ export async function runCli(argv, io = {}) {
|
|
|
14921
15883
|
case 'bundle':
|
|
14922
15884
|
return cmdBundle(options, flags, cwd, write);
|
|
14923
15885
|
case 'teach':
|
|
14924
|
-
return await cmdTeach(options, flags, cwd, write);
|
|
15886
|
+
return await cmdTeach(options, flags, cwd, write, writeErr, io.interactive ?? process.stdout.isTTY === true, io.teachGuardRunner ?? teachGuard, io.teachReinforceRunner ?? runTeachGuardReinforcement);
|
|
14925
15887
|
case 'consolidate':
|
|
14926
15888
|
return await cmdConsolidate(options, flags, cwd, write);
|
|
14927
15889
|
case 'recall':
|
|
14928
|
-
return await cmdRecall(options, flags, cwd, write);
|
|
15890
|
+
return await cmdRecall(options, flags, cwd, write, writeErr, io.classMatcher);
|
|
14929
15891
|
case 'vector':
|
|
14930
15892
|
return await cmdVector(options, flags, cwd, write);
|
|
14931
15893
|
case 'brain':
|
|
@@ -14978,6 +15940,8 @@ export async function runCli(argv, io = {}) {
|
|
|
14978
15940
|
return cmdDriftCheck(options, flags, cwd, write);
|
|
14979
15941
|
case 'hooks-sync':
|
|
14980
15942
|
return cmdHooksSync(options, flags, cwd, write, writeErr);
|
|
15943
|
+
case 'integrations-verify':
|
|
15944
|
+
return cmdIntegrationsVerify(options, flags, cwd, write, writeErr);
|
|
14981
15945
|
case 'agents-sync':
|
|
14982
15946
|
return cmdAgentsSync(options, flags, cwd, write, writeErr);
|
|
14983
15947
|
case 'sync-canonical':
|