@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
@@ -0,0 +1,129 @@
1
+ /**
2
+ * scm:sync-default (#3391 / #3377 Wave 3).
3
+ *
4
+ * Consumes the shared #3388 detector and #3390 syncMaxFiles. Opens dest-targeted
5
+ * sync PRs. Over the file limit, cut at merge commits when possible so each
6
+ * dest-based leg is at or under the threshold. Each leg is a new branch and a
7
+ * new PR. Never retarget or reuse an oversized PR.
8
+ */
9
+ import { type GitRunner } from "../session/git.js";
10
+ import { type BranchSyncDetection } from "./branch-sync.js";
11
+ import { type SyncMaxFilesProvenance } from "./sync-max-files.js";
12
+ export declare const SYNC_DEFAULT_VERB = "scm:sync-default";
13
+ export declare const FORBIDDEN_SYNC_PR_RETARGET = "gh pr edit --base";
14
+ export type SyncDefaultAction = "noop" | "single" | "staged";
15
+ export type SyncDefaultNoopReason = "not-sync" | "already-synced" | "zero-files" | "fetch-failed" | "diff-failed";
16
+ export type SyncDefaultCutKind = "merge" | "commit" | "tip";
17
+ export interface SyncDefaultCommit {
18
+ readonly sha: string;
19
+ readonly isMerge: boolean;
20
+ }
21
+ export interface SyncDefaultLeg {
22
+ readonly index: number;
23
+ readonly sha: string;
24
+ readonly fileCount: number;
25
+ readonly cutKind: SyncDefaultCutKind;
26
+ readonly branchName: string;
27
+ }
28
+ export interface SyncDefaultPlan {
29
+ readonly action: SyncDefaultAction;
30
+ readonly noopReason: SyncDefaultNoopReason | null;
31
+ readonly dest: string;
32
+ readonly source: string;
33
+ readonly threshold: number;
34
+ readonly provenance: SyncMaxFilesProvenance;
35
+ readonly totalCount: number | null;
36
+ readonly legs: readonly SyncDefaultLeg[];
37
+ readonly nextLegIndex: number | null;
38
+ readonly message: string;
39
+ readonly detectorReason: BranchSyncDetection["reason"];
40
+ }
41
+ export interface SyncDefaultOpenPull {
42
+ readonly number: number;
43
+ readonly htmlUrl: string;
44
+ readonly headRef: string;
45
+ readonly headSha: string;
46
+ readonly baseRef: string;
47
+ }
48
+ export interface SyncDefaultForge {
49
+ listOpenPulls(repo: string, base: string): readonly SyncDefaultOpenPull[];
50
+ createPull(repo: string, input: {
51
+ readonly title: string;
52
+ readonly head: string;
53
+ readonly base: string;
54
+ readonly body: string;
55
+ }): {
56
+ readonly number: number;
57
+ readonly htmlUrl: string;
58
+ };
59
+ }
60
+ export interface SyncDefaultOpened {
61
+ readonly leg: SyncDefaultLeg;
62
+ readonly prNumber: number;
63
+ readonly prUrl: string;
64
+ readonly branch: string;
65
+ readonly reusedExisting: boolean;
66
+ }
67
+ export interface SyncDefaultApplyResult {
68
+ readonly plan: SyncDefaultPlan;
69
+ readonly opened: readonly SyncDefaultOpened[];
70
+ readonly retargeted: false;
71
+ }
72
+ export declare function syncDefaultBranchName(dest: string, source: string, index: number, sha: string): string;
73
+ export declare function parseGithubOwnerRepo(remoteUrl: string): string | null;
74
+ export declare function countChangedFilesOnRange(options: {
75
+ readonly projectRoot: string;
76
+ readonly left: string;
77
+ readonly right: string;
78
+ readonly runGit?: GitRunner;
79
+ }): {
80
+ readonly count: number | null;
81
+ readonly error: string | null;
82
+ };
83
+ export declare function listCommitsAfter(options: {
84
+ readonly projectRoot: string;
85
+ readonly dest: string;
86
+ readonly source: string;
87
+ readonly runGit?: GitRunner;
88
+ }): {
89
+ readonly commits: readonly SyncDefaultCommit[];
90
+ readonly error: string | null;
91
+ };
92
+ /**
93
+ * Cut dest..source into dest-targeted legs whose dest-based file count is at
94
+ * or under the threshold when a merge (or other) cut exists.
95
+ */
96
+ export declare function cutSyncLegs(options: {
97
+ readonly dest: string;
98
+ readonly source: string;
99
+ readonly destRef: string;
100
+ readonly sourceSha: string;
101
+ readonly commits: readonly SyncDefaultCommit[];
102
+ readonly threshold: number;
103
+ readonly countFiles: (left: string, right: string) => number | null;
104
+ /** When set, later cuts must be descendants of the previous cut. */
105
+ readonly isAncestor?: (ancestor: string, descendant: string) => boolean;
106
+ }): {
107
+ readonly legs: readonly SyncDefaultLeg[];
108
+ readonly error: string | null;
109
+ };
110
+ export declare function planSyncDefault(options: {
111
+ readonly projectRoot: string;
112
+ readonly maxFiles?: number | null;
113
+ readonly destTipSha?: string;
114
+ readonly runGit?: GitRunner;
115
+ }): SyncDefaultPlan;
116
+ export declare function nextOpenableLeg(legs: readonly SyncDefaultLeg[], destTipSha: string, projectRoot: string, runGit: GitRunner): SyncDefaultLeg | null;
117
+ export declare function applySyncDefault(options: {
118
+ readonly projectRoot: string;
119
+ readonly repo: string;
120
+ readonly plan?: SyncDefaultPlan;
121
+ readonly maxFiles?: number | null;
122
+ readonly destTipSha?: string;
123
+ readonly dryRun?: boolean;
124
+ readonly runGit?: GitRunner;
125
+ readonly forge?: SyncDefaultForge;
126
+ }): SyncDefaultApplyResult;
127
+ export declare function syncDefaultPrBody(plan: SyncDefaultPlan, leg: SyncDefaultLeg): string;
128
+ export declare function formatSyncDefaultHuman(result: SyncDefaultApplyResult): string;
129
+ //# sourceMappingURL=sync-default.d.ts.map
@@ -0,0 +1,452 @@
1
+ /**
2
+ * scm:sync-default (#3391 / #3377 Wave 3).
3
+ *
4
+ * Consumes the shared #3388 detector and #3390 syncMaxFiles. Opens dest-targeted
5
+ * sync PRs. Over the file limit, cut at merge commits when possible so each
6
+ * dest-based leg is at or under the threshold. Each leg is a new branch and a
7
+ * new PR. Never retarget or reuse an oversized PR.
8
+ */
9
+ import { defaultGitRunner, gitIsAncestor } from "../session/git.js";
10
+ import { detectBranchSync, resolveSyncPolicyFromDestRef, } from "./branch-sync.js";
11
+ import { resolveGitDefaultDeliveryBranch } from "./delivery-branch.js";
12
+ import { resolveSyncMaxFiles } from "./sync-max-files.js";
13
+ export const SYNC_DEFAULT_VERB = "scm:sync-default";
14
+ export const FORBIDDEN_SYNC_PR_RETARGET = "gh pr edit --base";
15
+ function shortSha(sha) {
16
+ return sha.length >= 7 ? sha.slice(0, 7) : sha;
17
+ }
18
+ export function syncDefaultBranchName(dest, source, index, sha) {
19
+ const safe = (value) => value.replace(/[^A-Za-z0-9._-]+/g, "-");
20
+ return `sync/${safe(dest)}-from-${safe(source)}/leg-${index}-${shortSha(sha)}`;
21
+ }
22
+ export function parseGithubOwnerRepo(remoteUrl) {
23
+ const trimmed = remoteUrl.trim();
24
+ const withoutGit = trimmed.toLowerCase().endsWith(".git") ? trimmed.slice(0, -4) : trimmed;
25
+ const marker = "github.com";
26
+ const idx = withoutGit.toLowerCase().lastIndexOf(marker);
27
+ if (idx < 0)
28
+ return null;
29
+ const after = withoutGit.slice(idx + marker.length);
30
+ const rest = after.startsWith(":") || after.startsWith("/") ? after.slice(1) : after;
31
+ const parts = rest.split("/").filter((part) => part.length > 0);
32
+ const owner = parts[0];
33
+ const repo = parts[1];
34
+ if (owner === undefined || repo === undefined)
35
+ return null;
36
+ return `${owner}/${repo}`;
37
+ }
38
+ export function countChangedFilesOnRange(options) {
39
+ const left = options.left.trim();
40
+ const right = options.right.trim();
41
+ if (left.length === 0 || right.length === 0) {
42
+ return { count: null, error: "empty diff range" };
43
+ }
44
+ const runGit = options.runGit ?? defaultGitRunner;
45
+ const ranged = `${left}...${right}`;
46
+ const diffed = runGit(options.projectRoot, ["diff", "--name-only", ranged]);
47
+ if (diffed.code !== 0) {
48
+ return { count: null, error: diffed.stderr || `git diff failed for ${ranged}` };
49
+ }
50
+ const files = diffed.stdout.split(/\r?\n/).filter((line) => line.trim().length > 0);
51
+ return { count: files.length, error: null };
52
+ }
53
+ export function listCommitsAfter(options) {
54
+ const runGit = options.runGit ?? defaultGitRunner;
55
+ const logged = runGit(options.projectRoot, [
56
+ "log",
57
+ "--reverse",
58
+ "--format=%H%x09%P",
59
+ `origin/${options.dest}..origin/${options.source}`,
60
+ ]);
61
+ if (logged.code !== 0) {
62
+ return { commits: [], error: logged.stderr || "git log failed" };
63
+ }
64
+ const commits = [];
65
+ for (const line of logged.stdout.split(/\r?\n/)) {
66
+ const trimmed = line.trim();
67
+ if (trimmed.length === 0)
68
+ continue;
69
+ const tab = trimmed.indexOf("\t");
70
+ const sha = tab === -1 ? trimmed : trimmed.slice(0, tab);
71
+ const parents = tab === -1 ? "" : trimmed.slice(tab + 1).trim();
72
+ if (sha.length === 0)
73
+ continue;
74
+ commits.push({
75
+ sha,
76
+ isMerge: parents.split(/\s+/).filter((part) => part.length > 0).length > 1,
77
+ });
78
+ }
79
+ return { commits, error: null };
80
+ }
81
+ /**
82
+ * Cut dest..source into dest-targeted legs whose dest-based file count is at
83
+ * or under the threshold when a merge (or other) cut exists.
84
+ */
85
+ export function cutSyncLegs(options) {
86
+ const { dest, source, destRef, sourceSha, commits, threshold, countFiles, isAncestor } = options;
87
+ const legs = [];
88
+ let left = destRef;
89
+ let guard = 0;
90
+ while (guard < commits.length + 2) {
91
+ guard += 1;
92
+ const remaining = countFiles(left, sourceSha);
93
+ if (remaining === null) {
94
+ return { legs: [], error: `git diff failed for ${left}...${sourceSha}` };
95
+ }
96
+ if (remaining <= threshold) {
97
+ if (left !== sourceSha && remaining > 0) {
98
+ const tip = commits.find((commit) => commit.sha === sourceSha);
99
+ const cutKind = legs.length === 0 ? "tip" : tip?.isMerge === true ? "merge" : "tip";
100
+ const index = legs.length + 1;
101
+ legs.push({
102
+ index,
103
+ sha: sourceSha,
104
+ fileCount: remaining,
105
+ cutKind,
106
+ branchName: syncDefaultBranchName(dest, source, index, sourceSha),
107
+ });
108
+ }
109
+ return { legs, error: null };
110
+ }
111
+ const afterLeft = left === destRef
112
+ ? commits
113
+ : commits.filter((commit) => {
114
+ if (isAncestor !== undefined) {
115
+ return commit.sha !== left && isAncestor(left, commit.sha);
116
+ }
117
+ const leftIdx = commits.findIndex((row) => row.sha === left);
118
+ const idx = commits.findIndex((row) => row.sha === commit.sha);
119
+ return leftIdx >= 0 ? idx > leftIdx : true;
120
+ });
121
+ let bestMerge = null;
122
+ let bestCommit = null;
123
+ for (const commit of afterLeft) {
124
+ const counted = countFiles(left, commit.sha);
125
+ if (counted === null) {
126
+ return { legs: [], error: `git diff failed for ${left}...${commit.sha}` };
127
+ }
128
+ if (counted <= threshold) {
129
+ bestCommit = { sha: commit.sha, count: counted };
130
+ if (commit.isMerge)
131
+ bestMerge = { sha: commit.sha, count: counted };
132
+ }
133
+ }
134
+ const chosen = bestMerge ??
135
+ bestCommit ??
136
+ (afterLeft[0]
137
+ ? {
138
+ sha: afterLeft[0].sha,
139
+ count: countFiles(left, afterLeft[0].sha),
140
+ }
141
+ : null);
142
+ if (chosen === null || chosen.count === null) {
143
+ return { legs: [], error: "no commit to cut after dest" };
144
+ }
145
+ const cutKind = bestMerge !== null && chosen.sha === bestMerge.sha ? "merge" : "commit";
146
+ const index = legs.length + 1;
147
+ legs.push({
148
+ index,
149
+ sha: chosen.sha,
150
+ fileCount: chosen.count,
151
+ cutKind,
152
+ branchName: syncDefaultBranchName(dest, source, index, chosen.sha),
153
+ });
154
+ if (chosen.sha === sourceSha)
155
+ return { legs, error: null };
156
+ left = chosen.sha;
157
+ }
158
+ return { legs: [], error: "cut loop exceeded commit count" };
159
+ }
160
+ function formatPlanMessage(plan) {
161
+ if (plan.action === "noop") {
162
+ if (plan.noopReason === "not-sync") {
163
+ return `scm:sync-default: no-op (detector=${plan.detectorReason})`;
164
+ }
165
+ if (plan.noopReason === "already-synced") {
166
+ return `scm:sync-default: no-op (origin/${plan.source} already on origin/${plan.dest})`;
167
+ }
168
+ if (plan.noopReason === "zero-files") {
169
+ return `scm:sync-default: no-op (0 files origin/${plan.dest}...origin/${plan.source})`;
170
+ }
171
+ if (plan.noopReason === "fetch-failed") {
172
+ return "scm:sync-default: fetch failed";
173
+ }
174
+ return `scm:sync-default: no-op (${plan.noopReason ?? "unknown"})`;
175
+ }
176
+ if (plan.action === "single") {
177
+ return (`scm:sync-default: one new PR origin/${plan.source} -> origin/${plan.dest} ` +
178
+ `(${plan.totalCount ?? "?"} files, syncMaxFiles=${plan.threshold} ${plan.provenance})`);
179
+ }
180
+ const next = plan.nextLegIndex ?? 1;
181
+ return (`scm:sync-default: staged ${plan.legs.length} new-PR legs; ` +
182
+ `next is leg ${next} (each leg is a new branch + new PR; never ${FORBIDDEN_SYNC_PR_RETARGET})`);
183
+ }
184
+ export function planSyncDefault(options) {
185
+ const runGit = options.runGit ?? defaultGitRunner;
186
+ const guessedDest = resolveGitDefaultDeliveryBranch(options.projectRoot, runGit);
187
+ const policy = resolveSyncPolicyFromDestRef({
188
+ projectRoot: options.projectRoot,
189
+ prBase: guessedDest,
190
+ runGit,
191
+ });
192
+ const dest = policy.dest;
193
+ const source = policy.source;
194
+ const fetchedDest = runGit(options.projectRoot, ["fetch", "--quiet", "origin", dest]);
195
+ if (fetchedDest.code !== 0) {
196
+ const resolvedEarly = resolveSyncMaxFiles(options.projectRoot, options.maxFiles);
197
+ return {
198
+ action: "noop",
199
+ noopReason: "fetch-failed",
200
+ dest,
201
+ source,
202
+ threshold: resolvedEarly.maxFiles,
203
+ provenance: resolvedEarly.provenance,
204
+ totalCount: null,
205
+ legs: [],
206
+ nextLegIndex: null,
207
+ detectorReason: "fetch-failed",
208
+ message: "scm:sync-default: fetch failed",
209
+ };
210
+ }
211
+ const resolved = resolveSyncMaxFiles(options.projectRoot, options.maxFiles);
212
+ const empty = (extra) => {
213
+ const base = {
214
+ dest,
215
+ source,
216
+ threshold: resolved.maxFiles,
217
+ provenance: resolved.provenance,
218
+ totalCount: extra.totalCount ?? null,
219
+ legs: extra.legs ?? [],
220
+ nextLegIndex: extra.nextLegIndex ?? null,
221
+ ...extra,
222
+ };
223
+ return { ...base, message: formatPlanMessage(base) };
224
+ };
225
+ const fetchedSource = runGit(options.projectRoot, ["fetch", "--quiet", "origin", source]);
226
+ if (fetchedSource.code !== 0) {
227
+ return empty({
228
+ action: "noop",
229
+ noopReason: "fetch-failed",
230
+ detectorReason: "fetch-failed",
231
+ });
232
+ }
233
+ const tipParsed = runGit(options.projectRoot, ["rev-parse", "--verify", `origin/${source}`]);
234
+ if (tipParsed.code !== 0 || tipParsed.stdout.trim().length === 0) {
235
+ return empty({
236
+ action: "noop",
237
+ noopReason: "fetch-failed",
238
+ detectorReason: "fetch-failed",
239
+ });
240
+ }
241
+ const sourceSha = tipParsed.stdout.trim();
242
+ const destParsed = runGit(options.projectRoot, ["rev-parse", "--verify", `origin/${dest}`]);
243
+ const destSha = destParsed.code === 0 ? destParsed.stdout.trim() : "";
244
+ const destTip = options.destTipSha ?? destSha;
245
+ const sync = detectBranchSync({
246
+ dest,
247
+ source,
248
+ sourceTyped: policy.sourceTyped,
249
+ prBase: dest,
250
+ headSha: sourceSha,
251
+ projectRoot: options.projectRoot,
252
+ developHint: policy.developHint,
253
+ runGit,
254
+ });
255
+ if (!sync.isSync) {
256
+ return empty({
257
+ action: "noop",
258
+ noopReason: sync.reason === "fetch-failed" ? "fetch-failed" : "not-sync",
259
+ detectorReason: sync.reason,
260
+ });
261
+ }
262
+ if (destTip.length > 0 &&
263
+ gitIsAncestor(options.projectRoot, sourceSha, destTip, runGit) === true) {
264
+ return empty({
265
+ action: "noop",
266
+ noopReason: "already-synced",
267
+ detectorReason: "sync",
268
+ });
269
+ }
270
+ const destRef = `origin/${dest}`;
271
+ const total = countChangedFilesOnRange({
272
+ projectRoot: options.projectRoot,
273
+ left: destRef,
274
+ right: `origin/${source}`,
275
+ runGit,
276
+ });
277
+ if (total.count === null) {
278
+ return empty({
279
+ action: "noop",
280
+ noopReason: "diff-failed",
281
+ detectorReason: "sync",
282
+ });
283
+ }
284
+ if (total.count === 0) {
285
+ return empty({
286
+ action: "noop",
287
+ noopReason: "zero-files",
288
+ detectorReason: "sync",
289
+ totalCount: 0,
290
+ });
291
+ }
292
+ const listed = listCommitsAfter({
293
+ projectRoot: options.projectRoot,
294
+ dest,
295
+ source,
296
+ runGit,
297
+ });
298
+ if (listed.error !== null) {
299
+ return empty({
300
+ action: "noop",
301
+ noopReason: "diff-failed",
302
+ detectorReason: "sync",
303
+ totalCount: total.count,
304
+ });
305
+ }
306
+ const cut = cutSyncLegs({
307
+ dest,
308
+ source,
309
+ destRef,
310
+ sourceSha,
311
+ commits: listed.commits,
312
+ threshold: resolved.maxFiles,
313
+ countFiles: (left, right) => countChangedFilesOnRange({
314
+ projectRoot: options.projectRoot,
315
+ left,
316
+ right,
317
+ runGit,
318
+ }).count,
319
+ isAncestor: (ancestor, descendant) => gitIsAncestor(options.projectRoot, ancestor, descendant, runGit) === true,
320
+ });
321
+ if (cut.error !== null || cut.legs.length === 0) {
322
+ return empty({
323
+ action: "noop",
324
+ noopReason: "diff-failed",
325
+ detectorReason: "sync",
326
+ totalCount: total.count,
327
+ });
328
+ }
329
+ const next = nextOpenableLeg(cut.legs, destTip, options.projectRoot, runGit);
330
+ const action = cut.legs.length === 1 ? "single" : "staged";
331
+ return empty({
332
+ action,
333
+ noopReason: null,
334
+ detectorReason: "sync",
335
+ totalCount: total.count,
336
+ legs: cut.legs,
337
+ nextLegIndex: next?.index ?? null,
338
+ });
339
+ }
340
+ export function nextOpenableLeg(legs, destTipSha, projectRoot, runGit) {
341
+ for (const leg of legs) {
342
+ if (destTipSha.length > 0 && gitIsAncestor(projectRoot, leg.sha, destTipSha, runGit) === true) {
343
+ continue;
344
+ }
345
+ return leg;
346
+ }
347
+ return null;
348
+ }
349
+ function matchingOpenPull(pulls, dest, leg) {
350
+ for (const pull of pulls) {
351
+ if (pull.baseRef !== dest)
352
+ continue;
353
+ if (pull.headRef === leg.branchName && pull.headSha === leg.sha)
354
+ return pull;
355
+ }
356
+ return null;
357
+ }
358
+ export function applySyncDefault(options) {
359
+ const runGit = options.runGit ?? defaultGitRunner;
360
+ const plan = options.plan ??
361
+ planSyncDefault({
362
+ projectRoot: options.projectRoot,
363
+ maxFiles: options.maxFiles,
364
+ destTipSha: options.destTipSha,
365
+ runGit,
366
+ });
367
+ if (plan.action === "noop" || plan.legs.length === 0) {
368
+ return { plan, opened: [], retargeted: false };
369
+ }
370
+ const destParsed = runGit(options.projectRoot, ["rev-parse", "--verify", `origin/${plan.dest}`]);
371
+ const destTip = options.destTipSha ?? (destParsed.code === 0 ? destParsed.stdout.trim() : "");
372
+ const next = nextOpenableLeg(plan.legs, destTip, options.projectRoot, runGit);
373
+ if (next === null) {
374
+ return { plan, opened: [], retargeted: false };
375
+ }
376
+ if (options.dryRun === true || options.forge === undefined) {
377
+ return { plan, opened: [], retargeted: false };
378
+ }
379
+ const openPulls = options.forge.listOpenPulls(options.repo, plan.dest);
380
+ const existing = matchingOpenPull(openPulls, plan.dest, next);
381
+ if (existing !== null) {
382
+ return {
383
+ plan,
384
+ opened: [
385
+ {
386
+ leg: next,
387
+ prNumber: existing.number,
388
+ prUrl: existing.htmlUrl,
389
+ branch: existing.headRef,
390
+ reusedExisting: true,
391
+ },
392
+ ],
393
+ retargeted: false,
394
+ };
395
+ }
396
+ const pushed = runGit(options.projectRoot, [
397
+ "push",
398
+ "--quiet",
399
+ "origin",
400
+ `${next.sha}:refs/heads/${next.branchName}`,
401
+ ]);
402
+ if (pushed.code !== 0) {
403
+ throw new Error(pushed.stderr || `git push failed for ${next.branchName}`);
404
+ }
405
+ const created = options.forge.createPull(options.repo, {
406
+ title: `sync: ${plan.source} -> ${plan.dest} (leg ${next.index}/${plan.legs.length})`,
407
+ head: next.branchName,
408
+ base: plan.dest,
409
+ body: syncDefaultPrBody(plan, next),
410
+ });
411
+ return {
412
+ plan,
413
+ opened: [
414
+ {
415
+ leg: next,
416
+ prNumber: created.number,
417
+ prUrl: created.htmlUrl,
418
+ branch: next.branchName,
419
+ reusedExisting: false,
420
+ },
421
+ ],
422
+ retargeted: false,
423
+ };
424
+ }
425
+ export function syncDefaultPrBody(plan, leg) {
426
+ return [
427
+ `Staged dest sync via \`${SYNC_DEFAULT_VERB}\`.`,
428
+ "",
429
+ `- Dest: \`${plan.dest}\` (deliveryBranch)`,
430
+ `- Source: \`${plan.source}\` (typed baseBranch)`,
431
+ `- Leg ${leg.index} of ${plan.legs.length} at \`${leg.sha}\` (${leg.fileCount} files, cut=${leg.cutKind})`,
432
+ `- Threshold: syncMaxFiles=${plan.threshold} (${plan.provenance.replace(/\r?\n/g, " ")})`,
433
+ "",
434
+ "This PR is new. After it merges, run `task scm:sync-default` again.",
435
+ "The next leg is a new branch and a new PR. Do not retarget or reuse an oversized PR",
436
+ `(\`${FORBIDDEN_SYNC_PR_RETARGET}\` / close-reopen). Each leg must be new when the reviewer first sees it.`,
437
+ "",
438
+ "Required checks stay on. The only exemption is the Wave 1 core-guard sync predicate.",
439
+ ].join("\n");
440
+ }
441
+ export function formatSyncDefaultHuman(result) {
442
+ const lines = [result.plan.message];
443
+ for (const opened of result.opened) {
444
+ const reuse = opened.reusedExisting ? "existing" : "new";
445
+ lines.push(` ${reuse} PR #${opened.prNumber} ${opened.prUrl} (${opened.branch} @ ${shortSha(opened.leg.sha)})`);
446
+ }
447
+ if (result.plan.action === "staged" && result.opened.length > 0) {
448
+ lines.push(" remaining legs open as new PRs after this one merges");
449
+ }
450
+ return `${lines.join("\n")}\n`;
451
+ }
452
+ //# sourceMappingURL=sync-default.js.map
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Typed plan.policy.syncMaxFiles (#3390 / #3377 Wave 2).
3
+ *
4
+ * One integer. Unset is 400. `--max-files` overrides one run and does not
5
+ * write policy. Set 100 if SLizard is a required check. Do not probe
6
+ * reviewer APIs and do not encode vendor limits as code constants.
7
+ *
8
+ * File-count warn uses the shared #3388 detector. Count is
9
+ * `git diff --name-only origin/<dest>...origin/<source>`. No warn when
10
+ * the detector says this is not a sync. The #3391 verb consumes
11
+ * evaluateSyncMaxFilesWarn and the maxFiles one-run override.
12
+ */
13
+ import { type GitRunner } from "../session/git.js";
14
+ import { type BranchSyncDetection } from "./branch-sync.js";
15
+ export declare const FIELD_SYNC_MAX_FILES = "plan.policy.syncMaxFiles";
16
+ export declare const FIELD_SYNC_MAX_FILES_CLI_ALIAS = "syncMaxFiles";
17
+ /** Unset threshold. Not a vendor limit. */
18
+ export declare const DEFAULT_SYNC_MAX_FILES = 400;
19
+ /**
20
+ * Operator docs for the threshold. Recommendation only — not a coded
21
+ * Greptile or SLizard constant.
22
+ */
23
+ export declare const SYNC_MAX_FILES_DOCS = "Unset is 400. Set plan.policy.syncMaxFiles to 100 if SLizard is a required check.";
24
+ export type SyncMaxFilesSource = "typed" | "default" | "default-on-error" | "flag";
25
+ /** Provenance printed in the warn: default vs policy vs one-run flag. */
26
+ export type SyncMaxFilesProvenance = "default" | "policy" | "flag";
27
+ export interface SyncMaxFilesResolved {
28
+ readonly maxFiles: number;
29
+ readonly source: SyncMaxFilesSource;
30
+ readonly provenance: SyncMaxFilesProvenance;
31
+ readonly error: string | null;
32
+ }
33
+ export type SyncMaxFilesWarnReason = "not-sync" | "within-limit" | "exceeds" | "diff-failed";
34
+ export interface SyncMaxFilesWarnResult {
35
+ readonly warn: boolean;
36
+ readonly reason: SyncMaxFilesWarnReason;
37
+ readonly count: number | null;
38
+ readonly threshold: number;
39
+ readonly provenance: SyncMaxFilesProvenance;
40
+ readonly dest: string;
41
+ readonly source: string;
42
+ readonly message: string;
43
+ }
44
+ /** Parse `--max-files`. Invalid or absent values are ignored. */
45
+ export declare function parseMaxFilesFlag(raw: unknown): number | null;
46
+ /**
47
+ * Resolve the file-count threshold.
48
+ *
49
+ * `--max-files` (maxFiles) wins for this call only and is never written.
50
+ */
51
+ export declare function resolveSyncMaxFiles(projectRoot?: string | null, maxFiles?: number | null): SyncMaxFilesResolved;
52
+ export interface SyncMaxFilesPolicyField {
53
+ readonly name: typeof FIELD_SYNC_MAX_FILES;
54
+ readonly current: number;
55
+ readonly default: number;
56
+ readonly source: string;
57
+ }
58
+ /** Inspector row for `task policy:show --field=syncMaxFiles`. */
59
+ export declare function inspectSyncMaxFiles(_data: Record<string, unknown> | null, projectRoot?: string): SyncMaxFilesPolicyField;
60
+ /** Count files in `origin/<dest>...origin/<source>`. */
61
+ export declare function countSyncChangedFiles(options: {
62
+ readonly dest: string;
63
+ readonly source: string;
64
+ readonly projectRoot: string;
65
+ readonly runGit?: GitRunner;
66
+ }): {
67
+ readonly count: number | null;
68
+ readonly error: string | null;
69
+ };
70
+ export declare function formatSyncMaxFilesWarn(options: {
71
+ readonly count: number;
72
+ readonly threshold: number;
73
+ readonly provenance: SyncMaxFilesProvenance;
74
+ readonly dest: string;
75
+ readonly source: string;
76
+ }): string;
77
+ export interface EvaluateSyncMaxFilesWarnInput {
78
+ readonly projectRoot: string;
79
+ readonly prBase: string;
80
+ readonly headSha: string;
81
+ /** `--max-files` one-run override. Does not write policy. */
82
+ readonly maxFiles?: number | null;
83
+ readonly runGit?: GitRunner;
84
+ /** Optional precomputed detector result. */
85
+ readonly sync?: BranchSyncDetection;
86
+ }
87
+ /**
88
+ * Warn when a detected sync exceeds syncMaxFiles.
89
+ *
90
+ * No warn when the shared detector says this is not a sync.
91
+ */
92
+ export declare function evaluateSyncMaxFilesWarn(input: EvaluateSyncMaxFilesWarnInput): SyncMaxFilesWarnResult;
93
+ //# sourceMappingURL=sync-max-files.d.ts.map