@deftai/directive-core 0.104.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 (225) hide show
  1. package/dist/authz/classify.js +526 -42
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/named-cause.d.ts +1 -1
  5. package/dist/check/named-cause.js +33 -3
  6. package/dist/codebase/default-extractor.d.ts +5 -0
  7. package/dist/codebase/default-extractor.js +7 -15
  8. package/dist/deposit/copy-tree.js +60 -0
  9. package/dist/deposit/python-free.js +26 -18
  10. package/dist/doctor/checks.d.ts +8 -0
  11. package/dist/doctor/checks.js +37 -25
  12. package/dist/doctor/doctor-state.d.ts +4 -0
  13. package/dist/doctor/doctor-state.js +7 -1
  14. package/dist/doctor/main.js +9 -19
  15. package/dist/doctor/types.d.ts +9 -0
  16. package/dist/fs/contained-write.d.ts +115 -1
  17. package/dist/fs/contained-write.js +241 -5
  18. package/dist/fs/mutation-ledger.d.ts +54 -0
  19. package/dist/fs/mutation-ledger.js +133 -0
  20. package/dist/fs/non-product-dirs.d.ts +79 -0
  21. package/dist/fs/non-product-dirs.js +96 -0
  22. package/dist/hooks/dispatcher.d.ts +3 -1
  23. package/dist/hooks/dispatcher.js +14 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/init-deposit/agent-hooks.js +17 -15
  27. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  28. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  29. package/dist/init-deposit/gitignore.js +9 -0
  30. package/dist/init-deposit/hygiene.d.ts +47 -11
  31. package/dist/init-deposit/hygiene.js +172 -37
  32. package/dist/init-deposit/legacy-detect.js +6 -2
  33. package/dist/init-deposit/refresh.d.ts +21 -1
  34. package/dist/init-deposit/refresh.js +106 -9
  35. package/dist/init-deposit/scaffold.js +28 -279
  36. package/dist/init-deposit/slash-deposit.js +13 -7
  37. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  38. package/dist/init-deposit/xbrief-projections.js +24 -11
  39. package/dist/intake/clause-derivation.d.ts +38 -1
  40. package/dist/intake/clause-derivation.js +211 -9
  41. package/dist/intake/issue-ingest.d.ts +3 -1
  42. package/dist/intake/issue-ingest.js +35 -6
  43. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  44. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  45. package/dist/literal-acceptance/capture.d.ts +10 -0
  46. package/dist/literal-acceptance/capture.js +224 -75
  47. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  48. package/dist/literal-acceptance/evaluate.js +70 -2
  49. package/dist/literal-acceptance/index.d.ts +3 -3
  50. package/dist/literal-acceptance/index.js +3 -3
  51. package/dist/literal-acceptance/safety.d.ts +41 -0
  52. package/dist/literal-acceptance/safety.js +126 -2
  53. package/dist/literal-acceptance/types.d.ts +8 -0
  54. package/dist/orphan-active/evaluate.d.ts +7 -1
  55. package/dist/orphan-active/evaluate.js +102 -23
  56. package/dist/platform/platform-capabilities.js +2 -3
  57. package/dist/policy/base-branch.d.ts +37 -0
  58. package/dist/policy/base-branch.js +114 -0
  59. package/dist/policy/branch-sync.d.ts +80 -0
  60. package/dist/policy/branch-sync.js +211 -0
  61. package/dist/policy/ceremony-dial-escalation.js +1 -0
  62. package/dist/policy/ceremony-dial.js +4 -14
  63. package/dist/policy/delivery-branch.d.ts +5 -3
  64. package/dist/policy/delivery-branch.js +7 -3
  65. package/dist/policy/file-size-thresholds.d.ts +24 -0
  66. package/dist/policy/file-size-thresholds.js +23 -0
  67. package/dist/policy/forge-outage-retry.d.ts +47 -0
  68. package/dist/policy/forge-outage-retry.js +157 -0
  69. package/dist/policy/index.d.ts +7 -0
  70. package/dist/policy/index.js +66 -7
  71. package/dist/policy/project-invariants.d.ts +50 -0
  72. package/dist/policy/project-invariants.js +205 -0
  73. package/dist/policy/sync-default.d.ts +129 -0
  74. package/dist/policy/sync-default.js +452 -0
  75. package/dist/policy/sync-max-files.d.ts +93 -0
  76. package/dist/policy/sync-max-files.js +192 -0
  77. package/dist/pr-monitor/absent-required.d.ts +11 -0
  78. package/dist/pr-monitor/absent-required.js +61 -0
  79. package/dist/pr-monitor/constants.d.ts +7 -0
  80. package/dist/pr-monitor/constants.js +7 -0
  81. package/dist/pr-monitor/index.d.ts +2 -1
  82. package/dist/pr-monitor/index.js +2 -1
  83. package/dist/pr-monitor/main.js +4 -0
  84. package/dist/pr-monitor/monitor.js +24 -1
  85. package/dist/pr-wait-mergeable/classify.js +6 -0
  86. package/dist/preflight/evaluate.d.ts +4 -0
  87. package/dist/preflight/evaluate.js +35 -1
  88. package/dist/preflight/index.d.ts +4 -0
  89. package/dist/preflight/index.js +2 -0
  90. package/dist/preflight/intended-placement.d.ts +62 -0
  91. package/dist/preflight/intended-placement.js +275 -0
  92. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  93. package/dist/preflight/project-invariants-gate.js +82 -0
  94. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  95. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  96. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  97. package/dist/product-first-done-gate/acceptance.js +79 -8
  98. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  99. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  100. package/dist/product-first-done-gate/evaluate.d.ts +12 -0
  101. package/dist/product-first-done-gate/evaluate.js +254 -31
  102. package/dist/product-first-done-gate/index.d.ts +2 -0
  103. package/dist/product-first-done-gate/index.js +1 -0
  104. package/dist/release/build-dist.d.ts +101 -0
  105. package/dist/release/build-dist.js +428 -46
  106. package/dist/run-summary/emit.d.ts +16 -10
  107. package/dist/run-summary/emit.js +26 -15
  108. package/dist/run-summary/index.d.ts +3 -2
  109. package/dist/run-summary/index.js +2 -1
  110. package/dist/run-summary/session-id.d.ts +21 -0
  111. package/dist/run-summary/session-id.js +92 -0
  112. package/dist/run-summary/types.d.ts +29 -4
  113. package/dist/run-summary/types.js +1 -0
  114. package/dist/scope/acceptance-evidence.d.ts +19 -7
  115. package/dist/scope/acceptance-evidence.js +38 -11
  116. package/dist/scope/delivery-evidence.d.ts +3 -3
  117. package/dist/scope/delivery-evidence.js +22 -27
  118. package/dist/scope/index.d.ts +1 -0
  119. package/dist/scope/index.js +1 -0
  120. package/dist/scope/parent-lineage.js +7 -6
  121. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  122. package/dist/scope/project-invariant-coverage.js +322 -0
  123. package/dist/scope/transition.js +1 -1
  124. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  125. package/dist/scope-provenance/compare-intent.js +233 -0
  126. package/dist/scope-provenance/digest.d.ts +18 -1
  127. package/dist/scope-provenance/digest.js +16 -11
  128. package/dist/scope-provenance/evaluate.d.ts +5 -1
  129. package/dist/scope-provenance/evaluate.js +64 -4
  130. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  131. package/dist/scope-provenance/extract-intent.js +262 -0
  132. package/dist/scope-provenance/index.d.ts +7 -1
  133. package/dist/scope-provenance/index.js +7 -1
  134. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  135. package/dist/scope-provenance/intent-digest.js +33 -0
  136. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  137. package/dist/scope-provenance/intent-evaluate.js +149 -0
  138. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  139. package/dist/scope-provenance/json-tokenizer.js +225 -0
  140. package/dist/scope-provenance/known-machine.d.ts +38 -0
  141. package/dist/scope-provenance/known-machine.js +157 -0
  142. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  143. package/dist/scope-provenance/mint-artifacts.js +425 -0
  144. package/dist/session/ac-pass-banking.d.ts +4 -36
  145. package/dist/session/ac-pass-banking.js +30 -77
  146. package/dist/session/ac-pass-reuse.d.ts +53 -0
  147. package/dist/session/ac-pass-reuse.js +95 -0
  148. package/dist/session/git.js +19 -4
  149. package/dist/session/index.d.ts +4 -0
  150. package/dist/session/index.js +4 -0
  151. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  152. package/dist/session/occupancy-stress-worker.js +36 -0
  153. package/dist/session/occupancy.d.ts +97 -0
  154. package/dist/session/occupancy.js +422 -0
  155. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  156. package/dist/session/product-state-hash.d.ts +26 -0
  157. package/dist/session/product-state-hash.js +683 -0
  158. package/dist/session/session-ready.d.ts +2 -0
  159. package/dist/session/session-ready.js +52 -30
  160. package/dist/session/session-start.d.ts +7 -0
  161. package/dist/session/session-start.js +71 -7
  162. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  163. package/dist/session/verify-ac-session-cache.js +126 -0
  164. package/dist/session/verify-session-ritual.d.ts +5 -0
  165. package/dist/session/verify-session-ritual.js +9 -0
  166. package/dist/slash/openclaw-deposit.js +13 -9
  167. package/dist/slice/constants.d.ts +2 -2
  168. package/dist/slice/constants.js +2 -2
  169. package/dist/slice/existing.js +9 -3
  170. package/dist/slice/lock.d.ts +16 -1
  171. package/dist/slice/lock.js +137 -11
  172. package/dist/story-ready/evaluate.d.ts +2 -0
  173. package/dist/story-ready/evaluate.js +13 -0
  174. package/dist/swarm/complete-cohort.d.ts +3 -0
  175. package/dist/swarm/complete-cohort.js +59 -11
  176. package/dist/swarm/finalize-cohort.js +33 -2
  177. package/dist/swarm/launch.d.ts +22 -0
  178. package/dist/swarm/launch.js +155 -0
  179. package/dist/swarm/routing-set-cli.d.ts +11 -1
  180. package/dist/swarm/routing-set-cli.js +13 -3
  181. package/dist/swarm/routing-verify.d.ts +12 -0
  182. package/dist/swarm/routing-verify.js +59 -20
  183. package/dist/swarm/routing.d.ts +14 -0
  184. package/dist/swarm/routing.js +53 -3
  185. package/dist/telemetry-coverage/fake-trial.js +10 -0
  186. package/dist/telemetry-coverage/kinds.js +2 -0
  187. package/dist/triage/actions/index.js +4 -1
  188. package/dist/ts-check-lane/index.d.ts +3 -0
  189. package/dist/ts-check-lane/index.js +2 -0
  190. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  191. package/dist/ts-check-lane/progress-reporter.js +38 -0
  192. package/dist/ts-check-lane/progress.d.ts +32 -0
  193. package/dist/ts-check-lane/progress.js +57 -0
  194. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  195. package/dist/ts-check-lane/run-lane.js +8 -6
  196. package/dist/validate-content/validate-links.d.ts +8 -0
  197. package/dist/validate-content/validate-links.js +9 -15
  198. package/dist/value/feedback-file.js +12 -2
  199. package/dist/vbrief-build/index.d.ts +1 -0
  200. package/dist/vbrief-build/index.js +1 -0
  201. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  202. package/dist/vbrief-build/project-invariants-io.js +41 -0
  203. package/dist/vbrief-reconcile/graph.js +18 -1
  204. package/dist/vbrief-reconcile/index.d.ts +1 -1
  205. package/dist/vbrief-reconcile/index.js +1 -1
  206. package/dist/vbrief-reconcile/types.d.ts +13 -0
  207. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  208. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  209. package/dist/vbrief-validate/index.d.ts +1 -1
  210. package/dist/vbrief-validate/index.js +1 -1
  211. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  212. package/dist/vbrief-validate/plan-hooks.js +68 -0
  213. package/dist/verify-ac/evaluate.d.ts +7 -1
  214. package/dist/verify-ac/evaluate.js +34 -26
  215. package/dist/verify-ac/flag.d.ts +4 -0
  216. package/dist/verify-ac/flag.js +16 -0
  217. package/dist/verify-ac/index.d.ts +2 -2
  218. package/dist/verify-ac/index.js +2 -2
  219. package/dist/verify-source/contained-writes.js +2 -0
  220. package/dist/verify-source/verify-stubs.d.ts +15 -1
  221. package/dist/verify-source/verify-stubs.js +20 -16
  222. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  223. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  224. package/dist/xbrief-migrate/migrate-project.js +2 -5
  225. package/package.json +3 -3
@@ -8,8 +8,10 @@
8
8
  * and scope briefs are never installer-managed; if they reappear in
9
9
  * `installerManagedMatchers()`, unit tests and deposit-time assert fail closed.
10
10
  *
11
- * Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117, #3193.
11
+ * Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117, #3193, #3393.
12
12
  */
13
+ import { type MutationSummary } from "../fs/mutation-ledger.js";
14
+ import { type BranchSyncDetection } from "../policy/branch-sync.js";
13
15
  import { type InitDepositIo } from "./constants.js";
14
16
  export declare const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
15
17
  export declare const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
@@ -121,6 +123,13 @@ export interface PinContentFilePair {
121
123
  * (deposited guard + unit tests for #3193).
122
124
  */
123
125
  export declare function classifyMixedCoreAndApp(changedPaths: readonly string[], matchers?: readonly InstallerManagedMatcher[]): MixedCoreAndAppClassification;
126
+ /**
127
+ * Path classifier plus the shared #3388 sync predicate. Feature mixes still
128
+ * fail; a matching sync PR passes with a loud exemption message.
129
+ */
130
+ export declare function classifyMixedCoreAndAppForPr(changedPaths: readonly string[], sync: BranchSyncDetection, matchers?: readonly InstallerManagedMatcher[]): MixedCoreAndAppClassification & {
131
+ loudMessage: string | null;
132
+ };
124
133
  /**
125
134
  * Content-aware upgrade co-travel classifier (#3193).
126
135
  *
@@ -135,21 +144,37 @@ export declare function classifyMixedCoreAndApp(changedPaths: readonly string[],
135
144
  export declare function classifyMixedCoreAndAppContentAware(changedPaths: readonly string[], fileContents: Readonly<Partial<Record<string, PinContentFilePair>>>, matchers?: readonly InstallerManagedMatcher[]): MixedCoreAndAppClassification;
136
145
  export interface FrameworkStagePathsOptions {
137
146
  /**
138
- * Include the vendored `.deft/core` payload in the stage set. Defaults to
139
- * `true` for init and real payload swaps. No-op updates set this to `false`
140
- * so CRLF-only core noise cannot be staged while safe projections are repaired.
147
+ * Include the vendored `.deft/core` payload in the exist-walk stage set.
148
+ * Defaults to `true`. Ignored when a mutation ledger is bound (#3394)
149
+ * core paths stage only if they appear on this-run ledger.
141
150
  */
142
151
  readonly includeCore?: boolean;
143
152
  /**
144
- * Include the project-root ``Taskfile.yml`` in the stage set. Defaults to
145
- * ``false``: unlike the other allowlisted paths, ``Taskfile.yml`` is a
146
- * consumer-owned file that merely *contains* an installer-managed include
147
- * block, so it must only be staged when the installer actually wired that
148
- * block this run -- otherwise an unrelated user edit would be silently
149
- * ``git add``ed (#1576 review).
153
+ * Unused (#3394). Ledger intersection stages Taskfile.yml only when this run
154
+ * mutated it. Kept so existing callers compile. Exist-walk (no ledger) stages
155
+ * Taskfile.yml when the file exists do not reintroduce the include skip.
150
156
  */
151
157
  readonly includeTaskfile?: boolean;
152
158
  }
159
+ /** True for the vendored payload tree (not the installer-managed allowlist). */
160
+ export declare function isCoreStagePath(path: string): boolean;
161
+ export interface LedgerStageSplit {
162
+ readonly stagePaths: string[];
163
+ readonly unstagedRemainder: string[];
164
+ readonly skippedUntrackedDeletes: string[];
165
+ }
166
+ /**
167
+ * True when `git ls-files` named the path or a descendant (directory prune).
168
+ * Exact-only membership misses a ledgered directory whose tracked children
169
+ * are the ls-files hits (#3394 Greptile).
170
+ */
171
+ export declare function isTrackedDeletePath(path: string, trackedDeletes: ReadonlySet<string>): boolean;
172
+ /**
173
+ * Split this-run ledger paths into stageable (allowlist or core) vs remainder.
174
+ * Deleted paths not present in `trackedDeletes` (or under a tracked prefix)
175
+ * are skipped so one untracked delete cannot fail a batch `git add` (#3394).
176
+ */
177
+ export declare function splitLedgerForStaging(summary: MutationSummary, trackedDeletes?: ReadonlySet<string>): LedgerStageSplit;
153
178
  export declare function frameworkStagePaths(projectDir: string, deftDir: string, options?: FrameworkStagePathsOptions): string[];
154
179
  export interface StageFrameworkPathsSeams {
155
180
  gitPorcelain?: (projectRoot: string) => string | null;
@@ -198,6 +223,9 @@ export interface PrunePackageAbsentDepositPathsResult {
198
223
  * Individual removal failures are reported but do not throw — callers that must
199
224
  * refuse a VERSION stamp until the deposit matches the content package should
200
225
  * use {@link reconcileDepositToContentPackage} (#2913).
226
+ *
227
+ * Dest-only file removers go through {@link containedRemove} so a bound
228
+ * mutation ledger records them (#3392).
201
229
  */
202
230
  export declare function prunePackageAbsentDepositPaths(deftDir: string, contentRoot: string, io: InitDepositIo): Promise<PrunePackageAbsentDepositPathsResult>;
203
231
  /**
@@ -223,13 +251,21 @@ export interface PruneStrayDepositPathsResult {
223
251
  */
224
252
  export declare function pruneStrayDepositPaths(deftDir: string, contentRoot: string, io: InitDepositIo): Promise<PruneStrayDepositPathsResult>;
225
253
  export declare const COMMIT_HYGIENE_BRANCH_NAME = "chore/deft-framework-upgrade";
226
- export declare function printCommitGuidance(io: InitDepositIo, paths: readonly string[], staged: boolean): void;
254
+ export declare function printUnstagedLedgerRemainder(io: InitDepositIo, remainder: readonly string[]): void;
255
+ export declare function printCommitGuidance(io: InitDepositIo, paths: readonly string[], staged: boolean, unstagedRemainder?: readonly string[]): void;
227
256
  export interface DepositStagePathsOptions extends StageFrameworkPathsSeams, FrameworkStagePathsOptions {
228
257
  readCachedNames?: (projectDir: string) => string[];
258
+ /** `git ls-files` seam for filtering untracked ledger deletions (#3394). */
259
+ readTrackedNames?: (projectDir: string, paths: readonly string[]) => string[];
260
+ /** Explicit this-run ledger. Defaults to the bound snapshot. */
261
+ mutations?: MutationSummary;
262
+ printf?: (text: string) => void;
229
263
  }
230
264
  export declare function depositStagePaths(projectDir: string, options?: DepositStagePathsOptions): {
231
265
  stagePaths: string[];
232
266
  staged: boolean;
233
267
  stagedPaths: string[];
268
+ unstagedRemainder: string[];
269
+ skippedUntrackedDeletes: string[];
234
270
  };
235
271
  //# sourceMappingURL=hygiene.d.ts.map
@@ -8,12 +8,15 @@
8
8
  * and scope briefs are never installer-managed; if they reappear in
9
9
  * `installerManagedMatchers()`, unit tests and deposit-time assert fail closed.
10
10
  *
11
- * Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117, #3193.
11
+ * Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117, #3193, #3393.
12
12
  */
13
13
  import { execFileSync } from "node:child_process";
14
- import { existsSync, readdirSync, rmSync } from "node:fs";
14
+ import { existsSync, readdirSync } from "node:fs";
15
15
  import { readdir, rm, stat } from "node:fs/promises";
16
16
  import { dirname, join, relative } from "node:path";
17
+ import { containedDestExec, containedRemove } from "../fs/contained-write.js";
18
+ import { activeMutationLedger, isPortRecordMode, snapshotMutationSummary, } from "../fs/mutation-ledger.js";
19
+ import { applyCoreGuardWithBranchSync } from "../policy/branch-sync.js";
17
20
  import { gitPorcelain } from "../story-ready/git.js";
18
21
  import { CANONICAL_INSTALL_ROOT } from "./constants.js";
19
22
  import { CONSUMER_SKILL_DISCOVERY_INVENTORY } from "./skill-discovery-deposit.js";
@@ -68,6 +71,12 @@ export function installerManagedMatchers() {
68
71
  { exact: ".claude/settings.json" },
69
72
  { exact: ".grok/hooks/deft.json" },
70
73
  { exact: ".cursor/hooks.json" },
74
+ // Official Cursor adapter only (#3393). Exact paths — not a `.cursor/hooks/`
75
+ // prefix — so consumer-authored hooks stay app. Consumers using
76
+ // `pull_request_target` or pinned workflow refs evaluate the deposited
77
+ // guard from the previous allowlist for one PR; document, do not work around.
78
+ { exact: ".cursor/hooks/deft-cursor-hook-adapter.mjs" },
79
+ { exact: ".cursor/hooks/deft-cursor-hook-adapter.test.mjs" },
71
80
  { exact: ".codex/hooks.json" },
72
81
  // Multi-host slash product files only (#3054 / L8 prefer commit).
73
82
  // Exact paths — not directory prefixes — so consumer custom commands under
@@ -753,6 +762,15 @@ export function classifyMixedCoreAndApp(changedPaths, matchers = installerManage
753
762
  wouldFail: core.length > 0 && app.length > 0,
754
763
  };
755
764
  }
765
+ /**
766
+ * Path classifier plus the shared #3388 sync predicate. Feature mixes still
767
+ * fail; a matching sync PR passes with a loud exemption message.
768
+ */
769
+ export function classifyMixedCoreAndAppForPr(changedPaths, sync, matchers = installerManagedMatchers()) {
770
+ const base = classifyMixedCoreAndApp(changedPaths, matchers);
771
+ const applied = applyCoreGuardWithBranchSync(base, sync);
772
+ return { ...base, wouldFail: applied.wouldFail, loudMessage: applied.loudMessage };
773
+ }
756
774
  /**
757
775
  * Content-aware upgrade co-travel classifier (#3193).
758
776
  *
@@ -794,6 +812,65 @@ export function classifyMixedCoreAndAppContentAware(changedPaths, fileContents,
794
812
  pinContentRejected,
795
813
  };
796
814
  }
815
+ /** True for the vendored payload tree (not the installer-managed allowlist). */
816
+ export function isCoreStagePath(path) {
817
+ const normalized = path.replace(/\\/g, "/");
818
+ return normalized === ".deft/core" || normalized.startsWith(".deft/core/");
819
+ }
820
+ /**
821
+ * True when `git ls-files` named the path or a descendant (directory prune).
822
+ * Exact-only membership misses a ledgered directory whose tracked children
823
+ * are the ls-files hits (#3394 Greptile).
824
+ */
825
+ export function isTrackedDeletePath(path, trackedDeletes) {
826
+ const normalized = path.replace(/\\/g, "/");
827
+ if (trackedDeletes.has(normalized))
828
+ return true;
829
+ const prefix = normalized.endsWith("/") ? normalized : `${normalized}/`;
830
+ for (const tracked of trackedDeletes) {
831
+ if (tracked === normalized || tracked.startsWith(prefix))
832
+ return true;
833
+ }
834
+ return false;
835
+ }
836
+ /**
837
+ * Split this-run ledger paths into stageable (allowlist or core) vs remainder.
838
+ * Deleted paths not present in `trackedDeletes` (or under a tracked prefix)
839
+ * are skipped so one untracked delete cannot fail a batch `git add` (#3394).
840
+ */
841
+ export function splitLedgerForStaging(summary, trackedDeletes = new Set()) {
842
+ const seen = new Set();
843
+ const stagePaths = [];
844
+ const unstagedRemainder = [];
845
+ const skippedUntrackedDeletes = [];
846
+ const deleted = new Set(summary.deleted.map((path) => path.replace(/\\/g, "/")));
847
+ const visit = (raw) => {
848
+ const path = raw.replace(/\\/g, "/");
849
+ if (!path || seen.has(path))
850
+ return;
851
+ seen.add(path);
852
+ if (!isInstallerManagedPath(path) && !isCoreStagePath(path)) {
853
+ unstagedRemainder.push(path);
854
+ return;
855
+ }
856
+ if (deleted.has(path) && !isTrackedDeletePath(path, trackedDeletes)) {
857
+ skippedUntrackedDeletes.push(path);
858
+ return;
859
+ }
860
+ stagePaths.push(path);
861
+ };
862
+ for (const path of summary.wrote)
863
+ visit(path);
864
+ for (const path of summary.stripped)
865
+ visit(path);
866
+ for (const path of summary.deleted)
867
+ visit(path);
868
+ for (const path of summary.chmod)
869
+ visit(path);
870
+ for (const path of summary.exec)
871
+ visit(path);
872
+ return { stagePaths, unstagedRemainder, skippedUntrackedDeletes };
873
+ }
797
874
  export function frameworkStagePaths(projectDir, deftDir, options = {}) {
798
875
  const paths = [];
799
876
  const seen = new Set();
@@ -814,8 +891,6 @@ export function frameworkStagePaths(projectDir, deftDir, options = {}) {
814
891
  add(relDeft);
815
892
  }
816
893
  for (const matcher of installerManagedMatchers()) {
817
- if (matcher.exact === "Taskfile.yml" && !options.includeTaskfile)
818
- continue;
819
894
  if (matcher.exact) {
820
895
  add(matcher.exact);
821
896
  }
@@ -834,11 +909,15 @@ export function stageFrameworkPaths(projectDir, paths, seams = {}) {
834
909
  return { staged: false, error: null };
835
910
  const runGitAdd = seams.runGitAdd ??
836
911
  ((root, stagePaths) => {
837
- execFileSync("git", ["add", ...stagePaths], {
838
- cwd: root,
839
- encoding: "utf8",
840
- stdio: ["ignore", "pipe", "pipe"],
912
+ const result = containedDestExec({
913
+ root,
914
+ destTarget: join(".git", "index"),
915
+ file: "git",
916
+ args: ["add", "--", ...stagePaths],
841
917
  });
918
+ if (!result.ok) {
919
+ throw new Error("git add failed");
920
+ }
842
921
  });
843
922
  try {
844
923
  runGitAdd(projectDir, paths);
@@ -961,7 +1040,7 @@ async function pruneEmptyParentsForFile(deftDir, contentDirs, relFile) {
961
1040
  try {
962
1041
  if (!existsSync(abs) || readdirSync(abs).length !== 0)
963
1042
  break;
964
- rmSync(abs, { recursive: false, force: true });
1043
+ containedRemove({ root: deftDir, target: abs, mutation: false });
965
1044
  prunedDirs.push(normalized);
966
1045
  }
967
1046
  catch {
@@ -979,6 +1058,9 @@ async function pruneEmptyParentsForFile(deftDir, contentDirs, relFile) {
979
1058
  * Individual removal failures are reported but do not throw — callers that must
980
1059
  * refuse a VERSION stamp until the deposit matches the content package should
981
1060
  * use {@link reconcileDepositToContentPackage} (#2913).
1061
+ *
1062
+ * Dest-only file removers go through {@link containedRemove} so a bound
1063
+ * mutation ledger records them (#3392).
982
1064
  */
983
1065
  export async function prunePackageAbsentDepositPaths(deftDir, contentRoot, io) {
984
1066
  const absent = await findPackageAbsentDepositPaths(deftDir, contentRoot);
@@ -987,7 +1069,7 @@ export async function prunePackageAbsentDepositPaths(deftDir, contentRoot, io) {
987
1069
  const prunedDirs = [];
988
1070
  for (const rel of absent) {
989
1071
  try {
990
- rmSync(join(deftDir, rel), { force: true });
1072
+ containedRemove({ root: deftDir, target: join(deftDir, rel) });
991
1073
  pruned.push(rel);
992
1074
  prunedDirs.push(...(await pruneEmptyParentsForFile(deftDir, contentDirs, rel)));
993
1075
  }
@@ -1015,6 +1097,10 @@ export async function reconcileDepositToContentPackage(deftDir, contentRoot, io)
1015
1097
  const result = await prunePackageAbsentDepositPaths(deftDir, contentRoot, io);
1016
1098
  const remaining = await findPackageAbsentDepositPaths(deftDir, contentRoot);
1017
1099
  if (remaining.length > 0) {
1100
+ if (isPortRecordMode()) {
1101
+ // Dest IO was skipped; dest-only paths stay on disk and are already ledgered.
1102
+ return result;
1103
+ }
1018
1104
  const sample = remaining.slice(0, 5).join(", ");
1019
1105
  const more = remaining.length > 5 ? ` (+${remaining.length - 5} more)` : "";
1020
1106
  throw new Error(`deposit reconcile failed: ${remaining.length} package-absent path(s) remain under .deft/core ` +
@@ -1063,31 +1149,42 @@ export async function pruneStrayDepositPaths(deftDir, contentRoot, io) {
1063
1149
  return { pruned };
1064
1150
  }
1065
1151
  export const COMMIT_HYGIENE_BRANCH_NAME = "chore/deft-framework-upgrade";
1066
- export function printCommitGuidance(io, paths, staged) {
1067
- if (paths.length === 0)
1152
+ export function printUnstagedLedgerRemainder(io, remainder) {
1153
+ if (remainder.length === 0)
1068
1154
  return;
1069
- const addCmd = `git add ${paths.join(" ")}`;
1070
- io.printf("\nCommit hygiene (#1453, #1671, #3127, #3193): keep the framework upgrade in its OWN branch/PR.\n");
1071
- io.printf("Do NOT use `git add -A` -- mixing the payload with product/app files trips the\n");
1072
- io.printf("deft-core-guard CI check.\n");
1073
- io.printf("One upgrade PR MAY co-travel: .deft/core/** + installer-managed deposits + package.json\n");
1074
- io.printf("pin/lock (Directive pin-only + lock follow-through, #3193) + .deft/GENERATION.json.\n");
1075
- io.printf("True app/product paths still require a separate PR.\n");
1076
- if (staged) {
1077
- io.printf("The installer already staged ONLY these framework + installer-managed paths:\n");
1078
- io.printf(` ${addCmd}\n`);
1155
+ io.printf("Left unstaged (not installer-managed this run; do not git add -A):\n");
1156
+ for (const path of remainder) {
1157
+ io.printf(` ${path}\n`);
1079
1158
  }
1080
- else {
1081
- io.printf("Stage ONLY these framework + installer-managed paths:\n");
1082
- io.printf(` ${addCmd}\n`);
1159
+ }
1160
+ export function printCommitGuidance(io, paths, staged, unstagedRemainder = []) {
1161
+ if (paths.length === 0 && unstagedRemainder.length === 0)
1162
+ return;
1163
+ if (paths.length > 0) {
1164
+ const addCmd = `git add -- ${paths.join(" ")}`;
1165
+ io.printf("\nCommit hygiene (#1453, #1671, #3127, #3193, #3394): keep the framework upgrade in its OWN branch/PR.\n");
1166
+ io.printf("Do NOT use `git add -A` -- mixing the payload with product/app files trips the\n");
1167
+ io.printf("deft-core-guard CI check.\n");
1168
+ io.printf("One upgrade PR MAY co-travel: .deft/core/** + installer-managed deposits + package.json\n");
1169
+ io.printf("pin/lock (Directive pin-only + lock follow-through, #3193) + .deft/GENERATION.json.\n");
1170
+ io.printf("True app/product paths still require a separate PR.\n");
1171
+ if (staged) {
1172
+ io.printf("The installer already staged ONLY these framework + installer-managed paths:\n");
1173
+ io.printf(` ${addCmd}\n`);
1174
+ }
1175
+ else {
1176
+ io.printf("Stage ONLY these framework + installer-managed paths:\n");
1177
+ io.printf(` ${addCmd}\n`);
1178
+ }
1179
+ io.printf("Then take the framework deposit through the full PR lifecycle so deft-core-guard\n");
1180
+ io.printf("evaluates a clean, standalone upgrade PR:\n");
1181
+ io.printf(` 1. Branch: git switch -c ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
1182
+ io.printf(' 2. Commit: git commit -m "chore(deft): update framework payload"\n');
1183
+ io.printf(` 3. Push: git push -u origin ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
1184
+ io.printf(' 4. PR: gh pr create --fill --title "chore(deft): update framework payload"\n');
1185
+ io.printf(" 5. Merge: gh pr merge --squash --delete-branch # after deft-core-guard passes\n");
1083
1186
  }
1084
- io.printf("Then take the framework deposit through the full PR lifecycle so deft-core-guard\n");
1085
- io.printf("evaluates a clean, standalone upgrade PR:\n");
1086
- io.printf(` 1. Branch: git switch -c ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
1087
- io.printf(' 2. Commit: git commit -m "chore(deft): update framework payload"\n');
1088
- io.printf(` 3. Push: git push -u origin ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
1089
- io.printf(' 4. PR: gh pr create --fill --title "chore(deft): update framework payload"\n');
1090
- io.printf(" 5. Merge: gh pr merge --squash --delete-branch # after deft-core-guard passes\n");
1187
+ printUnstagedLedgerRemainder(io, unstagedRemainder);
1091
1188
  }
1092
1189
  function defaultCachedNames(projectDir) {
1093
1190
  try {
@@ -1115,12 +1212,48 @@ function defaultCachedNames(projectDir) {
1115
1212
  function actuallyStagedPaths(stagePaths, cachedNames) {
1116
1213
  return stagePaths.filter((candidate) => cachedNames.some((name) => name === candidate || name.startsWith(`${candidate}/`)));
1117
1214
  }
1215
+ function defaultTrackedNames(projectDir, paths) {
1216
+ if (paths.length === 0)
1217
+ return [];
1218
+ try {
1219
+ const out = execFileSync("git", ["ls-files", "-z", "--", ...paths], {
1220
+ cwd: projectDir,
1221
+ encoding: "utf8",
1222
+ stdio: ["ignore", "pipe", "pipe"],
1223
+ });
1224
+ return out
1225
+ .split("\0")
1226
+ .map((entry) => entry.trim().replace(/\\/g, "/"))
1227
+ .filter(Boolean);
1228
+ }
1229
+ catch {
1230
+ return [];
1231
+ }
1232
+ }
1118
1233
  export function depositStagePaths(projectDir, options = {}) {
1119
- const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
1120
- const stagePaths = frameworkStagePaths(projectDir, deftDir, {
1121
- includeCore: options.includeCore,
1122
- includeTaskfile: options.includeTaskfile ?? false,
1123
- });
1234
+ const summary = options.mutations ?? snapshotMutationSummary();
1235
+ const ledgerBound = options.mutations !== undefined || activeMutationLedger() !== undefined;
1236
+ let stagePaths;
1237
+ let unstagedRemainder = [];
1238
+ let skippedUntrackedDeletes = [];
1239
+ if (ledgerBound) {
1240
+ const readTracked = options.readTrackedNames ?? defaultTrackedNames;
1241
+ const tracked = new Set(readTracked(projectDir, summary.deleted));
1242
+ const split = splitLedgerForStaging(summary, tracked);
1243
+ stagePaths = split.stagePaths;
1244
+ unstagedRemainder = split.unstagedRemainder;
1245
+ skippedUntrackedDeletes = split.skippedUntrackedDeletes;
1246
+ }
1247
+ else {
1248
+ const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
1249
+ stagePaths = frameworkStagePaths(projectDir, deftDir, {
1250
+ includeCore: options.includeCore,
1251
+ });
1252
+ }
1253
+ const leftover = [...unstagedRemainder, ...skippedUntrackedDeletes];
1254
+ if (leftover.length > 0 && options.printf) {
1255
+ printUnstagedLedgerRemainder({ printf: options.printf }, leftover);
1256
+ }
1124
1257
  const { staged } = stageFrameworkPaths(projectDir, stagePaths, options);
1125
1258
  const readCachedNames = options.readCachedNames ?? defaultCachedNames;
1126
1259
  const cachedNames = staged ? readCachedNames(projectDir) : [];
@@ -1128,6 +1261,8 @@ export function depositStagePaths(projectDir, options = {}) {
1128
1261
  stagePaths,
1129
1262
  staged,
1130
1263
  stagedPaths: staged ? actuallyStagedPaths(stagePaths, cachedNames) : [],
1264
+ unstagedRemainder,
1265
+ skippedUntrackedDeletes,
1131
1266
  };
1132
1267
  }
1133
1268
  //# sourceMappingURL=hygiene.js.map
@@ -24,11 +24,12 @@
24
24
  * Refs #1912, #1669.
25
25
  */
26
26
  import { execFileSync } from "node:child_process";
27
- import { rmSync, statSync } from "node:fs";
27
+ import { statSync } from "node:fs";
28
28
  import { join } from "node:path";
29
29
  import { GO_BRIDGE_RELEASES_URL, UPGRADING_DOC_URL } from "../doctor/constants.js";
30
30
  import { extractManagedSection, parseInstallRootFromAgentsMd } from "../doctor/manifest.js";
31
31
  import { readTextSafe } from "../doctor/paths.js";
32
+ import { containedRemove } from "../fs/contained-write.js";
32
33
  import { gitPorcelain } from "../story-ready/git.js";
33
34
  /** Non-zero exit code for a use-time legacy-layout refusal (needs-action). */
34
35
  export const LEGACY_LAYOUT_REFUSED_EXIT_CODE = 2;
@@ -314,7 +315,10 @@ export function tryCleanupLegacyDeftTree(projectDir, seams = {}) {
314
315
  };
315
316
  }
316
317
  const deftDir = join(projectDir, LEGACY_DEFT_DIR);
317
- const removeDir = seams.removeDir ?? ((p) => rmSync(p, { recursive: true, force: true }));
318
+ const removeDir = seams.removeDir ??
319
+ ((p) => {
320
+ containedRemove({ root: projectDir, target: p, recursive: true });
321
+ });
318
322
  const runGitRm = seams.runGitRm ??
319
323
  ((root, relPath) => {
320
324
  execFileSync("git", ["rm", "-r", "--ignore-unmatch", relPath], {
@@ -8,6 +8,7 @@
8
8
  * Refs #1942, #1430, #1671.
9
9
  */
10
10
  import type { ResolutionFacts, ResolutionPlan } from "@deftai/directive-types";
11
+ import { type MutationSummary } from "../fs/mutation-ledger.js";
11
12
  import { type ClassifySeams, type EngineInstallRunner, type EngineResolution, type LadderFacts, type ReprojectRunner } from "../resolution/index.js";
12
13
  import { type AgentHookReadinessResult } from "../verify-env/agent-hook-readiness.js";
13
14
  import { type GitLsFiles } from "./gitignore.js";
@@ -30,10 +31,13 @@ export interface RefreshDepositResult {
30
31
  readonly legacyLayout: boolean;
31
32
  readonly taskfileWired: boolean;
32
33
  readonly stagedPaths: string[];
34
+ /** This-run write/remove ledger (#3392). Same source as printf + JSON. */
35
+ readonly mutations: MutationSummary;
33
36
  }
34
37
  export type RefreshDepositStrategy = "file-swap" | "no-op";
35
38
  export interface RefreshDepositSeams {
36
39
  resolveContentRoot?: () => Promise<string>;
40
+ /** Folded into the dest port (ADR-004). Default {@link replaceTree} records dest writes/removes. */
37
41
  copyContent?: (src: string, dst: string) => Promise<void>;
38
42
  readPackageVersion?: () => string;
39
43
  readEngineVersion?: () => string;
@@ -48,7 +52,7 @@ export interface RefreshDepositSeams {
48
52
  gitLsFiles?: GitLsFiles;
49
53
  /** #2530: injected git config seams for {@link writeConsumerGitHooks}. */
50
54
  gitHooks?: GitHooksSeams;
51
- /** #2822: optional seam for trusted-org policy force-on migration. */
55
+ /** #2822: optional seam. Default {@link runOrgForceOnMigration} already writes via containedWrite. */
52
56
  runOrgForceOn?: (projectRoot: string) => void;
53
57
  /** Post-deposit functional readiness gate (#3100). */
54
58
  evaluateAgentHookReadiness?: (projectRoot: string) => AgentHookReadinessResult;
@@ -126,12 +130,22 @@ export interface RefreshSideEffects {
126
130
  export declare function frameworkRefreshSideEffects(projectDir: string, options?: FrameworkRefreshSideEffectsOptions): RefreshSideEffects;
127
131
  export declare function printRefreshSideEffects(io: InitDepositIo, effects: RefreshSideEffects): void;
128
132
  export declare function buildVersionSkewNotice(engineVersion: string, contentVersion: string, previousDepositVersion: string | null): string | null;
133
+ /** True when recorded deposit version and content package version disagree (#3437). */
134
+ export declare function depositRefreshPending(previousDepositVersion: string | null, contentVersion: string): boolean;
135
+ /** First-line dry-run headline when deposit manifest and content disagree (#3437). */
136
+ export declare function buildDepositVersionSkewHeadline(previousDepositVersion: string | null, contentVersion: string): string | null;
129
137
  export declare function buildUpdateSummaryJson(input: {
130
138
  result: RefreshDepositResult;
131
139
  options: RefreshDepositArgs;
132
140
  updateState: UpdateState | undefined;
133
141
  readiness: AgentHookReadinessResult | undefined;
134
142
  }): Record<string, unknown>;
143
+ /** Consumer-owned prefixes whose rewrite can fail a repo Prettier/fmt gate (#3395). */
144
+ export declare const PRETTIER_SENSITIVE_CONSUMER_PREFIXES: readonly ["xbrief/schemas/"];
145
+ export declare const PRETTIER_SENSITIVE_FMT_HINT = "run `task fmt` or your repo formatter before the upgrade PR";
146
+ /** Ledger `wrote` paths under prettier-sensitive consumer-owned prefixes. */
147
+ export declare function prettierSensitiveRewrites(summary: MutationSummary): string[];
148
+ export declare function formatPrettierSensitiveAnnounce(paths: readonly string[]): string;
135
149
  export declare function printUpdateComplete(result: RefreshDepositResult, io: InitDepositIo, updateState?: UpdateState): void;
136
150
  export declare function runRefreshDeposit(args: RefreshDepositArgs, io: InitDepositIo, seams?: RefreshDepositSeams): Promise<RefreshDepositResult>;
137
151
  export interface RunRefreshDepositCliOptions extends RefreshDepositArgs {
@@ -149,6 +163,12 @@ export interface RunRefreshDepositCliOptions extends RefreshDepositArgs {
149
163
  /** #2266: injected content re-projection after a self-heal install. */
150
164
  readonly reproject?: ReprojectRunner;
151
165
  }
166
+ /**
167
+ * Dest effects the port cannot yet capture (ADR-004 condition 3).
168
+ * Shrink as sites fold; never drop the printed label.
169
+ */
170
+ export declare const UPDATE_DRY_RUN_EXCLUSIONS: readonly ["openclaw-home-skills"];
171
+ export declare const UPDATE_DRY_RUN_EXCLUSIONS_LABEL = "Excluded from dest plan (not yet captured by the port): OpenClaw $HOME skills.";
152
172
  /** CLI-facing wrapper: runs refresh, emits JSON or wizard UX, returns exit code. */
153
173
  export declare function runRefreshDepositCli(options: RunRefreshDepositCliOptions): Promise<number>;
154
174
  export declare function parseUpdateArgv(canonicalArgv: readonly string[], userArgv?: readonly string[]): RefreshDepositArgs;