@dzhechkov/harness-core 0.8.2 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.dz-manifest.json +146 -58
  2. package/README.md +72 -2
  3. package/dist/cmd-usage.d.ts +148 -0
  4. package/dist/cmd-usage.d.ts.map +1 -0
  5. package/dist/cmd-usage.js +548 -0
  6. package/dist/cmd-usage.js.map +1 -0
  7. package/dist/compounding.d.ts +4 -0
  8. package/dist/compounding.d.ts.map +1 -1
  9. package/dist/compounding.js +6 -0
  10. package/dist/compounding.js.map +1 -1
  11. package/dist/contract-checklist.d.ts +123 -0
  12. package/dist/contract-checklist.d.ts.map +1 -0
  13. package/dist/contract-checklist.js +700 -0
  14. package/dist/contract-checklist.js.map +1 -0
  15. package/dist/feature-adr-checkpoints.d.ts +11 -2
  16. package/dist/feature-adr-checkpoints.d.ts.map +1 -1
  17. package/dist/feature-adr-checkpoints.js +37 -2
  18. package/dist/feature-adr-checkpoints.js.map +1 -1
  19. package/dist/feature-adr-routing.d.ts +58 -23
  20. package/dist/feature-adr-routing.d.ts.map +1 -1
  21. package/dist/feature-adr-routing.js +208 -59
  22. package/dist/feature-adr-routing.js.map +1 -1
  23. package/dist/guard.d.ts +25 -0
  24. package/dist/guard.d.ts.map +1 -1
  25. package/dist/guard.js +59 -1
  26. package/dist/guard.js.map +1 -1
  27. package/dist/index.d.ts +9 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +8 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/loop-blobs.generated.js +8 -8
  32. package/dist/loop-blobs.generated.js.map +1 -1
  33. package/dist/loop-plan.d.ts +13 -1
  34. package/dist/loop-plan.d.ts.map +1 -1
  35. package/dist/loop-plan.js +15 -1
  36. package/dist/loop-plan.js.map +1 -1
  37. package/dist/loop-render.d.ts.map +1 -1
  38. package/dist/loop-render.js +51 -6
  39. package/dist/loop-render.js.map +1 -1
  40. package/dist/loop-trace.d.ts +20 -1
  41. package/dist/loop-trace.d.ts.map +1 -1
  42. package/dist/loop-trace.js +83 -1
  43. package/dist/loop-trace.js.map +1 -1
  44. package/dist/model-recommender.d.ts +8 -0
  45. package/dist/model-recommender.d.ts.map +1 -1
  46. package/dist/model-recommender.js +31 -4
  47. package/dist/model-recommender.js.map +1 -1
  48. package/dist/qe-bridge.d.ts.map +1 -1
  49. package/dist/qe-bridge.js +9 -0
  50. package/dist/qe-bridge.js.map +1 -1
  51. package/dist/restart-advisor.d.ts +103 -0
  52. package/dist/restart-advisor.d.ts.map +1 -0
  53. package/dist/restart-advisor.js +445 -0
  54. package/dist/restart-advisor.js.map +1 -0
  55. package/dist/slop-lint.d.ts +128 -0
  56. package/dist/slop-lint.d.ts.map +1 -0
  57. package/dist/slop-lint.js +607 -0
  58. package/dist/slop-lint.js.map +1 -0
  59. package/dist/workflow-run.d.ts.map +1 -1
  60. package/dist/workflow-run.js +18 -12
  61. package/dist/workflow-run.js.map +1 -1
  62. package/package.json +19 -15
  63. package/sbom.json +277 -57
  64. package/src/cmd-usage.ts +720 -0
  65. package/src/compounding.ts +13 -0
  66. package/src/contract-checklist.ts +973 -0
  67. package/src/deadwood-allowlist.json +80 -0
  68. package/src/feature-adr-checkpoints.ts +38 -2
  69. package/src/feature-adr-routing.ts +238 -55
  70. package/src/guard.ts +79 -1
  71. package/src/index.ts +81 -1
  72. package/src/loop-blobs.generated.ts +8 -8
  73. package/src/loop-plan.ts +36 -3
  74. package/src/loop-render.ts +50 -6
  75. package/src/loop-trace.ts +91 -2
  76. package/src/model-recommender.ts +35 -4
  77. package/src/qe-bridge.ts +9 -0
  78. package/src/restart-advisor.ts +579 -0
  79. package/src/slop-lint.ts +762 -0
  80. package/src/slop-markers.json +71 -0
  81. package/src/workflow-run.ts +18 -11
package/src/guard.ts CHANGED
@@ -69,6 +69,29 @@ export interface GuardFacts {
69
69
  readonly counts?: readonly { readonly label: string; readonly a: number; readonly b: number }[];
70
70
  /** for store-bloat-cap: current learned-store size vs its cap. */
71
71
  readonly store?: { readonly count: number; readonly cap: number };
72
+ /** Advisory freshness evidence for the auto-cost outcome store. Missing evidence fails open. */
73
+ readonly routingFreshness?: { readonly unfedRunIds: readonly string[] };
74
+ /**
75
+ * for marketplace-parity: the CLI's read-only regeneration result. The whole field being absent
76
+ * means evidence could not be gathered, so this advisory rule reports nothing.
77
+ */
78
+ readonly marketplaceParity?: {
79
+ /** false ⇒ this repository has no .claude-plugin/ showcase and is out of scope. */
80
+ readonly applicable: boolean;
81
+ /** true ⇒ exactly one of plugin.json / marketplace.json is present. */
82
+ readonly onlyOnePresent?: boolean;
83
+ /** true ⇒ registry-derived composition differs from a fresh regeneration. */
84
+ readonly diverged?: boolean;
85
+ /** Operator-owned published version, carried only to render the exact repair command. */
86
+ readonly publishedVersion?: string;
87
+ /** Existing published manifests that could not be read or parsed. */
88
+ readonly manifestFailures?: readonly {
89
+ readonly file: 'plugin.json' | 'marketplace.json';
90
+ readonly error: string;
91
+ }[];
92
+ /** true ⇒ regeneration was attempted but could not complete. */
93
+ readonly regenerateFailed?: boolean;
94
+ };
72
95
  /** for skills-registrable: per skill pack, dirs that would ship un-registrable (no depth-1 SKILL.md). */
73
96
  readonly skillPacks?: readonly { readonly name: string; readonly nonRegistrable: readonly string[] }[];
74
97
  /** for readme-first: per publishable package, is a version bump staged without a README change? */
@@ -262,6 +285,8 @@ export const DEFAULT_RULES: readonly GuardRule[] = [
262
285
  { id: 'readme-consistency', severity: 'soft', ops: ['publish'], description: 'README counts agree (CJM header vs All Commands, etc.)' },
263
286
  { id: 'skills-registrable', severity: 'soft', ops: ['publish'], description: 'every skill directory in a skill pack has a depth-1 SKILL.md (a buried or missing one ships un-registrable — the health-advisor 1.2.0 class)' },
264
287
  { id: 'readme-first', severity: 'soft', ops: ['publish'], description: 'a package with a staged version bump must update its own README.md in the same change (README-first)' },
288
+ { id: 'routing-store-stale', severity: 'soft', ops: ['publish'], description: 'harvested routing telemetry has been applied to the auto-cost outcome store' },
289
+ { id: 'marketplace-parity', severity: 'soft', ops: ['publish'], description: 'the published .claude-plugin/ showcase composition matches a fresh regeneration from the live registry (version excluded — an operator field)' },
265
290
  { id: 'agents-md-policy-sync', severity: 'soft', ops: ['publish'], description: 'proves the AGENTS.md copy is in SYNC with its source — not that the runtime read or obeyed it; heal drift with dz agents-sync' },
266
291
  { id: 'lockfile-in-sync', severity: 'soft', ops: ['publish'], description: 'every workspace @dzhechkov/* dependency spec matches the specifier pnpm-lock.yaml records for that importer (a dep bump without a lockfile refresh breaks CI with ERR_PNPM_OUTDATED_LOCKFILE). SOFT-ONLY — a config cannot promote it to HARD' },
267
292
  { id: 'store-bloat-cap', severity: 'soft', ops: ['teach', 'consolidate'], description: 'the learned store is within its size cap' },
@@ -360,6 +385,59 @@ const CHECKERS: Record<string, (f: GuardFacts, sev: GuardSeverity) => Violation[
360
385
  }
361
386
  return out;
362
387
  },
388
+ 'routing-store-stale': (f, _sev) => {
389
+ const ids = f.routingFreshness?.unfedRunIds;
390
+ if (!Array.isArray(ids) || ids.length === 0) return [];
391
+ const valid = [...new Set(ids.filter((id): id is string => typeof id === 'string' && id !== ''))].sort();
392
+ if (valid.length === 0) return [];
393
+ return [{
394
+ rule: 'routing-store-stale',
395
+ severity: 'soft',
396
+ detail: `${valid.length} harvested run(s) are not reflected in the auto-cost store: ${valid.slice(0, 5).join(', ')}${valid.length > 5 ? '…' : ''} — run dz routing recommend --apply`,
397
+ }];
398
+ },
399
+ 'marketplace-parity': (f, _sev) => {
400
+ const fact = f.marketplaceParity;
401
+ if (fact === undefined || fact.applicable !== true) return [];
402
+ const hasPublishedVersion = typeof fact.publishedVersion === 'string' && fact.publishedVersion !== '';
403
+ const fix = `dz plugin --version ${hasPublishedVersion ? fact.publishedVersion : 'X.Y.Z'}`;
404
+ const fixHint = hasPublishedVersion ? '' : ' (substitute the published version for X.Y.Z)';
405
+ const manifestFailures = Array.isArray(fact.manifestFailures)
406
+ ? fact.manifestFailures.filter((failure) => failure
407
+ && (failure.file === 'plugin.json' || failure.file === 'marketplace.json')
408
+ && typeof failure.error === 'string'
409
+ && failure.error !== '')
410
+ : [];
411
+ if (manifestFailures.length > 0) {
412
+ return manifestFailures.map((failure) => ({
413
+ rule: 'marketplace-parity',
414
+ severity: 'soft',
415
+ detail: `.claude-plugin/${failure.file} exists but could not be read or parsed: ${failure.error} — run \`${fix}\`${fixHint} and commit the result`,
416
+ }));
417
+ }
418
+ if (fact.onlyOnePresent === true) {
419
+ return [{
420
+ rule: 'marketplace-parity',
421
+ severity: 'soft',
422
+ detail: `.claude-plugin/ has only one of plugin.json / marketplace.json — a broken half-showcase; run \`${fix}\`${fixHint} and commit the result`,
423
+ }];
424
+ }
425
+ if (fact.regenerateFailed === true) {
426
+ return [{
427
+ rule: 'marketplace-parity',
428
+ severity: 'soft',
429
+ detail: 'could not verify .claude-plugin/ composition because fresh regeneration failed',
430
+ }];
431
+ }
432
+ if (fact.diverged === true) {
433
+ return [{
434
+ rule: 'marketplace-parity',
435
+ severity: 'soft',
436
+ detail: `.claude-plugin/ composition does not match a fresh regeneration from the live registry — run \`${fix}\`${fixHint} and commit the result`,
437
+ }];
438
+ }
439
+ return [];
440
+ },
363
441
  'review-round': (f, sev) => {
364
442
  // The publish gate had eleven rules and not one asked whether anyone but the author had read the
365
443
  // code. MEASURED cost (health-advisor slice H): five rounds graded F, thirteen packages published
@@ -508,7 +586,7 @@ const CHECKERS: Record<string, (f: GuardFacts, sev: GuardSeverity) => Violation[
508
586
  * may not understand a file, and "I might be wrong" plus "block the publish" is the wrong pair. Disabling
509
587
  * such a rule stays allowed — only the promotion is refused.
510
588
  */
511
- export const SOFT_ONLY_RULES: readonly string[] = ['lockfile-in-sync', 'agents-md-policy-sync'];
589
+ export const SOFT_ONLY_RULES: readonly string[] = ['lockfile-in-sync', 'agents-md-policy-sync', 'routing-store-stale', 'marketplace-parity'];
512
590
 
513
591
  /**
514
592
  * A well-formed PROMOTED rule: an id the engine does not know, made enforceable by a template +
package/src/index.ts CHANGED
@@ -224,6 +224,28 @@ export type { PretrainResult, DetectedTech } from './pretrain.js';
224
224
  export type { RecommendationReport, SkillRecommendation } from './recommend.js';
225
225
  export { claimCheck, summarize, decideClaimCheckText, severityCounts, isGated } from './claim-check.js';
226
226
  export type { ClaimFinding, ClaimCheckResult, ClaimTextDecision, FailOn } from './claim-check.js';
227
+ export {
228
+ BUNDLED_SLOP_REGISTRY_URL,
229
+ DEFAULT_SLOP_CONFIG,
230
+ parseSlopRegistry,
231
+ slopLint,
232
+ validateSlopLintConfig,
233
+ } from './slop-lint.js';
234
+ export type {
235
+ SlopDiagnostic,
236
+ SlopEvidence,
237
+ SlopFinding,
238
+ SlopFindingMetrics,
239
+ SlopFindingThresholds,
240
+ SlopLanguage,
241
+ SlopLintConfig,
242
+ SlopLintResult,
243
+ SlopRegistry,
244
+ SlopRegistryEntry,
245
+ SlopRuleId,
246
+ SlopValidationError,
247
+ ValidationResult,
248
+ } from './slop-lint.js';
227
249
  export { hookDecision, isFenced, isNewLine, ESCAPE_TEACHING } from './claim-check-hook-policy.js';
228
250
  export type { HookDecision, HookDecisionOpts } from './claim-check-hook-policy.js';
229
251
  export { step8ClaimGate } from './feature-adr-claim-gate.js';
@@ -261,7 +283,7 @@ export {
261
283
  codeCheckpointPersistAllowed,
262
284
  codeStageResultShapeValid,
263
285
  } from './feature-adr-checkpoints.js';
264
- export type { CheckpointStage, CheckpointEntry, ResumeMode, ResumeDecision, ParsedCheckpointRead, TrainingPairFamily, TrainingPair, TrainingPairEvaluation, TrainingPairProvenance, TrainingPairTruncation,
286
+ export type { CheckpointStage, CheckpointEntry, ResumeMode, ResumeDecision, ParsedCheckpointRead, TrainingPairFamily, TrainingPair, TrainingPairEvaluation, TrainingPairProvenance, TrainingPairBudget, TrainingPairTruncation,
265
287
  CheckpointWriteVerdict,
266
288
  } from './feature-adr-checkpoints.js';
267
289
 
@@ -290,6 +312,35 @@ export { decidePublishSigning, decidePostSigningVerification, decideSignableSet,
290
312
  export type { PublishSigningVerdict, PublishSigningDecision, SignableSetDecision } from './publish-signing.js';
291
313
  export type { RecordKind, RecordVerdict, RecordDecision } from './run-records.js';
292
314
  export type { AmendmentRow, AmendmentVerdict, AmendmentResolution, AmendmentOutcome, AmendmentDecision, PlanCoverageGap } from './amendment-trace.js';
315
+ // contract-checklist (ADR-001): pure extraction, canonical rendering, typed report parsing, and
316
+ // exact per-item verification. Filesystem discovery/containment stays in harness-cli.
317
+ export {
318
+ extractContractChecklist,
319
+ renderContractChecklist,
320
+ parseContractVerdictReport,
321
+ verifyContractVerdicts,
322
+ } from './contract-checklist.js';
323
+ export type {
324
+ ContractSourceKind,
325
+ ContractVerdict,
326
+ ContractObservedOutcome,
327
+ ContractGrade,
328
+ ContractSourceArtifact,
329
+ ContractChecklistSource,
330
+ ContractItem,
331
+ ContractChecklist,
332
+ ContractDiagnostic,
333
+ ContractChecklistResult,
334
+ ContractVerdictEvidence,
335
+ ContractVerdictItem,
336
+ ContractVerdictReport,
337
+ ContractVerdictParseResult,
338
+ ContractEvidenceReadResult,
339
+ ContractEvidenceReader,
340
+ ContractItemVerification,
341
+ ContractVerificationCounts,
342
+ ContractVerification,
343
+ } from './contract-checklist.js';
293
344
  export {
294
345
  DOMAIN_LIFT_EXACT,
295
346
  DOMAIN_LIFT_RELATED,
@@ -576,6 +627,7 @@ export {
576
627
  resolveCoderSpec,
577
628
  coderIsCodex,
578
629
  resolveQeSpec,
630
+ resolveQeSpecForCoder,
579
631
  crossFamilyQe,
580
632
  decideModeBScope,
581
633
  partitionReviewFindings,
@@ -792,6 +844,9 @@ export * from './skills-verify.js';
792
844
  // finding, not a pass).
793
845
  export * from './compounding.js';
794
846
 
847
+ // Advisory command-invocation telemetry + deadwood report (feature dz-deadwood).
848
+ export * from './cmd-usage.js';
849
+
795
850
  // Cold-vs-warm EPOCH RUNNER (feature epoch-replay, scout idea #4) — the RESULT leg to compounding's
796
851
  // readiness leg. Orchestrates + scores; never calls a model. SUPPORTED requires two DISJOINT Wilson
797
852
  // intervals; INCONCLUSIVE is a first-class honest outcome.
@@ -872,6 +927,31 @@ export { decideCadenceWindow, isoWeekOf, weeklyBuckets, guardRepeatDecay, buildC
872
927
  export type { CadenceWindow, CadenceReport, CadenceWindowDecision } from './cadence.js';
873
928
  export { readQeRounds, countQeRounds, QE_ROUNDS_DEFAULT_CEILING } from './qe-rounds.js';
874
929
  export type { QeRound, QeFailedAttempt, QeRoundsReport, QeRoundsStatus } from './qe-rounds.js';
930
+ export {
931
+ adviseRestart,
932
+ decideRestartRecommendation,
933
+ parseCheckpointQeHistory,
934
+ parseTrainingPairQeHistory,
935
+ renderRestartDecisionLog,
936
+ RESTART_ADVISOR_SCHEMA,
937
+ RESTART_ADVISOR_MAX_DIAGNOSTICS,
938
+ RESTART_ADVISOR_MAX_EVIDENCE,
939
+ } from './restart-advisor.js';
940
+ export type {
941
+ ParsedRestartHistory,
942
+ RestartAdvice,
943
+ RestartAdvisorInput,
944
+ RestartAdvisorPolicy,
945
+ RestartDecision,
946
+ RestartDecisionInput,
947
+ RestartGrade,
948
+ RestartNormalizedRound,
949
+ RestartPolicyOrigin,
950
+ RestartReason,
951
+ RestartRecommendation,
952
+ RestartSource,
953
+ RestartThreshold,
954
+ } from './restart-advisor.js';
875
955
  export { describeStoreLocation, storeLocationLine } from './store-location.js';
876
956
  export type { StoreLocation, StoreOrigin } from './store-location.js';
877
957
  export { mergeStoreHits, sameStore, globalStoreRoot, storeCountLabel } from './store-merge.js';
@@ -42,7 +42,7 @@ export const BLOB_COVERAGE_MANIFEST: { coveredWorkflows: string[] } = {
42
42
  export const BLOBS: Record<string, LoopBlob> = {
43
43
  "checkpoints": {
44
44
  name: "checkpoints",
45
- version: "1.1.0",
45
+ version: "1.2.0",
46
46
  contentHash: "a44560c6036fd143b7a3f125fec00fa8b91c3c06ac5b9e1ecfb83d27a5211e9b",
47
47
  sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-checkpoints.ts",
48
48
  requires: [],
@@ -51,30 +51,30 @@ export const BLOBS: Record<string, LoopBlob> = {
51
51
  },
52
52
  "training-pairs": {
53
53
  name: "training-pairs",
54
- version: "1.1.0",
55
- contentHash: "199eaaa8ce5dba4f54744b4a4f34eea32a3c33d4de8dbbddd3d119b70459361d",
54
+ version: "1.2.0",
55
+ contentHash: "7c836995b72f0b8fc69074cf6bb7b8a58fac609e4f5c365c82b8745c655da39c",
56
56
  sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-checkpoints.ts",
57
57
  requires: ["checkpoints"],
58
58
  exports: ["TRAINPAIR_SCHEMA_VERSION","TRAINPAIR_MAX_IO_CHARS","trainingPairFamily","trainingPairPath","TRAINPAIR_PRIVACY_NOTE","buildTrainingPair","serializeTrainingPair","trainingPairAppendCmd","decideCaptureMode","captureFailureRecord","trainingPairBackfillCmd","TP_BACKFILL_OK","TP_BACKFILL_SKIP"],
59
- code: "function decideCaptureMode(opts) {\n if (!opts.enabled)\n return 'skip-disabled';\n if (!Number.isInteger(opts.recordCount) || opts.recordCount <= 0)\n return 'skip-empty';\n return opts.resumed ? 'backfill' : 'capture';\n}\nfunction captureFailureRecord(stage, mode, reason, detail) {\n const normalizedStage = typeof stage === 'string' && stage.trim() !== '' ? stage : 'unknown';\n const normalizedMode = mode === 'capture' || mode === 'backfill' || mode === 'skip-disabled' || mode === 'skip-empty'\n ? mode\n : null;\n const normalizedReason = reason === 'threw' || reason === 'unserializable' || reason === 'unverified' || reason === 'backfill-unverified' || reason === 'empty-output'\n ? reason\n : 'threw';\n let normalizedDetail = null;\n if (detail !== null && detail !== undefined) {\n try {\n const text = String(detail);\n if (text !== '')\n normalizedDetail = text.length > 500 ? text.slice(0, 500) + '…' : text;\n }\n catch {\n normalizedDetail = null;\n }\n }\n return { stage: normalizedStage, mode: normalizedMode, reason: normalizedReason, detail: normalizedDetail };\n}\nconst TRAINPAIR_SCHEMA_VERSION = 'fa-trainpair-2';\nconst TRAINPAIR_MAX_IO_CHARS = 48000;\nfunction trainingPairFamily(spec) {\n return /codex|gpt|openai/i.test(String(spec ?? '')) ? 'codex' : 'claude';\n}\nfunction trainingPairPath(slug, stage) {\n return '.dz/fa-training/' + slug + '/' + stage + '.jsonl';\n}\nconst TRAINPAIR_PRIVACY_NOTE = \"feature-adr TRAINING PAIRS (backlog 70e0f083): per-stage SFT records - STAGE INPUT (full prompt/context) -> STAGE OUTPUT (artifact/result) -> EVALUATION (QE grade + injected lessons) with model+family provenance; one JSONL file per stage per slug. PRIVACY: pairs may contain TARGET-REPO CODE and full prompts. This directory is NOT gitignored yet by explicit owner decision - review contents before sharing or publishing anything that embeds it. ts is the CAPTURE time. On a record with captureMode: 'backfill' that is the RECONSTRUCTION time, NOT the stage's observation time — the original stage's timing lives in that run's .fa-state checkpoint.\";\nconst TP_PROFILE_MARKER_START = '<!-- dz:profile:start -->';\nconst TP_PROFILE_MARKER_END = '<!-- dz:profile:end -->';\nconst TP_PROFILE_REDACTED = '[dz:profile REDACTED]';\nfunction redactProfileBlock(text) {\n if (typeof text !== 'string' || text === '')\n return typeof text === 'string' ? text : '';\n let out = '';\n let rest = text;\n for (;;) {\n const start = rest.indexOf(TP_PROFILE_MARKER_START);\n if (start === -1)\n return out + rest;\n out += rest.slice(0, start) + TP_PROFILE_REDACTED;\n const end = rest.indexOf(TP_PROFILE_MARKER_END, start + TP_PROFILE_MARKER_START.length);\n if (end === -1)\n return out;\n rest = rest.slice(end + TP_PROFILE_MARKER_END.length);\n }\n}\nfunction coerceText(v) {\n if (typeof v === 'string')\n return v;\n if (v === null || v === undefined)\n return '';\n try {\n const s = JSON.stringify(v);\n return typeof s === 'string' ? s : String(v);\n }\n catch {\n return String(v);\n }\n}\nfunction buildTrainingPair(opts) {\n let input = redactProfileBlock(coerceText(opts.input));\n let output = redactProfileBlock(coerceText(opts.output));\n let truncated = null;\n if (input.length + output.length > TRAINPAIR_MAX_IO_CHARS) {\n truncated = { inputChars: input.length, outputChars: output.length, inputHash: fnv1a64(input), outputHash: fnv1a64(output) };\n const half = Math.floor(TRAINPAIR_MAX_IO_CHARS / 2);\n let inKeep = input.length;\n let outKeep = output.length;\n if (outKeep <= half)\n inKeep = TRAINPAIR_MAX_IO_CHARS - outKeep;\n else if (inKeep <= half)\n outKeep = TRAINPAIR_MAX_IO_CHARS - inKeep;\n else {\n inKeep = half;\n outKeep = TRAINPAIR_MAX_IO_CHARS - half;\n }\n if (inKeep < input.length)\n input = input.slice(0, inKeep) + '\\n…[TRUNCATED ' + (truncated.inputChars - inKeep) + ' chars — full-text fnv1a64=' + truncated.inputHash + ']';\n if (outKeep < output.length)\n output = output.slice(0, outKeep) + '\\n…[TRUNCATED ' + (truncated.outputChars - outKeep) + ' chars — full-text fnv1a64=' + truncated.outputHash + ']';\n }\n const ev = opts.evaluation || {};\n const pv = opts.provenance || {};\n return {\n schema: TRAINPAIR_SCHEMA_VERSION,\n slug: opts.slug,\n stage: opts.stage,\n ts: opts.ts === undefined ? null : opts.ts,\n input,\n output,\n evaluation: {\n grade: typeof ev.grade === 'string' && ev.grade.trim() !== '' ? ev.grade : null,\n gradedBy: typeof ev.gradedBy === 'string' && ev.gradedBy !== '' ? ev.gradedBy : null,\n lessonsInjected: Array.isArray(ev.lessonsInjected) ? ev.lessonsInjected.filter((s) => typeof s === 'string' && s !== '') : [],\n },\n provenance: {\n model: typeof pv.model === 'string' && pv.model !== '' ? pv.model : 'unknown',\n family: pv.family === 'claude' || pv.family === 'codex' ? pv.family : trainingPairFamily(pv.model),\n role: typeof pv.role === 'string' && pv.role !== '' ? pv.role : 'unknown',\n tokens: typeof pv.tokens === 'number' && Number.isFinite(pv.tokens) ? pv.tokens : null,\n minutes: typeof pv.minutes === 'number' && Number.isFinite(pv.minutes) ? pv.minutes : null,\n },\n truncated,\n captureMode: opts.captureMode === 'backfill' ? 'backfill' : 'capture',\n resumed: opts.resumed === true,\n };\n}\nfunction serializeTrainingPair(pair) {\n try {\n const line = JSON.stringify(pair);\n return typeof line === 'string' ? line : null;\n }\n catch {\n return null;\n }\n}\nfunction trainingPairAppendCmd(repoAbs, slug, stage, line) {\n const dirAbs = repoAbs + '/.dz/fa-training/' + slug;\n const readmeAbs = repoAbs + '/.dz/fa-training/README.md';\n const fileAbs = dirAbs + '/' + stage + '.jsonl';\n return ('mkdir -p ' + shellQuote(dirAbs) +\n ' && { [ -f ' + shellQuote(readmeAbs) + ' ] || printf \\'%s\\\\n\\' ' + shellQuote(TRAINPAIR_PRIVACY_NOTE) + ' > ' + shellQuote(readmeAbs) + '; }' +\n \" && printf '%s\\\\n' \" + shellQuote(line) + ' >> ' + shellQuote(fileAbs));\n}\nconst TP_BACKFILL_OK = 'TP-BACKFILL-OK';\nconst TP_BACKFILL_SKIP = 'TP-BACKFILL-SKIP';\nconst TP_BACKFILL_DUP = 'TP-BACKFILL-DUP';\nfunction trainingPairBackfillCmd(repoAbs, slug, stage, lines, markKey) {\n if (typeof repoAbs !== 'string' || repoAbs === '')\n return null;\n if (typeof slug !== 'string' || slug === '')\n return null;\n if (typeof stage !== 'string' || stage === '')\n return null;\n if (!Array.isArray(lines) || lines.length === 0 || !lines.every(line => typeof line === 'string' && line !== ''))\n return null;\n const dirAbs = repoAbs + '/.dz/fa-training/' + slug;\n const readmeAbs = repoAbs + '/.dz/fa-training/README.md';\n const fileAbs = dirAbs + '/' + stage + '.jsonl';\n const markDir = repoAbs + '/.dz/fa-training/.backfill-marks';\n const markStage = stage.replace(/\\.\\./g, '_').replace(/\\//g, '_');\n const resolvedMarkKey = markKey === undefined ? fnv1a64(stage + '\\0' + lines.join('\\n')) : markKey;\n const markPath = markDir + '/' + markStage + '-' + resolvedMarkKey;\n const appends = lines\n .map(line => \"printf '%s\\\\n' \" + shellQuote(line) + ' >> ' + shellQuote(fileAbs))\n .join(' && ');\n return ('mkdir -p ' + shellQuote(dirAbs) +\n ' && { [ -f ' + shellQuote(readmeAbs) + ' ] || printf \\'%s\\\\n\\' ' + shellQuote(TRAINPAIR_PRIVACY_NOTE) + ' > ' + shellQuote(readmeAbs) + '; }' +\n ' && mkdir -p ' + shellQuote(markDir) +\n ' && if mkdir ' + shellQuote(markPath) + ' 2>/dev/null; then ' +\n 'if [ -f ' + shellQuote(fileAbs) + ' ]; then echo ' + shellQuote(TP_BACKFILL_SKIP) +\n '; else { ' + appends + ' && echo ' + shellQuote(TP_BACKFILL_OK) + '; } || { rmdir ' + shellQuote(markPath) + ' 2>/dev/null; false; }; fi' +\n '; else echo ' + shellQuote(TP_BACKFILL_DUP) + '; fi');\n}",
59
+ code: "function decideCaptureMode(opts) {\n if (!opts.enabled)\n return 'skip-disabled';\n if (!Number.isInteger(opts.recordCount) || opts.recordCount <= 0)\n return 'skip-empty';\n return opts.resumed ? 'backfill' : 'capture';\n}\nfunction captureFailureRecord(stage, mode, reason, detail) {\n const normalizedStage = typeof stage === 'string' && stage.trim() !== '' ? stage : 'unknown';\n const normalizedMode = mode === 'capture' || mode === 'backfill' || mode === 'skip-disabled' || mode === 'skip-empty'\n ? mode\n : null;\n const normalizedReason = reason === 'threw' || reason === 'unserializable' || reason === 'unverified' || reason === 'backfill-unverified' || reason === 'empty-output'\n ? reason\n : 'threw';\n let normalizedDetail = null;\n if (detail !== null && detail !== undefined) {\n try {\n const text = String(detail);\n if (text !== '')\n normalizedDetail = text.length > 500 ? text.slice(0, 500) + '…' : text;\n }\n catch {\n normalizedDetail = null;\n }\n }\n return { stage: normalizedStage, mode: normalizedMode, reason: normalizedReason, detail: normalizedDetail };\n}\nconst TRAINPAIR_SCHEMA_VERSION = 'fa-trainpair-3';\nconst TRAINPAIR_MAX_IO_CHARS = 48000;\nfunction trainingPairFamily(spec) {\n return /codex|gpt|openai/i.test(String(spec ?? '')) ? 'codex' : 'claude';\n}\nfunction trainingPairPath(slug, stage) {\n return '.dz/fa-training/' + slug + '/' + stage + '.jsonl';\n}\nconst TRAINPAIR_PRIVACY_NOTE = \"feature-adr TRAINING PAIRS (backlog 70e0f083): per-stage SFT records - STAGE INPUT (full prompt/context) -> STAGE OUTPUT (artifact/result) -> EVALUATION (QE grade + injected lessons) with model+family provenance; one JSONL file per stage per slug. PRIVACY: pairs may contain TARGET-REPO CODE and full prompts. This directory is NOT gitignored yet by explicit owner decision - review contents before sharing or publishing anything that embeds it. ts is the CAPTURE time. On a record with captureMode: 'backfill' that is the RECONSTRUCTION time, NOT the stage's observation time — the original stage's timing lives in that run's .fa-state checkpoint.\";\nconst TP_PROFILE_MARKER_START = '<!-- dz:profile:start -->';\nconst TP_PROFILE_MARKER_END = '<!-- dz:profile:end -->';\nconst TP_PROFILE_REDACTED = '[dz:profile REDACTED]';\nfunction redactProfileBlock(text) {\n if (typeof text !== 'string' || text === '')\n return typeof text === 'string' ? text : '';\n let out = '';\n let rest = text;\n for (;;) {\n const start = rest.indexOf(TP_PROFILE_MARKER_START);\n if (start === -1)\n return out + rest;\n out += rest.slice(0, start) + TP_PROFILE_REDACTED;\n const end = rest.indexOf(TP_PROFILE_MARKER_END, start + TP_PROFILE_MARKER_START.length);\n if (end === -1)\n return out;\n rest = rest.slice(end + TP_PROFILE_MARKER_END.length);\n }\n}\nfunction coerceText(v) {\n if (typeof v === 'string')\n return v;\n if (v === null || v === undefined)\n return '';\n try {\n const s = JSON.stringify(v);\n return typeof s === 'string' ? s : String(v);\n }\n catch {\n return String(v);\n }\n}\nfunction normalizeTrainingPairBudget(raw) {\n try {\n if (raw === undefined)\n return { primary: 'claude', claude: 'normal', codex: 'normal', preset: 'unset' };\n if (raw === null || typeof raw !== 'object')\n return null;\n const value = raw;\n const primary = value.primary;\n const claude = value.claude;\n const codex = value.codex;\n if (primary !== 'claude' && primary !== 'codex')\n return null;\n if (claude !== 'normal' && claude !== 'eco')\n return null;\n if (codex !== 'normal' && codex !== 'eco')\n return null;\n if (value.preset === 'unset')\n return { primary, claude, codex, preset: 'unset' };\n let preset = 'custom';\n if (claude === 'normal' && codex === 'normal')\n preset = 'normal';\n else if (claude === 'eco' && codex === 'eco')\n preset = 'eco';\n else if (claude === 'eco' && codex === 'normal')\n preset = 'hybrid';\n return { primary, claude, codex, preset };\n }\n catch {\n return null;\n }\n}\nfunction buildTrainingPair(opts) {\n let input = redactProfileBlock(coerceText(opts.input));\n let output = redactProfileBlock(coerceText(opts.output));\n let truncated = null;\n if (input.length + output.length > TRAINPAIR_MAX_IO_CHARS) {\n truncated = { inputChars: input.length, outputChars: output.length, inputHash: fnv1a64(input), outputHash: fnv1a64(output) };\n const half = Math.floor(TRAINPAIR_MAX_IO_CHARS / 2);\n let inKeep = input.length;\n let outKeep = output.length;\n if (outKeep <= half)\n inKeep = TRAINPAIR_MAX_IO_CHARS - outKeep;\n else if (inKeep <= half)\n outKeep = TRAINPAIR_MAX_IO_CHARS - inKeep;\n else {\n inKeep = half;\n outKeep = TRAINPAIR_MAX_IO_CHARS - half;\n }\n if (inKeep < input.length)\n input = input.slice(0, inKeep) + '\\n…[TRUNCATED ' + (truncated.inputChars - inKeep) + ' chars — full-text fnv1a64=' + truncated.inputHash + ']';\n if (outKeep < output.length)\n output = output.slice(0, outKeep) + '\\n…[TRUNCATED ' + (truncated.outputChars - outKeep) + ' chars — full-text fnv1a64=' + truncated.outputHash + ']';\n }\n const ev = opts.evaluation || {};\n const pv = opts.provenance || {};\n return {\n schema: TRAINPAIR_SCHEMA_VERSION,\n slug: opts.slug,\n stage: opts.stage,\n ts: opts.ts === undefined ? null : opts.ts,\n input,\n output,\n evaluation: {\n grade: typeof ev.grade === 'string' && ev.grade.trim() !== '' ? ev.grade : null,\n gradedBy: typeof ev.gradedBy === 'string' && ev.gradedBy !== '' ? ev.gradedBy : null,\n lessonsInjected: Array.isArray(ev.lessonsInjected) ? ev.lessonsInjected.filter((s) => typeof s === 'string' && s !== '') : [],\n },\n provenance: {\n model: typeof pv.model === 'string' && pv.model !== '' ? pv.model : 'unknown',\n family: pv.family === 'claude' || pv.family === 'codex' ? pv.family : trainingPairFamily(pv.model),\n role: typeof pv.role === 'string' && pv.role !== '' ? pv.role : 'unknown',\n tokens: typeof pv.tokens === 'number' && Number.isFinite(pv.tokens) ? pv.tokens : null,\n minutes: typeof pv.minutes === 'number' && Number.isFinite(pv.minutes) ? pv.minutes : null,\n },\n budgetMode: normalizeTrainingPairBudget(opts.budgetMode),\n truncated,\n captureMode: opts.captureMode === 'backfill' ? 'backfill' : 'capture',\n resumed: opts.resumed === true,\n };\n}\nfunction serializeTrainingPair(pair) {\n try {\n const line = JSON.stringify(pair);\n return typeof line === 'string' ? line : null;\n }\n catch {\n return null;\n }\n}\nfunction trainingPairAppendCmd(repoAbs, slug, stage, line) {\n const dirAbs = repoAbs + '/.dz/fa-training/' + slug;\n const readmeAbs = repoAbs + '/.dz/fa-training/README.md';\n const fileAbs = dirAbs + '/' + stage + '.jsonl';\n return ('mkdir -p ' + shellQuote(dirAbs) +\n ' && { [ -f ' + shellQuote(readmeAbs) + ' ] || printf \\'%s\\\\n\\' ' + shellQuote(TRAINPAIR_PRIVACY_NOTE) + ' > ' + shellQuote(readmeAbs) + '; }' +\n \" && printf '%s\\\\n' \" + shellQuote(line) + ' >> ' + shellQuote(fileAbs));\n}\nconst TP_BACKFILL_OK = 'TP-BACKFILL-OK';\nconst TP_BACKFILL_SKIP = 'TP-BACKFILL-SKIP';\nconst TP_BACKFILL_DUP = 'TP-BACKFILL-DUP';\nfunction trainingPairBackfillCmd(repoAbs, slug, stage, lines, markKey) {\n if (typeof repoAbs !== 'string' || repoAbs === '')\n return null;\n if (typeof slug !== 'string' || slug === '')\n return null;\n if (typeof stage !== 'string' || stage === '')\n return null;\n if (!Array.isArray(lines) || lines.length === 0 || !lines.every(line => typeof line === 'string' && line !== ''))\n return null;\n const dirAbs = repoAbs + '/.dz/fa-training/' + slug;\n const readmeAbs = repoAbs + '/.dz/fa-training/README.md';\n const fileAbs = dirAbs + '/' + stage + '.jsonl';\n const markDir = repoAbs + '/.dz/fa-training/.backfill-marks';\n const markStage = stage.replace(/\\.\\./g, '_').replace(/\\//g, '_');\n const resolvedMarkKey = markKey === undefined ? fnv1a64(stage + '\\0' + lines.join('\\n')) : markKey;\n const markPath = markDir + '/' + markStage + '-' + resolvedMarkKey;\n const appends = lines\n .map(line => \"printf '%s\\\\n' \" + shellQuote(line) + ' >> ' + shellQuote(fileAbs))\n .join(' && ');\n return ('mkdir -p ' + shellQuote(dirAbs) +\n ' && { [ -f ' + shellQuote(readmeAbs) + ' ] || printf \\'%s\\\\n\\' ' + shellQuote(TRAINPAIR_PRIVACY_NOTE) + ' > ' + shellQuote(readmeAbs) + '; }' +\n ' && mkdir -p ' + shellQuote(markDir) +\n ' && if mkdir ' + shellQuote(markPath) + ' 2>/dev/null; then ' +\n 'if [ -f ' + shellQuote(fileAbs) + ' ]; then echo ' + shellQuote(TP_BACKFILL_SKIP) +\n '; else { ' + appends + ' && echo ' + shellQuote(TP_BACKFILL_OK) + '; } || { rmdir ' + shellQuote(markPath) + ' 2>/dev/null; false; }; fi' +\n '; else echo ' + shellQuote(TP_BACKFILL_DUP) + '; fi');\n}",
60
60
  },
61
61
  "model-resolver": {
62
62
  name: "model-resolver",
63
63
  version: "1.0.0",
64
- contentHash: "e82ac2137279537f5b65725cbd8de5817fcbc89571068e2df843587352fa83f5",
64
+ contentHash: "fcf8c1dfc4b5364adb6e532565953ef30d94a45070b2b903411170965a1fb372",
65
65
  sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-routing.ts",
66
66
  requires: [],
67
67
  exports: ["specToOpts","resolveStageModel","KNOWN_CODEX","mergeOpts","stageLabel","modelLabel"],
68
- code: "const OVERRIDE_REASONING = {\n router: 'high',\n requirements: 'xhigh',\n research: 'xhigh',\n adr: 'xhigh',\n ideation: 'xhigh',\n ddd: 'xhigh',\n architecture: 'xhigh',\n plan: 'xhigh',\n code: 'xhigh',\n qe: 'high',\n fleet: 'high',\n};\nfunction topCodexId(env) {\n let top = env.CODEX_MODEL;\n if (top === 'auto') {\n const ids = Object.keys(KNOWN_CODEX);\n for (let i = 0; i < ids.length; i++) {\n if (ids[i] !== 'auto')\n top = ids[i] || top;\n }\n }\n return top;\n}\nconst KNOWN_CODEX = { auto: 1, 'gpt-5.5': 1, 'gpt-5.6': 1, 'gpt-5.6-luna': 1, 'gpt-5.6-terra': 1, 'gpt-5.6-sol': 1 };\nconst CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 };\nconst VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1 };\nconst DEFAULT_MODELS = {\n router: 'fable',\n requirements: 'sonnet',\n research: 'sonnet',\n adr: 'opus',\n ideation: 'sonnet',\n ddd: 'opus',\n architecture: 'opus',\n plan: 'sonnet',\n code: null,\n qe: null,\n fleet: 'sonnet',\n};\nfunction specToOpts(spec, env) {\n const log = env.log || function () { };\n if (!spec)\n return {};\n const parts = String(spec).split(':');\n const head = parts[0] || '';\n if (head === 'codex') {\n let id = parts[1] || env.CODEX_MODEL;\n if (id !== 'auto' && !KNOWN_CODEX[id]) {\n log('models: unknown codex id ' + id + ' — using ' + env.CODEX_MODEL);\n id = env.CODEX_MODEL;\n }\n let reasoning = parts[2] || 'high';\n if (!VALID_REASONING[reasoning]) {\n log('models: unknown reasoning ' + reasoning + ' — using high');\n reasoning = 'high';\n }\n return { agentType: 'codex:codex-rescue', codexModel: id, _reasoning: reasoning };\n }\n if (CLAUDE_NAMES[head])\n return { model: head };\n log('models: unknown spec ' + spec + ' — session-inherited');\n return {};\n}\nfunction resolveCoderSpec(env) {\n if (env.CODER === 'codex' || env.CODER === 'codex-fallback')\n return 'codex:' + env.CODEX_MODEL + ':high';\n return 'opus';\n}\nfunction coderIsCodex(env) {\n if (env.CODER === 'codex' || env.CODER === 'codex-fallback')\n return true;\n const codeSpec = env.MODELS.code;\n if (codeSpec && String(codeSpec).split(':')[0] === 'codex')\n return true;\n return false;\n}\nfunction resolveQeSpec(env) {\n if (coderIsCodex(env))\n return 'opus';\n const CODEX_AVAILABLE = env.codexAvailable !== false;\n if (!CODEX_AVAILABLE)\n return 'opus';\n return 'codex:' + topCodexId(env) + ':high';\n}\nfunction routingRequested(env) {\n return (Object.keys(env.MODELS).length > 0 ||\n env.PLANNER === 'codex' ||\n env.CODER === 'codex' ||\n env.CODER === 'codex-fallback' ||\n env.QE_REVIEWER === 'codex' ||\n env.QE_REVIEWER === 'codex-fallback');\n}\nfunction resolveStageModel(stage, env) {\n if (env.usageOverride) {\n const r = (env.usageReasoning && env.usageReasoning[stage]) || OVERRIDE_REASONING[stage] || 'high';\n const o = specToOpts('codex:' + topCodexId(env) + ':' + r, env);\n o._usageSwitched = true;\n return o;\n }\n let spec = env.MODELS[stage];\n if (spec === undefined) {\n if (!routingRequested(env))\n return {};\n spec = DEFAULT_MODELS[stage];\n }\n if (stage === 'code' && (spec === null || spec === undefined))\n return specToOpts(resolveCoderSpec(env), env);\n if (stage === 'qe' && (spec === null || spec === undefined))\n return specToOpts(resolveQeSpec(env), env);\n return specToOpts(spec, env);\n}\nfunction modelLabel(opts) {\n if (opts && opts.agentType === 'codex:codex-rescue') {\n const base = 'codex:' + opts.codexModel + ':' + opts._reasoning;\n return opts._usageSwitched ? base + ' (usage-switched)' : base;\n }\n if (opts && opts.model)\n return opts.model;\n return 'session';\n}\nfunction stageLabel(base, opts) {\n const m = modelLabel(opts);\n return m === 'session' ? base : base + ' · ' + m;\n}\nfunction mergeOpts(base, extra) {\n const out = {};\n for (const k in base)\n out[k] = base[k];\n for (const k in extra)\n out[k] = extra[k];\n return out;\n}",
68
+ code: "const STAGE_EFFORT = { override: {\n router: 'medium',\n requirements: 'medium',\n research: 'medium',\n adr: 'high',\n ideation: 'medium',\n ddd: 'high',\n architecture: 'high',\n plan: 'high',\n code: 'medium',\n qe: 'high',\n fleet: 'medium',\n } };\nfunction topCodexId(env) {\n return env.CODEX_MODEL !== 'auto' ? env.CODEX_MODEL : CODEX_TIERS.flagship;\n}\nconst KNOWN_CODEX = { auto: 1, 'gpt-5.5': 1, 'gpt-5.6': 1, 'gpt-5.6-luna': 1, 'gpt-5.6-terra': 1, 'gpt-5.6-sol': 1 };\nconst CODEX_TIERS = {\n flagship: 'gpt-5.6-sol',\n workhorse: 'gpt-5.6-terra',\n 'high-volume': 'gpt-5.6-luna',\n};\nfunction codexIdForTier(tier, env) {\n return env.CODEX_MODEL !== 'auto' ? env.CODEX_MODEL : CODEX_TIERS[tier];\n}\nconst BUDGET_PRESETS = {\n normal: { claude: 'normal', codex: 'normal' },\n eco: { claude: 'eco', codex: 'eco' },\n hybrid: { claude: 'eco', codex: 'normal' },\n};\nfunction resolveBudgetMode(raw) {\n if (raw === undefined)\n return BUDGET_PRESETS.normal;\n if (typeof raw === 'string') {\n const preset = BUDGET_PRESETS[raw];\n if (!preset)\n throw new RangeError('budget: unknown preset \"' + raw + '\" — valid: normal|eco|hybrid');\n return preset;\n }\n if (raw && typeof raw === 'object') {\n const value = raw;\n for (const key of Object.keys(value)) {\n if (key !== 'claude' && key !== 'codex') {\n throw new RangeError('budget: unknown family key \"' + key + '\" — valid: claude|codex');\n }\n }\n for (const key of ['claude', 'codex']) {\n const level = value[key];\n if (level !== undefined && level !== 'normal' && level !== 'eco') {\n throw new RangeError('budget.' + key + ': unknown level \"' + level + '\" — valid: normal|eco');\n }\n }\n return {\n claude: value.claude || 'normal',\n codex: value.codex || 'normal',\n };\n }\n throw new RangeError('budget: expected a preset name or {claude,codex} object, got ' + typeof raw);\n}\nconst CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 };\nconst VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1, max: 1 };\nconst DEFAULT_MODELS = {\n router: 'fable',\n requirements: 'sonnet',\n research: 'sonnet',\n adr: 'opus',\n ideation: 'sonnet',\n ddd: 'opus',\n architecture: 'opus',\n plan: 'sonnet',\n code: null,\n qe: null,\n fleet: 'sonnet',\n};\nconst ROUTING_TABLES = {\n claude: {\n claude: {\n normal: { router: 'sonnet', requirements: 'sonnet', research: 'sonnet', adr: 'fable', ideation: 'sonnet', ddd: 'fable', architecture: 'fable', plan: 'opus', code: 'sonnet', fleet: 'sonnet' },\n eco: { router: 'sonnet', requirements: 'sonnet', research: 'sonnet', adr: 'opus', ideation: 'sonnet', ddd: 'opus', architecture: 'opus', plan: 'sonnet', code: 'sonnet', fleet: 'sonnet' },\n },\n codex: { normal: {}, eco: {} },\n },\n codex: {\n claude: {\n normal: { router: 'sonnet', qe: 'sonnet', fleet: 'sonnet' },\n eco: { router: 'sonnet', qe: 'sonnet', fleet: 'sonnet' },\n },\n codex: { normal: {}, eco: {} },\n },\n};\nfunction codexCell(tier, effort, env) {\n return 'codex:' + codexIdForTier(tier, env) + ':' + effort;\n}\nfunction budgetTable(primary, mode, env) {\n const claudeHalf = ROUTING_TABLES[primary].claude[mode.claude];\n let codexHalf;\n if (primary === 'claude') {\n const qeSpec = mode.codex === 'normal'\n ? codexCell('flagship', 'high', env)\n : codexCell('workhorse', 'medium', env);\n codexHalf = { ...ROUTING_TABLES.claude.codex[mode.codex], qe: env.codexAvailable === false ? 'opus' : qeSpec };\n }\n else {\n const normal = mode.codex === 'normal';\n const design = codexCell(normal ? 'flagship' : 'workhorse', normal ? 'high' : 'medium', env);\n codexHalf = {\n requirements: design,\n research: design,\n adr: design,\n ideation: design,\n ddd: design,\n architecture: design,\n plan: codexCell(normal ? 'flagship' : 'workhorse', normal ? 'high' : 'low', env),\n code: codexCell(normal ? 'flagship' : 'workhorse', 'medium', env),\n };\n }\n return { ...claudeHalf, ...codexHalf };\n}\nfunction specToOpts(spec, env) {\n const log = env.log || function () { };\n if (!spec)\n return {};\n const parts = String(spec).split(':');\n const head = parts[0] || '';\n if (head === 'codex') {\n let id = parts[1] || env.CODEX_MODEL;\n if (id !== 'auto' && !KNOWN_CODEX[id]) {\n log('models: unknown codex id ' + id + ' — using ' + env.CODEX_MODEL);\n id = env.CODEX_MODEL;\n }\n let reasoning = parts[2] || 'high';\n if (!VALID_REASONING[reasoning]) {\n throw new RangeError('models: invalid reasoning \"' + reasoning + '\"valid: ' + Object.keys(VALID_REASONING).join('|'));\n }\n return { agentType: 'codex:codex-rescue', codexModel: id, _reasoning: reasoning };\n }\n if (CLAUDE_NAMES[head])\n return { model: head };\n log('models: unknown spec ' + spec + ' — session-inherited');\n return {};\n}\nfunction resolveCoderSpec(env) {\n if (env.CODER === 'codex' || env.CODER === 'codex-fallback')\n return 'codex:' + env.CODEX_MODEL + ':high';\n return 'opus';\n}\nfunction coderIsCodex(env) {\n const codeSpec = env.MODELS.code;\n if (codeSpec !== undefined && codeSpec !== null)\n return String(codeSpec).split(':')[0] === 'codex';\n if (env.CODER === 'codex' || env.CODER === 'codex-fallback')\n return true;\n return env.primary === 'codex';\n}\nfunction resolveQeSpecForCoder(coderCodex, env) {\n if (coderCodex)\n return 'sonnet';\n const CODEX_AVAILABLE = env.codexAvailable !== false;\n if (!CODEX_AVAILABLE)\n return 'opus';\n const budget = resolveBudgetMode(env.budget);\n return budget.codex === 'eco'\n ? 'codex:' + codexIdForTier('workhorse', env) + ':medium'\n : 'codex:' + codexIdForTier('flagship', env) + ':high';\n}\nfunction resolveQeSpec(env) {\n return resolveQeSpecForCoder(coderIsCodex(env), env);\n}\nfunction routingRequested(env) {\n return (Object.keys(env.MODELS).length > 0 ||\n env.primary !== undefined ||\n env.budget !== undefined ||\n env.PLANNER === 'codex' ||\n env.CODER === 'codex' ||\n env.CODER === 'codex-fallback' ||\n env.QE_REVIEWER === 'codex' ||\n env.QE_REVIEWER === 'codex-fallback');\n}\nfunction resolveStageModel(stage, env) {\n if (env.usageOverride) {\n const r = (env.usageReasoning && env.usageReasoning[stage]) || STAGE_EFFORT.override[stage] || 'medium';\n const o = specToOpts('codex:' + topCodexId(env) + ':' + r, env);\n o._usageSwitched = true;\n return o;\n }\n let spec = env.MODELS[stage];\n if (spec === undefined) {\n if (!routingRequested(env))\n return {};\n if (stage === 'code' && (env.CODER === 'codex' || env.CODER === 'codex-fallback')) {\n return specToOpts(resolveCoderSpec(env), env);\n }\n if (stage === 'plan' && env.PLANNER === 'codex') {\n return specToOpts('codex:' + env.CODEX_MODEL + ':high', env);\n }\n if (stage === 'qe') {\n return specToOpts(resolveQeSpec(env), env);\n }\n const resolvedPrimary = env.primary || 'claude';\n const cell = budgetTable(resolvedPrimary, resolveBudgetMode(env.budget), env)[stage];\n spec = cell !== undefined ? cell : DEFAULT_MODELS[stage];\n }\n if (stage === 'code' && (spec === null || spec === undefined))\n return specToOpts(resolveCoderSpec(env), env);\n if (stage === 'qe' && (spec === null || spec === undefined))\n return specToOpts(resolveQeSpec(env), env);\n return specToOpts(spec, env);\n}\nfunction modelLabel(opts) {\n if (opts && opts.agentType === 'codex:codex-rescue') {\n const base = 'codex:' + opts.codexModel + ':' + opts._reasoning;\n return opts._usageSwitched ? base + ' (usage-switched)' : base;\n }\n if (opts && opts.model)\n return opts.model;\n return 'session';\n}\nfunction stageLabel(base, opts) {\n const m = modelLabel(opts);\n return m === 'session' ? base : base + ' · ' + m;\n}\nfunction mergeOpts(base, extra) {\n const out = {};\n for (const k in base)\n out[k] = base[k];\n for (const k in extra)\n out[k] = extra[k];\n return out;\n}",
69
69
  },
70
70
  "usage-probes": {
71
71
  name: "usage-probes",
72
72
  version: "1.0.0",
73
- contentHash: "4ae2504a50fe05aad4383e223d460b21396805b17dfefab50e60022d58cdd19d",
73
+ contentHash: "a7d7fecc30202595c05cd31d5c36ee3bfa59f803f3e707e3c28c3ae372bc373b",
74
74
  sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-routing.ts",
75
75
  requires: ["model-resolver"],
76
76
  exports: ["decideUsageAction","OVERRIDE_REASONING","topCodexId"],
77
- code: "function decideUsageAction(prevOverride, signal, threshold) {\n if (signal === null || signal === undefined) {\n if (prevOverride)\n return { override: true, action: 'keep' };\n return { override: true, action: 'fail-safe-switch' };\n }\n const s = signal.sessionPct;\n const w = signal.weeklyPct;\n const sKnown = typeof s === 'number' && isFinite(s) && s >= 0;\n const wKnown = typeof w === 'number' && isFinite(w) && w >= 0;\n if ((sKnown && s >= threshold) || (wKnown && w >= threshold)) {\n return { override: true, action: prevOverride ? 'keep' : 'switch' };\n }\n if (sKnown && wKnown) {\n return { override: false, action: prevOverride ? 'restore' : 'none' };\n }\n return { override: prevOverride, action: prevOverride ? 'keep' : 'none' };\n}",
77
+ code: "const OVERRIDE_REASONING = Object.freeze(STAGE_EFFORT.override);\nfunction decideUsageAction(prevOverride, signal, threshold) {\n if (signal === null || signal === undefined) {\n if (prevOverride)\n return { override: true, action: 'keep' };\n return { override: true, action: 'fail-safe-switch' };\n }\n const s = signal.sessionPct;\n const w = signal.weeklyPct;\n const sKnown = typeof s === 'number' && isFinite(s) && s >= 0;\n const wKnown = typeof w === 'number' && isFinite(w) && w >= 0;\n if ((sKnown && s >= threshold) || (wKnown && w >= threshold)) {\n return { override: true, action: prevOverride ? 'keep' : 'switch' };\n }\n if (sKnown && wKnown) {\n return { override: false, action: prevOverride ? 'restore' : 'none' };\n }\n return { override: prevOverride, action: prevOverride ? 'keep' : 'none' };\n}",
78
78
  },
79
79
  "codex-dispatch": {
80
80
  name: "codex-dispatch",
package/src/loop-plan.ts CHANGED
@@ -150,6 +150,11 @@ export interface LoopFanout {
150
150
  registry: string[];
151
151
  /** Hard concurrency bound — REQUIRED, >= 1 (INV-2; unbounded fanout is unrepresentable-invalid). */
152
152
  maxFanout: number;
153
+ /** Admission policy when registry length exceeds maxFanout. Absent means window: dispatch all,
154
+ * while maxFanout bounds only concurrent work. */
155
+ overflow?: 'window' | 'truncate';
156
+ /** Mandatory human-readable receipt when overflow:'truncate' deliberately drops work. */
157
+ truncateReason?: string;
153
158
  dedup?: boolean;
154
159
  reasonRequired?: boolean;
155
160
  /** Per-item step chain (pipeline shape) — stepIds run in sequence per member. */
@@ -371,6 +376,8 @@ export const FIELD_DOMAINS: Record<string, FieldDomain> = {
371
376
  'LoopFanout.stage': { t: 'string' },
372
377
  'LoopFanout.registry': { t: 'string[]' },
373
378
  'LoopFanout.maxFanout': { t: 'number' },
379
+ 'LoopFanout.overflow': { t: 'enum', values: ['window', 'truncate'] },
380
+ 'LoopFanout.truncateReason': { t: 'string' },
374
381
  'LoopFanout.dedup': { t: 'boolean' },
375
382
  'LoopFanout.reasonRequired': { t: 'boolean' },
376
383
  'LoopFanout.chain': { t: 'string[]' },
@@ -992,6 +999,11 @@ export function validatePlan(plan: LoopPlan): Diagnostic[] {
992
999
  if (!Array.isArray(f.registry) || f.registry.length === 0) {
993
1000
  out.push({ invariant: 'INV-2', path: `$.fanouts[${f.stage}].registry`, message: 'a fanout requires a non-empty member registry' });
994
1001
  }
1002
+ if (f.overflow !== undefined && f.overflow !== 'window' && f.overflow !== 'truncate') {
1003
+ out.push({ invariant: 'INV-2b', path: `$.fanouts[${f.stage}].overflow`, message: `overflow must be one of window|truncate (received ${JSON.stringify(f.overflow)})` });
1004
+ } else if (f.overflow === 'truncate' && (typeof f.truncateReason !== 'string' || f.truncateReason.trim() === '')) {
1005
+ out.push({ invariant: 'INV-2b', path: `$.fanouts[${f.stage}].overflow`, message: 'overflow:"truncate" requires a non-blank truncateReason — deliberate dropped work needs a receipt' });
1006
+ }
995
1007
  });
996
1008
 
997
1009
  // INV-3: every fanout (parallel region) names an explicit join with a closed-set joinPolicy.
@@ -1388,7 +1400,7 @@ export function toLintProjection(plan: LoopPlan): LintProjection {
1388
1400
  }
1389
1401
 
1390
1402
  export interface TraceProjection {
1391
- kind: 'trace-projection/1';
1403
+ kind: 'trace-projection/2';
1392
1404
  /** Expected happens-before edges: every dispatch of `step` must come after the settle of each
1393
1405
  * dep. Only DISPATCHING step kinds (agent/gate) appear — fanout/join/pause are structural
1394
1406
  * pseudo-steps that emit no trace events of their own (their ordering lives in `regions`). */
@@ -1396,7 +1408,20 @@ export interface TraceProjection {
1396
1408
  /** Parallel regions with the runtime bound + concurrency shape the trace must exhibit.
1397
1409
  * `after` = the dispatching steps that depend on the region (fanout or join) — the trace-visible
1398
1410
  * witnesses of the barrier. */
1399
- regions: { fanout: string; join: string; joinPolicy: string; maxFanout: number; members: string[]; shape: ConcurrencyShape; after: string[] }[];
1411
+ regions: {
1412
+ fanout: string;
1413
+ join: string;
1414
+ joinPolicy: string;
1415
+ maxFanout: number;
1416
+ members: string[];
1417
+ shape: ConcurrencyShape;
1418
+ after: string[];
1419
+ /** Projection/2 evidence used to prove positional dispatch coverage. */
1420
+ registry: string[];
1421
+ registrySize: number;
1422
+ overflow: 'window' | 'truncate';
1423
+ dedup: boolean;
1424
+ }[];
1400
1425
  /** Steps expected to appear in the trace, keyed by stepId (iteration/attempt keys are runtime axes). */
1401
1426
  expectedSteps: string[];
1402
1427
  }
@@ -1432,10 +1457,14 @@ export function toTraceProjection(plan: LoopPlan): TraceProjection {
1432
1457
  members: [...(f.chain ?? [])],
1433
1458
  shape: shapes.get(f.stage) ?? 'barrier',
1434
1459
  after,
1460
+ registry: [...f.registry],
1461
+ registrySize: f.registry.length,
1462
+ overflow: f.overflow ?? 'window',
1463
+ dedup: f.dedup === true,
1435
1464
  };
1436
1465
  });
1437
1466
  return {
1438
- kind: 'trace-projection/1',
1467
+ kind: 'trace-projection/2',
1439
1468
  happensBefore,
1440
1469
  regions,
1441
1470
  expectedSteps: norm.steps.filter((s) => s.kind === 'agent' || s.kind === 'gate').map((s) => s.stepId),
@@ -1501,6 +1530,8 @@ export interface RunBoundary {
1501
1530
  maxFanout: number;
1502
1531
  registry: string[];
1503
1532
  dedup: boolean;
1533
+ overflow: 'window' | 'truncate';
1534
+ truncateReason: string | null;
1504
1535
  shape: ConcurrencyShape;
1505
1536
  chain: RunStepSpec[];
1506
1537
  };
@@ -1586,6 +1617,8 @@ export function toRunProjection(plan: LoopPlan): RunProjection {
1586
1617
  maxFanout: typeof f?.maxFanout === 'number' ? f.maxFanout : 0,
1587
1618
  registry: [...(f?.registry ?? [])],
1588
1619
  dedup: f?.dedup === true,
1620
+ overflow: f?.overflow ?? 'window',
1621
+ truncateReason: typeof f?.truncateReason === 'string' ? f.truncateReason : null,
1589
1622
  shape: s.concurrency ?? 'barrier',
1590
1623
  chain: (f?.chain ?? []).map((c) => byId.get(c)).filter((c): c is LoopStep => c !== undefined).map(specOf),
1591
1624
  },
@@ -431,9 +431,18 @@ function renderFanout(plan: LoopPlan, fanoutStep: LoopStep): string {
431
431
  lines.push(G(`step:${id} kind=fanout shape=${shape} maxFanout=${f.maxFanout}`));
432
432
  for (const ms of members) lines.push(...stepPromptAssembly(ms, plan));
433
433
  lines.push(`const REGISTRY_${ident(id)} = ${JSON.stringify(f.registry)}`);
434
- // dedup (enacted — QE round-3 B1): a declared dedup DEDUPLICATES the registry before the cap.
434
+ // dedup (enacted — QE round-3 B1): a declared dedup DEDUPLICATES the registry before admission.
435
435
  const registryExpr = f.dedup === true ? `REGISTRY_${ident(id)}.filter(function (x, i) { return REGISTRY_${ident(id)}.indexOf(x) === i })` : `REGISTRY_${ident(id)}`;
436
- lines.push(`const MEMBERS_${ident(id)} = ${registryExpr}.slice(0, ${f.maxFanout}) // bounded fanout (INV-2): never args-derived, never uncapped`);
436
+ if (f.overflow === 'truncate') {
437
+ const reason = f.truncateReason ?? '';
438
+ const commentReason = reason.replace(/\r?\n/g, ' ').replace(/\*\//g, '* /');
439
+ lines.push(`// ==================== FANOUT TRUNCATION DECLARED: ${commentReason} ====================`);
440
+ lines.push(`const MEMBERS_${ident(id)} = ${registryExpr}.slice(0, ${f.maxFanout}) // declared truncation (INV-2b): intentionally drops registry positions beyond maxFanout`);
441
+ lines.push(`console.error('[fanout-truncated] ${id}: ' + MEMBERS_${ident(id)}.length + ' of ' + REGISTRY_${ident(id)}.length + ' items — ' + ${jsString(reason)})`);
442
+ lines.push(`if (__hooks.onFanoutTruncated) { __hooks.onFanoutTruncated({ stage: ${jsString(id)}, registrySize: REGISTRY_${ident(id)}.length, dispatched: MEMBERS_${ident(id)}.length, reason: ${jsString(reason)} }) }`);
443
+ } else {
444
+ lines.push(`const MEMBERS_${ident(id)} = ${registryExpr} // full registry (INV-2b): every item dispatched; maxFanout bounds CONCURRENCY, not work`);
445
+ }
437
446
  // ROUND-6 B3 SHAPE: the region's awaits (member dispatches via parallel + the join) ride ONE
438
447
  // settle-routed try, mirroring the per-step shape. Member runStep failures settle themselves
439
448
  // durably before rejecting; the outer catch is the structural belt for the region as a whole.
@@ -445,7 +454,7 @@ function renderFanout(plan: LoopPlan, fanoutStep: LoopStep): string {
445
454
  // dispatch(B:item1) is allocated a seq before settle(A:item3). Predecessor by POSITION (G15),
446
455
  // qualified by the branch OCCURRENCE (round 7 — duplicate registry values are distinct
447
456
  // branches, so a chain predecessor is looked up per occurrence, never per value).
448
- lines.push(` R_${ident(id)} = await __drainAll(MEMBERS_${ident(id)}.map((it, __ix) => async () => {`);
457
+ lines.push(` R_${ident(id)} = await __drainAllWindowed(MEMBERS_${ident(id)}.map((it, __ix) => async () => {`);
449
458
  let prev: string | null = null;
450
459
  for (let ci = 0; ci < members.length; ci++) {
451
460
  const cs = members[ci] as LoopStep;
@@ -457,13 +466,13 @@ function renderFanout(plan: LoopPlan, fanoutStep: LoopStep): string {
457
466
  prev = `v_${ident(cs.stepId)}`;
458
467
  }
459
468
  lines.push(` return ${prev ?? 'null'}`);
460
- lines.push(` }))`);
469
+ lines.push(` }), ${f.maxFanout})`);
461
470
  } else {
462
471
  // BARRIER shape: all members dispatched, one join closes the region (exactly one member step —
463
472
  // MEMBER-2; extra barrier chain entries used to be silently never dispatched).
464
473
  const ms = members[0] as LoopStep;
465
474
  const call = stepCallExpr(ms, memberDeps(ms), { chainCausedBy: null, inputExpr: null });
466
- lines.push(` R_${ident(id)} = await __drainAll(MEMBERS_${ident(id)}.map((it, __ix) => () => ${call}))`);
475
+ lines.push(` R_${ident(id)} = await __drainAllWindowed(MEMBERS_${ident(id)}.map((it, __ix) => () => ${call}), ${f.maxFanout})`);
467
476
  }
468
477
  lines.push(` J_${ident(id)} = await __joinSettled(${jsString(j.stage)}, ${jsString(fanoutStep.phase)}, R_${ident(id)}, { policy: ${jsString(j.joinPolicy)}, onInvalid: ${jsString(j.onInvalid ?? 'named-failure')}, region: ${jsString(id)} })`);
469
478
  lines.push(`} catch (__stepErr) { await __settleStep({ stepId: ${jsString(id)}, phase: ${jsString(fanoutStep.phase)}, outcome: 'failed', error: __stepErr }) }`);
@@ -478,6 +487,7 @@ function renderFanout(plan: LoopPlan, fanoutStep: LoopStep): string {
478
487
  * attach hooks INSIDE it, and when opted out the hook sites are no-ops). */
479
488
  function renderRuntime(plan: LoopPlan, planDig: string, execFp: string, blobs: LoopBlob[]): string {
480
489
  const traceOn = plan.trace?.emit === true;
490
+ const hasDeclaredTruncation = (plan.fanouts ?? []).some((f) => f.overflow === 'truncate');
481
491
  const ckptOn = plan.checkpointing?.enabled === true || plan.subsystems?.checkpoints === true;
482
492
  // budget: declared per-step budgets PLUS the declared gate-redo allowance (QE round-3 B1 — a
483
493
  // plan-declared redo must be affordable; an undeclared one still hits the guard loudly). The
@@ -505,7 +515,7 @@ function renderRuntime(plan: LoopPlan, planDig: string, execFp: string, blobs: L
505
515
  lines.push(`let __ledgerDone = false`);
506
516
  if (traceOn) lines.push(`let __faLegWarned = false`);
507
517
  lines.push(`function __spendBudget(stepId) { if (__budget.left <= 0) { throw new Error('loop budget exhausted before ' + stepId) } __budget.left-- }`);
508
- lines.push(`const __hooks = { onDispatch: null, onSettle: null }`);
518
+ lines.push(`const __hooks = { onDispatch: null, onSettle: null${hasDeclaredTruncation ? ', onFanoutTruncated: null' : ''} }`);
509
519
  lines.push(`const __settled = {}`);
510
520
  lines.push(`// settle identity is PER-OCCURRENCE (round-7; Codex round-6 R2: with dedup:false and a`);
511
521
  lines.push(`// duplicated registry value, two branches shared one (stepId,itemKey) slot — the second`);
@@ -650,12 +660,46 @@ function renderRuntime(plan: LoopPlan, planDig: string, execFp: string, blobs: L
650
660
  lines.push(` if (__primary !== null) { throw __primary.error } // drained FIRST, then the primary failure propagates`);
651
661
  lines.push(` return __results`);
652
662
  lines.push(`}`);
663
+ lines.push(`async function __drainAllWindowed(thunks, limit) {`);
664
+ lines.push(` const __limit = Math.max(1, Math.floor(Number(limit) || 1))`);
665
+ lines.push(` if (thunks.length <= __limit) { return __drainAll(thunks) }`);
666
+ lines.push(` const __out = new Array(thunks.length)`);
667
+ lines.push(` const __values = new Array(thunks.length)`);
668
+ lines.push(` let __next = 0`);
669
+ lines.push(` let __order = 0`);
670
+ lines.push(` const __workers = []`);
671
+ lines.push(` const __workerCount = Math.min(__limit, thunks.length)`);
672
+ lines.push(` for (let w = 0; w < __workerCount; w++) {`);
673
+ lines.push(` __workers.push(async function () {`);
674
+ lines.push(` while (__next < thunks.length) {`);
675
+ lines.push(` const i = __next++`);
676
+ lines.push(` try { const v = await thunks[i](); __values[i] = v; __out[i] = { ok: true, value: v, at: __order++ } }`);
677
+ lines.push(` catch (e) { __out[i] = { ok: false, error: e, at: __order++ } }`);
678
+ lines.push(` }`);
679
+ lines.push(` })`);
680
+ lines.push(` }`);
681
+ lines.push(` await parallel(__workers) // workers never reject ⇒ all registry positions drain to SETTLEMENT`);
682
+ lines.push(` let __primary = null`);
683
+ lines.push(` for (let i = 0; i < __out.length; i++) {`);
684
+ lines.push(` const o = __out[i]`);
685
+ lines.push(` if (o && o.ok !== true && (__primary === null || o.at < __primary.at)) { __primary = o }`);
686
+ lines.push(` }`);
687
+ lines.push(` if (__primary !== null) { throw __primary.error }`);
688
+ lines.push(` return __values`);
689
+ lines.push(`}`);
653
690
  }
654
691
  if (traceOn) {
655
692
  lines.push(`// trace wiring (blob-provided emitter; hooks INSIDE runStep — ADR-003)`);
656
693
  lines.push(`const __traceState = traceInit(RUN_ID, PLAN_DIGEST, EXEC_FP, 'rendered-script')`);
657
694
  lines.push(`__hooks.onDispatch = function (e) { return traceOnDispatch(__traceState, e) }`);
658
695
  lines.push(`__hooks.onSettle = function (e) { return traceOnSettle(__traceState, e) }`);
696
+ if (hasDeclaredTruncation) {
697
+ lines.push(`__hooks.onFanoutTruncated = function (e) {`);
698
+ lines.push(` const ev = { v: 1, runId: __traceState.runId, seq: ++__traceState.seq, event: 'fanout-truncated', stage: e.stage, registrySize: e.registrySize, dispatched: e.dispatched, reason: e.reason }`);
699
+ lines.push(` __traceState.buffer.push(JSON.stringify(ev))`);
700
+ lines.push(` return ev.seq`);
701
+ lines.push(`}`);
702
+ }
659
703
  lines.push(`async function __traceFlushNow(phaseName, stepLabel) {`);
660
704
  lines.push(` if (TRACE_FILE === null) { return }`);
661
705
  lines.push(` // cmd must be let: the trace payload stays LEFT and must never be replaced by the fa-record panel leg; both ride the SAME writer agent.`);