@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.4

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 (167) hide show
  1. package/bin/doflow.js +2 -2
  2. package/core/.claude-plugin/plugin.json +1 -1
  3. package/core/.codex-plugin/plugin.json +1 -1
  4. package/core/.plugin/plugin.json +1 -1
  5. package/core/registry/storage.json +87 -83
  6. package/core/registry/verification.json +1 -1
  7. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
  8. package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
  9. package/package.json +4 -2
  10. package/src/adapters/antigravity/{index.ts → index.js} +60 -60
  11. package/src/adapters/claude/{index.ts → index.js} +48 -48
  12. package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
  13. package/src/adapters/codex/{agents.ts → agents.js} +24 -24
  14. package/src/adapters/codex/{config.ts → config.js} +49 -49
  15. package/src/adapters/codex/{hooks.ts → hooks.js} +23 -23
  16. package/src/adapters/codex/{index.ts → index.js} +98 -98
  17. package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
  18. package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
  19. package/src/adapters/copilot/{index.ts → index.js} +42 -42
  20. package/src/adapters/{copy-tree.ts → copy-tree.js} +70 -70
  21. package/src/adapters/gemini/{hooks.ts → hooks.js} +24 -24
  22. package/src/adapters/gemini/{index.ts → index.js} +41 -41
  23. package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
  24. package/src/adapters/{index.ts → index.js} +13 -13
  25. package/src/adapters/kiro/{index.ts → index.js} +34 -34
  26. package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
  27. package/src/adapters/opencode/{index.ts → index.js} +52 -52
  28. package/src/adapters/pi/{index.ts → index.js} +30 -30
  29. package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
  30. package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
  31. package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
  32. package/src/cli/commands/{install.ts → install.js} +24 -24
  33. package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
  34. package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
  35. package/src/cli/commands/{remove.ts → remove.js} +10 -10
  36. package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
  37. package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
  38. package/src/cli/commands/{status.ts → status.js} +24 -24
  39. package/src/cli/commands/{tools.ts → tools.js} +16 -16
  40. package/src/cli/commands/{update.ts → update.js} +19 -19
  41. package/src/cli/{index.ts → index.js} +26 -26
  42. package/src/cli/{runtime-commands.ts → runtime-commands.js} +48 -48
  43. package/src/cli/{shared.ts → shared.js} +40 -40
  44. package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
  45. package/src/helper/{git.ts → git.js} +2 -2
  46. package/src/helper/{guardrails.ts → guardrails.js} +10 -10
  47. package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
  48. package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
  49. package/src/helper/{prompt.ts → prompt.js} +5 -5
  50. package/src/helper/{repo-root.ts → repo-root.js} +2 -2
  51. package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
  52. package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
  53. package/src/helper/{toml.ts → toml.js} +48 -48
  54. package/src/install/{backup-set.ts → backup-set.js} +16 -16
  55. package/src/install/{backup.ts → backup.js} +65 -65
  56. package/src/install/{context.ts → context.js} +15 -15
  57. package/src/install/{manifest.ts → manifest.js} +27 -27
  58. package/src/install/{mcp.ts → mcp.js} +30 -30
  59. package/src/install/{paths.ts → paths.js} +15 -15
  60. package/src/install/{targets.ts → targets.js} +10 -10
  61. package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
  62. package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
  63. package/src/lifecycle/{index.ts → index.js} +140 -140
  64. package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
  65. package/src/lifecycle/{policies.ts → policies.js} +11 -11
  66. package/src/lifecycle/{view.ts → view.js} +28 -28
  67. package/src/registry/{index.ts → index.js} +60 -60
  68. package/src/release/build-package.js +91 -0
  69. package/src/release/changelog-section.js +70 -0
  70. package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
  71. package/src/runtime/advice/{cli.ts → cli.js} +31 -31
  72. package/src/runtime/advice/{client.ts → client.js} +44 -44
  73. package/src/runtime/advice/{config.ts → config.js} +16 -16
  74. package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
  75. package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
  76. package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
  77. package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
  78. package/src/runtime/advice/{record.ts → record.js} +19 -19
  79. package/src/runtime/advice/{registry.ts → registry.js} +16 -16
  80. package/src/runtime/advice/{strength.ts → strength.js} +7 -7
  81. package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
  82. package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
  83. package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
  84. package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
  85. package/src/runtime/{claims.ts → claims.js} +30 -30
  86. package/src/runtime/{cli-result.ts → cli-result.js} +4 -4
  87. package/src/runtime/{cli.ts → cli.js} +37 -37
  88. package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
  89. package/src/runtime/{context-pack.ts → context-pack.js} +20 -20
  90. package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
  91. package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +25 -25
  92. package/src/runtime/failure/{capture.ts → capture.js} +29 -29
  93. package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
  94. package/src/runtime/failure/{cli.ts → cli.js} +30 -30
  95. package/src/runtime/failure/{home.ts → home.js} +6 -6
  96. package/src/runtime/failure/{store.ts → store.js} +32 -32
  97. package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
  98. package/src/runtime/{freshness.ts → freshness.js} +15 -15
  99. package/src/runtime/{health.ts → health.js} +72 -72
  100. package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
  101. package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
  102. package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
  103. package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
  104. package/src/runtime/inventory/{index.ts → index.js} +36 -36
  105. package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
  106. package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
  107. package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
  108. package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
  109. package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
  110. package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
  111. package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
  112. package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
  113. package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
  114. package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
  115. package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
  116. package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
  117. package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
  118. package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
  119. package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
  120. package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
  121. package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
  122. package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
  123. package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
  124. package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
  125. package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
  126. package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
  127. package/src/runtime/{mask.ts → mask.js} +22 -22
  128. package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
  129. package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
  130. package/src/runtime/{model-router.ts → model-router.js} +20 -20
  131. package/src/runtime/{outcome.ts → outcome.js} +27 -27
  132. package/src/runtime/{reach.ts → reach.js} +21 -21
  133. package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
  134. package/src/runtime/{readiness.ts → readiness.js} +35 -35
  135. package/src/runtime/{recovery.ts → recovery.js} +15 -15
  136. package/src/runtime/{research-request.ts → research-request.js} +34 -34
  137. package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +40 -40
  138. package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +19 -19
  139. package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
  140. package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
  141. package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
  142. package/src/runtime/storage/{cli.ts → cli.js} +9 -9
  143. package/src/runtime/storage/{engine.ts → engine.js} +37 -37
  144. package/src/runtime/storage/{registry.ts → registry.js} +19 -19
  145. package/src/runtime/storage/{roots.ts → roots.js} +8 -8
  146. package/src/runtime/storage/{rules.ts → rules.js} +26 -26
  147. package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
  148. package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
  149. package/src/runtime/{task-attempts.ts → task-attempts.js} +61 -61
  150. package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
  151. package/src/runtime/{task-scope.ts → task-scope.js} +13 -13
  152. package/src/runtime/{task-state.ts → task-state.js} +15 -15
  153. package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
  154. package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
  155. package/src/runtime/trace/{render.ts → render.js} +7 -7
  156. package/src/runtime/trace/{views.ts → views.js} +40 -40
  157. package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
  158. package/src/runtime/verification/{engine.ts → engine.js} +143 -143
  159. package/src/runtime/verification/{registry.ts → registry.js} +6 -6
  160. package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
  161. package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
  162. package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
  163. package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
  164. package/src/runtime/{worktree.ts → worktree.js} +31 -31
  165. package/src/state/{index.ts → index.js} +26 -26
  166. package/src/state/{lockfile.ts → lockfile.js} +20 -20
  167. package/src/state/{run-lock.ts → run-lock.js} +44 -44
@@ -35,26 +35,26 @@
35
35
 
36
36
  const nodeFs = require('node:fs');
37
37
  const { spawnSync } = require('node:child_process');
38
- const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.ts');
39
- const { appendEvents, readFold } = require('./event-store.ts');
40
- const { deriveStatuses, readGitFacts, behindNote, subjectNames, readMerges, readBranchTips, readHistory } = require('./status.ts');
41
- const { FollowupUsageError, oneLine, channelBy } = require('./followup.ts');
38
+ const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.js');
39
+ const { appendEvents, readFold } = require('./event-store.js');
40
+ const { deriveStatuses, readGitFacts, behindNote, subjectNames, readMerges, readBranchTips, readHistory } = require('./status.js');
41
+ const { FollowupUsageError, oneLine, channelBy } = require('./followup.js');
42
42
 
43
43
  /** The release-tag pattern of IC-020, the one `git-state` filters by. */
44
44
  const RELEASE_TAG = /^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$/;
45
45
 
46
- const refusal = (action: any, finding: any, message: any) => ({ ok: false, action, finding, message });
46
+ const refusal = (action , finding , message ) => ({ ok: false, action, finding, message });
47
47
 
48
48
  /** Trimmed stdout of a git call, or null when git exits non-zero. */
49
- function gitOut(root: any, args: any) {
49
+ function gitOut(root , args ) {
50
50
  const run = spawnSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
51
51
  return run.status === 0 ? run.stdout.trim() : null;
52
52
  }
53
53
 
54
- function commitOf(root: any, ref: any) { return gitOut(root, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]) || null; }
54
+ function commitOf(root , ref ) { return gitOut(root, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]) || null; }
55
55
 
56
56
  /** `a` is an ancestor of (or equal to) `b`. */
57
- function isAncestor(root: any, a: any, b: any) {
57
+ function isAncestor(root , a , b ) {
58
58
  return spawnSync('git', ['merge-base', '--is-ancestor', a, b], { cwd: root, stdio: 'ignore' }).status === 0;
59
59
  }
60
60
 
@@ -63,14 +63,14 @@ function isAncestor(root: any, a: any, b: any) {
63
63
  * since tracking, whose subject names the slug or whose non-first parent is an ancestor of the branch tip.
64
64
  * `merges` are the pinned ref's first-parent merges inside X, newest first; `history` holds X and the tip.
65
65
  */
66
- function earlierMerge({ slug, evidence, tip, lowerBound, merges, bound, history }: any) {
66
+ function earlierMerge({ slug, evidence, tip, lowerBound, merges, bound, history } ) {
67
67
  if (tip && history.isAncestor(tip, bound)) return { ...evidence };
68
68
  for (const merge of merges) {
69
69
  if (merge.ct < lowerBound) continue;
70
70
  // The merged side is this branch's own when it is an ancestor of the tip and the merge itself is not: a
71
71
  // branch cut from the integration ref after that merge has the merge in its history and did not make it.
72
72
  // A parent outside the first parent's history is one this merge introduced.
73
- const own = (parent: any) => history.introducer(parent) === merge.sha && history.isAncestor(parent, tip);
73
+ const own = (parent ) => history.introducer(parent) === merge.sha && history.isAncestor(parent, tip);
74
74
  if (tip && merge.parents.slice(1).some(own) && !history.isAncestor(merge.sha, tip)) return { ...evidence };
75
75
  if (subjectNames(merge.subject, slug)) return { kind: 'merge-subject', ref: merge.sha.slice(0, 7) };
76
76
  }
@@ -83,14 +83,14 @@ function earlierMerge({ slug, evidence, tip, lowerBound, merges, bound, history
83
83
  * other than `tag` merged into X (git's version order). A `v*` tag that is not a release (`vnext`)
84
84
  * never qualifies.
85
85
  */
86
- function previousTagOf(root: any, { tag, facts, bound }: any) {
86
+ function previousTagOf(root , { tag, facts, bound } ) {
87
87
  const known = new Set(facts.release_tags);
88
88
  if (facts.base_tag && facts.base_tag !== tag && known.has(facts.base_tag) && isAncestor(root, `refs/tags/${facts.base_tag}`, bound)) return facts.base_tag;
89
89
  const merged = gitOut(root, ['tag', '--list', 'v*', '--sort=-v:refname', '--merged', bound]);
90
- return (merged || '').split('\n').find((name: any) => name && name !== tag && known.has(name)) || null;
90
+ return (merged || '').split('\n').find((name ) => name && name !== tag && known.has(name)) || null;
91
91
  }
92
92
 
93
- function slugList(raw: any, label: any) {
93
+ function slugList(raw , label ) {
94
94
  const list = [...new Set((Array.isArray(raw) ? raw : raw === undefined || raw === null ? [] : [raw]).map(String))];
95
95
  for (const slug of list) if (!isSafeSlug(slug)) throw new FollowupUsageError(`${label}: ${invalidSlugRefusal(slug).message}`);
96
96
  return list;
@@ -107,7 +107,7 @@ function slugList(raw: any, label: any) {
107
107
  * @param {string} [options.channel]
108
108
  * @param {Object} [options.facts] `readGitFacts` output, for a caller that already has it
109
109
  */
110
- function releaseFeatures({ root, tag, confirm = false, features, exclude, channel, now = new Date(), fsImpl = nodeFs, facts: givenFacts }: any) {
110
+ function releaseFeatures({ root, tag, confirm = false, features, exclude, channel, now = new Date(), fsImpl = nodeFs, facts: givenFacts } ) {
111
111
  if (typeof tag !== 'string' || !RELEASE_TAG.test(tag)) throw new FollowupUsageError(`--tag is required and must look like v1.2.3 or v1.2.3-rc.1 (got '${tag ?? ''}')`);
112
112
  const added = slugList(features, '--feature');
113
113
  const excluded = slugList(exclude, '--exclude');
@@ -118,7 +118,7 @@ function releaseFeatures({ root, tag, confirm = false, features, exclude, channe
118
118
  if (!facts.integration_ref) return refusal('release', 'no-integration-ref', 'no integration ref resolves (develop, main, master, origin/HEAD), so no release can be previewed or recorded. Nothing was written.');
119
119
 
120
120
  const fold = readFold(root, { fsImpl, now });
121
- const trackedSlugs = new Set(fold.features.map((f: any) => f.slug));
121
+ const trackedSlugs = new Set(fold.features.map((f ) => f.slug));
122
122
  // An exclusion is permanent for its tag, so a mistyped slug must not be recorded any more than a mistyped addition.
123
123
  const untracked = [...new Set([...added, ...excluded])].filter((slug) => !trackedSlugs.has(slug));
124
124
  if (untracked.length) {
@@ -126,7 +126,7 @@ function releaseFeatures({ root, tag, confirm = false, features, exclude, channe
126
126
  }
127
127
 
128
128
  // DEC-030: containment in the tag being recorded never finishes a feature before its record exists.
129
- const otherTags = { ...facts, release_tags: facts.release_tags.filter((name: any) => name !== tag) };
129
+ const otherTags = { ...facts, release_tags: facts.release_tags.filter((name ) => name !== tag) };
130
130
  const derived = deriveStatuses({ root, fold, facts: otherTags });
131
131
  if (derived.releaseMode === 'unknown' || !derived.integrationSha) return refusal('release', 'no-integration-ref', `${derived.reason || `${facts.integration_ref} does not resolve to a commit`}. Nothing was written.`);
132
132
 
@@ -137,23 +137,23 @@ function releaseFeatures({ root, tag, confirm = false, features, exclude, channe
137
137
  // A hotfix merged only into the tag's own history has no evidence on the integration chain: derive against the tag as well.
138
138
  const atTag = tagCommit && tagCommit !== derived.integrationSha ? deriveStatuses({ root, fold, facts: { ...otherTags, integration_ref: tagCommit } }) : null;
139
139
 
140
- const recorded = new Set(fold.releases.flatMap((release: any) => release.features.map((f: any) => f.slug)));
140
+ const recorded = new Set(fold.releases.flatMap((release ) => release.features.map((f ) => f.slug)));
141
141
  const tagged = derived.releaseMode === 'tagged';
142
142
  // While X is the pinned commit, every evidence commit is in its history. Once X is a tag elsewhere, one
143
143
  // graph read of the pinned ref, X and the evidence branch tips answers every ancestry question about X.
144
- let read: any = null;
144
+ let read = null;
145
145
  const readOfX = () => {
146
146
  if (read) return read;
147
- const branchRefs = new Set(fold.features.map((f: any) => derived.statuses[f.slug].evidence).filter((e: any) => e && e.kind === 'branch').map((e: any) => e.ref));
147
+ const branchRefs = new Set(fold.features.map((f ) => derived.statuses[f.slug].evidence).filter((e ) => e && e.kind === 'branch').map((e ) => e.ref));
148
148
  // Local branches are listed first, so a ref resolves as refs/heads/<ref> before refs/remotes/<ref>.
149
149
  const tips = new Map();
150
150
  for (const t of readBranchTips(root)) if (branchRefs.has(t.short) && !tips.has(t.short)) tips.set(t.short, t.sha);
151
151
  const history = readHistory(root, derived.integrationSha, [bound, ...tips.values()]);
152
- const merges = readMerges(root, derived.integrationSha).filter((m: any) => history.isAncestor(m.sha, bound)).reverse();
152
+ const merges = readMerges(root, derived.integrationSha).filter((m ) => history.isAncestor(m.sha, bound)).reverse();
153
153
  read = { history, merges, tips };
154
154
  return read;
155
155
  };
156
- const inX = (commit: any) => bound === derived.integrationSha || readOfX().history.isAncestor(commit, bound);
156
+ const inX = (commit ) => bound === derived.integrationSha || readOfX().history.isAncestor(commit, bound);
157
157
  const candidates = [];
158
158
  const notDetected = [];
159
159
  // A git failure while reading X's history is an environment fault: a refusal, never a throw that would
@@ -181,7 +181,7 @@ function releaseFeatures({ root, tag, confirm = false, features, exclude, channe
181
181
  }
182
182
  if (found) candidates.push({ slug, evidence: found.kind, ref: found.ref }); else notDetected.push(slug);
183
183
  }
184
- } catch (error: any) {
184
+ } catch (error ) {
185
185
  return refusal('release', 'no-integration-ref', `git could not answer while reading the history of ${tagCommit ? tag : facts.integration_ref} (git-state-failed): ${String(error.message).split('\n')[0]}. Nothing was written.`);
186
186
  }
187
187
 
@@ -189,9 +189,9 @@ function releaseFeatures({ root, tag, confirm = false, features, exclude, channe
189
189
  for (const slug of added) if (!shipped.some((c) => c.slug === slug)) shipped.push({ slug, evidence: 'confirmed', ref: null });
190
190
  const finishing = shipped.filter((c) => !excluded.includes(c.slug));
191
191
  const finishingSlugs = new Set(finishing.map((c) => c.slug));
192
- const followupsDone = fold.followups.filter((item: any) => item.state === 'taken' && finishingSlugs.has(item.takenBy)).map((item: any) => item.id);
192
+ const followupsDone = fold.followups.filter((item ) => item.state === 'taken' && finishingSlugs.has(item.takenBy)).map((item ) => item.id);
193
193
 
194
- const base: any = { ok: true, action: 'release', tag, previousTag, bound: tagCommit ? tag : facts.integration_ref, integrationRef: facts.integration_ref };
194
+ const base = { ok: true, action: 'release', tag, previousTag, bound: tagCommit ? tag : facts.integration_ref, integrationRef: facts.integration_ref };
195
195
  if (derived.integrationBehind > 0) base.note = behindNote(facts.integration_ref, derived.integrationBehind);
196
196
 
197
197
  if (!confirm) {
@@ -206,7 +206,7 @@ function releaseFeatures({ root, tag, confirm = false, features, exclude, channe
206
206
  if (!tagCommit) return refusal('release', 'tag-missing', `${tag} does not exist; create the tag first, then confirm. Nothing was written.`);
207
207
  const out = appendEvents(root, [{ type: 'release.recorded', by, data: { tag, commit: tagCommit, features: finishing, excluded } }], { now, fsImpl });
208
208
  if (!out.ok) return refusal('release', out.finding, out.message);
209
- return { ...base, recorded: true, candidates, added, excluded, notDetected, followupsDone, events: out.written.map((w: any) => w.file), next: [] };
209
+ return { ...base, recorded: true, candidates, added, excluded, notDetected, followupsDone, events: out.written.map((w ) => w.file), next: [] };
210
210
  }
211
211
 
212
212
  /**
@@ -214,17 +214,17 @@ function releaseFeatures({ root, tag, confirm = false, features, exclude, channe
214
214
  * cherry-pick). It records an observed fact after the merge, one `feature.merged` event, and the
215
215
  * status stays derived from events on read, so nothing is written ahead of the fact (DEC-022).
216
216
  */
217
- function recordMerged({ root, slug, reason, channel, now = new Date(), fsImpl = nodeFs }: any) {
217
+ function recordMerged({ root, slug, reason, channel, now = new Date(), fsImpl = nodeFs } ) {
218
218
  if (!slug) throw new FollowupUsageError('--slug is required for --action merged');
219
219
  if (!isSafeSlug(slug)) throw new FollowupUsageError(invalidSlugRefusal(slug).message);
220
- const problems: any[] = [];
220
+ const problems = [];
221
221
  const cleanReason = reason === undefined ? null : oneLine(reason, '--reason', problems);
222
222
  if (!problems.length && !cleanReason) problems.push('--reason is required for --action merged: how the merge happened, for example "squash-merged by hand"');
223
223
  if (problems.length) throw new FollowupUsageError(problems.join('; '));
224
224
  const by = channelBy(channel);
225
225
 
226
226
  const fold = readFold(root, { fsImpl, now });
227
- if (!fold.features.some((f: any) => f.slug === slug)) {
227
+ if (!fold.features.some((f ) => f.slug === slug)) {
228
228
  return refusal('merged', 'untracked-feature', `${slug} is not a tracked feature, so no merge can be confirmed for it; run doflow-run lifecycle --action init --slug ${slug} first. Nothing was written.`);
229
229
  }
230
230
  const before = deriveStatuses({ root, fold }).statuses[slug];
@@ -235,7 +235,7 @@ function recordMerged({ root, slug, reason, channel, now = new Date(), fsImpl =
235
235
  const out = appendEvents(root, [{ type: 'feature.merged', by, data: { slug, reason: cleanReason } }], { now, fsImpl });
236
236
  if (!out.ok) return refusal('merged', out.finding, out.message);
237
237
  const after = deriveStatuses({ root, fold: readFold(root, { fsImpl, now }) }).statuses[slug];
238
- return { ok: true, action: 'merged', slug, status: after.status, evidence: after.evidence, release: after.release, events: out.written.map((w: any) => w.file), next: [] };
238
+ return { ok: true, action: 'merged', slug, status: after.status, evidence: after.evidence, release: after.release, events: out.written.map((w ) => w.file), next: [] };
239
239
  }
240
240
 
241
241
  module.exports = { releaseFeatures, recordMerged, RELEASE_TAG };
@@ -19,7 +19,7 @@
19
19
  const nodeFs = require('node:fs');
20
20
  const path = require('node:path');
21
21
  const crypto = require('node:crypto');
22
- const mask = require('../mask.ts');
22
+ const mask = require('../mask.js');
23
23
 
24
24
  /** A `--file` or `--stdin` source is read for at most this many bytes; the rest could not be kept anyway. */
25
25
  const MAX_READ_BYTES = 16 * 1024 * 1024;
@@ -44,32 +44,32 @@ class ReportInputError extends Error {}
44
44
  * repeated here rather than required from the failure module, so the follow-up service still loads in
45
45
  * a build or an install where the failure modules cannot be loaded (IC-016).
46
46
  */
47
- function reportsHome(env: Record<string, string | undefined> = process.env): string | null {
47
+ function reportsHome(env = process.env) {
48
48
  const xdg = env.XDG_CONFIG_HOME;
49
49
  if (typeof xdg === 'string' && xdg !== '') return path.isAbsolute(xdg) ? path.join(xdg, 'doflow', 'reports') : null;
50
50
  const home = env.HOME;
51
51
  return typeof home === 'string' && path.isAbsolute(home) ? path.join(home, '.config', 'doflow', 'reports') : null;
52
52
  }
53
53
 
54
- function realProjectRoot(root: string, fsImpl: any = nodeFs): string {
54
+ function realProjectRoot(root , fsImpl = nodeFs) {
55
55
  try { return fsImpl.realpathSync(root); } catch { return root; }
56
56
  }
57
57
 
58
- function projectKey(root: string, fsImpl: any = nodeFs): string {
58
+ function projectKey(root , fsImpl = nodeFs) {
59
59
  return crypto.createHash('sha256').update(realProjectRoot(root, fsImpl)).digest('hex').slice(0, 12);
60
60
  }
61
61
 
62
62
  /** @returns {string|null} the body file of a report of this project, or null when no home resolves */
63
- function bodyPath(root: string, id: string, { env = process.env, fsImpl = nodeFs }: any = {}): string | null {
63
+ function bodyPath(root , id , { env = process.env, fsImpl = nodeFs } = {}) {
64
64
  const home = reportsHome(env);
65
65
  return home === null ? null : path.join(home, projectKey(root, fsImpl), `${id}.txt`);
66
66
  }
67
67
 
68
68
  /** The `hasBody` the fold needs (IC-003): a report whose body file is on this machine. */
69
- function bodyChecker(root: string, { env = process.env, fsImpl = nodeFs }: any = {}): (item: any) => boolean {
69
+ function bodyChecker(root , { env = process.env, fsImpl = nodeFs } = {}) {
70
70
  const home = reportsHome(env);
71
71
  const folder = home === null ? null : path.join(home, projectKey(root, fsImpl));
72
- return (item: any): boolean => {
72
+ return (item ) => {
73
73
  // The id comes from an event file, so it is checked before it becomes part of a path.
74
74
  if (folder === null || !FU_ID.test(String(item.id)) || item.bodyRef !== `local:${item.id}`) return false;
75
75
  try { return fsImpl.statSync(path.join(folder, `${item.id}.txt`)).isFile(); } catch { return false; }
@@ -77,8 +77,8 @@ function bodyChecker(root: string, { env = process.env, fsImpl = nodeFs }: any =
77
77
  }
78
78
 
79
79
  /** Reads at most `max` bytes from an open descriptor. */
80
- function readCapped(fsImpl: any, fd: number, max: number): Buffer {
81
- const chunks: Buffer[] = [];
80
+ function readCapped(fsImpl , fd , max ) {
81
+ const chunks = [];
82
82
  let total = 0;
83
83
  const chunk = Buffer.allocUnsafe(64 * 1024);
84
84
  while (total < max) {
@@ -96,24 +96,24 @@ function readCapped(fsImpl: any, fd: number, max: number): Buffer {
96
96
  * @param {{file?: string, stdin?: boolean, text?: string}} source
97
97
  * @returns {string}
98
98
  */
99
- function readReportText({ file, stdin: stdinFlag, text }: { file?: string; stdin?: boolean; text?: string }, { fsImpl = nodeFs }: any = {}): string {
99
+ function readReportText({ file, stdin: stdinFlag, text } , { fsImpl = nodeFs } = {}) {
100
100
  // `--file -` is stdin, as everywhere else a `-` stands for it.
101
101
  const stdin = Boolean(stdinFlag) || file === '-';
102
102
  if (file === '-') file = undefined;
103
103
  const given = [file !== undefined, Boolean(stdin), text !== undefined].filter(Boolean).length;
104
104
  if (given !== 1) throw new ReportInputError('give the report body with exactly one of --file <path>, --stdin or --text "<body>" (--file and --stdin also take text that starts with -)');
105
105
  if (text !== undefined) return String(text);
106
- let bytes: Buffer;
106
+ let bytes ;
107
107
  if (stdin) {
108
- try { bytes = readCapped(fsImpl, 0, MAX_READ_BYTES); } catch (error: any) { throw new ReportInputError(`cannot read the report from stdin: ${error.message}`); }
108
+ try { bytes = readCapped(fsImpl, 0, MAX_READ_BYTES); } catch (error ) { throw new ReportInputError(`cannot read the report from stdin: ${error.message}`); }
109
109
  } else {
110
- const target = path.resolve(file as string);
111
- let fd: number | undefined;
110
+ const target = path.resolve(file );
111
+ let fd ;
112
112
  try {
113
113
  if (!fsImpl.statSync(target).isFile()) throw new ReportInputError(`--file ${file} is not a regular file`);
114
114
  fd = fsImpl.openSync(target, 'r');
115
- bytes = readCapped(fsImpl, fd as number, MAX_READ_BYTES);
116
- } catch (error: any) {
115
+ bytes = readCapped(fsImpl, fd , MAX_READ_BYTES);
116
+ } catch (error ) {
117
117
  if (error instanceof ReportInputError) throw error;
118
118
  throw new ReportInputError(`cannot read --file ${file}: ${error.code || error.message}`);
119
119
  } finally {
@@ -125,7 +125,7 @@ function readReportText({ file, stdin: stdinFlag, text }: { file?: string; stdin
125
125
  }
126
126
 
127
127
  /** The length of the longest prefix of `buffer` of at most `limit` bytes that ends on a UTF-8 boundary. */
128
- function boundary(buffer: Buffer, limit: number): number {
128
+ function boundary(buffer , limit ) {
129
129
  let end = Math.min(limit, buffer.length);
130
130
  if (end === buffer.length) return end;
131
131
  while (end > 0 && (buffer[end] & 0xc0) === 0x80) end -= 1;
@@ -139,7 +139,7 @@ function boundary(buffer: Buffer, limit: number): number {
139
139
  * boundary; a token straddling the cut goes with the incomplete tail, never half-masked.
140
140
  * @returns {{text: string, cut: boolean}}
141
141
  */
142
- function boundMaskInput(raw: string): { text: string; cut: boolean } {
142
+ function boundMaskInput(raw ) {
143
143
  const stripped = raw.replace(ANSI, '');
144
144
  const whole = Buffer.from(stripped, 'utf8');
145
145
  if (whole.length <= MASK_INPUT_BYTES) return { text: stripped, cut: false };
@@ -157,9 +157,9 @@ function boundMaskInput(raw: string): { text: string; cut: boolean } {
157
157
  * @param {{home?: string}} [options] the home directory the mask replaces with `~`
158
158
  * @returns {{buffer: Buffer, masked: number, truncated: boolean}}
159
159
  */
160
- function prepareBody(raw: string, { home }: { home?: string } = {}): { buffer: Buffer; masked: number; truncated: boolean } {
160
+ function prepareBody(raw , { home } = {}) {
161
161
  const { text: bounded, cut } = boundMaskInput(raw);
162
- let result: any;
162
+ let result ;
163
163
  try {
164
164
  result = mask.maskBody(bounded, home === undefined ? undefined : { home });
165
165
  } catch {
@@ -178,7 +178,7 @@ function prepareBody(raw: string, { home }: { home?: string } = {}): { buffer: B
178
178
  * @param {Buffer} body
179
179
  * @returns {string}
180
180
  */
181
- function excerptOf(body: Buffer): string {
181
+ function excerptOf(body ) {
182
182
  if (body.length <= EXCERPT_BYTES) return body.toString('utf8').replace(/\s+$/, '');
183
183
  const head = body.subarray(0, boundary(body, EXCERPT_BYTES));
184
184
  const lineEnd = head.lastIndexOf(0x0a);
@@ -189,7 +189,7 @@ function excerptOf(body: Buffer): string {
189
189
  * Writes the body once: exclusive create, file 0600, folders 0700, never inside the repository.
190
190
  * @returns {{ok: true, file: string}|{ok: false, reason: 'no-home'|'exists'|'error', message?: string}}
191
191
  */
192
- function writeBody(root: string, id: string, buffer: Buffer, { env = process.env, fsImpl = nodeFs }: any = {}): { ok: boolean; file?: string; reason?: string; message?: string } {
192
+ function writeBody(root , id , buffer , { env = process.env, fsImpl = nodeFs } = {}) {
193
193
  const file = bodyPath(root, id, { env, fsImpl });
194
194
  if (file === null) return { ok: false, reason: 'no-home' };
195
195
  const folder = path.dirname(file);
@@ -200,18 +200,18 @@ function writeBody(root: string, id: string, buffer: Buffer, { env = process.env
200
200
  fsImpl.chmodSync(path.dirname(folder), 0o700);
201
201
  try {
202
202
  fsImpl.writeFileSync(path.join(folder, '.root'), `${realProjectRoot(root, fsImpl)}\n`, { flag: 'wx', mode: 0o600 });
203
- } catch (error: any) {
203
+ } catch (error ) {
204
204
  if (error.code !== 'EEXIST') throw error;
205
205
  }
206
206
  fsImpl.writeFileSync(file, buffer, { flag: 'wx', mode: 0o600 });
207
207
  return { ok: true, file };
208
- } catch (error: any) {
208
+ } catch (error ) {
209
209
  return error.code === 'EEXIST' ? { ok: false, reason: 'exists' } : { ok: false, reason: 'error', message: error.code || error.message };
210
210
  }
211
211
  }
212
212
 
213
213
  /** Removes a body this process just wrote, when the event that names it could not be written. */
214
- function removeBody(file: string, fsImpl: any = nodeFs): void {
214
+ function removeBody(file , fsImpl = nodeFs) {
215
215
  try { fsImpl.rmSync(file, { force: true }); } catch { /* the failure being reported stands */ }
216
216
  }
217
217
 
@@ -17,15 +17,15 @@
17
17
 
18
18
  const nodeFs = require('node:fs');
19
19
  const { isDeepStrictEqual } = require('node:util');
20
- const { REPO_ROOT } = require('../../helper/repo-root.ts');
21
- const { loadStorageRegistry } = require('../storage/registry.ts');
22
- const { resolveRoots, readPruneSetting } = require('../storage/roots.ts');
23
- const { foldInto, finalize } = require('./fold.ts');
20
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
21
+ const { loadStorageRegistry } = require('../storage/registry.js');
22
+ const { resolveRoots, readPruneSetting } = require('../storage/roots.js');
23
+ const { foldInto, finalize } = require('./fold.js');
24
24
 
25
25
  const HOUR_MS = 3600000;
26
26
 
27
27
  /** @returns {{state: 'off'} | {state: 'on', hours: number, ms: number} | {state: 'invalid', raw: string}} */
28
- function parseWindow(env: Record<string, string | undefined> = process.env): { state: 'off' } | { state: 'on'; hours: number; ms: number } | { state: 'invalid'; raw: string } {
28
+ function parseWindow(env = process.env) {
29
29
  const raw = env.DOFLOW_RETENTION_HOURS;
30
30
  if (raw === undefined || raw === '') return { state: 'off' };
31
31
  if (/^[1-9][0-9]*$/.test(raw) && Number.isSafeInteger(Number(raw))) {
@@ -36,11 +36,11 @@ function parseWindow(env: Record<string, string | undefined> = process.env): { s
36
36
  }
37
37
 
38
38
  /** Composes IC-013's explicit environment override with the declared lifecycle default. */
39
- function retentionWindow({ env = process.env, projectRoot, registry, fsImpl = nodeFs }: any = {}): any {
39
+ function retentionWindow({ env = process.env, projectRoot, registry, fsImpl = nodeFs } = {}) {
40
40
  const parsed = parseWindow(env);
41
41
  if (parsed.state === 'invalid') return parsed;
42
42
  if (parsed.state === 'on') return { ...parsed, source: 'env' };
43
- let activeRegistry: any;
43
+ let activeRegistry ;
44
44
  try {
45
45
  activeRegistry = registry || loadStorageRegistry({ repoRoot: REPO_ROOT, fsImpl });
46
46
  } catch {
@@ -48,23 +48,23 @@ function retentionWindow({ env = process.env, projectRoot, registry, fsImpl = no
48
48
  }
49
49
  const roots = resolveRoots({ registry: activeRegistry, projectRoot, env });
50
50
  if (readPruneSetting({ registry: activeRegistry, roots, rootId: 'project-state', fsImpl }).setting !== 'on') return { state: 'off' };
51
- const lifecycleStore = activeRegistry.stores.find((store: any) => store.id === 'lifecycle-events');
51
+ const lifecycleStore = activeRegistry.stores.find((store ) => store.id === 'lifecycle-events');
52
52
  if (!lifecycleStore) throw new Error('Storage registry has no lifecycle-events store');
53
53
  const hours = lifecycleStore.bound.defaultHours;
54
54
  return { state: 'on', hours, ms: hours * HOUR_MS, source: 'default' };
55
55
  }
56
56
 
57
- function isText(value: any): boolean { return typeof value === 'string' && value.trim() !== ''; }
57
+ function isText(value ) { return typeof value === 'string' && value.trim() !== ''; }
58
58
 
59
59
  /** The item keys an event names, or none when it is not about a follow-up or a goal. */
60
- function itemsOf(event: any): string[] {
60
+ function itemsOf(event ) {
61
61
  const d = event.data;
62
- const ids = (list: any): string[] => (Array.isArray(list) && list.length > 0 && list.every(isText) ? list : []);
62
+ const ids = (list ) => (Array.isArray(list) && list.length > 0 && list.every(isText) ? list : []);
63
63
  switch (event.type) {
64
64
  case 'followup.added': return isText(d.id) ? [`followup:${d.id}`] : [];
65
65
  case 'followup.taken':
66
- case 'followup.promoted': return ids(d.ids).map((id: string) => `followup:${id}`);
67
- case 'followup.settled': return (isText(d.id) ? [d.id] : ids(d.ids)).map((id: string) => `followup:${id}`);
66
+ case 'followup.promoted': return ids(d.ids).map((id ) => `followup:${id}`);
67
+ case 'followup.settled': return (isText(d.id) ? [d.id] : ids(d.ids)).map((id ) => `followup:${id}`);
68
68
  case 'goal.added':
69
69
  case 'goal.item-added':
70
70
  case 'goal.checked':
@@ -75,17 +75,17 @@ function itemsOf(event: any): string[] {
75
75
  }
76
76
 
77
77
  /** Groups the items into units: items named together by one event share a unit. */
78
- function unitsOf(events: any[], shouldStop?: () => boolean): any[] | null {
79
- const parent = new Map<string, string>();
80
- const find = (key: string): string | null => {
78
+ function unitsOf(events , shouldStop ) {
79
+ const parent = new Map ();
80
+ const find = (key ) => {
81
81
  while (parent.get(key) !== key) {
82
82
  if (shouldStop?.()) return null;
83
- parent.set(key, parent.get(parent.get(key) as string) as string);
84
- key = parent.get(key) as string;
83
+ parent.set(key, parent.get(parent.get(key) ) );
84
+ key = parent.get(key) ;
85
85
  }
86
86
  return key;
87
87
  };
88
- const named: Array<{ event: any; items: string[] }> = [];
88
+ const named = [];
89
89
  for (const event of events) {
90
90
  if (shouldStop?.()) return null;
91
91
  named.push({ event, items: [...new Set(itemsOf(event))] });
@@ -99,7 +99,7 @@ function unitsOf(events: any[], shouldStop?: () => boolean): any[] | null {
99
99
  parent.set(root, head);
100
100
  }
101
101
  }
102
- const units = new Map<string, any>();
102
+ const units = new Map ();
103
103
  for (const { event, items } of named) {
104
104
  if (shouldStop?.()) return null;
105
105
  if (items.length === 0) continue;
@@ -113,7 +113,7 @@ function unitsOf(events: any[], shouldStop?: () => boolean): any[] | null {
113
113
  return [...units.values()];
114
114
  }
115
115
 
116
- function isSettled(state: any, key: string): boolean {
116
+ function isSettled(state , key ) {
117
117
  const [kind, id] = [key.slice(0, key.indexOf(':')), key.slice(key.indexOf(':') + 1)];
118
118
  if (kind === 'followup') {
119
119
  const item = state.items.get(id);
@@ -124,15 +124,15 @@ function isSettled(state: any, key: string): boolean {
124
124
  }
125
125
 
126
126
  /** The parts of a fold that removing a unit must leave unchanged. */
127
- function comparable(result: any): any {
127
+ function comparable(result ) {
128
128
  const { followups, features, merged, goals, releases, conflicts } = result;
129
129
  return { followups, features, merged, goals, releases, conflicts };
130
130
  }
131
131
 
132
132
  /** Whether dropping `removed` units changes nothing but the removed items themselves. */
133
- function refoldEqual(events: any[], full: any, removed: any[], now: Date, shouldStop?: () => boolean): boolean | null {
133
+ function refoldEqual(events , full , removed , now , shouldStop ) {
134
134
  if (shouldStop?.()) return null;
135
- const gone = new Set<string>(), keys = new Set<string>();
135
+ const gone = new Set (), keys = new Set ();
136
136
  for (const unit of removed) {
137
137
  if (shouldStop?.()) return null;
138
138
  for (const event of unit.events) gone.add(event.id);
@@ -140,11 +140,11 @@ function refoldEqual(events: any[], full: any, removed: any[], now: Date, should
140
140
  }
141
141
  const expected = {
142
142
  ...full,
143
- followups: full.followups.filter((f: any) => !keys.has(`followup:${f.id}`)),
144
- goals: full.goals.filter((g: any) => !keys.has(`goal:${g.goal}`)),
145
- conflicts: full.conflicts.filter((c: any) => !gone.has(c.event)),
143
+ followups: full.followups.filter((f ) => !keys.has(`followup:${f.id}`)),
144
+ goals: full.goals.filter((g ) => !keys.has(`goal:${g.goal}`)),
145
+ conflicts: full.conflicts.filter((c ) => !gone.has(c.event)),
146
146
  };
147
- const remaining: any[] = [];
147
+ const remaining = [];
148
148
  for (const event of events) {
149
149
  if (shouldStop?.()) return null;
150
150
  if (!gone.has(event.id)) remaining.push(event);
@@ -160,23 +160,23 @@ function refoldEqual(events: any[], full: any, removed: any[], now: Date, should
160
160
  * @returns {{files: string[], units: Array<{items: string[], newestAt: string, files: string[]}>, incomplete?: true}}
161
161
  * files: `<id>.json` names, sorted
162
162
  */
163
- function selectExpired(events: any[], { now, windowMs, shouldStop }: any = {}): any {
164
- const incomplete = (): any => ({ files: [], units: [], incomplete: true });
163
+ function selectExpired(events , { now, windowMs, shouldStop } = {}) {
164
+ const incomplete = () => ({ files: [], units: [], incomplete: true });
165
165
  if (shouldStop?.()) return incomplete();
166
166
  const state = foldInto(events, { now, shouldStop });
167
167
  if (state.incomplete || shouldStop?.()) return incomplete();
168
168
  const cutoff = now.getTime() - windowMs;
169
- const served = new Set<string>();
169
+ const served = new Set ();
170
170
  for (const feature of state.tracked.values()) {
171
171
  if (shouldStop?.()) return incomplete();
172
172
  if (feature.goal) served.add(feature.goal);
173
173
  }
174
174
  const named = unitsOf(events, shouldStop);
175
175
  if (!named || shouldStop?.()) return incomplete();
176
- const candidates: any[] = [];
176
+ const candidates = [];
177
177
  for (const unit of named) {
178
178
  if (shouldStop?.()) return incomplete();
179
- const newest = unit.events.reduce((max: string, event: any) => (Date.parse(event.at) > Date.parse(max) ? event.at : max), unit.events[0].at);
179
+ const newest = unit.events.reduce((max , event ) => (Date.parse(event.at) > Date.parse(max) ? event.at : max), unit.events[0].at);
180
180
  if (shouldStop?.()) return incomplete();
181
181
  let eligible = Date.parse(newest) < cutoff;
182
182
  if (eligible) for (const key of unit.items) {
@@ -191,7 +191,7 @@ function selectExpired(events: any[], { now, windowMs, shouldStop }: any = {}):
191
191
  if (allSafe === null || shouldStop?.()) return incomplete();
192
192
  if (!allSafe) {
193
193
  selected = [];
194
- const ordered = [...candidates].sort((a: any, b: any) => Date.parse(a.newestAt) - Date.parse(b.newestAt));
194
+ const ordered = [...candidates].sort((a , b ) => Date.parse(a.newestAt) - Date.parse(b.newestAt));
195
195
  if (shouldStop?.()) return incomplete();
196
196
  for (const unit of ordered) {
197
197
  if (shouldStop?.()) return incomplete();
@@ -200,13 +200,13 @@ function selectExpired(events: any[], { now, windowMs, shouldStop }: any = {}):
200
200
  if (safe) selected.push(unit);
201
201
  }
202
202
  }
203
- const units: any[] = [];
203
+ const units = [];
204
204
  for (const unit of selected) {
205
205
  if (shouldStop?.()) return incomplete();
206
- units.push({ items: [...unit.items].sort(), newestAt: unit.newestAt, files: unit.events.map((event: any) => `${event.id}.json`).sort() });
206
+ units.push({ items: [...unit.items].sort(), newestAt: unit.newestAt, files: unit.events.map((event ) => `${event.id}.json`).sort() });
207
207
  }
208
208
  if (shouldStop?.()) return incomplete();
209
- return { files: units.flatMap((unit: any) => unit.files).sort(), units };
209
+ return { files: units.flatMap((unit ) => unit.files).sort(), units };
210
210
  }
211
211
 
212
212
  module.exports = { parseWindow, retentionWindow, selectExpired };
@@ -11,19 +11,19 @@ const fs = require('node:fs');
11
11
  const path = require('node:path');
12
12
  const { execFileSync } = require('node:child_process');
13
13
 
14
- interface WorktreeEntry { path: string; bare: boolean; prunable: boolean }
14
+
15
15
 
16
- function git(cwd: string, args: string[]): string {
16
+ function git(cwd , args ) {
17
17
  return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
18
18
  }
19
19
 
20
- function real(target: string): string {
20
+ function real(target ) {
21
21
  try { return fs.realpathSync(target); } catch { return path.resolve(target); }
22
22
  }
23
23
 
24
24
  /** The entries of `git worktree list --porcelain`: `{path, bare, prunable}`, in git's order. */
25
- function worktreeEntries(porcelain: string): WorktreeEntry[] {
26
- const entries: WorktreeEntry[] = [];
25
+ function worktreeEntries(porcelain ) {
26
+ const entries = [];
27
27
  for (const block of porcelain.split(/\n\s*\n/)) {
28
28
  const lines = block.split('\n').map((line) => line.replace(/\r$/, ''));
29
29
  const head = lines.find((line) => line.startsWith('worktree '));
@@ -39,12 +39,12 @@ function worktreeEntries(porcelain: string): WorktreeEntry[] {
39
39
 
40
40
  /** A working tree that exists and that git itself calls one: a submodule's or a separate git dir's
41
41
  * entry points at a git directory, where `--is-inside-work-tree` prints `false`. */
42
- function isRealWorkTree(entry: WorktreeEntry): boolean {
42
+ function isRealWorkTree(entry ) {
43
43
  if (entry.bare || entry.prunable || !fs.existsSync(entry.path)) return false;
44
44
  try { return git(entry.path, ['rev-parse', '--is-inside-work-tree']).trim() === 'true'; } catch { return false; }
45
45
  }
46
46
 
47
- function showToplevel(cwd: string): string | null {
47
+ function showToplevel(cwd ) {
48
48
  try {
49
49
  const top = git(cwd, ['rev-parse', '--show-toplevel']).trim();
50
50
  return top ? real(top) : null;
@@ -60,8 +60,8 @@ function showToplevel(cwd: string): string | null {
60
60
  * @param {string} [cwd] defaults to the process working directory
61
61
  * @returns {string} an absolute, real path
62
62
  */
63
- function projectRoot(cwd: string = process.cwd()): string {
64
- let entries: WorktreeEntry[] = [];
63
+ function projectRoot(cwd = process.cwd()) {
64
+ let entries = [];
65
65
  try { entries = worktreeEntries(git(cwd, ['worktree', 'list', '--porcelain'])); } catch { /* not a repository */ }
66
66
  const first = entries[0];
67
67
  if (first && !first.bare && isRealWorkTree(first)) return real(first.path);