@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
@@ -17,10 +17,10 @@
17
17
  const fs = require('node:fs');
18
18
  const os = require('node:os');
19
19
  const path = require('node:path');
20
- const { REPO_ROOT } = require('../../helper/repo-root.ts');
21
- const { normaliseMessage, maskLine } = require('../mask.ts');
22
- const { isProgrammingError, errorKind } = require('./classifier.ts');
23
- const { failureHome, captureIsOff, eventsPath } = require('./home.ts');
20
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
21
+ const { normaliseMessage, maskLine } = require('../mask.js');
22
+ const { isProgrammingError, errorKind } = require('./classifier.js');
23
+ const { failureHome, captureIsOff, eventsPath } = require('./home.js');
24
24
 
25
25
  const MAX_LINE_BYTES = 2048;
26
26
  const MAX_RAW_MESSAGE = 4096;
@@ -33,24 +33,24 @@ const COMMAND_NAME = /^[a-z][a-z0-9-]{0,39}$/;
33
33
  /** Errors already recorded in this process, so one error caught at two points counts once. */
34
34
  const recorded = new WeakSet();
35
35
 
36
- let cachedVersion: string | null = null;
36
+ let cachedVersion = null;
37
37
  /** The package version; the projected runtime ships no package.json, so a tolerant read. */
38
- function packageVersion(): string {
38
+ function packageVersion() {
39
39
  if (cachedVersion === null) {
40
40
  try { cachedVersion = String(require('../../../package.json').version); } catch { cachedVersion = 'unknown'; }
41
41
  }
42
42
  return cachedVersion;
43
43
  }
44
44
 
45
- function harnessName(env: any): string {
45
+ function harnessName(env ) {
46
46
  const raw = env.DOFLOW_AGENT;
47
47
  return typeof raw === 'string' && /^[A-Za-z0-9._-]{1,40}$/.test(raw) ? raw : 'none';
48
48
  }
49
49
 
50
50
  /** The absolute, non-root homes to compare a working directory with: HOME as given, its real path, and `os.homedir()`. */
51
- function homeCandidates(env: any): string[] {
52
- const out: string[] = [];
53
- const add = (value: any) => {
51
+ function homeCandidates(env ) {
52
+ const out = [];
53
+ const add = (value ) => {
54
54
  if (typeof value !== 'string' || !path.isAbsolute(value)) return;
55
55
  const home = value.replace(/\/+$/, '');
56
56
  if (home.length > 1 && !out.includes(home)) out.push(home);
@@ -67,8 +67,8 @@ function homeCandidates(env: any): string[] {
67
67
  * directory under no known home, when `HOME` itself is not usable, is stored as its folder name only,
68
68
  * so an absolute path with a user name in it never reaches the file.
69
69
  */
70
- function projectName(env: any): string {
71
- let cwd: string;
70
+ function projectName(env ) {
71
+ let cwd ;
72
72
  try { cwd = process.cwd(); } catch { return ''; }
73
73
  const home = homeCandidates(env).find((h) => cwd === h || cwd.startsWith(`${h}/`));
74
74
  if (home) return maskLine(`~${cwd.slice(home.length)}`, { home: null }).text.slice(0, 200);
@@ -77,7 +77,7 @@ function projectName(env: any): string {
77
77
  return maskLine(cwd, { home: env.HOME }).text.slice(0, 200);
78
78
  }
79
79
 
80
- function commandName(command: any): string {
80
+ function commandName(command ) {
81
81
  return typeof command === 'string' && COMMAND_NAME.test(command) ? command : 'unknown';
82
82
  }
83
83
 
@@ -88,8 +88,8 @@ function commandName(command: any): string {
88
88
  * @param {*} error
89
89
  * @returns {string|null}
90
90
  */
91
- function doflowFrame(error: any): string | null {
92
- let stack: string;
91
+ function doflowFrame(error ) {
92
+ let stack ;
93
93
  try { stack = typeof error.stack === 'string' ? error.stack : ''; } catch { return null; }
94
94
  const root = REPO_ROOT + path.sep;
95
95
  for (const line of stack.split('\n')) {
@@ -97,15 +97,15 @@ function doflowFrame(error: any): string | null {
97
97
  if (!match) continue;
98
98
  const file = match[2].replace(/^file:\/\//, '');
99
99
  if (!file.startsWith(root) || file.includes(`${path.sep}node_modules${path.sep}`)) continue;
100
- const fn = (match[1] || '').replace(/^new /, '').split('.').pop()!.replace(/\s.*$/, '') || '<anonymous>';
100
+ const fn = (match[1] || '').replace(/^new /, '').split('.').pop() .replace(/\s.*$/, '') || '<anonymous>';
101
101
  return `${path.relative(REPO_ROOT, file).split(path.sep).join('/')}:${fn}`;
102
102
  }
103
103
  return null;
104
104
  }
105
105
 
106
106
  /** One line at or under MAX_LINE_BYTES including the newline: shorten `message`, then `project`. */
107
- function buildLine(record: any): string | null {
108
- const bytes = (r: any) => Buffer.byteLength(JSON.stringify(r), 'utf8') + 1;
107
+ function buildLine(record ) {
108
+ const bytes = (r ) => Buffer.byteLength(JSON.stringify(r), 'utf8') + 1;
109
109
  const out = { ...record };
110
110
  for (const field of ['message', 'project']) {
111
111
  while (bytes(out) > MAX_LINE_BYTES && out[field].length > 0) {
@@ -115,23 +115,23 @@ function buildLine(record: any): string | null {
115
115
  return bytes(out) > MAX_LINE_BYTES ? null : `${JSON.stringify(out)}\n`;
116
116
  }
117
117
 
118
- const stamp = (date: Date): string => date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
118
+ const stamp = (date ) => date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
119
119
 
120
120
  /** Rotated files with their size and modification time, oldest last-modified first (names alone tie inside one second). */
121
- function rotatedFiles(home: string): Array<{ name: string; size: number; mtime: number }> {
122
- return fs.readdirSync(home).filter((name: string) => ROTATED.test(name)).map((name: string) => {
121
+ function rotatedFiles(home ) {
122
+ return fs.readdirSync(home).filter((name ) => ROTATED.test(name)).map((name ) => {
123
123
  let size = 0;
124
124
  let mtime = 0;
125
125
  try { const st = fs.statSync(path.join(home, name)); size = st.size; mtime = st.mtimeMs; } catch { /* gone */ }
126
126
  return { name, size, mtime };
127
- }).sort((a: any, b: any) => a.mtime - b.mtime || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
127
+ }).sort((a , b ) => a.mtime - b.mtime || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
128
128
  }
129
129
 
130
130
  /**
131
131
  * Removes rotated files beyond the newest four full ones, and partial ones (see rotateIfDue) once
132
132
  * they are an hour old. A partial file never counts toward the four, so it cannot push a real one out.
133
133
  */
134
- function prune(home: string, now = Date.now()): void {
134
+ function prune(home , now = Date.now()) {
135
135
  const files = rotatedFiles(home);
136
136
  const full = files.filter((f) => f.size >= ROTATE_AT_BYTES);
137
137
  const doomed = [
@@ -157,7 +157,7 @@ function prune(home: string, now = Date.now()): void {
157
157
  * @param {string} home the failure home
158
158
  * @returns {boolean} whether this call rotated a full file
159
159
  */
160
- function rotateIfDue(home: string): boolean {
160
+ function rotateIfDue(home ) {
161
161
  const live = eventsPath(home);
162
162
  try {
163
163
  if (fs.statSync(live).size < ROTATE_AT_BYTES) return false;
@@ -188,9 +188,9 @@ function rotateIfDue(home: string): boolean {
188
188
  * descriptor is checked before the write, so a file swapped in after any earlier check is still safe.
189
189
  * @returns {boolean} whether the line was written
190
190
  */
191
- function appendRegular(file: string, line: string): boolean {
191
+ function appendRegular(file , line ) {
192
192
  const c = fs.constants;
193
- let fd: number | undefined;
193
+ let fd ;
194
194
  try {
195
195
  fd = fs.openSync(file, c.O_WRONLY | c.O_APPEND | c.O_CREAT | (c.O_NOFOLLOW || 0) | (c.O_NONBLOCK || 0), 0o600);
196
196
  if (!fs.fstatSync(fd).isFile()) return false;
@@ -208,7 +208,7 @@ function appendRegular(file: string, line: string): boolean {
208
208
  * @param {Object} [env] defaults to process.env
209
209
  * @returns {boolean} whether a line was written
210
210
  */
211
- function captureFailure(fields: any, env = process.env): boolean {
211
+ function captureFailure(fields , env = process.env) {
212
212
  try {
213
213
  const home = failureHome(env);
214
214
  if (!home || captureIsOff(home, env)) return false;
@@ -235,7 +235,7 @@ function captureFailure(fields: any, env = process.env): boolean {
235
235
  }
236
236
 
237
237
  /** The error's message cut to 4096 characters before any masking, so no input makes the rules do unbounded work. */
238
- function rawMessage(error: any): string {
238
+ function rawMessage(error ) {
239
239
  try {
240
240
  const message = error.message;
241
241
  return typeof message === 'string' ? message.slice(0, MAX_RAW_MESSAGE) : '';
@@ -251,7 +251,7 @@ function rawMessage(error: any): string {
251
251
  * @param {Object} [env]
252
252
  * @returns {boolean} whether a line was written
253
253
  */
254
- function captureError(error: any, context: any = {}, env = process.env): boolean {
254
+ function captureError(error , context = {}, env = process.env) {
255
255
  try {
256
256
  if (!isProgrammingError(error) || recorded.has(error)) return false;
257
257
  recorded.add(error);
@@ -14,12 +14,12 @@
14
14
  const CLASS_NAMES = new Set(['TypeError', 'RangeError', 'ReferenceError']);
15
15
 
16
16
  /** Reads one property without ever throwing: a throwing getter reads as undefined. */
17
- function read(error: any, key: string): any {
17
+ function read(error , key ) {
18
18
  try { return error[key]; } catch { return undefined; }
19
19
  }
20
20
 
21
21
  /** `error instanceof Class`, never throwing. */
22
- function isA(error: any, Class: any): boolean {
22
+ function isA(error , Class ) {
23
23
  try { return error instanceof Class; } catch { return false; }
24
24
  }
25
25
 
@@ -29,7 +29,7 @@ function isA(error: any, Class: any): boolean {
29
29
  * @param {*} error
30
30
  * @returns {boolean}
31
31
  */
32
- function isProgrammingError(error: any): boolean {
32
+ function isProgrammingError(error ) {
33
33
  try {
34
34
  if (error === null || typeof error !== 'object') return false;
35
35
  const code = read(error, 'code');
@@ -53,7 +53,7 @@ function isProgrammingError(error: any): boolean {
53
53
  * @param {*} error
54
54
  * @returns {string}
55
55
  */
56
- function errorKind(error: any): string {
56
+ function errorKind(error ) {
57
57
  for (const Class of [TypeError, RangeError, ReferenceError]) {
58
58
  if (isA(error, Class)) return Class.name;
59
59
  }
@@ -12,14 +12,14 @@
12
12
  */
13
13
 
14
14
  const fs = require('node:fs');
15
- const { finishRuntime, usageError } = require('../cli-result.ts');
16
- const { maskLine, printSafe } = require('../mask.ts');
17
- const { failureHome, captureSwitch, sentinelPath } = require('./home.ts');
18
- const { rotateIfDue } = require('./capture.ts');
19
- const store = require('./store.ts');
20
- const { projectRoot } = require('../lifecycle/root.ts');
21
- const { appendEvents, readFold, randomChars, StoreUnsafeError, StoreLockedError } = require('../lifecycle/event-store.ts');
22
- const { prepareStore } = require('../lifecycle/store-upkeep.ts');
15
+ const { finishRuntime, usageError } = require('../cli-result.js');
16
+ const { maskLine, printSafe } = require('../mask.js');
17
+ const { failureHome, captureSwitch, sentinelPath } = require('./home.js');
18
+ const { rotateIfDue } = require('./capture.js');
19
+ const store = require('./store.js');
20
+ const { projectRoot } = require('../lifecycle/root.js');
21
+ const { appendEvents, readFold, randomChars, StoreUnsafeError, StoreLockedError } = require('../lifecycle/event-store.js');
22
+ const { prepareStore } = require('../lifecycle/store-upkeep.js');
23
23
 
24
24
  const ACTIONS = ['list', 'settle', 'capture'];
25
25
  const STATEMENT_MAX = 280;
@@ -29,10 +29,10 @@ const CONTROL = /[\u0000-\u001F\u007F-\u009F\u2028\u2029\u202A-\u202E\u2066-\u20
29
29
  /** A caller mistake (exit 2). */
30
30
  class FailureUsageError extends Error {}
31
31
 
32
- const refusal = (action: string, finding: string, message: string) => ({ ok: false, action, finding, message });
32
+ const refusal = (action , finding , message ) => ({ ok: false, action, finding, message });
33
33
 
34
34
  /** A one-line reason, masked like any other free text, or null when none was given. */
35
- function cleanReason(raw: any): string | null {
35
+ function cleanReason(raw ) {
36
36
  if (raw === undefined) return null;
37
37
  const rawLength = String(raw).length;
38
38
  if (rawLength > STATEMENT_MAX * 4) throw new FailureUsageError(`--reason is ${rawLength} characters; the limit is ${STATEMENT_MAX}`);
@@ -44,19 +44,19 @@ function cleanReason(raw: any): string | null {
44
44
  }
45
45
 
46
46
  /** `DoFlow <command> <kind>: <message>`, built only from stored fields, masked and cut to 280 characters. */
47
- function importedStatement(entry: any): string {
47
+ function importedStatement(entry ) {
48
48
  const raw = `DoFlow ${entry.command} ${entry.kind}${entry.message ? `: ${entry.message}` : ''}`;
49
49
  return maskLine(raw.replace(CONTROL, ' ')).text.replace(/\s+/g, ' ').trim().slice(0, STATEMENT_MAX);
50
50
  }
51
51
 
52
52
  // ── list ───────────────────────────────────────────────────────────────────────────────────────
53
53
 
54
- function listFailures({ all = false, env = process.env } = {}): any {
54
+ function listFailures({ all = false, env = process.env } = {}) {
55
55
  const home = failureHome(env);
56
56
  if (home) rotateIfDue(home);
57
57
  const loaded = store.loadEntries({ env });
58
- const shown = loaded.entries.filter((entry: any) => all || store.SHOWN_BY_DEFAULT.has(entry.status)).map(store.listedEntry);
59
- const result: any = { ok: true, action: 'list', capture: loaded.capture };
58
+ const shown = loaded.entries.filter((entry ) => all || store.SHOWN_BY_DEFAULT.has(entry.status)).map(store.listedEntry);
59
+ const result = { ok: true, action: 'list', capture: loaded.capture };
60
60
  if (loaded.home === null) result.home = null;
61
61
  result.entries = shown;
62
62
  result.counts = loaded.counts;
@@ -70,7 +70,7 @@ function listFailures({ all = false, env = process.env } = {}): any {
70
70
 
71
71
  // ── settle ─────────────────────────────────────────────────────────────────────────────────────
72
72
 
73
- function settleFailure({ fp, as, reason, cwd, env = process.env, now = new Date() }: any): any {
73
+ function settleFailure({ fp, as, reason, cwd, env = process.env, now = new Date() } ) {
74
74
  if (typeof fp !== 'string' || !FP.test(fp)) throw new FailureUsageError('--fp is required: the 16 hex characters of an entry from `failure --action list`');
75
75
  if (!store.SETTLE_AS.includes(as)) throw new FailureUsageError(`--as must be one of ${store.SETTLE_AS.join(', ')}${as ? ` (got '${as}')` : ''}`);
76
76
  const cleaned = cleanReason(reason);
@@ -88,7 +88,7 @@ function settleFailure({ fp, as, reason, cwd, env = process.env, now = new Date(
88
88
  for (const text of prepared.lines) console.error(printSafe(`doflow failure: ${text}`));
89
89
  if (!prepared.ok) return refusal('settle', prepared.finding, prepared.message);
90
90
  }
91
- const entry = store.loadEntries({ env }).entries.find((e: any) => e.fp === fp);
91
+ const entry = store.loadEntries({ env }).entries.find((e ) => e.fp === fp);
92
92
  if (!entry) return refusal('settle', 'unknown-fp', `${fp} is not a failure entry on this machine; list them with doflow-run failure --action list. Nothing was written.`);
93
93
 
94
94
  if (as !== 'imported') {
@@ -98,10 +98,10 @@ function settleFailure({ fp, as, reason, cwd, env = process.env, now = new Date(
98
98
  // Deduplicated on the project's own follow-ups, whatever the settlements say: a follow-up that
99
99
  // already comes from this fingerprint is never created twice, even after a noise or fixed settlement.
100
100
  const followups = readFold(root, { now }).followups;
101
- const existing = followups.find((f: any) => f.source && f.source.kind === 'failure' && f.source.ref === fp);
101
+ const existing = followups.find((f ) => f.source && f.source.kind === 'failure' && f.source.ref === fp);
102
102
  if (existing) return refusal('settle', 'already-imported', `${fp} was already imported as ${existing.id}. Nothing was written.`);
103
- const taken = new Set(followups.map((f: any) => f.id));
104
- let id: string;
103
+ const taken = new Set(followups.map((f ) => f.id));
104
+ let id ;
105
105
  do { id = `FU-${randomChars(6)}`; } while (taken.has(id));
106
106
  const statement = importedStatement(entry);
107
107
  const source = { kind: 'failure', ref: fp };
@@ -113,13 +113,13 @@ function settleFailure({ fp, as, reason, cwd, env = process.env, now = new Date(
113
113
  return {
114
114
  ok: true, action: 'settle', fp, as,
115
115
  followup: { id, statement, source, state: 'open' },
116
- events: out.written.map((w: any) => w.file),
116
+ events: out.written.map((w ) => w.file),
117
117
  };
118
118
  }
119
119
 
120
120
  // ── capture ────────────────────────────────────────────────────────────────────────────────────
121
121
 
122
- function captureState({ set, env = process.env, now = new Date() }: any = {}): any {
122
+ function captureState({ set, env = process.env, now = new Date() } = {}) {
123
123
  if (set !== undefined && set !== 'on' && set !== 'off') throw new FailureUsageError(`--set must be on or off (got '${set}')`);
124
124
  const home = failureHome(env);
125
125
  if (home !== null && set === 'off') {
@@ -130,21 +130,21 @@ function captureState({ set, env = process.env, now = new Date() }: any = {}): a
130
130
  }
131
131
  const state = captureSwitch(home, env);
132
132
  // With no failure home nothing is captured, whatever the switch says.
133
- const result: any = { ok: true, action: 'capture', effective: home === null ? 'off' : state.effective, sentinel: state.sentinel, env: state.env };
133
+ const result = { ok: true, action: 'capture', effective: home === null ? 'off' : state.effective, sentinel: state.sentinel, env: state.env };
134
134
  if (home === null) result.home = null;
135
135
  return result;
136
136
  }
137
137
 
138
138
  // ── verb ───────────────────────────────────────────────────────────────────────────────────────
139
139
 
140
- function lines(result: any): string[] {
140
+ function lines(result ) {
141
141
  if (result.home === null) return ['home: none (capture skipped)', 'capture: off'];
142
142
  if (result.action === 'list') {
143
143
  const out = [`capture ${result.capture}: ${result.entries.length} entr${result.entries.length === 1 ? 'y' : 'ies'} listed (new ${result.counts.new}, regressed ${result.counts.regressed}, noise ${result.counts.noise}, fixed ${result.counts.fixed}, imported ${result.counts.imported})`];
144
144
  for (const e of result.entries) out.push(`${e.fp} ${e.status} x${e.count} ${e.command} ${e.kind}${e.message ? `: ${e.message}` : ''} (last seen ${e.lastSeen}, ${e.lastVersion})`);
145
145
  if (result.unreadable.length) out.push(`unreadable failure files (not read): ${result.unreadable.join(', ')}`);
146
146
  if (result.skippedLines) out.push(`${result.skippedLines} unreadable line${result.skippedLines === 1 ? '' : 's'} skipped`);
147
- return [...out, ...result.next.map((n: string) => `next: ${n}`)];
147
+ return [...out, ...result.next.map((n ) => `next: ${n}`)];
148
148
  }
149
149
  if (result.action === 'settle') return [`${result.fp} settled as ${result.as}${result.followup ? `, follow-up ${result.followup.id}` : ''}`];
150
150
  return [`capture ${result.effective}${result.sentinel ? ' (switched off on this machine)' : ''}${result.env ? ` (DOFLOW_FAILURE_CAPTURE=${result.env})` : ''}`];
@@ -157,17 +157,17 @@ function lines(result: any): string[] {
157
157
  * @param {boolean} [options.all] `list`: every status, not just new and regressed
158
158
  * @param {Object} [options.flags] fp, as, reason, set
159
159
  */
160
- function handleFailureCommand({ action, cwd, json = false, all = false, flags = {} }: any = {}): any {
161
- let result: any;
160
+ function handleFailureCommand({ action, cwd, json = false, all = false, flags = {} } = {}) {
161
+ let result ;
162
162
  try {
163
163
  if (!ACTIONS.includes(action)) throw new FailureUsageError(`--action is required: one of ${ACTIONS.join(', ')}${action ? ` (got '${action}')` : ''}`);
164
164
  if (action === 'list') result = listFailures({ all });
165
- else if (action === 'settle') result = settleFailure({ fp: (flags as any).fp, as: (flags as any).as, reason: (flags as any).reason, cwd });
166
- else result = captureState({ set: (flags as any).set });
165
+ else if (action === 'settle') result = settleFailure({ fp: (flags ).fp, as: (flags ).as, reason: (flags ).reason, cwd });
166
+ else result = captureState({ set: (flags ).set });
167
167
  } catch (error) {
168
- if (error instanceof FailureUsageError || error instanceof StoreUnsafeError) return usageError('failure', (error as Error).message, json);
168
+ if (error instanceof FailureUsageError || error instanceof StoreUnsafeError) return usageError('failure', (error ).message, json);
169
169
  if (!(error instanceof StoreLockedError)) throw error;
170
- result = refusal(action, 'store-locked', (error as Error).message);
170
+ result = refusal(action, 'store-locked', (error ).message);
171
171
  }
172
172
  if (json) console.log(JSON.stringify(result, null, 2));
173
173
  else if (result.ok === false) console.log(printSafe(`${result.finding}: ${result.message}`));
@@ -18,7 +18,7 @@ const OFF_VALUES = new Set(['off', '0', 'false', 'no']);
18
18
 
19
19
  /** @param {Object} [env] defaults to process.env
20
20
  * @returns {string|null} the failure home, an absolute path, or null when none can be resolved */
21
- function failureHome(env: any = process.env): string | null {
21
+ function failureHome(env = process.env) {
22
22
  const xdg = env.XDG_CONFIG_HOME;
23
23
  if (typeof xdg === 'string' && xdg !== '') {
24
24
  return path.isAbsolute(xdg) ? path.join(xdg, 'doflow', 'failures') : null;
@@ -28,11 +28,11 @@ function failureHome(env: any = process.env): string | null {
28
28
  return null;
29
29
  }
30
30
 
31
- const sentinelPath = (home: string): string => path.join(home, 'off');
32
- const eventsPath = (home: string): string => path.join(home, 'events.jsonl');
31
+ const sentinelPath = (home ) => path.join(home, 'off');
32
+ const eventsPath = (home ) => path.join(home, 'events.jsonl');
33
33
 
34
34
  /** The value of `DOFLOW_FAILURE_CAPTURE` when it is set to anything, else null. */
35
- function envSetting(env: any = process.env): string | null {
35
+ function envSetting(env = process.env) {
36
36
  const raw = env.DOFLOW_FAILURE_CAPTURE;
37
37
  return typeof raw === 'string' && raw !== '' ? raw : null;
38
38
  }
@@ -44,7 +44,7 @@ function envSetting(env: any = process.env): string | null {
44
44
  * @param {Object} [env]
45
45
  * @returns {{effective: 'on'|'off', sentinel: boolean, env: string|null}}
46
46
  */
47
- function captureSwitch(home: string | null, env: any = process.env): { effective: 'on' | 'off'; sentinel: boolean; env: string | null } {
47
+ function captureSwitch(home , env = process.env) {
48
48
  let sentinel = false;
49
49
  if (home) {
50
50
  try { sentinel = fs.statSync(sentinelPath(home)).isFile(); } catch { sentinel = false; }
@@ -55,7 +55,7 @@ function captureSwitch(home: string | null, env: any = process.env): { effective
55
55
  }
56
56
 
57
57
  /** The hot-path check a writer makes: the environment first, then one `stat` of the sentinel. */
58
- function captureIsOff(home: string, env: any = process.env): boolean {
58
+ function captureIsOff(home , env = process.env) {
59
59
  const setting = envSetting(env);
60
60
  if (setting !== null && OFF_VALUES.has(setting.trim().toLowerCase())) return true;
61
61
  try { return fs.statSync(sentinelPath(home)).isFile(); } catch { return false; }
@@ -18,8 +18,8 @@
18
18
  const fs = require('node:fs');
19
19
  const path = require('node:path');
20
20
  const crypto = require('node:crypto');
21
- const { failureHome, captureSwitch, eventsPath } = require('./home.ts');
22
- const { printSafe } = require('../mask.ts');
21
+ const { failureHome, captureSwitch, eventsPath } = require('./home.js');
22
+ const { printSafe } = require('../mask.js');
23
23
 
24
24
  const ROTATED = /^events-\d{8}T\d{6}Z-\d+\.jsonl$/;
25
25
  const SETTLEMENTS = 'settlements.jsonl';
@@ -29,18 +29,18 @@ const STATUSES = ['new', 'regressed', 'noise', 'fixed', 'imported'];
29
29
  const SHOWN_BY_DEFAULT = new Set(['new', 'regressed']);
30
30
  const PROJECTS_KEPT = 5;
31
31
 
32
- const settlementsPath = (home: string): string => path.join(home, SETTLEMENTS);
32
+ const settlementsPath = (home ) => path.join(home, SETTLEMENTS);
33
33
 
34
34
  /** @param {{source: string, command: string, kind: string, message: string, frame: string|null}} event */
35
- function fingerprint(event: any): string {
35
+ function fingerprint(event ) {
36
36
  const text = [event.source, event.command, event.kind, event.message, event.frame || ''].join('\n');
37
37
  return crypto.createHash('sha256').update(text).digest('hex').slice(0, 16);
38
38
  }
39
39
 
40
40
  /** One IC-011 line, or null. Only the fields a fold reads are checked. */
41
- function asEvent(value: any): any {
41
+ function asEvent(value ) {
42
42
  if (!value || typeof value !== 'object' || value.v !== 1) return null;
43
- const text = (x: any) => typeof x === 'string';
43
+ const text = (x ) => typeof x === 'string';
44
44
  if (!text(value.at) || !SOURCES.has(value.source) || !text(value.command) || !text(value.kind)) return null;
45
45
  return {
46
46
  at: value.at,
@@ -61,20 +61,20 @@ function asEvent(value: any): any {
61
61
  * not a regular file (a FIFO would block the read, a device never ends, a symlink may leave the folder)
62
62
  * or cannot be read is `unreadable`, and nothing is read from it.
63
63
  */
64
- function readLines(file: string, accept: (v: any) => any): { rows: any[]; skipped: number; unreadable: boolean } {
64
+ function readLines(file , accept ) {
65
65
  const c = fs.constants;
66
- let text: string;
67
- let fd: number | undefined;
66
+ let text ;
67
+ let fd ;
68
68
  try {
69
69
  fd = fs.openSync(file, c.O_RDONLY | (c.O_NOFOLLOW || 0) | (c.O_NONBLOCK || 0));
70
70
  if (!fs.fstatSync(fd).isFile()) return { rows: [], skipped: 0, unreadable: true };
71
71
  text = fs.readFileSync(fd, 'utf8');
72
- } catch (error: any) {
72
+ } catch (error ) {
73
73
  return { rows: [], skipped: 0, unreadable: error.code !== 'ENOENT' };
74
74
  } finally {
75
75
  if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* read already done */ } }
76
76
  }
77
- const rows: any[] = [];
77
+ const rows = [];
78
78
  let skipped = 0;
79
79
  for (const line of text.split('\n')) {
80
80
  if (line.trim() === '') continue;
@@ -90,13 +90,13 @@ function readLines(file: string, accept: (v: any) => any): { rows: any[]; skippe
90
90
  * @param {string} home
91
91
  * @returns {{events: Object[], skippedLines: number, unreadable: string[]}} `unreadable` names the files that were not read
92
92
  */
93
- function readEvents(home: string): { events: any[]; skippedLines: number; unreadable: string[] } {
94
- let rotated: string[] = [];
95
- try { rotated = fs.readdirSync(home).filter((name: string) => ROTATED.test(name)).sort(); } catch { /* no folder yet */ }
96
- const events: any[] = [];
93
+ function readEvents(home ) {
94
+ let rotated = [];
95
+ try { rotated = fs.readdirSync(home).filter((name ) => ROTATED.test(name)).sort(); } catch { /* no folder yet */ }
96
+ const events = [];
97
97
  let skippedLines = 0;
98
- const unreadable: string[] = [];
99
- for (const file of [...rotated.map((name: string) => path.join(home, name)), eventsPath(home)]) {
98
+ const unreadable = [];
99
+ for (const file of [...rotated.map((name ) => path.join(home, name)), eventsPath(home)]) {
100
100
  const read = readLines(file, asEvent);
101
101
  events.push(...read.rows);
102
102
  skippedLines += read.skipped;
@@ -105,20 +105,20 @@ function readEvents(home: string): { events: any[]; skippedLines: number; unread
105
105
  return { events, skippedLines, unreadable };
106
106
  }
107
107
 
108
- function asSettlement(value: any): any {
108
+ function asSettlement(value ) {
109
109
  if (!value || typeof value !== 'object' || value.v !== 1) return null;
110
110
  if (typeof value.at !== 'string' || !/^[0-9a-f]{16}$/.test(value.fp) || !SETTLE_AS.includes(value.as)) return null;
111
111
  return { at: value.at, fp: value.fp, as: value.as, reason: typeof value.reason === 'string' ? value.reason : null, followup: typeof value.followup === 'string' ? value.followup : null };
112
112
  }
113
113
 
114
114
  /** @returns {{settlements: Object[], skippedLines: number, unreadable: string[]}} in file order, which is time order */
115
- function readSettlements(home: string): { settlements: any[]; skippedLines: number; unreadable: string[] } {
115
+ function readSettlements(home ) {
116
116
  const read = readLines(settlementsPath(home), asSettlement);
117
117
  return { settlements: read.rows, skippedLines: read.skipped, unreadable: read.unreadable ? [SETTLEMENTS] : [] };
118
118
  }
119
119
 
120
120
  /** The IC-013 status of an entry from its latest settlement and the events seen after it. */
121
- function statusOf(settlement: any, sinceSettlement: number): string {
121
+ function statusOf(settlement , sinceSettlement ) {
122
122
  if (!settlement) return 'new';
123
123
  if (settlement.as === 'fixed') return sinceSettlement > 0 ? 'regressed' : 'fixed';
124
124
  return settlement.as;
@@ -130,7 +130,7 @@ function statusOf(settlement: any, sinceSettlement: number): string {
130
130
  * @param {Object[]} settlements
131
131
  * @returns {Object[]}
132
132
  */
133
- function foldEntries(events: any[], settlements: any[]): any[] {
133
+ function foldEntries(events , settlements ) {
134
134
  const latest = new Map();
135
135
  for (const s of settlements) latest.set(s.fp, s);
136
136
  const groups = new Map();
@@ -139,14 +139,14 @@ function foldEntries(events: any[], settlements: any[]): any[] {
139
139
  if (!groups.has(fp)) groups.set(fp, []);
140
140
  groups.get(fp).push(event);
141
141
  }
142
- const entries: any[] = [];
142
+ const entries = [];
143
143
  for (const [fp, group] of groups) {
144
- const ordered = (group as any[]).map((event: any, index: number) => ({ event, index })).sort((a: any, b: any) => (a.event.at < b.event.at ? -1 : a.event.at > b.event.at ? 1 : a.index - b.index)).map((x: any) => x.event);
144
+ const ordered = (group ).map((event , index ) => ({ event, index })).sort((a , b ) => (a.event.at < b.event.at ? -1 : a.event.at > b.event.at ? 1 : a.index - b.index)).map((x ) => x.event);
145
145
  const first = ordered[0];
146
146
  const last = ordered[ordered.length - 1];
147
147
  const settlement = latest.get(fp) || null;
148
- const since = settlement ? ordered.filter((e: any) => e.at > settlement.at).length : ordered.length;
149
- const projects: string[] = [];
148
+ const since = settlement ? ordered.filter((e ) => e.at > settlement.at).length : ordered.length;
149
+ const projects = [];
150
150
  for (let i = ordered.length - 1; i >= 0 && projects.length < PROJECTS_KEPT; i--) {
151
151
  if (ordered[i].project && !projects.includes(ordered[i].project)) projects.push(ordered[i].project);
152
152
  }
@@ -168,16 +168,16 @@ function foldEntries(events: any[], settlements: any[]): any[] {
168
168
  message: first.message,
169
169
  frame: first.frame,
170
170
  projects,
171
- harnesses: [...new Set(ordered.map((e: any) => e.harness))].sort(),
172
- exitCodes: [...new Set(ordered.map((e: any) => e.exit).filter((x: any) => x !== null))].sort((a: any, b: any) => a - b),
171
+ harnesses: [...new Set(ordered.map((e ) => e.harness))].sort(),
172
+ exitCodes: [...new Set(ordered.map((e ) => e.exit).filter((x ) => x !== null))].sort((a , b ) => a - b),
173
173
  settlement: settlement && { as: settlement.as, at: settlement.at, reason: settlement.reason, followup: settlement.followup },
174
174
  }));
175
175
  }
176
- return entries.sort((a: any, b: any) => (a.lastSeen < b.lastSeen ? 1 : a.lastSeen > b.lastSeen ? -1 : a.fp < b.fp ? -1 : 1));
176
+ return entries.sort((a , b ) => (a.lastSeen < b.lastSeen ? 1 : a.lastSeen > b.lastSeen ? -1 : a.fp < b.fp ? -1 : 1));
177
177
  }
178
178
 
179
179
  /** The shape a listing shows (IC-019). */
180
- function listedEntry(entry: any): any {
180
+ function listedEntry(entry ) {
181
181
  return {
182
182
  fp: entry.fp, status: entry.status, count: entry.count, command: entry.command, kind: entry.kind,
183
183
  message: entry.message, lastSeen: entry.lastSeen, lastVersion: entry.lastVersion,
@@ -190,7 +190,7 @@ function listedEntry(entry: any): any {
190
190
  * @param {Object} [options.env] defaults to process.env
191
191
  * @returns {{home: string|null, capture: 'on'|'off', entries: Object[], counts: Object, skippedLines: number, unreadable: string[]}}
192
192
  */
193
- function loadEntries({ env = process.env } = {}): any {
193
+ function loadEntries({ env = process.env } = {}) {
194
194
  const home = failureHome(env);
195
195
  const counts = Object.fromEntries(STATUSES.map((status) => [status, 0]));
196
196
  // With no failure home nothing is captured, whatever the switch says.
@@ -207,7 +207,7 @@ function loadEntries({ env = process.env } = {}): any {
207
207
  * @param {string} home
208
208
  * @param {{fp: string, as: string, reason?: string|null, followup?: string|null, now?: Date}} settlement
209
209
  */
210
- function appendSettlement(home: string, { fp, as, reason = null, followup = null, now = new Date() }: any): void {
210
+ function appendSettlement(home , { fp, as, reason = null, followup = null, now = new Date() } ) {
211
211
  fs.mkdirSync(home, { recursive: true, mode: 0o700 });
212
212
  const line = JSON.stringify({ v: 1, at: now.toISOString(), fp, as, reason, followup });
213
213
  fs.appendFileSync(settlementsPath(home), `${line}\n`, { flag: 'a', mode: 0o600 });
@@ -216,7 +216,7 @@ function appendSettlement(home: string, { fp, as, reason = null, followup = null
216
216
  const DOFLOW_PACKAGE = '@khoavu882/doflow';
217
217
 
218
218
  /** Whether `root` is the DoFlow repository itself: its `package.json` is named `@khoavu882/doflow`. */
219
- function isDoflowRepo(root: string): boolean {
219
+ function isDoflowRepo(root ) {
220
220
  try { return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).name === DOFLOW_PACKAGE; } catch { return false; }
221
221
  }
222
222
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  const path = require('node:path');
6
6
  const { execFileSync } = require('node:child_process');
7
- const { resolveBashHelper } = require('../helper/bash-helper.ts');
7
+ const { resolveBashHelper } = require('../helper/bash-helper.js');
8
8
 
9
9
  // `resolveActiveFeature` was moved here from `scaffold/generate.js` (which had it from
10
10
  // bin/doflow.js) so the scaffold and the decision register ask the same resolver. The root comes
@@ -31,18 +31,18 @@ const PATHS_HELPER = resolveBashHelper('do-paths.sh');
31
31
  * `paths` is the resolver's own parsed JSON (repo-root-relative artifact paths and has_* flags),
32
32
  * so a caller needs no second resolver call.
33
33
  */
34
- function resolveActiveFeature({ projectRoot, slug = null }: { projectRoot: string; slug?: string | null }): any {
34
+ function resolveActiveFeature({ projectRoot, slug = null } ) {
35
35
  const helper = resolveBashHelper('do-paths.sh');
36
36
  if (!helper) {
37
37
  return { error: 'resolver-missing', message: 'the feature resolver (do-paths.sh) is missing from this install' };
38
38
  }
39
- const args: string[] = [helper, '--json', '--require', 'feature'];
39
+ const args = [helper, '--json', '--require', 'feature'];
40
40
  if (slug) args.push(`--slug=${slug}`);
41
41
 
42
- let stdout: string;
42
+ let stdout ;
43
43
  try {
44
44
  stdout = execFileSync('bash', args, { cwd: projectRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
45
- } catch (error: any) {
45
+ } catch (error ) {
46
46
  // Exit 2 is the resolver's one non-zero path (no active feature, or an ambiguous one) and it
47
47
  // still prints its error object on stdout — that object carries the hint the user needs, so
48
48
  // it is read rather than replaced with a generic message. Anything else (no bash, NFR-003)
@@ -56,7 +56,7 @@ function resolveActiveFeature({ projectRoot, slug = null }: { projectRoot: strin
56
56
  }
57
57
  }
58
58
 
59
- let paths: any;
59
+ let paths ;
60
60
  try {
61
61
  paths = JSON.parse(stdout);
62
62
  } catch {