@teambit/ci 1.0.485 → 1.0.487

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 (97) hide show
  1. package/ci.docs.mdx +344 -83
  2. package/commands/sync.cmd.ts +77 -0
  3. package/dist/ci.docs.mdx +344 -83
  4. package/dist/ci.main.runtime.d.ts +67 -6
  5. package/dist/ci.main.runtime.js +152 -25
  6. package/dist/ci.main.runtime.js.map +1 -1
  7. package/dist/commands/sync.cmd.d.ts +34 -0
  8. package/dist/commands/sync.cmd.js +57 -0
  9. package/dist/commands/sync.cmd.js.map +1 -0
  10. package/dist/git.d.ts +11 -1
  11. package/dist/git.js +11 -1
  12. package/dist/git.js.map +1 -1
  13. package/dist/index.d.ts +4 -0
  14. package/dist/index.js +26 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/{preview-1785858957533.js → preview-1785883495880.js} +1 -1
  17. package/dist/sync/git-host-provider.d.ts +47 -0
  18. package/dist/sync/git-host-provider.js +54 -0
  19. package/dist/sync/git-host-provider.js.map +1 -0
  20. package/dist/sync/git-host-provider.spec.d.ts +1 -0
  21. package/dist/sync/git-host-provider.spec.js +171 -0
  22. package/dist/sync/git-host-provider.spec.js.map +1 -0
  23. package/dist/sync/git-ops.d.ts +171 -0
  24. package/dist/sync/git-ops.js +354 -0
  25. package/dist/sync/git-ops.js.map +1 -0
  26. package/dist/sync/git-ops.spec.d.ts +1 -0
  27. package/dist/sync/git-ops.spec.js +476 -0
  28. package/dist/sync/git-ops.spec.js.map +1 -0
  29. package/dist/sync/github-client.d.ts +79 -0
  30. package/dist/sync/github-client.js +198 -0
  31. package/dist/sync/github-client.js.map +1 -0
  32. package/dist/sync/github-client.spec.d.ts +1 -0
  33. package/dist/sync/github-client.spec.js +205 -0
  34. package/dist/sync/github-client.spec.js.map +1 -0
  35. package/dist/sync/init-scaffold.d.ts +40 -0
  36. package/dist/sync/init-scaffold.js +326 -0
  37. package/dist/sync/init-scaffold.js.map +1 -0
  38. package/dist/sync/init-scaffold.spec.d.ts +1 -0
  39. package/dist/sync/init-scaffold.spec.js +169 -0
  40. package/dist/sync/init-scaffold.spec.js.map +1 -0
  41. package/dist/sync/lane-sync-executor.d.ts +256 -0
  42. package/dist/sync/lane-sync-executor.js +1249 -0
  43. package/dist/sync/lane-sync-executor.js.map +1 -0
  44. package/dist/sync/lane-sync-executor.spec.d.ts +1 -0
  45. package/dist/sync/lane-sync-executor.spec.js +416 -0
  46. package/dist/sync/lane-sync-executor.spec.js.map +1 -0
  47. package/dist/sync/main-sync-executor.d.ts +62 -0
  48. package/dist/sync/main-sync-executor.js +305 -0
  49. package/dist/sync/main-sync-executor.js.map +1 -0
  50. package/dist/sync/sync-config.d.ts +83 -0
  51. package/dist/sync/sync-config.js +187 -0
  52. package/dist/sync/sync-config.js.map +1 -0
  53. package/dist/sync/sync-config.spec.d.ts +1 -0
  54. package/dist/sync/sync-config.spec.js +254 -0
  55. package/dist/sync/sync-config.spec.js.map +1 -0
  56. package/dist/sync/sync-orchestrator.d.ts +76 -0
  57. package/dist/sync/sync-orchestrator.js +396 -0
  58. package/dist/sync/sync-orchestrator.js.map +1 -0
  59. package/dist/sync/sync-orchestrator.spec.d.ts +1 -0
  60. package/dist/sync/sync-orchestrator.spec.js +155 -0
  61. package/dist/sync/sync-orchestrator.spec.js.map +1 -0
  62. package/dist/sync/sync-planner.d.ts +60 -0
  63. package/dist/sync/sync-planner.js +114 -0
  64. package/dist/sync/sync-planner.js.map +1 -0
  65. package/dist/sync/sync-planner.spec.d.ts +1 -0
  66. package/dist/sync/sync-planner.spec.js +210 -0
  67. package/dist/sync/sync-planner.spec.js.map +1 -0
  68. package/dist/sync/sync-state.d.ts +94 -0
  69. package/dist/sync/sync-state.js +178 -0
  70. package/dist/sync/sync-state.js.map +1 -0
  71. package/dist/sync/sync-state.spec.d.ts +1 -0
  72. package/dist/sync/sync-state.spec.js +271 -0
  73. package/dist/sync/sync-state.spec.js.map +1 -0
  74. package/dist/sync/workspace-lane.d.ts +13 -0
  75. package/dist/sync/workspace-lane.js +27 -0
  76. package/dist/sync/workspace-lane.js.map +1 -0
  77. package/package.json +20 -15
  78. package/sync/git-host-provider.spec.ts +197 -0
  79. package/sync/git-host-provider.ts +89 -0
  80. package/sync/git-ops.spec.ts +506 -0
  81. package/sync/git-ops.ts +334 -0
  82. package/sync/github-client.spec.ts +174 -0
  83. package/sync/github-client.ts +209 -0
  84. package/sync/init-scaffold.spec.ts +142 -0
  85. package/sync/init-scaffold.ts +340 -0
  86. package/sync/lane-sync-executor.spec.ts +389 -0
  87. package/sync/lane-sync-executor.ts +1363 -0
  88. package/sync/main-sync-executor.ts +321 -0
  89. package/sync/sync-config.spec.ts +283 -0
  90. package/sync/sync-config.ts +214 -0
  91. package/sync/sync-orchestrator.spec.ts +120 -0
  92. package/sync/sync-orchestrator.ts +386 -0
  93. package/sync/sync-planner.spec.ts +126 -0
  94. package/sync/sync-planner.ts +99 -0
  95. package/sync/sync-state.spec.ts +263 -0
  96. package/sync/sync-state.ts +176 -0
  97. package/sync/workspace-lane.ts +20 -0
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_chai","data","require","_syncOrchestrator","_syncConfig","orchestrator","SyncOrchestrator","conflicts","all","branch","lane","main","init","describe","it","options","expected","label","JSON","stringify","message","sync","catch","err","forEach","fragment","expect","to","contain","deps","config","workspace","defaultScope","ci","getDefaultBranchName","listGitHostProviders","lanes","getDefaultLaneId","name","logger","console","consoleWarning","debug","result","equal","assertCleanForDryRun","e","paths","Array","from","length","_","index","not","throw","cfg","resolveSyncConfig","branchToLaneName","map","b","syncableLaneNameForBranch","filter","Boolean","deep","prefixed","branchPrefix","undefined","overridden","branches"],"sources":["sync-orchestrator.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport { SyncOrchestrator, assertCleanForDryRun } from './sync-orchestrator';\nimport { branchToLaneName, resolveSyncConfig, syncableLaneNameForBranch } from './sync-config';\n\n// The deps are never reached: every conflict case throws before the first `this.deps` access.\nfunction orchestrator(): SyncOrchestrator {\n return new SyncOrchestrator({} as any);\n}\n\n/** Each row must name every selector it refused, and which one would otherwise have silently won. */\nconst conflicts: Array<[Record<string, unknown>, string[]]> = [\n [{ all: true, branch: 'my-branch' }, ['--all cannot be combined with', '--branch (\"my-branch\")']],\n [{ all: true, lane: 'my-lane' }, ['--all cannot be combined with', 'a lane argument (\"my-lane\")']],\n [{ all: true, main: true }, ['--all cannot be combined with', '--main']],\n [\n { lane: 'my-lane', branch: 'my-branch' },\n [\n 'a lane argument (\"my-lane\") cannot be combined with --branch (\"my-branch\")',\n 'only --branch (\"my-branch\") would have run',\n ],\n ],\n [\n { lane: 'my-lane', main: true },\n ['a lane argument (\"my-lane\") cannot be combined with --main', 'only --main would have run'],\n ],\n [\n { branch: 'my-branch', main: true },\n ['--branch (\"my-branch\") cannot be combined with --main', 'only --main would have run'],\n ],\n [\n { lane: 'my-lane', branch: 'my-branch', main: true },\n [\n 'a lane argument (\"my-lane\") cannot be combined with --branch (\"my-branch\") or with --main',\n 'only --main would have run',\n ],\n ],\n // --init only scaffolds, so pairing it with a target could not mean anything.\n [{ init: true, branch: 'x' }, ['--init cannot be combined']],\n];\n\ndescribe('SyncOrchestrator target guards', () => {\n it('refuses every combination of selectors, naming the ones it saw', async () => {\n for (const [options, expected] of conflicts) {\n const label = JSON.stringify(options);\n let message = '';\n await orchestrator()\n .sync(options as any)\n .catch((err) => {\n message = err.message;\n });\n expected.forEach((fragment) => expect(message, label).to.contain(fragment));\n }\n });\n\n it('reports a branch whose mapped name is not a valid lane name, instead of syncing it', async () => {\n const deps = {\n config: { sync: {} },\n workspace: { defaultScope: 'org.scope' },\n ci: { getDefaultBranchName: async () => 'main', listGitHostProviders: () => [] },\n lanes: { getDefaultLaneId: () => ({ name: 'main' }) },\n logger: { console() {}, consoleWarning() {}, debug() {} },\n };\n const result = await new SyncOrchestrator(deps as any).sync({ branch: 'feature/foo' });\n expect(result).to.equal('branch feature/foo does not map to a valid lane name; nothing to do');\n });\n});\n\n// A dry run computes the main-scope plan by force-checking-out the sync branch, so a dirty tree would be\n// discarded by a run that promised to write nothing.\ndescribe('assertCleanForDryRun', () => {\n it('refuses a dirty tree, naming the paths at stake and how to keep them', () => {\n let message = '';\n try {\n assertCleanForDryRun(['comp1/index.js', 'notes.txt']);\n } catch (e: any) {\n message = e.message;\n }\n expect(message).to.contain('--dry-run refuses to run');\n expect(message).to.contain('2 uncommitted change(s)');\n expect(message).to.contain('comp1/index.js, notes.txt');\n expect(message).to.contain('Commit or stash them first');\n });\n\n it('caps the named paths so a large diff cannot produce an unreadable error', () => {\n const paths = Array.from({ length: 12 }, (_, index) => `file-${index}.js`);\n let message = '';\n try {\n assertCleanForDryRun(paths);\n } catch (e: any) {\n message = e.message;\n }\n expect(message).to.contain('12 uncommitted change(s)');\n expect(message).to.contain('file-9.js');\n expect(message).to.not.contain('file-10.js');\n expect(message).to.contain('…');\n });\n\n it('permits a clean tree: the write-then-restore is only acceptable when nothing can be lost', () => {\n expect(() => assertCleanForDryRun([])).to.not.throw();\n });\n});\n\ndescribe('branch -> lane enumeration filter', () => {\n it('queues only the branches whose mapped name could actually be a lane', () => {\n const cfg = resolveSyncConfig({});\n // non-vacuity: the default mapping really is the identity, so the filter is what excludes anything\n expect(branchToLaneName('feature/foo', cfg)).to.equal('feature/foo');\n expect(\n ['feature/foo', 'FEATURE', 'ok-lane'].map((b) => syncableLaneNameForBranch(b, cfg)).filter(Boolean)\n ).to.deep.equal(['ok-lane']);\n });\n\n it('honours the prefix (stripped before the name is checked) and an explicit branches override', () => {\n const prefixed = resolveSyncConfig({ branchPrefix: 'lane/' });\n expect(syncableLaneNameForBranch('lane/my-lane', prefixed)).to.equal('my-lane');\n expect(syncableLaneNameForBranch('feature/foo', prefixed)).to.equal(undefined);\n const overridden = resolveSyncConfig({ branches: { 'my-lane': 'custom/branch' } });\n expect(syncableLaneNameForBranch('custom/branch', overridden)).to.equal('my-lane');\n });\n});\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * How much of a claim this lane/branch pair has on `origin/<branch>` — the only thing that licenses
3
+ * retiring it, because `close-pr` deletes the branch. Under the default config every branch name maps
4
+ * to a same-named lane, and branches cut after a merged sync PR inherit its `.bitmap`, so "looks
5
+ * bit-managed" is not enough. Evidence = attribution (the branch's `.bitmap` lane pointer) plus
6
+ * reachability of the state commit / tip from the default branch:
7
+ *
8
+ * - `own-live` — names this lane; state commit not in the default branch (real, unmerged lane branch).
9
+ * - `own-merged` — names this lane; branch tip already in the default branch (deleting loses nothing).
10
+ * - `own-superseded` — state commit merged but tip not: close the PR, KEEP the branch.
11
+ * - `inherited-or-none` — no pointer, another lane's, or unreadable: not ours, touch nothing.
12
+ */
13
+ export type LaneOwnershipEvidence = 'own-live' | 'own-merged' | 'own-superseded' | 'inherited-or-none';
14
+ export type LaneSyncInput = {
15
+ /** fingerprint of the lane's component heads on bit.cloud; undefined when the lane is gone */
16
+ laneHead?: string;
17
+ branchExists: boolean;
18
+ /**
19
+ * The lane-component fingerprint as the branch's committed `.bitmap` records it; undefined when the
20
+ * branch holds no state for this lane. Equality with `laneHead` is the definition of converged.
21
+ */
22
+ lastSyncedHead?: string;
23
+ /** whether the branch carries commits on top of the commit that last wrote its `.bitmap` */
24
+ hasDevCommits: boolean;
25
+ /**
26
+ * Whether the reconciler itself wrote the tip commit. The one exception to "markers are annotations":
27
+ * consulted only on the branch-deletion path, and only ever to WITHHOLD a deletion. Must be computed
28
+ * with `isSyncAuthoredMessage`, not the loop guard's substring match.
29
+ */
30
+ tipIsSyncCommit: boolean;
31
+ conflictLabelPresent: boolean;
32
+ /** @see LaneOwnershipEvidence — consulted only on the "lane is gone" path. */
33
+ ownership: LaneOwnershipEvidence;
34
+ };
35
+ /**
36
+ * Why a `close-pr` is keeping the branch rather than deleting it. `tip-advanced-during-run` is the one
37
+ * the planner never emits: it is discovered when the executor re-reads the branch just before deleting.
38
+ */
39
+ export type BranchKeepReason = 'unmerged-commits' | 'tip-not-a-sync-commit' | 'tip-advanced-during-run';
40
+ export type LaneSyncAction = {
41
+ type: 'noop';
42
+ reason: string;
43
+ } | {
44
+ type: 'import-lane';
45
+ } | {
46
+ type: 'export-branch';
47
+ } | {
48
+ type: 'merge-diverged';
49
+ } | {
50
+ type: 'close-pr';
51
+ deleteBranch: true;
52
+ } | {
53
+ type: 'close-pr';
54
+ deleteBranch: false;
55
+ keepReason: BranchKeepReason;
56
+ } | {
57
+ type: 'halt';
58
+ reason: string;
59
+ };
60
+ export declare function planLaneSync(input: LaneSyncInput): LaneSyncAction;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.planLaneSync = planLaneSync;
7
+ /**
8
+ * How much of a claim this lane/branch pair has on `origin/<branch>` — the only thing that licenses
9
+ * retiring it, because `close-pr` deletes the branch. Under the default config every branch name maps
10
+ * to a same-named lane, and branches cut after a merged sync PR inherit its `.bitmap`, so "looks
11
+ * bit-managed" is not enough. Evidence = attribution (the branch's `.bitmap` lane pointer) plus
12
+ * reachability of the state commit / tip from the default branch:
13
+ *
14
+ * - `own-live` — names this lane; state commit not in the default branch (real, unmerged lane branch).
15
+ * - `own-merged` — names this lane; branch tip already in the default branch (deleting loses nothing).
16
+ * - `own-superseded` — state commit merged but tip not: close the PR, KEEP the branch.
17
+ * - `inherited-or-none` — no pointer, another lane's, or unreadable: not ours, touch nothing.
18
+ */
19
+
20
+ /**
21
+ * Why a `close-pr` is keeping the branch rather than deleting it. `tip-advanced-during-run` is the one
22
+ * the planner never emits: it is discovered when the executor re-reads the branch just before deleting.
23
+ */
24
+
25
+ function planLaneSync(input) {
26
+ const {
27
+ laneHead,
28
+ branchExists,
29
+ lastSyncedHead,
30
+ hasDevCommits,
31
+ conflictLabelPresent,
32
+ ownership
33
+ } = input;
34
+ const {
35
+ tipIsSyncCommit
36
+ } = input;
37
+ if (conflictLabelPresent) {
38
+ return {
39
+ type: 'noop',
40
+ reason: 'PR is labeled bit-sync-conflict; resolve and remove the label to resume'
41
+ };
42
+ }
43
+ if (!laneHead) {
44
+ if (!branchExists) return {
45
+ type: 'noop',
46
+ reason: 'lane and branch both absent'
47
+ };
48
+ switch (ownership) {
49
+ case 'own-live':
50
+ // DELETION NEEDS BOTH HALVES: `.bitmap` attribution alone is not enough — a developer who commits
51
+ // their own `.bitmap` write (bit create / unexported snap / deps set) looks exactly like
52
+ // "own-live, nothing above the state commit". The marker can only withhold a deletion, never
53
+ // authorize one; a false keep is harmless, a false delete destroys the only copy of the work.
54
+ if (!hasDevCommits && tipIsSyncCommit) return {
55
+ type: 'close-pr',
56
+ deleteBranch: true
57
+ };
58
+ return {
59
+ type: 'close-pr',
60
+ deleteBranch: false,
61
+ keepReason: hasDevCommits ? 'unmerged-commits' : 'tip-not-a-sync-commit'
62
+ };
63
+ case 'own-merged':
64
+ // No marker conjunction needed: the tip being an ancestor of the default branch is unforgeable
65
+ // proof that deleting loses nothing.
66
+ return {
67
+ type: 'close-pr',
68
+ deleteBranch: true
69
+ };
70
+ case 'own-superseded':
71
+ // Those commits are in no other ref.
72
+ return {
73
+ type: 'close-pr',
74
+ deleteBranch: false,
75
+ keepReason: 'unmerged-commits'
76
+ };
77
+ default:
78
+ return {
79
+ type: 'noop',
80
+ reason: 'branch maps to no lane and has no sync history of its own; ignoring'
81
+ };
82
+ }
83
+ }
84
+ if (!branchExists) return {
85
+ type: 'import-lane'
86
+ };
87
+ if (!lastSyncedHead) {
88
+ if (hasDevCommits) {
89
+ return {
90
+ type: 'halt',
91
+ reason: 'branch has commits but its .bitmap records no state for this lane; cannot tell which side is newer'
92
+ };
93
+ }
94
+ return {
95
+ type: 'import-lane'
96
+ };
97
+ }
98
+ const laneMoved = laneHead !== lastSyncedHead;
99
+ if (laneMoved && hasDevCommits) return {
100
+ type: 'merge-diverged'
101
+ };
102
+ if (laneMoved) return {
103
+ type: 'import-lane'
104
+ };
105
+ if (hasDevCommits) return {
106
+ type: 'export-branch'
107
+ };
108
+ return {
109
+ type: 'noop',
110
+ reason: 'converged'
111
+ };
112
+ }
113
+
114
+ //# sourceMappingURL=sync-planner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["planLaneSync","input","laneHead","branchExists","lastSyncedHead","hasDevCommits","conflictLabelPresent","ownership","tipIsSyncCommit","type","reason","deleteBranch","keepReason","laneMoved"],"sources":["sync-planner.ts"],"sourcesContent":["/**\n * How much of a claim this lane/branch pair has on `origin/<branch>` — the only thing that licenses\n * retiring it, because `close-pr` deletes the branch. Under the default config every branch name maps\n * to a same-named lane, and branches cut after a merged sync PR inherit its `.bitmap`, so \"looks\n * bit-managed\" is not enough. Evidence = attribution (the branch's `.bitmap` lane pointer) plus\n * reachability of the state commit / tip from the default branch:\n *\n * - `own-live` — names this lane; state commit not in the default branch (real, unmerged lane branch).\n * - `own-merged` — names this lane; branch tip already in the default branch (deleting loses nothing).\n * - `own-superseded` — state commit merged but tip not: close the PR, KEEP the branch.\n * - `inherited-or-none` — no pointer, another lane's, or unreadable: not ours, touch nothing.\n */\nexport type LaneOwnershipEvidence = 'own-live' | 'own-merged' | 'own-superseded' | 'inherited-or-none';\n\nexport type LaneSyncInput = {\n /** fingerprint of the lane's component heads on bit.cloud; undefined when the lane is gone */\n laneHead?: string;\n branchExists: boolean;\n /**\n * The lane-component fingerprint as the branch's committed `.bitmap` records it; undefined when the\n * branch holds no state for this lane. Equality with `laneHead` is the definition of converged.\n */\n lastSyncedHead?: string;\n /** whether the branch carries commits on top of the commit that last wrote its `.bitmap` */\n hasDevCommits: boolean;\n /**\n * Whether the reconciler itself wrote the tip commit. The one exception to \"markers are annotations\":\n * consulted only on the branch-deletion path, and only ever to WITHHOLD a deletion. Must be computed\n * with `isSyncAuthoredMessage`, not the loop guard's substring match.\n */\n tipIsSyncCommit: boolean;\n conflictLabelPresent: boolean;\n /** @see LaneOwnershipEvidence — consulted only on the \"lane is gone\" path. */\n ownership: LaneOwnershipEvidence;\n};\n\n/**\n * Why a `close-pr` is keeping the branch rather than deleting it. `tip-advanced-during-run` is the one\n * the planner never emits: it is discovered when the executor re-reads the branch just before deleting.\n */\nexport type BranchKeepReason = 'unmerged-commits' | 'tip-not-a-sync-commit' | 'tip-advanced-during-run';\n\nexport type LaneSyncAction =\n | { type: 'noop'; reason: string }\n | { type: 'import-lane' }\n | { type: 'export-branch' }\n | { type: 'merge-diverged' }\n // Two variants rather than an optional `keepReason` so the type enforces that a keep always says why.\n | { type: 'close-pr'; deleteBranch: true }\n | { type: 'close-pr'; deleteBranch: false; keepReason: BranchKeepReason }\n | { type: 'halt'; reason: string };\n\nexport function planLaneSync(input: LaneSyncInput): LaneSyncAction {\n const { laneHead, branchExists, lastSyncedHead, hasDevCommits, conflictLabelPresent, ownership } = input;\n const { tipIsSyncCommit } = input;\n if (conflictLabelPresent) {\n return { type: 'noop', reason: 'PR is labeled bit-sync-conflict; resolve and remove the label to resume' };\n }\n if (!laneHead) {\n if (!branchExists) return { type: 'noop', reason: 'lane and branch both absent' };\n switch (ownership) {\n case 'own-live':\n // DELETION NEEDS BOTH HALVES: `.bitmap` attribution alone is not enough — a developer who commits\n // their own `.bitmap` write (bit create / unexported snap / deps set) looks exactly like\n // \"own-live, nothing above the state commit\". The marker can only withhold a deletion, never\n // authorize one; a false keep is harmless, a false delete destroys the only copy of the work.\n if (!hasDevCommits && tipIsSyncCommit) return { type: 'close-pr', deleteBranch: true };\n return {\n type: 'close-pr',\n deleteBranch: false,\n keepReason: hasDevCommits ? 'unmerged-commits' : 'tip-not-a-sync-commit',\n };\n case 'own-merged':\n // No marker conjunction needed: the tip being an ancestor of the default branch is unforgeable\n // proof that deleting loses nothing.\n return { type: 'close-pr', deleteBranch: true };\n case 'own-superseded':\n // Those commits are in no other ref.\n return { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' };\n default:\n return { type: 'noop', reason: 'branch maps to no lane and has no sync history of its own; ignoring' };\n }\n }\n if (!branchExists) return { type: 'import-lane' };\n if (!lastSyncedHead) {\n if (hasDevCommits) {\n return {\n type: 'halt',\n reason: 'branch has commits but its .bitmap records no state for this lane; cannot tell which side is newer',\n };\n }\n return { type: 'import-lane' };\n }\n const laneMoved = laneHead !== lastSyncedHead;\n if (laneMoved && hasDevCommits) return { type: 'merge-diverged' };\n if (laneMoved) return { type: 'import-lane' };\n if (hasDevCommits) return { type: 'export-branch' };\n return { type: 'noop', reason: 'converged' };\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAyBA;AACA;AACA;AACA;;AAaO,SAASA,YAAYA,CAACC,KAAoB,EAAkB;EACjE,MAAM;IAAEC,QAAQ;IAAEC,YAAY;IAAEC,cAAc;IAAEC,aAAa;IAAEC,oBAAoB;IAAEC;EAAU,CAAC,GAAGN,KAAK;EACxG,MAAM;IAAEO;EAAgB,CAAC,GAAGP,KAAK;EACjC,IAAIK,oBAAoB,EAAE;IACxB,OAAO;MAAEG,IAAI,EAAE,MAAM;MAAEC,MAAM,EAAE;IAA0E,CAAC;EAC5G;EACA,IAAI,CAACR,QAAQ,EAAE;IACb,IAAI,CAACC,YAAY,EAAE,OAAO;MAAEM,IAAI,EAAE,MAAM;MAAEC,MAAM,EAAE;IAA8B,CAAC;IACjF,QAAQH,SAAS;MACf,KAAK,UAAU;QACb;QACA;QACA;QACA;QACA,IAAI,CAACF,aAAa,IAAIG,eAAe,EAAE,OAAO;UAAEC,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE;QAAK,CAAC;QACtF,OAAO;UACLF,IAAI,EAAE,UAAU;UAChBE,YAAY,EAAE,KAAK;UACnBC,UAAU,EAAEP,aAAa,GAAG,kBAAkB,GAAG;QACnD,CAAC;MACH,KAAK,YAAY;QACf;QACA;QACA,OAAO;UAAEI,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE;QAAK,CAAC;MACjD,KAAK,gBAAgB;QACnB;QACA,OAAO;UAAEF,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE,KAAK;UAAEC,UAAU,EAAE;QAAmB,CAAC;MAClF;QACE,OAAO;UAAEH,IAAI,EAAE,MAAM;UAAEC,MAAM,EAAE;QAAsE,CAAC;IAC1G;EACF;EACA,IAAI,CAACP,YAAY,EAAE,OAAO;IAAEM,IAAI,EAAE;EAAc,CAAC;EACjD,IAAI,CAACL,cAAc,EAAE;IACnB,IAAIC,aAAa,EAAE;MACjB,OAAO;QACLI,IAAI,EAAE,MAAM;QACZC,MAAM,EAAE;MACV,CAAC;IACH;IACA,OAAO;MAAED,IAAI,EAAE;IAAc,CAAC;EAChC;EACA,MAAMI,SAAS,GAAGX,QAAQ,KAAKE,cAAc;EAC7C,IAAIS,SAAS,IAAIR,aAAa,EAAE,OAAO;IAAEI,IAAI,EAAE;EAAiB,CAAC;EACjE,IAAII,SAAS,EAAE,OAAO;IAAEJ,IAAI,EAAE;EAAc,CAAC;EAC7C,IAAIJ,aAAa,EAAE,OAAO;IAAEI,IAAI,EAAE;EAAgB,CAAC;EACnD,OAAO;IAAEA,IAAI,EAAE,MAAM;IAAEC,MAAM,EAAE;EAAY,CAAC;AAC9C","ignoreList":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+
3
+ function _chai() {
4
+ const data = require("chai");
5
+ _chai = function () {
6
+ return data;
7
+ };
8
+ return data;
9
+ }
10
+ function _syncPlanner() {
11
+ const data = require("./sync-planner");
12
+ _syncPlanner = function () {
13
+ return data;
14
+ };
15
+ return data;
16
+ }
17
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
18
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
19
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
20
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
21
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
22
+ const base = {
23
+ laneHead: 'L1',
24
+ branchExists: true,
25
+ lastSyncedHead: 'L1',
26
+ hasDevCommits: false,
27
+ tipIsSyncCommit: true,
28
+ conflictLabelPresent: false,
29
+ ownership: 'own-live'
30
+ };
31
+
32
+ /** the "lane deleted remotely, branch still there" shape — the only path that reads `ownership` */
33
+ const laneGone = ownership => ({
34
+ laneHead: undefined,
35
+ ownership
36
+ });
37
+
38
+ /**
39
+ * The decision table. `action` (exact) is required on every row that decides a branch deletion: under
40
+ * the defaults an ordinary developer branch reaches the planner in the same shape as a deleted lane's
41
+ * branch, so `deleteBranch` and the keep reason are part of the contract, not incidental output.
42
+ */
43
+ const table = [{
44
+ name: 'converged -> noop',
45
+ input: {},
46
+ type: 'noop',
47
+ reason: 'converged'
48
+ }, {
49
+ name: 'conflict label present -> noop until resolved',
50
+ input: {
51
+ laneHead: 'L2',
52
+ conflictLabelPresent: true
53
+ },
54
+ type: 'noop'
55
+ }, {
56
+ name: 'lane gone + no branch -> noop',
57
+ input: {
58
+ laneHead: undefined,
59
+ branchExists: false
60
+ },
61
+ type: 'noop'
62
+ }, {
63
+ name: "own-live, no dev commits, OUR tip (the branch is exactly the lane's mirror) -> close-pr AND delete",
64
+ input: laneGone('own-live'),
65
+ type: 'close-pr',
66
+ action: {
67
+ type: 'close-pr',
68
+ deleteBranch: true
69
+ }
70
+ }, {
71
+ // A developer who commits their own `.bitmap` write looks own-live with nothing above the state
72
+ // commit; the marker is what separates our mirror from their branch.
73
+ name: 'own-live, no dev commits, a tip WE DID NOT WRITE -> close-pr but KEEP the branch',
74
+ input: _objectSpread(_objectSpread({}, laneGone('own-live')), {}, {
75
+ tipIsSyncCommit: false
76
+ }),
77
+ type: 'close-pr',
78
+ action: {
79
+ type: 'close-pr',
80
+ deleteBranch: false,
81
+ keepReason: 'tip-not-a-sync-commit'
82
+ }
83
+ }, {
84
+ name: 'own-live WITH dev commits (never-exported work above the state commit) -> KEEP the branch',
85
+ input: _objectSpread(_objectSpread({}, laneGone('own-live')), {}, {
86
+ hasDevCommits: true
87
+ }),
88
+ type: 'close-pr',
89
+ action: {
90
+ type: 'close-pr',
91
+ deleteBranch: false,
92
+ keepReason: 'unmerged-commits'
93
+ }
94
+ }, {
95
+ // "tip-not-a-sync-commit" would send the reader after the wrong thing when there is real work.
96
+ name: 'own-live with dev commits AND a foreign tip -> keeps, naming the commits as the reason',
97
+ input: _objectSpread(_objectSpread({}, laneGone('own-live')), {}, {
98
+ hasDevCommits: true,
99
+ tipIsSyncCommit: false
100
+ }),
101
+ type: 'close-pr',
102
+ action: {
103
+ type: 'close-pr',
104
+ deleteBranch: false,
105
+ keepReason: 'unmerged-commits'
106
+ }
107
+ }, {
108
+ name: 'own-merged (branch tip already in the default branch) -> close-pr AND delete',
109
+ input: laneGone('own-merged'),
110
+ type: 'close-pr',
111
+ action: {
112
+ type: 'close-pr',
113
+ deleteBranch: true
114
+ }
115
+ }, {
116
+ // Reachability alone proves deleting loses nothing, so no marker conjunction applies here.
117
+ name: 'own-merged deletes even with a foreign tip and dev commits',
118
+ input: _objectSpread(_objectSpread({}, laneGone('own-merged')), {}, {
119
+ tipIsSyncCommit: false,
120
+ hasDevCommits: true
121
+ }),
122
+ type: 'close-pr',
123
+ action: {
124
+ type: 'close-pr',
125
+ deleteBranch: true
126
+ }
127
+ }, {
128
+ name: 'own-superseded (PR merged, then more commits pushed) -> close-pr but KEEP the branch',
129
+ input: laneGone('own-superseded'),
130
+ type: 'close-pr',
131
+ action: {
132
+ type: 'close-pr',
133
+ deleteBranch: false,
134
+ keepReason: 'unmerged-commits'
135
+ }
136
+ }, {
137
+ name: 'inherited-or-none -> noop, and never close-pr',
138
+ input: laneGone('inherited-or-none'),
139
+ type: 'noop',
140
+ reason: 'maps to no lane and has no sync history'
141
+ }, {
142
+ name: 'inherited-or-none with the branch carrying its own dev commits -> still noop',
143
+ input: _objectSpread(_objectSpread({}, laneGone('inherited-or-none')), {}, {
144
+ lastSyncedHead: undefined,
145
+ hasDevCommits: true
146
+ }),
147
+ type: 'noop'
148
+ },
149
+ // The adopt paths need no claim: nothing on them can delete a branch.
150
+ {
151
+ name: 'no branch yet -> import-lane',
152
+ input: {
153
+ branchExists: false,
154
+ lastSyncedHead: undefined,
155
+ ownership: 'inherited-or-none'
156
+ },
157
+ type: 'import-lane'
158
+ }, {
159
+ name: 'lane moved, branch untouched -> import-lane',
160
+ input: {
161
+ laneHead: 'L2'
162
+ },
163
+ type: 'import-lane'
164
+ }, {
165
+ name: 'dev commits, lane unchanged -> export-branch',
166
+ input: {
167
+ hasDevCommits: true
168
+ },
169
+ type: 'export-branch'
170
+ }, {
171
+ name: 'both moved -> merge-diverged',
172
+ input: {
173
+ laneHead: 'L2',
174
+ hasDevCommits: true
175
+ },
176
+ type: 'merge-diverged'
177
+ }, {
178
+ name: 'existing branch, never synced, no dev commits, lane EXISTS -> import-lane (adopt branch)',
179
+ input: {
180
+ lastSyncedHead: undefined,
181
+ ownership: 'inherited-or-none'
182
+ },
183
+ type: 'import-lane'
184
+ }, {
185
+ name: 'existing branch, never synced, has dev commits, lane EXISTS -> halt (ambiguous)',
186
+ input: {
187
+ lastSyncedHead: undefined,
188
+ ownership: 'inherited-or-none',
189
+ hasDevCommits: true
190
+ },
191
+ type: 'halt'
192
+ }];
193
+ describe('planLaneSync', () => {
194
+ table.forEach(({
195
+ name,
196
+ input,
197
+ type,
198
+ action,
199
+ reason
200
+ }) => {
201
+ it(name, () => {
202
+ const result = (0, _syncPlanner().planLaneSync)(_objectSpread(_objectSpread({}, base), input));
203
+ (0, _chai().expect)(result.type, name).to.equal(type);
204
+ if (action) (0, _chai().expect)(result).to.deep.equal(action);
205
+ if (reason) (0, _chai().expect)(result.reason).to.contain(reason);
206
+ });
207
+ });
208
+ });
209
+
210
+ //# sourceMappingURL=sync-planner.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_chai","data","require","_syncPlanner","ownKeys","e","r","t","Object","keys","getOwnPropertySymbols","o","filter","getOwnPropertyDescriptor","enumerable","push","apply","_objectSpread","arguments","length","forEach","_defineProperty","getOwnPropertyDescriptors","defineProperties","defineProperty","_toPropertyKey","value","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","base","laneHead","branchExists","lastSyncedHead","hasDevCommits","tipIsSyncCommit","conflictLabelPresent","ownership","laneGone","undefined","table","name","input","type","reason","action","deleteBranch","keepReason","describe","it","result","planLaneSync","expect","to","equal","deep","contain"],"sources":["sync-planner.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport type { LaneOwnershipEvidence, LaneSyncAction, LaneSyncInput } from './sync-planner';\nimport { planLaneSync } from './sync-planner';\n\nconst base: LaneSyncInput = {\n laneHead: 'L1',\n branchExists: true,\n lastSyncedHead: 'L1',\n hasDevCommits: false,\n tipIsSyncCommit: true,\n conflictLabelPresent: false,\n ownership: 'own-live',\n};\n\n/** the \"lane deleted remotely, branch still there\" shape — the only path that reads `ownership` */\nconst laneGone = (ownership: LaneOwnershipEvidence) => ({ laneHead: undefined, ownership });\n\n/**\n * The decision table. `action` (exact) is required on every row that decides a branch deletion: under\n * the defaults an ordinary developer branch reaches the planner in the same shape as a deleted lane's\n * branch, so `deleteBranch` and the keep reason are part of the contract, not incidental output.\n */\nconst table: Array<{\n name: string;\n input: Partial<LaneSyncInput>;\n type: LaneSyncAction['type'];\n action?: LaneSyncAction;\n reason?: string;\n}> = [\n { name: 'converged -> noop', input: {}, type: 'noop', reason: 'converged' },\n {\n name: 'conflict label present -> noop until resolved',\n input: { laneHead: 'L2', conflictLabelPresent: true },\n type: 'noop',\n },\n { name: 'lane gone + no branch -> noop', input: { laneHead: undefined, branchExists: false }, type: 'noop' },\n\n {\n name: \"own-live, no dev commits, OUR tip (the branch is exactly the lane's mirror) -> close-pr AND delete\",\n input: laneGone('own-live'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n // A developer who commits their own `.bitmap` write looks own-live with nothing above the state\n // commit; the marker is what separates our mirror from their branch.\n name: 'own-live, no dev commits, a tip WE DID NOT WRITE -> close-pr but KEEP the branch',\n input: { ...laneGone('own-live'), tipIsSyncCommit: false },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'tip-not-a-sync-commit' },\n },\n {\n name: 'own-live WITH dev commits (never-exported work above the state commit) -> KEEP the branch',\n input: { ...laneGone('own-live'), hasDevCommits: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n // \"tip-not-a-sync-commit\" would send the reader after the wrong thing when there is real work.\n name: 'own-live with dev commits AND a foreign tip -> keeps, naming the commits as the reason',\n input: { ...laneGone('own-live'), hasDevCommits: true, tipIsSyncCommit: false },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n name: 'own-merged (branch tip already in the default branch) -> close-pr AND delete',\n input: laneGone('own-merged'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n // Reachability alone proves deleting loses nothing, so no marker conjunction applies here.\n name: 'own-merged deletes even with a foreign tip and dev commits',\n input: { ...laneGone('own-merged'), tipIsSyncCommit: false, hasDevCommits: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n name: 'own-superseded (PR merged, then more commits pushed) -> close-pr but KEEP the branch',\n input: laneGone('own-superseded'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n name: 'inherited-or-none -> noop, and never close-pr',\n input: laneGone('inherited-or-none'),\n type: 'noop',\n reason: 'maps to no lane and has no sync history',\n },\n {\n name: 'inherited-or-none with the branch carrying its own dev commits -> still noop',\n input: { ...laneGone('inherited-or-none'), lastSyncedHead: undefined, hasDevCommits: true },\n type: 'noop',\n },\n\n // The adopt paths need no claim: nothing on them can delete a branch.\n {\n name: 'no branch yet -> import-lane',\n input: { branchExists: false, lastSyncedHead: undefined, ownership: 'inherited-or-none' },\n type: 'import-lane',\n },\n { name: 'lane moved, branch untouched -> import-lane', input: { laneHead: 'L2' }, type: 'import-lane' },\n { name: 'dev commits, lane unchanged -> export-branch', input: { hasDevCommits: true }, type: 'export-branch' },\n { name: 'both moved -> merge-diverged', input: { laneHead: 'L2', hasDevCommits: true }, type: 'merge-diverged' },\n {\n name: 'existing branch, never synced, no dev commits, lane EXISTS -> import-lane (adopt branch)',\n input: { lastSyncedHead: undefined, ownership: 'inherited-or-none' },\n type: 'import-lane',\n },\n {\n name: 'existing branch, never synced, has dev commits, lane EXISTS -> halt (ambiguous)',\n input: { lastSyncedHead: undefined, ownership: 'inherited-or-none', hasDevCommits: true },\n type: 'halt',\n },\n];\n\ndescribe('planLaneSync', () => {\n table.forEach(({ name, input, type, action, reason }) => {\n it(name, () => {\n const result = planLaneSync({ ...base, ...input });\n expect(result.type, name).to.equal(type);\n if (action) expect(result).to.deep.equal(action);\n if (reason) expect((result as { reason?: string }).reason).to.contain(reason);\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Sync state is derived from the `.bitmap` committed on a branch — the lane pointer (`_bit_lane`) and the
3
+ * per-component versions — never from commit messages, which are forgeable and rewritten by squash/rebase.
4
+ */
5
+ /** What a branch's committed `.bitmap` says about the branch's bit state. */
6
+ export type BranchBitmapState = {
7
+ /**
8
+ * The scope-qualified lane id `.bitmap` points at (`<scope>/<name>`), or undefined when there is no
9
+ * usable pointer — none at all (branch on main), or one bit marked not exported.
10
+ */
11
+ laneIdStr?: string;
12
+ /** `<scope>/<name>` -> version (a snap hash, or a semver tag on main), exactly as `.bitmap` records it. */
13
+ versions: Record<string, string>;
14
+ };
15
+ /**
16
+ * Parse a `.bitmap` file's content — no workspace, no filesystem. Fail-safe: an unreadable file returns
17
+ * undefined, and a pointer that parses but cannot attribute (unscoped id, `exported: false`) withholds
18
+ * `laneIdStr`; either way no lane id licenses nothing, so a parse failure can never authorize a deletion.
19
+ * An unexported pointer is ignored because its lane never existed on any remote and treating it as a
20
+ * mirror would retire the branch.
21
+ */
22
+ export declare function parseBranchBitmap(content: string | undefined, defaultScope: string): BranchBitmapState | undefined;
23
+ /**
24
+ * Fingerprint of `<component-id>@<version>` pairs: sorted (so listing/key order cannot perturb it), then
25
+ * sha1'd (the value doubles as a single-token `Bit-Lane-Head` trailer).
26
+ */
27
+ export declare function fingerprintIdVersions(idAtVersion: string[]): string;
28
+ /**
29
+ * Placeholder for a lane component the branch's `.bitmap` lacks. It must participate in the fingerprint,
30
+ * or "the lane grew a component" would fingerprint identically to "converged".
31
+ */
32
+ export declare const ABSENT_ON_BRANCH = "<absent>";
33
+ /**
34
+ * What the branch reflects, comparable to the lane's own fingerprint. Only the lane's components count:
35
+ * the `.bitmap` also carries non-lane components at their main versions, and including those would make
36
+ * an untouched pair read as diverged after any unrelated release.
37
+ */
38
+ export declare function branchStateFingerprint(state: BranchBitmapState, laneComponentIds: string[]): string;
39
+ export declare const LANE_HEAD_TRAILER = "Bit-Lane-Head";
40
+ /**
41
+ * Marks a commit as machine-generated. Duplicated in the `bit-git-sync` action repo's event router
42
+ * (the loop guard) — changing one copy without the other makes the reconciler re-trigger itself.
43
+ */
44
+ export declare const SYNC_COMMIT_MARKER = "[bit-sync]";
45
+ export declare const CONFLICT_LABEL = "bit-sync-conflict";
46
+ /**
47
+ * Permissive substring probe for "looks machine-generated" — the loop guard. Must NOT decide anything
48
+ * irreversible; see `isSyncAuthoredMessage`.
49
+ */
50
+ export declare function hasSyncMarker(message: string): boolean;
51
+ /**
52
+ * Strict probe for "we wrote this commit": the marker alone on its own line. This is an input to branch
53
+ * deletion, so a developer merely quoting the marker must never count as authorship. `\r?` tolerates CRLF;
54
+ * a recognition failure errs toward keeping the branch.
55
+ */
56
+ export declare function isSyncAuthoredMessage(message: string): boolean;
57
+ /**
58
+ * The sync commit's message. Every part is an annotation (audit trail + loop-guard marker); nothing reads
59
+ * it as state — messages are forgeable and rewritten on squash-merge. State comes from `.bitmap`.
60
+ */
61
+ export declare function buildSyncCommitMessage(laneIdStr: string, laneHead: string): string;
62
+ export type BranchSyncState = {
63
+ /**
64
+ * The newest first-parent commit that changed `.bitmap` — the reachability anchor for branch ownership
65
+ * and the baseline for `hasDevCommits`. Undefined when `.bitmap` was never written on the branch.
66
+ */
67
+ stateCommit?: string;
68
+ /**
69
+ * The branch tip's `.bitmap`, parsed; undefined when absent/unparseable (treated as "not ours").
70
+ * Reading at the tip equals reading at `stateCommit` — nothing after it changed the file.
71
+ */
72
+ bitmap?: BranchBitmapState;
73
+ /** Whether the branch carries commits on top of its bit state — work bit has not seen. */
74
+ hasDevCommits: boolean;
75
+ /** The branch tip's full commit message (subject + body), for the sync-marker loop-guard probe. */
76
+ tipMessage: string;
77
+ /**
78
+ * The tip sha every field above was read from. The retirement path re-reads the branch and refuses to
79
+ * delete anything else — the evidence licensed deleting THIS commit, not a later one.
80
+ */
81
+ tipSha?: string;
82
+ };
83
+ /**
84
+ * Whether a `rev-list --count` output reports commits. An unreadable count answers `true`: `false` is an
85
+ * input to branch retirement, so not knowing must withhold a deletion rather than license one. Empty
86
+ * output is reachable — simple-git's `raw` resolves with it on some non-zero exits.
87
+ */
88
+ export declare function parseDevCommitCount(raw: string): boolean;
89
+ /**
90
+ * Read a remote branch's sync state from its committed `.bitmap`. Assumes `git fetch origin` already ran.
91
+ * `--first-parent` is a correctness requirement: `git log` orders across all parents, so a `.bitmap`
92
+ * change that arrived through a merge would otherwise be picked as this branch's own state commit.
93
+ */
94
+ export declare function readBranchSyncState(branch: string, defaultBranch: string, defaultScope: string): Promise<BranchSyncState>;