@deftai/directive-core 0.103.0 → 0.105.0

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 (255) hide show
  1. package/dist/authz/classify.js +682 -35
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/gate-lists.d.ts +3 -2
  5. package/dist/check/gate-lists.js +5 -2
  6. package/dist/check/index.d.ts +1 -0
  7. package/dist/check/index.js +1 -0
  8. package/dist/check/named-cause.d.ts +1 -1
  9. package/dist/check/named-cause.js +33 -3
  10. package/dist/check/session-completed-ac.d.ts +39 -0
  11. package/dist/check/session-completed-ac.js +198 -0
  12. package/dist/codebase/default-extractor.d.ts +5 -0
  13. package/dist/codebase/default-extractor.js +7 -15
  14. package/dist/deposit/copy-tree.js +60 -0
  15. package/dist/deposit/python-free.js +26 -18
  16. package/dist/doctor/checks.d.ts +8 -0
  17. package/dist/doctor/checks.js +37 -25
  18. package/dist/doctor/doctor-state.d.ts +4 -0
  19. package/dist/doctor/doctor-state.js +7 -1
  20. package/dist/doctor/main.js +9 -14
  21. package/dist/doctor/types.d.ts +9 -0
  22. package/dist/fs/contained-write.d.ts +115 -1
  23. package/dist/fs/contained-write.js +242 -5
  24. package/dist/fs/mutation-ledger.d.ts +54 -0
  25. package/dist/fs/mutation-ledger.js +133 -0
  26. package/dist/fs/non-product-dirs.d.ts +79 -0
  27. package/dist/fs/non-product-dirs.js +96 -0
  28. package/dist/hooks/dispatcher.d.ts +3 -1
  29. package/dist/hooks/dispatcher.js +14 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +3 -0
  32. package/dist/init-deposit/agent-hooks.js +17 -15
  33. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  34. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  35. package/dist/init-deposit/gitignore.js +9 -0
  36. package/dist/init-deposit/hygiene.d.ts +47 -11
  37. package/dist/init-deposit/hygiene.js +172 -37
  38. package/dist/init-deposit/legacy-detect.js +6 -2
  39. package/dist/init-deposit/refresh.d.ts +21 -1
  40. package/dist/init-deposit/refresh.js +106 -9
  41. package/dist/init-deposit/scaffold.js +28 -279
  42. package/dist/init-deposit/slash-deposit.js +13 -7
  43. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  44. package/dist/init-deposit/xbrief-projections.js +24 -11
  45. package/dist/intake/clause-derivation.d.ts +74 -0
  46. package/dist/intake/clause-derivation.js +409 -0
  47. package/dist/intake/index.d.ts +1 -0
  48. package/dist/intake/index.js +1 -0
  49. package/dist/intake/issue-ingest.d.ts +3 -1
  50. package/dist/intake/issue-ingest.js +36 -43
  51. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  52. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  53. package/dist/literal-acceptance/capture.d.ts +10 -0
  54. package/dist/literal-acceptance/capture.js +224 -75
  55. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  56. package/dist/literal-acceptance/evaluate.js +70 -2
  57. package/dist/literal-acceptance/index.d.ts +3 -3
  58. package/dist/literal-acceptance/index.js +3 -3
  59. package/dist/literal-acceptance/safety.d.ts +41 -0
  60. package/dist/literal-acceptance/safety.js +126 -2
  61. package/dist/literal-acceptance/types.d.ts +8 -0
  62. package/dist/orphan-active/evaluate.d.ts +7 -1
  63. package/dist/orphan-active/evaluate.js +102 -23
  64. package/dist/platform/platform-capabilities.js +2 -3
  65. package/dist/policy/base-branch.d.ts +37 -0
  66. package/dist/policy/base-branch.js +114 -0
  67. package/dist/policy/branch-sync.d.ts +80 -0
  68. package/dist/policy/branch-sync.js +211 -0
  69. package/dist/policy/ceremony-dial-escalation.js +1 -0
  70. package/dist/policy/ceremony-dial.js +4 -14
  71. package/dist/policy/delivery-branch.d.ts +5 -3
  72. package/dist/policy/delivery-branch.js +7 -3
  73. package/dist/policy/file-size-thresholds.d.ts +24 -0
  74. package/dist/policy/file-size-thresholds.js +23 -0
  75. package/dist/policy/forge-outage-retry.d.ts +47 -0
  76. package/dist/policy/forge-outage-retry.js +157 -0
  77. package/dist/policy/index.d.ts +7 -0
  78. package/dist/policy/index.js +66 -7
  79. package/dist/policy/project-invariants.d.ts +50 -0
  80. package/dist/policy/project-invariants.js +205 -0
  81. package/dist/policy/sync-default.d.ts +129 -0
  82. package/dist/policy/sync-default.js +452 -0
  83. package/dist/policy/sync-max-files.d.ts +93 -0
  84. package/dist/policy/sync-max-files.js +192 -0
  85. package/dist/pr-monitor/absent-required.d.ts +11 -0
  86. package/dist/pr-monitor/absent-required.js +61 -0
  87. package/dist/pr-monitor/constants.d.ts +7 -0
  88. package/dist/pr-monitor/constants.js +7 -0
  89. package/dist/pr-monitor/index.d.ts +2 -1
  90. package/dist/pr-monitor/index.js +2 -1
  91. package/dist/pr-monitor/main.js +4 -0
  92. package/dist/pr-monitor/monitor.js +24 -1
  93. package/dist/pr-wait-mergeable/classify.js +6 -0
  94. package/dist/preflight/evaluate.d.ts +9 -0
  95. package/dist/preflight/evaluate.js +49 -1
  96. package/dist/preflight/index.d.ts +5 -1
  97. package/dist/preflight/index.js +2 -0
  98. package/dist/preflight/intended-placement.d.ts +62 -0
  99. package/dist/preflight/intended-placement.js +275 -0
  100. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  101. package/dist/preflight/project-invariants-gate.js +82 -0
  102. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  103. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  104. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  105. package/dist/product-first-done-gate/acceptance.js +79 -8
  106. package/dist/product-first-done-gate/empty-resolution.d.ts +5 -2
  107. package/dist/product-first-done-gate/empty-resolution.js +13 -2
  108. package/dist/product-first-done-gate/evaluate.d.ts +29 -0
  109. package/dist/product-first-done-gate/evaluate.js +307 -33
  110. package/dist/product-first-done-gate/index.d.ts +3 -1
  111. package/dist/product-first-done-gate/index.js +2 -1
  112. package/dist/release/build-dist.d.ts +101 -0
  113. package/dist/release/build-dist.js +428 -46
  114. package/dist/run-summary/emit.d.ts +50 -4
  115. package/dist/run-summary/emit.js +262 -26
  116. package/dist/run-summary/index.d.ts +3 -2
  117. package/dist/run-summary/index.js +2 -1
  118. package/dist/run-summary/session-id.d.ts +21 -0
  119. package/dist/run-summary/session-id.js +92 -0
  120. package/dist/run-summary/share.d.ts +1 -1
  121. package/dist/run-summary/share.js +2 -2
  122. package/dist/run-summary/types.d.ts +38 -6
  123. package/dist/run-summary/types.js +1 -0
  124. package/dist/scope/acceptance-activate-gate.d.ts +1 -1
  125. package/dist/scope/acceptance-activate-gate.js +4 -4
  126. package/dist/scope/acceptance-evidence.d.ts +27 -0
  127. package/dist/scope/acceptance-evidence.js +66 -0
  128. package/dist/scope/capacity-stamp.d.ts +2 -0
  129. package/dist/scope/capacity-stamp.js +4 -0
  130. package/dist/scope/delivery-evidence.d.ts +9 -3
  131. package/dist/scope/delivery-evidence.js +39 -27
  132. package/dist/scope/index.d.ts +1 -0
  133. package/dist/scope/index.js +1 -0
  134. package/dist/scope/parent-lineage.js +7 -6
  135. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  136. package/dist/scope/project-invariant-coverage.js +322 -0
  137. package/dist/scope/transition.js +66 -4
  138. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  139. package/dist/scope-provenance/compare-intent.js +233 -0
  140. package/dist/scope-provenance/digest.d.ts +18 -1
  141. package/dist/scope-provenance/digest.js +16 -11
  142. package/dist/scope-provenance/evaluate.d.ts +5 -1
  143. package/dist/scope-provenance/evaluate.js +64 -4
  144. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  145. package/dist/scope-provenance/extract-intent.js +262 -0
  146. package/dist/scope-provenance/index.d.ts +7 -1
  147. package/dist/scope-provenance/index.js +7 -1
  148. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  149. package/dist/scope-provenance/intent-digest.js +33 -0
  150. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  151. package/dist/scope-provenance/intent-evaluate.js +149 -0
  152. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  153. package/dist/scope-provenance/json-tokenizer.js +225 -0
  154. package/dist/scope-provenance/known-machine.d.ts +38 -0
  155. package/dist/scope-provenance/known-machine.js +157 -0
  156. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  157. package/dist/scope-provenance/mint-artifacts.js +425 -0
  158. package/dist/session/ac-pass-banking.d.ts +4 -36
  159. package/dist/session/ac-pass-banking.js +30 -77
  160. package/dist/session/ac-pass-reuse.d.ts +53 -0
  161. package/dist/session/ac-pass-reuse.js +95 -0
  162. package/dist/session/ceremony-dial-evidence.d.ts +42 -0
  163. package/dist/session/ceremony-dial-evidence.js +194 -0
  164. package/dist/session/git.js +19 -4
  165. package/dist/session/index.d.ts +5 -0
  166. package/dist/session/index.js +5 -0
  167. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  168. package/dist/session/occupancy-stress-worker.js +36 -0
  169. package/dist/session/occupancy.d.ts +97 -0
  170. package/dist/session/occupancy.js +422 -0
  171. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  172. package/dist/session/product-state-hash.d.ts +26 -0
  173. package/dist/session/product-state-hash.js +683 -0
  174. package/dist/session/session-ready.d.ts +2 -0
  175. package/dist/session/session-ready.js +52 -30
  176. package/dist/session/session-start.d.ts +7 -0
  177. package/dist/session/session-start.js +88 -5
  178. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  179. package/dist/session/verify-ac-session-cache.js +126 -0
  180. package/dist/session/verify-session-ritual.d.ts +5 -0
  181. package/dist/session/verify-session-ritual.js +9 -0
  182. package/dist/slash/openclaw-deposit.js +13 -9
  183. package/dist/slice/constants.d.ts +2 -2
  184. package/dist/slice/constants.js +2 -2
  185. package/dist/slice/existing.js +21 -4
  186. package/dist/slice/lock.d.ts +16 -1
  187. package/dist/slice/lock.js +137 -11
  188. package/dist/slice/record.d.ts +8 -5
  189. package/dist/slice/record.js +21 -13
  190. package/dist/story-ready/evaluate.d.ts +2 -0
  191. package/dist/story-ready/evaluate.js +13 -0
  192. package/dist/swarm/complete-cohort.d.ts +3 -0
  193. package/dist/swarm/complete-cohort.js +59 -11
  194. package/dist/swarm/finalize-cohort.js +33 -2
  195. package/dist/swarm/launch.d.ts +22 -0
  196. package/dist/swarm/launch.js +155 -0
  197. package/dist/swarm/routing-set-cli.d.ts +11 -1
  198. package/dist/swarm/routing-set-cli.js +13 -3
  199. package/dist/swarm/routing-verify.d.ts +12 -0
  200. package/dist/swarm/routing-verify.js +59 -20
  201. package/dist/swarm/routing.d.ts +14 -0
  202. package/dist/swarm/routing.js +53 -3
  203. package/dist/telemetry-coverage/evaluate.d.ts +42 -0
  204. package/dist/telemetry-coverage/evaluate.js +148 -0
  205. package/dist/telemetry-coverage/fake-trial.d.ts +45 -0
  206. package/dist/telemetry-coverage/fake-trial.js +204 -0
  207. package/dist/telemetry-coverage/index.d.ts +5 -0
  208. package/dist/telemetry-coverage/index.js +5 -0
  209. package/dist/telemetry-coverage/kinds.d.ts +19 -0
  210. package/dist/telemetry-coverage/kinds.js +54 -0
  211. package/dist/telemetry-coverage/scan-callers.d.ts +25 -0
  212. package/dist/telemetry-coverage/scan-callers.js +168 -0
  213. package/dist/triage/actions/index.js +4 -1
  214. package/dist/ts-check-lane/index.d.ts +3 -0
  215. package/dist/ts-check-lane/index.js +2 -0
  216. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  217. package/dist/ts-check-lane/progress-reporter.js +38 -0
  218. package/dist/ts-check-lane/progress.d.ts +32 -0
  219. package/dist/ts-check-lane/progress.js +57 -0
  220. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  221. package/dist/ts-check-lane/run-lane.js +8 -6
  222. package/dist/validate-content/validate-links.d.ts +8 -0
  223. package/dist/validate-content/validate-links.js +9 -15
  224. package/dist/value/feedback-file.js +12 -2
  225. package/dist/vbrief-build/index.d.ts +1 -0
  226. package/dist/vbrief-build/index.js +1 -0
  227. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  228. package/dist/vbrief-build/project-invariants-io.js +41 -0
  229. package/dist/vbrief-reconcile/graph.js +18 -1
  230. package/dist/vbrief-reconcile/index.d.ts +3 -1
  231. package/dist/vbrief-reconcile/index.js +2 -1
  232. package/dist/vbrief-reconcile/origin-freshness.d.ts +44 -0
  233. package/dist/vbrief-reconcile/origin-freshness.js +221 -0
  234. package/dist/vbrief-reconcile/types.d.ts +13 -0
  235. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  236. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  237. package/dist/vbrief-validate/index.d.ts +1 -1
  238. package/dist/vbrief-validate/index.js +1 -1
  239. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  240. package/dist/vbrief-validate/plan-hooks.js +68 -0
  241. package/dist/verify-ac/evaluate.d.ts +7 -1
  242. package/dist/verify-ac/evaluate.js +34 -26
  243. package/dist/verify-ac/flag.d.ts +4 -0
  244. package/dist/verify-ac/flag.js +16 -0
  245. package/dist/verify-ac/index.d.ts +2 -2
  246. package/dist/verify-ac/index.js +2 -2
  247. package/dist/verify-source/contained-writes.js +2 -0
  248. package/dist/verify-source/index.d.ts +1 -0
  249. package/dist/verify-source/index.js +1 -0
  250. package/dist/verify-source/verify-stubs.d.ts +15 -1
  251. package/dist/verify-source/verify-stubs.js +20 -16
  252. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  253. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  254. package/dist/xbrief-migrate/migrate-project.js +2 -5
  255. package/package.json +3 -3
@@ -21,6 +21,28 @@ import { readTextSafe } from "./paths.js";
21
21
  export const XBRIEF_ENVELOPE_MIGRATE_COMMAND = "deft migrate:xbrief";
22
22
  /** Doctor check name for envelope major mismatch (Q5 Option 2, #3243). */
23
23
  export const XBRIEF_ENVELOPE_MAJOR_CHECK = "xbrief-envelope-version";
24
+ /**
25
+ * One SoT for advisory (exit-exempt) check fails (#3379).
26
+ * Union of the previous `deriveExitCode` / `cmdDoctor` name lists.
27
+ * A fail is advisory when `data.advisory === true` or the name is in this set.
28
+ */
29
+ export const DOCTOR_ADVISORY_FAIL_CHECKS = new Set([
30
+ "legacy-layout",
31
+ "canonical-vendored-npm-signpost",
32
+ "manifest-version-reportable",
33
+ "gitignore-coverage",
34
+ "stale-xbrief-schema-deposit",
35
+ "typescript-7-side-by-side",
36
+ "completed-open-items",
37
+ "coverage-check-resume-policy",
38
+ ]);
39
+ /** True when a check fail must stay a warning (not lastErrorCount / exit 1). */
40
+ export function isDoctorAdvisoryFail(name, data) {
41
+ return data?.advisory === true || DOCTOR_ADVISORY_FAIL_CHECKS.has(name);
42
+ }
43
+ function stampAdvisory(data) {
44
+ return { ...data, advisory: true };
45
+ }
24
46
  /** True when an fs error means the path is cleanly absent (not unreadable). */
25
47
  function isEnoentError(err) {
26
48
  return (typeof err === "object" &&
@@ -650,13 +672,13 @@ export function checkLegacyLayout(projectRoot, seams = {}) {
650
672
  name: "legacy-layout",
651
673
  status: "fail",
652
674
  detail: dualLayoutSignpostLine(dualLayout),
653
- data: {
675
+ data: stampAdvisory({
654
676
  legacy_layout: true,
655
677
  legacy_layout_kind: dualLayout.kind,
656
678
  evidence: [...dualLayout.evidence],
657
679
  upgrading_doc_url: UPGRADING_DOC_URL,
658
680
  go_bridge_releases_url: GO_BRIDGE_RELEASES_URL,
659
- },
681
+ }),
660
682
  };
661
683
  }
662
684
  const detection = detectLegacyLayout(projectRoot, legacySeams);
@@ -672,13 +694,13 @@ export function checkLegacyLayout(projectRoot, seams = {}) {
672
694
  name: "legacy-layout",
673
695
  status: "fail",
674
696
  detail: legacyLayoutSignpostLine(detection),
675
- data: {
697
+ data: stampAdvisory({
676
698
  legacy_layout: true,
677
699
  legacy_layout_kind: detection.kind,
678
700
  evidence: [...detection.evidence],
679
701
  upgrading_doc_url: UPGRADING_DOC_URL,
680
702
  go_bridge_releases_url: GO_BRIDGE_RELEASES_URL,
681
- },
703
+ }),
682
704
  };
683
705
  }
684
706
  /**
@@ -723,14 +745,14 @@ export function checkCanonicalVendoredNpmSignpost(projectRoot, seams = {}) {
723
745
  name: "canonical-vendored-npm-signpost",
724
746
  status: "fail",
725
747
  detail,
726
- data: {
748
+ data: stampAdvisory({
727
749
  canonical_vendored: true,
728
750
  npm_managed: false,
729
751
  manifest_path: manifestPath,
730
752
  sentinel_key: NPM_MANAGED_SENTINEL_KEY,
731
753
  sentinel_value: NPM_MANAGED_SENTINEL_VALUE,
732
754
  upgrading_doc_url: UPGRADING_DOC_URL,
733
- },
755
+ }),
734
756
  };
735
757
  }
736
758
  /**
@@ -786,14 +808,14 @@ export function checkManifestVersionReportable(projectRoot, installRoot, seams =
786
808
  "so the framework version is unreportable. This happens on legacy `deft-install` deposits " +
787
809
  `made without a release pin (#2294). Run \`${CANONICAL_UPGRADE_COMMAND}\` then \`directive update\` ` +
788
810
  `to obtain a pinned npm-managed manifest. See ${UPGRADING_DOC_URL}.`,
789
- data: {
811
+ data: stampAdvisory({
790
812
  manifest_path: manifestPath,
791
813
  version: null,
792
814
  source: reportable.source,
793
815
  sha: reportable.sha,
794
816
  upgrade_command: CANONICAL_UPGRADE_COMMAND,
795
817
  upgrading_doc_url: UPGRADING_DOC_URL,
796
- },
818
+ }),
797
819
  };
798
820
  }
799
821
  return {
@@ -875,11 +897,11 @@ export function checkStaleXbriefSchemaDeposit(projectRoot, seams = {}) {
875
897
  status: "fail",
876
898
  detail: `Stale deposited schema at ${STALE_VBRIEF_CORE_SCHEMA_REL} still carries the legacy ${LEGACY_INFO_ROOT_KEY} root key on an already-migrated xbrief/ layout. ` +
877
899
  "Run `directive update` to refresh deposited schema files — not `deft migrate:xbrief` (no vbrief/ tree remains to convert).",
878
- data: {
900
+ data: stampAdvisory({
879
901
  schema_path: schemaPath,
880
902
  convergence_state: convergence.state,
881
903
  suggested_fix: "directive update",
882
- },
904
+ }),
883
905
  };
884
906
  }
885
907
  const TS7_SIDE_BY_SIDE_CHECK = "typescript-7-side-by-side";
@@ -1011,11 +1033,11 @@ export function checkTypescript7SideBySide(projectRoot, seams = {}) {
1011
1033
  detail: "typescript resolves to 7.x without the @typescript/typescript6 alias while typescript-eslint and eslint are present. " +
1012
1034
  "Until TS 7.1, use the side-by-side alias pattern in languages/typescript.md § TypeScript 7 side-by-side (pre-7.1) " +
1013
1035
  "(Cartograph #111: keep typescript on npm:@typescript/typescript6 and install TS 7 under @typescript/native).",
1014
- data: {
1036
+ data: stampAdvisory({
1015
1037
  package_json_path: packageJsonPath,
1016
1038
  typescript: typescriptValue,
1017
1039
  suggested_fix: "languages/typescript.md#typescript-7-side-by-side-pre-71",
1018
- },
1040
+ }),
1019
1041
  };
1020
1042
  }
1021
1043
  /**
@@ -1062,11 +1084,11 @@ export function checkGitignoreCoverage(projectRoot, seams = {}) {
1062
1084
  `missing (${missing.slice(0, 3).join(", ")}${missing.length > 3 ? ", …" : ""}). ` +
1063
1085
  "Run `directive update` to add the missing entries (idempotent, safe on re-run). " +
1064
1086
  `Full missing list: ${JSON.stringify(missing)}.`,
1065
- data: {
1087
+ data: stampAdvisory({
1066
1088
  gitignore_path: gitignorePath,
1067
1089
  missing,
1068
1090
  suggested_fix: "directive update",
1069
- },
1091
+ }),
1070
1092
  };
1071
1093
  }
1072
1094
  /**
@@ -1181,20 +1203,10 @@ export function checkCoverageCheckResumePolicy(projectRoot) {
1181
1203
  };
1182
1204
  }
1183
1205
  export function deriveExitCode(checks, errors) {
1184
- const exitExempt = new Set([
1185
- "canonical-vendored-npm-signpost",
1186
- "manifest-version-reportable",
1187
- "gitignore-coverage",
1188
- "stale-xbrief-schema-deposit",
1189
- "typescript-7-side-by-side",
1190
- "coverage-check-resume-policy",
1191
- // Historical pre-#2862 completed/ corpora; hard fail is scope:complete (#3242).
1192
- "completed-open-items",
1193
- ]);
1194
1206
  if (errors.length > 0 || checks.some((c) => c.status === "error")) {
1195
1207
  return 2;
1196
1208
  }
1197
- if (checks.some((c) => c.status === "fail" && !exitExempt.has(c.name))) {
1209
+ if (checks.some((c) => c.status === "fail" && !isDoctorAdvisoryFail(c.name, c.data))) {
1198
1210
  return 1;
1199
1211
  }
1200
1212
  return 0;
@@ -11,6 +11,10 @@ export declare function writeState(projectRoot: string, payload: {
11
11
  export declare function decideThrottle(state: DoctorState | null, now?: Date): ThrottleDecision;
12
12
  export declare function formatUtcIso(when: Date): string;
13
13
  export declare function formatIsoZ(when: Date | null): string;
14
+ /** Dirty doctor / ritual copy: --full re-probes only; do not prescribe a write (#3379). */
15
+ export declare const DOCTOR_HARD_ERROR_NOTE = "these are hard errors (advisory warnings do not block writes)";
16
+ export declare const DOCTOR_DIRTY_REPROBE_HINT = "run `deft doctor --full` to re-probe only";
17
+ export declare function dirtyDoctorHint(): string;
14
18
  export declare function renderDoctorStatusLine(decision: ThrottleDecision, now?: Date): string;
15
19
  export {};
16
20
  //# sourceMappingURL=doctor-state.d.ts.map
@@ -126,6 +126,12 @@ export function formatIsoZ(when) {
126
126
  }
127
127
  return formatUtcIso(when);
128
128
  }
129
+ /** Dirty doctor / ritual copy: --full re-probes only; do not prescribe a write (#3379). */
130
+ export const DOCTOR_HARD_ERROR_NOTE = "these are hard errors (advisory warnings do not block writes)";
131
+ export const DOCTOR_DIRTY_REPROBE_HINT = "run `deft doctor --full` to re-probe only";
132
+ export function dirtyDoctorHint() {
133
+ return `${DOCTOR_HARD_ERROR_NOTE}; ${DOCTOR_DIRTY_REPROBE_HINT}`;
134
+ }
129
135
  export function renderDoctorStatusLine(decision, now = new Date()) {
130
136
  const ageH = Math.max(Math.floor(decision.ageHours), 0);
131
137
  if (decision.dirty) {
@@ -133,7 +139,7 @@ export function renderDoctorStatusLine(decision, now = new Date()) {
133
139
  const warns = Math.max(decision.lastFindingCount - decision.lastErrorCount, 0);
134
140
  const errPhrase = `${errs} error${errs !== 1 ? "s" : ""}`;
135
141
  const warnPhrase = `${warns} warning${warns !== 1 ? "s" : ""}`;
136
- return `[doctor] ran ${ageH}h ago, ${errPhrase} / ${warnPhrase} -- UNRESOLVED; run \`deft doctor --full\` to re-probe or address findings.`;
142
+ return `[doctor] ran ${ageH}h ago, ${errPhrase} / ${warnPhrase} -- UNRESOLVED; ${DOCTOR_HARD_ERROR_NOTE}. ${DOCTOR_DIRTY_REPROBE_HINT}.`;
137
143
  }
138
144
  const remainingMs = (decision.nextEligibleAt?.getTime() ?? now.getTime()) - now.getTime();
139
145
  const remainingH = Math.max(Math.floor(remainingMs / 3_600_000), 0);
@@ -12,9 +12,9 @@ import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
12
12
  import { evaluateAgentHooks } from "../verify-env/agent-hooks.js";
13
13
  import { probeAgentHooksLive } from "../verify-env/agent-hooks-live-probe.js";
14
14
  import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
15
- import { checkXbriefEnvelopeMajorVersion, runChecks, XBRIEF_ENVELOPE_MAJOR_CHECK, XBRIEF_ENVELOPE_MIGRATE_COMMAND, } from "./checks.js";
15
+ import { checkXbriefEnvelopeMajorVersion, DOCTOR_ADVISORY_FAIL_CHECKS, runChecks, XBRIEF_ENVELOPE_MAJOR_CHECK, XBRIEF_ENVELOPE_MIGRATE_COMMAND, } from "./checks.js";
16
16
  import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, NETWORK_DISCLOSURE_LINE, PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
17
- import { decideThrottle, formatIsoZ, readState, renderDoctorStatusLine, writeState, } from "./doctor-state.js";
17
+ import { decideThrottle, dirtyDoctorHint, formatIsoZ, readState, renderDoctorStatusLine, writeState, } from "./doctor-state.js";
18
18
  import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } from "./flags.js";
19
19
  import { formatDoctorHelp } from "./help.js";
20
20
  import { pythonJsonDump } from "./json.js";
@@ -166,9 +166,7 @@ export function cmdDoctor(args, seams = {}) {
166
166
  runLocalSignpostChecks(projectRoot, throttleSink, (finding) => {
167
167
  throttleFindings.push(finding);
168
168
  }, seams);
169
- const hint = decision.dirty
170
- ? "run `deft doctor --full` to re-probe or address findings"
171
- : "--full forces";
169
+ const hint = decision.dirty ? dirtyDoctorHint() : "--full forces";
172
170
  if (jsonMode) {
173
171
  const payload = {
174
172
  status: "throttle-skipped",
@@ -700,11 +698,14 @@ function runInstallIntegrityChecks(projectRoot, sink, addFinding, seams) {
700
698
  return null;
701
699
  }
702
700
  });
703
- const result = runChecks(projectRoot, { isDir, isFile, readText });
701
+ const result = (seams.runChecks ?? runChecks)(projectRoot, { isDir, isFile, readText });
702
+ // Advisory fails stay warnings so lastErrorCount stays 0
703
+ // (gated ritual / PreToolUse). Hard stays hard: completed-lifecycle-consistency.
704
704
  for (const entry of result.checks ?? []) {
705
705
  const name = String(entry.name ?? "install-integrity");
706
706
  const status = String(entry.status ?? "");
707
707
  const detail = String(entry.detail ?? "");
708
+ const data = entry.data ?? {};
708
709
  // Authoritative envelope-major emission is runXbriefEnvelopeVersionCheck
709
710
  // (runs for framework repo + consumers). Skip install-integrity duplicate.
710
711
  if (name === XBRIEF_ENVELOPE_MAJOR_CHECK) {
@@ -718,13 +719,7 @@ function runInstallIntegrityChecks(projectRoot, sink, addFinding, seams) {
718
719
  sink.info(`${name}: skip -- ${detail}`);
719
720
  continue;
720
721
  }
721
- if ((name === "legacy-layout" ||
722
- name === "canonical-vendored-npm-signpost" ||
723
- name === "manifest-version-reportable" ||
724
- name === "gitignore-coverage" ||
725
- name === "stale-xbrief-schema-deposit" ||
726
- name === "typescript-7-side-by-side") &&
727
- status === "fail") {
722
+ if (status === "fail" && (data.advisory === true || DOCTOR_ADVISORY_FAIL_CHECKS.has(name))) {
728
723
  sink.warn(`${name}: ${detail}`);
729
724
  addFinding({
730
725
  severity: "warning",
@@ -732,7 +727,7 @@ function runInstallIntegrityChecks(projectRoot, sink, addFinding, seams) {
732
727
  check: `install-integrity:${name}`,
733
728
  install_check: name,
734
729
  status,
735
- data: entry.data ?? {},
730
+ data,
736
731
  });
737
732
  continue;
738
733
  }
@@ -166,6 +166,15 @@ export interface DoctorSeams {
166
166
  * its `plan` object; returns [] when no project definition is present.
167
167
  */
168
168
  readonly detectPlanExtensionShadows?: (projectRoot: string) => readonly ShadowedPlanExtension[];
169
+ /**
170
+ * Override install-integrity `runChecks` for tests (#3379). Default is
171
+ * `runChecks` from `./checks.js`.
172
+ */
173
+ readonly runChecks?: (projectRoot: string, seams?: {
174
+ readonly readText?: (path: string) => string | null;
175
+ readonly isFile?: (path: string) => boolean;
176
+ readonly isDir?: (path: string) => boolean;
177
+ }) => Record<string, unknown>;
169
178
  /** Read-only agent-host hook registration probe (#2438). */
170
179
  readonly evaluateAgentHooks?: (projectRoot: string) => AgentHookHealthResult;
171
180
  /** Live hook spawn probe for doctor --full (#2852). */
@@ -15,7 +15,20 @@
15
15
  * onto this API over bespoke per-sink checks when equivalent. See
16
16
  * `docs/reference/contained-write.md`.
17
17
  *
18
- * Refs #2951.
18
+ * Delete coverage (#3392 / #3456): real dest deletes MUST go through
19
+ * {@link containedRemove} so a bound ledger (and ADR-004 record mode) records
20
+ * them. Raw `rmSync` on a dest path is a chokepoint bypass.
21
+ *
22
+ * Plan-mode zero-mutation (#3456 / ADR-004): `deft update --dry-run` binds
23
+ * this port in record mode (`runInPortRecordMode`). Dest write / remove /
24
+ * chmod / dest-mutating exec record and skip dest IO. Temp-file cleanups
25
+ * either use this chokepoint or live outside the project root.
26
+ *
27
+ * `tryCleanupLegacyDeftTree` is not on the update/dry-run path (classify
28
+ * refuses `migration-required` first). Its default removeDir still routes
29
+ * through containedRemove so the site is not a silent exception.
30
+ *
31
+ * Refs #2951 / #3392 / #3456.
19
32
  */
20
33
  /** Stable machine-readable error codes for contained-write refusals. */
21
34
  export declare const ContainedWriteErrorCode: {
@@ -78,6 +91,38 @@ export interface ContainedWriteInput {
78
91
  * {@link assertWriteTargetSafe}.
79
92
  */
80
93
  readonly followSymlinks?: boolean;
94
+ /**
95
+ * Ledger side-effect (#3392). Default records `wrote` when a ledger is bound.
96
+ * `false` skips. `kind`/`path` override the recorded entry (atomic tmp+rename).
97
+ */
98
+ readonly mutation?: false | {
99
+ readonly kind?: "wrote" | "stripped";
100
+ readonly path?: string;
101
+ };
102
+ }
103
+ export interface ContainedRemoveInput {
104
+ /** Absolute containment root (project root, deposit root, or sandbox). */
105
+ readonly root: string;
106
+ /**
107
+ * Remove target. Relative paths are resolved under `root`. Absolute paths
108
+ * must stay under `root` after normalization.
109
+ */
110
+ readonly target: string;
111
+ /**
112
+ * Ledger side-effect (#3392). Default records `deleted` when a ledger is bound.
113
+ * `false` skips. `path` overrides the recorded path.
114
+ */
115
+ readonly mutation?: false | {
116
+ readonly path?: string;
117
+ };
118
+ /** When true, remove directories recursively. Default `false`. */
119
+ readonly recursive?: boolean;
120
+ }
121
+ export interface ContainedRemoveResult {
122
+ /** Absolute path considered for removal. */
123
+ readonly path: string;
124
+ /** True when a file existed and was removed. */
125
+ readonly removed: boolean;
81
126
  }
82
127
  export interface ContainedWriteResult {
83
128
  /** Absolute path written. */
@@ -86,6 +131,37 @@ export interface ContainedWriteResult {
86
131
  readonly bytesWritten: number;
87
132
  readonly mode: ContainedWriteMode;
88
133
  }
134
+ export interface ContainedChmodInput {
135
+ readonly root: string;
136
+ readonly target: string;
137
+ readonly mode: number;
138
+ readonly mutation?: false | {
139
+ readonly path?: string;
140
+ };
141
+ }
142
+ export interface ContainedRenameInput {
143
+ readonly root: string;
144
+ readonly from: string;
145
+ readonly to: string;
146
+ /**
147
+ * Default records `wrote` on `to`. `false` skips (caller already ledgered).
148
+ */
149
+ readonly mutation?: false | {
150
+ readonly kind?: "wrote" | "stripped";
151
+ readonly path?: string;
152
+ };
153
+ }
154
+ export interface ContainedDestExecInput {
155
+ readonly root: string;
156
+ /** Dest path this exec mutates (e.g. `.git/config`). */
157
+ readonly destTarget: string;
158
+ readonly file: string;
159
+ readonly args: readonly string[];
160
+ }
161
+ export interface ContainedDestExecResult {
162
+ readonly ok: boolean;
163
+ readonly stdout: string;
164
+ }
89
165
  /**
90
166
  * Resolve `target` under `root`. Relative targets join root; absolute targets
91
167
  * must already be nested under root (path-segment containment).
@@ -98,4 +174,42 @@ export declare function resolveContainedTarget(root: string, target: string): st
98
174
  * @throws {ContainedWriteError} on containment refusal or mode violation
99
175
  */
100
176
  export declare function containedWrite(input: ContainedWriteInput): ContainedWriteResult;
177
+ /**
178
+ * Contained remove: resolve under root, refuse parent-path symlink / out-of-root,
179
+ * then delete. An in-root leaf symlink is unlinked (not followed, not refused).
180
+ * Missing targets are a no-op (not ledgered).
181
+ *
182
+ * @throws {ContainedWriteError} on containment refusal
183
+ */
184
+ export declare function containedRemove(input: ContainedRemoveInput): ContainedRemoveResult;
185
+ /**
186
+ * Contained mkdir: nest under root. Record mode skips dest IO (dirs are not
187
+ * dest-file membership). Live path creates recursively.
188
+ */
189
+ export declare function containedMkdir(input: {
190
+ readonly root: string;
191
+ readonly target: string;
192
+ }): {
193
+ path: string;
194
+ };
195
+ /**
196
+ * Contained chmod: resolve under root, refuse parent-path symlink / out-of-root,
197
+ * then set mode. Record mode records `chmod` and skips dest IO.
198
+ */
199
+ export declare function containedChmod(input: ContainedChmodInput): {
200
+ path: string;
201
+ };
202
+ /**
203
+ * Contained rename: both paths must nest under root. Record mode records
204
+ * `wrote` on `to` (unless `mutation: false`) and skips dest IO.
205
+ */
206
+ export declare function containedRename(input: ContainedRenameInput): {
207
+ from: string;
208
+ to: string;
209
+ };
210
+ /**
211
+ * Dest-mutating exec (git config, git add, …). Record mode records `exec` on
212
+ * `destTarget` and skips the child process. Allowlisted as the port impl.
213
+ */
214
+ export declare function containedDestExec(input: ContainedDestExecInput): ContainedDestExecResult;
101
215
  //# sourceMappingURL=contained-write.d.ts.map