@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
@@ -10,8 +10,8 @@
10
10
  const fs = require('node:fs');
11
11
  const os = require('node:os');
12
12
  const path = require('node:path');
13
- const { nativeMcpCatalog } = require('../../registry/index.ts');
14
- const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.ts');
13
+ const { nativeMcpCatalog } = require('../../registry/index.js');
14
+ const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
15
15
 
16
16
  const HARNESS = 'claude';
17
17
  const MCP_CONTAINER = 'mcpServers';
@@ -20,39 +20,39 @@ const MCP_RENDERER = 'claude-mcp';
20
20
  // install carries.
21
21
  const MCP_ASSET_ID = 'guidance.core';
22
22
 
23
- function ownershipIdentity(id: any) { return `doflow:${HARNESS}:mcp-server:${id}`; }
23
+ function ownershipIdentity(id ) { return `doflow:${HARNESS}:mcp-server:${id}`; }
24
24
 
25
25
  /** Claude Code's own per-server shape, the one the registry's native catalog renders. */
26
- function rendered(servers: any) {
26
+ function rendered(servers ) {
27
27
  const { serverDefs } = nativeMcpCatalog(servers);
28
- return servers.map((server: any) => ({ id: server.id, entry: serverDefs[server.id] }));
28
+ return servers.map((server ) => ({ id: server.id, entry: serverDefs[server.id] }));
29
29
  }
30
30
 
31
31
  /** Everything the shared entry-ownership rules need from this adapter. */
32
- function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger }: any) {
33
- const rows = (ledger?.resources || []).filter((row: any) => row.kind === 'mcp-server');
32
+ function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger } ) {
33
+ const rows = (ledger?.resources || []).filter((row ) => row.kind === 'mcp-server');
34
34
  return {
35
35
  file: paths.mcp, selected: rendered(mcp), adoptable: rendered(mcpAdoptable),
36
- ownRows: rows.filter((row: any) => row.harness === HARNESS).map((row: any) => ({
36
+ ownRows: rows.filter((row ) => row.harness === HARNESS).map((row ) => ({
37
37
  identity: row.identity, target: row.target, fingerprint: row.fingerprint ?? null, ownershipIdentity: row.ownershipIdentity, legacy: false,
38
38
  })),
39
- foreignRows: rows.filter((row: any) => row.harness !== HARNESS).map((row: any) => ({ harness: row.harness, identity: row.identity, target: row.target })),
39
+ foreignRows: rows.filter((row ) => row.harness !== HARNESS).map((row ) => ({ harness: row.harness, identity: row.identity, target: row.target })),
40
40
  identityFor: ownershipIdentity, assetId: MCP_ASSET_ID, renderer: MCP_RENDERER, label: 'Claude MCP',
41
41
  };
42
42
  }
43
43
 
44
- function readFiles(entries: any, fsImpl: any) {
44
+ function readFiles(entries , fsImpl ) {
45
45
  return readMcpFiles({ file: entries.file, ownRows: entries.ownRows, container: MCP_CONTAINER, fsImpl });
46
46
  }
47
47
 
48
48
  /** Server names Claude Code merges into a project from `~/.claude.json` beside the project's own
49
49
  * `.mcp.json`: the local scope (`projects[<root>].mcpServers`) and the user scope (`mcpServers`).
50
50
  * Read-only and for reach alone; a file that cannot be read contributes nothing (null). */
51
- function claudeJsonNames({ scopeRoot, homeDir, fsImpl }: any) {
51
+ function claudeJsonNames({ scopeRoot, homeDir, fsImpl } ) {
52
52
  let doc;
53
53
  try { doc = JSON.parse(fsImpl.readFileSync(path.join(homeDir, '.claude.json'), 'utf8')); } catch { return null; }
54
54
  if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return null;
55
- const names = (servers: any) => (servers && typeof servers === 'object' && !Array.isArray(servers) ? Object.keys(servers) : []);
55
+ const names = (servers ) => (servers && typeof servers === 'object' && !Array.isArray(servers) ? Object.keys(servers) : []);
56
56
  const projects = doc.projects && typeof doc.projects === 'object' ? doc.projects : {};
57
57
  let real = scopeRoot;
58
58
  try { real = fsImpl.realpathSync(scopeRoot); } catch { /* the path as given */ }
@@ -64,7 +64,7 @@ function claudeJsonNames({ scopeRoot, homeDir, fsImpl }: any) {
64
64
  * (`mcpConfigured`): the scope's file, plus at project scope the local- and user-scope entries in
65
65
  * `~/.claude.json`. Claude Code also loads plugin, managed and connector servers DoFlow does not
66
66
  * read, so the view is never complete (`mcpComplete: false`) and absence is never proof. */
67
- function discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeDir = os.homedir(), fsImpl = fs }: any) {
67
+ function discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeDir = os.homedir(), fsImpl = fs } ) {
68
68
  const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
69
69
  const mcpSnapshot = readFiles(entries, fsImpl);
70
70
  // `null` while nothing could be read; every name otherwise, whoever wrote it.
@@ -75,17 +75,17 @@ function discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeD
75
75
  return { mcpSnapshot, mcpOwned: ownedMcpIds({ ...entries, files: mcpSnapshot }), mcpConfigured, mcpComplete: false };
76
76
  }
77
77
 
78
- function planMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, fsImpl = fs }: any) {
78
+ function planMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, fsImpl = fs } ) {
79
79
  const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
80
80
  return planMcpEntries({ ...entries, files: snapshot ?? readFiles(entries, fsImpl), removing });
81
81
  }
82
82
 
83
83
  /** An emptied `mcpServers` stays as `{}`, as Claude Code itself leaves it. */
84
- function writeMcp(changes: any, { fsImpl = fs } = {}) {
84
+ function writeMcp(changes , { fsImpl = fs } = {}) {
85
85
  return writeMcpEntries(changes, { container: MCP_CONTAINER, keepEmptyContainer: true, fsImpl });
86
86
  }
87
87
 
88
- function verifyMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, sourceVersion, fsImpl = fs }: any) {
88
+ function verifyMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, sourceVersion, fsImpl = fs } ) {
89
89
  const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
90
90
  const files = readFiles(entries, fsImpl);
91
91
  return verifyMcpEntries({ ...entries, files, snapshot: snapshot ?? files, removing, harness: HARNESS, sourceVersion });
@@ -8,7 +8,7 @@
8
8
  const crypto = require('node:crypto');
9
9
  const fs = require('node:fs');
10
10
  const path = require('node:path');
11
- const { atomicWrite } = require('./config.ts');
11
+ const { atomicWrite } = require('./config.js');
12
12
 
13
13
  const AGENT_DIRECTORY = 'agents';
14
14
  const AGENT_KIND = 'custom-agent';
@@ -23,38 +23,38 @@ const IMPLEMENTATION_ROLES = new Set([
23
23
  ]);
24
24
  const ALLOWED_FIELDS = new Set([...REQUIRED_FIELDS, 'sandbox_mode']);
25
25
 
26
- function fingerprint(content: string): string {
26
+ function fingerprint(content ) {
27
27
  return `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`;
28
28
  }
29
29
 
30
- function assertAgentName(name: any, label: string = 'agent name'): string {
30
+ function assertAgentName(name , label = 'agent name') {
31
31
  if (typeof name !== 'string' || !/^[a-z][a-z0-9-]*$/.test(name)) {
32
32
  throw new Error(`${label} must be a lowercase kebab-case identifier`);
33
33
  }
34
34
  return name;
35
35
  }
36
36
 
37
- function filenameFor(name: string): string {
37
+ function filenameFor(name ) {
38
38
  assertAgentName(name);
39
39
  return `${name}.toml`;
40
40
  }
41
41
 
42
- function expectedName(fileName: any): string {
42
+ function expectedName(fileName ) {
43
43
  if (typeof fileName !== 'string' || path.basename(fileName) !== fileName || !fileName.endsWith('.toml')) {
44
44
  throw new Error('Agent filename must be a direct .toml filename');
45
45
  }
46
46
  return assertAgentName(fileName.slice(0, -'.toml'.length), 'Agent filename');
47
47
  }
48
48
 
49
- function parseQuoted(value: string, field: string, line: number): string {
49
+ function parseQuoted(value , field , line ) {
50
50
  if (!/^"(?:[^"\\\n]|\\.)*"$/.test(value)) throw new Error(`${field} must be a quoted TOML string on line ${line}`);
51
51
  try { return JSON.parse(value); } catch { throw new Error(`${field} has an invalid quoted value on line ${line}`); }
52
52
  }
53
53
 
54
54
  /** Parse the intentionally small TOML contract supported by Codex agent definitions. */
55
- function parseAgentToml(source: any, { fileName }: { fileName?: string } = {}): Record<string, string> {
55
+ function parseAgentToml(source , { fileName } = {}) {
56
56
  if (typeof source !== 'string') throw new Error('Agent TOML source must be text');
57
- const values: Record<string, string> = {};
57
+ const values = {};
58
58
  const lines = source.split(/\r?\n/);
59
59
  for (let index = 0; index < lines.length; index++) {
60
60
  const line = lines[index];
@@ -64,7 +64,7 @@ function parseAgentToml(source: any, { fileName }: { fileName?: string } = {}):
64
64
  const field = triple[1];
65
65
  if (!ALLOWED_FIELDS.has(field)) throw new Error(`Unsupported agent field '${field}'`);
66
66
  if (Object.hasOwn(values, field)) throw new Error(`Duplicate agent field '${field}'`);
67
- const body: string[] = [];
67
+ const body = [];
68
68
  let closed = false;
69
69
  while (++index < lines.length) {
70
70
  if (/^"""\s*$/.test(lines[index])) { closed = true; break; }
@@ -85,7 +85,7 @@ function parseAgentToml(source: any, { fileName }: { fileName?: string } = {}):
85
85
  return values;
86
86
  }
87
87
 
88
- function validateAgentContract(source: any, { fileName }: { fileName?: string } = {}): any {
88
+ function validateAgentContract(source , { fileName } = {}) {
89
89
  const agent = parseAgentToml(source, { fileName });
90
90
  for (const field of REQUIRED_FIELDS) {
91
91
  if (typeof agent[field] !== 'string' || !agent[field].trim()) throw new Error(`Agent is missing required '${field}'`);
@@ -106,7 +106,7 @@ function validateAgentContract(source: any, { fileName }: { fileName?: string }
106
106
  return Object.freeze({ ...agent, fileName: filenameFor(agent.name) });
107
107
  }
108
108
 
109
- function agentDirectory({ scope, codexDir, projectRoot, paths }: { scope?: string, codexDir?: string, projectRoot?: string, paths?: any } = {}): string {
109
+ function agentDirectory({ scope, codexDir, projectRoot, paths } = {}) {
110
110
  // Preferred: the declared path resolved by the adapter (harnesses.json "paths".agentsDirectory).
111
111
  if (paths?.agentsDirectory) return paths.agentsDirectory;
112
112
  // Legacy derivation for direct callers passing raw scope inputs; kept byte-compatible with the
@@ -122,19 +122,19 @@ function agentDirectory({ scope, codexDir, projectRoot, paths }: { scope?: strin
122
122
  throw new Error(`Unsupported Codex agent scope: ${scope}`);
123
123
  }
124
124
 
125
- function destinationFor(directory: string, name: string): string {
125
+ function destinationFor(directory , name ) {
126
126
  const file = path.resolve(directory, filenameFor(name));
127
127
  const root = `${path.resolve(directory)}${path.sep}`;
128
128
  if (!file.startsWith(root)) throw new Error(`Refusing to write agent '${name}' outside the Codex agents directory`);
129
129
  return file;
130
130
  }
131
131
 
132
- function discoverCodexAgents(sourceDir: any, fsImpl: any = fs): any[] {
132
+ function discoverCodexAgents(sourceDir , fsImpl = fs) {
133
133
  if (!sourceDir) throw new Error('sourceDir is required for Codex agents');
134
134
  const directory = path.resolve(sourceDir);
135
135
  if (!fsImpl.existsSync(directory)) throw new Error(`Codex agent source directory does not exist: ${sourceDir}`);
136
- const agents: any[] = [];
137
- for (const entry of fsImpl.readdirSync(directory, { withFileTypes: true }).sort((a: any, b: any) => a.name.localeCompare(b.name))) {
136
+ const agents = [];
137
+ for (const entry of fsImpl.readdirSync(directory, { withFileTypes: true }).sort((a , b ) => a.name.localeCompare(b.name))) {
138
138
  if (!entry.name.endsWith('.toml')) continue;
139
139
  if (!entry.isFile()) throw new Error(`Codex agent source '${entry.name}' must be a regular file`);
140
140
  const name = expectedName(entry.name);
@@ -145,11 +145,11 @@ function discoverCodexAgents(sourceDir: any, fsImpl: any = fs): any[] {
145
145
  return agents;
146
146
  }
147
147
 
148
- function isOwnedResource(resource: any, scope: any): boolean {
148
+ function isOwnedResource(resource , scope ) {
149
149
  return resource && resource.target === 'codex' && resource.scope === scope && resource.kind === AGENT_KIND && typeof resource.identity === 'string';
150
150
  }
151
151
 
152
- function resourceFor(agent: any, scope: any, sourceVersion: any): any {
152
+ function resourceFor(agent , scope , sourceVersion ) {
153
153
  return {
154
154
  target: 'codex', scope, kind: AGENT_KIND, identity: `agent:${agent.name}`,
155
155
  sourceVersion, fingerprint: agent.fingerprint, selection: true,
@@ -163,13 +163,13 @@ function resourceFor(agent: any, scope: any, sourceVersion: any): any {
163
163
  * recording ownership instead of refusing. See planCodexConfig for why this is not `force`: a
164
164
  * record that disagrees with the file on disk stays a conflict regardless of `adopt`.
165
165
  */
166
- function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersion = 'unknown', managedResources = [], adopt = false, fsImpl = fs }: any): any {
166
+ function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersion = 'unknown', managedResources = [], adopt = false, fsImpl = fs } ) {
167
167
  const directory = agentDirectory({ scope, codexDir, projectRoot });
168
168
  const agents = discoverCodexAgents(sourceDir, fsImpl);
169
- const owned = new Map<string, any>(managedResources.filter((resource: any) => isOwnedResource(resource, scope)).map((resource: any) => [resource.identity, resource]));
170
- const changes: any[] = [];
171
- const conflicts: string[] = [];
172
- const nextManagedResources = managedResources.filter((resource: any) => !isOwnedResource(resource, scope));
169
+ const owned = new Map (managedResources.filter((resource ) => isOwnedResource(resource, scope)).map((resource ) => [resource.identity, resource]));
170
+ const changes = [];
171
+ const conflicts = [];
172
+ const nextManagedResources = managedResources.filter((resource ) => !isOwnedResource(resource, scope));
173
173
  for (const agent of agents) {
174
174
  const file = destinationFor(directory, agent.name);
175
175
  const identity = `agent:${agent.name}`;
@@ -191,13 +191,13 @@ function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersio
191
191
  };
192
192
  }
193
193
 
194
- function applyCodexAgents(plan: any, { dryRun = false, fsImpl = fs }: { dryRun?: boolean, fsImpl?: any } = {}): any {
194
+ function applyCodexAgents(plan , { dryRun = false, fsImpl = fs } = {}) {
195
195
  if (!plan.ok || dryRun || plan.status === 'unchanged') return { ...plan, applied: false };
196
196
  for (const change of plan.changes) atomicWrite(change.file, change.content, fsImpl);
197
197
  return { ...plan, applied: true };
198
198
  }
199
199
 
200
- function reconcileCodexAgents(options: any): any {
200
+ function reconcileCodexAgents(options ) {
201
201
  return applyCodexAgents(planCodexAgents(options), options);
202
202
  }
203
203
 
@@ -8,12 +8,12 @@
8
8
  const crypto = require('node:crypto');
9
9
  const fs = require('node:fs');
10
10
  const path = require('node:path');
11
- const { parseToml, stripComment, tableViolations } = require('../../helper/toml.ts');
11
+ const { parseToml, stripComment, tableViolations } = require('../../helper/toml.js');
12
12
 
13
13
  const CONFIG_NAME = 'config.toml';
14
14
  const CONFIG_KIND = 'configuration-entry';
15
15
 
16
- function configPath({ scope, codexDir, projectRoot, paths }: { scope?: string, codexDir?: string, projectRoot?: string, paths?: any } = {}): string {
16
+ function configPath({ scope, codexDir, projectRoot, paths } = {}) {
17
17
  // Preferred: the declared path resolved by the adapter (harnesses.json "paths".configFile).
18
18
  if (paths?.configFile) return paths.configFile;
19
19
  // Legacy derivation for direct callers passing raw scope inputs (tests, reconcileCodexConfig);
@@ -29,23 +29,23 @@ function configPath({ scope, codexDir, projectRoot, paths }: { scope?: string, c
29
29
  throw new Error(`Unsupported Codex configuration scope: ${scope}`);
30
30
  }
31
31
 
32
- function fingerprint(value: any): string {
32
+ function fingerprint(value ) {
33
33
  return `sha256:${crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
34
34
  }
35
35
 
36
- function commentSuffix(line: string): string {
36
+ function commentSuffix(line ) {
37
37
  const withoutComment = stripComment(line);
38
38
  return line.slice(withoutComment.length);
39
39
  }
40
40
 
41
- function renderValue(value: any): string {
41
+ function renderValue(value ) {
42
42
  if (typeof value === 'boolean' || typeof value === 'number') return String(value);
43
43
  if (typeof value === 'string') return JSON.stringify(value);
44
44
  throw new Error('Managed Codex config values must be boolean, number, or string');
45
45
  }
46
46
 
47
- function normaliseDesired(resources: any): Map<string, any> {
48
- const result = new Map<string, any>();
47
+ function normaliseDesired(resources ) {
48
+ const result = new Map ();
49
49
  for (const resource of resources || []) {
50
50
  if (!resource || (resource.kind && resource.kind !== CONFIG_KIND && resource.kind !== 'config-entry')) continue;
51
51
  const identity = resource.identity ?? resource.key;
@@ -62,49 +62,49 @@ function normaliseDesired(resources: any): Map<string, any> {
62
62
 
63
63
  /** The comment on a line DoFlow added to a table the user opened with no entry: it is how removal
64
64
  * knows to keep the user's header and restore the file's exact bytes. */
65
- function addedMarker(table: string): string { return ` # DoFlow added this to your [${table}] table`; }
65
+ function addedMarker(table ) { return ` # DoFlow added this to your [${table}] table`; }
66
66
 
67
67
  /** Where a new entry `<table>.<key>` goes in the parsed file: after the last entry of its table;
68
68
  * on the line right after the table's empty header, marked; as a root dotted line after the last
69
69
  * root dotted line of that table; refused (`valueEntry`) when the table or a parent of it is set as
70
70
  * a value, which TOML cannot extend; otherwise in a new table (no `at`). */
71
- function placeEntry(identity: string, value: any, parsed: any): any {
71
+ function placeEntry(identity , value , parsed ) {
72
72
  const parts = identity.split('.');
73
73
  const table = parts.slice(0, -1).join('.');
74
74
  const rendered = renderValue(value);
75
75
  const assignment = `${parts.at(-1)} = ${rendered}`;
76
- const entries: any[] = [...parsed.entries];
77
- const after = (items: any[]) => Math.max(...items.map(([, entry]: any) => entry.line)) + 1;
78
- const inTable = entries.filter(([, entry]: any) => entry.table === table);
76
+ const entries = [...parsed.entries];
77
+ const after = (items ) => Math.max(...items.map(([, entry] ) => entry.line)) + 1;
78
+ const inTable = entries.filter(([, entry] ) => entry.table === table);
79
79
  if (inTable.length) return { table, at: after(inTable), line: assignment };
80
- const header = parsed.headers.find((item: any) => item.table === table);
80
+ const header = parsed.headers.find((item ) => item.table === table);
81
81
  if (header) return { table, at: header.line + 1, line: `${assignment}${addedMarker(table)}` };
82
- const dotted = entries.filter(([key, entry]: any) => entry.table === '' && key.startsWith(`${table}.`));
82
+ const dotted = entries.filter(([key, entry] ) => entry.table === '' && key.startsWith(`${table}.`));
83
83
  if (dotted.length) return { table, at: after(dotted), line: `${identity} = ${rendered}` };
84
- const valueEntry = entries.find(([key]: any) => key === table || table.startsWith(`${key}.`));
84
+ const valueEntry = entries.find(([key] ) => key === table || table.startsWith(`${key}.`));
85
85
  if (valueEntry) return { table, valueEntry };
86
86
  return { table, line: assignment };
87
87
  }
88
88
 
89
89
  /** Whether a managed entry's line is one placeEntry put into the user's own table: marked, or
90
90
  * written as a dotted key outside its table. */
91
- function placedLine(identity: string, entry: any, lines: any[]): boolean {
91
+ function placedLine(identity , entry , lines ) {
92
92
  const table = identity.split('.').slice(0, -1).join('.');
93
93
  return entry.table !== table || lines[entry.line].trimEnd().endsWith(addedMarker(table));
94
94
  }
95
95
 
96
96
  /** A conflict when `content` breaks a TOML table rule that `original` did not already break, so a
97
97
  * file that was valid is never written invalid; null otherwise. */
98
- function newViolation({ file, original, content, identities }: { file: string, original: string, content: string, identities: string[] }): string | null {
99
- const refused = (reason: string) => `${file}: DoFlow cannot write ${identities.map((identity) => `'${identity}'`).join(', ')} without making the file invalid TOML (${reason}). Nothing was written.`;
98
+ function newViolation({ file, original, content, identities } ) {
99
+ const refused = (reason ) => `${file}: DoFlow cannot write ${identities.map((identity) => `'${identity}'`).join(', ')} without making the file invalid TOML (${reason}). Nothing was written.`;
100
100
  const before = tableViolations(original);
101
101
  let after;
102
- try { after = tableViolations(content); } catch (error: any) { return refused(error.message); }
103
- const added = after.find((item: any) => !before.some((known: any) => known.kind === item.kind && known.table === item.table));
102
+ try { after = tableViolations(content); } catch (error ) { return refused(error.message); }
103
+ const added = after.find((item ) => !before.some((known ) => known.kind === item.kind && known.table === item.table));
104
104
  return added ? refused(`${added.kind} '${added.table}' on line ${added.line}`) : null;
105
105
  }
106
106
 
107
- function isOwnedRecord(resource: any, scope: any): boolean {
107
+ function isOwnedRecord(resource , scope ) {
108
108
  return resource && resource.target === 'codex' && resource.scope === scope &&
109
109
  (resource.kind === CONFIG_KIND || resource.kind === 'config-entry') && typeof resource.identity === 'string';
110
110
  }
@@ -118,20 +118,20 @@ function isOwnedRecord(resource: any, scope: any): boolean {
118
118
  * entries written before the neutral ledger existed could never be re-adopted — the condition
119
119
  * blocking the install was the same condition an install had to run to clear.
120
120
  */
121
- function planCodexConfig({ file, scope, managedResources = [], desiredResources = [], adopt = false }: { file: string, scope?: any, managedResources?: any[], desiredResources?: any[], adopt?: boolean }): any {
121
+ function planCodexConfig({ file, scope, managedResources = [], desiredResources = [], adopt = false } ) {
122
122
  const original = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
123
123
  let parsed;
124
- try { parsed = parseToml(original); } catch (error: any) {
124
+ try { parsed = parseToml(original); } catch (error ) {
125
125
  return { ok: false, status: 'malformed', file, original, changes: [], conflicts: [error.message], managedResources };
126
126
  }
127
127
  let desired;
128
- try { desired = normaliseDesired(desiredResources); } catch (error: any) {
128
+ try { desired = normaliseDesired(desiredResources); } catch (error ) {
129
129
  return { ok: false, status: 'invalid-request', file, original, changes: [], conflicts: [error.message], managedResources };
130
130
  }
131
- const owned = new Map<string, any>(managedResources.filter((resource: any) => isOwnedRecord(resource, scope)).map((resource: any) => [resource.identity, resource]));
132
- const changes: any[] = [];
133
- const conflicts: string[] = [];
134
- const placements = new Map<string, any>();
131
+ const owned = new Map (managedResources.filter((resource ) => isOwnedRecord(resource, scope)).map((resource ) => [resource.identity, resource]));
132
+ const changes = [];
133
+ const conflicts = [];
134
+ const placements = new Map ();
135
135
  const all = new Set([...desired.keys(), ...owned.keys()]);
136
136
  for (const identity of all) {
137
137
  const entry = parsed.entries.get(identity);
@@ -164,10 +164,10 @@ function planCodexConfig({ file, scope, managedResources = [], desiredResources
164
164
  }
165
165
  if (conflicts.length) return { ok: false, status: 'conflict', file, original, changes: [], conflicts, managedResources };
166
166
  const nextLines = [...parsed.lines];
167
- for (const change of changes.filter((change: any) => change.type === 'update')) {
167
+ for (const change of changes.filter((change ) => change.type === 'update')) {
168
168
  const key = change.identity.split('.').at(-1);
169
169
  const originalLine = nextLines[change.line];
170
- const indentation = originalLine.match(/^\s*/)![0];
170
+ const indentation = originalLine.match(/^\s*/) [0];
171
171
  // A user comment beside an owned value is still user content. Retain it even though the
172
172
  // owned scalar changes, so reconciliation does not silently erase unrelated explanation.
173
173
  const suffix = commentSuffix(originalLine);
@@ -175,18 +175,18 @@ function planCodexConfig({ file, scope, managedResources = [], desiredResources
175
175
  }
176
176
  // A line DoFlow placed in the user's own table (marker or dotted form) is deleted outright and its
177
177
  // header kept, so the file returns to its exact bytes. Any other removal leaves a blank line.
178
- const removals = changes.filter((change: any) => change.type === 'remove');
179
- const placedInUserTable = removals.filter((change: any) => placedLine(change.identity, parsed.entries.get(change.identity), parsed.lines));
178
+ const removals = changes.filter((change ) => change.type === 'remove');
179
+ const placedInUserTable = removals.filter((change ) => placedLine(change.identity, parsed.entries.get(change.identity), parsed.lines));
180
180
  for (const change of removals) nextLines[change.line] = placedInUserTable.includes(change) ? null : '';
181
- dropEmptiedTables(nextLines, changes.filter((change: any) => !placedInUserTable.includes(change)), parsed.headers);
181
+ dropEmptiedTables(nextLines, changes.filter((change ) => !placedInUserTable.includes(change)), parsed.headers);
182
182
  // A new key must land INSIDE its own table. Appending at end-of-file only happens to be
183
183
  // correct when that table is the file's last one — otherwise the key silently joins whichever
184
184
  // table trails the file, so `features.hooks` written after an `[mcp_servers.x]` block becomes
185
185
  // `mcp_servers.x.hooks`. placeEntry chooses the spot; genuinely new tables are appended once
186
186
  // each, with all of their keys grouped under a single header.
187
- const insertions: { at: number, line: string }[] = [];
188
- const newTables = new Map<string, string[]>();
189
- for (const change of changes.filter((change: any) => change.type === 'create')) {
187
+ const insertions = [];
188
+ const newTables = new Map ();
189
+ for (const change of changes.filter((change ) => change.type === 'create')) {
190
190
  const placement = placements.get(change.identity);
191
191
  if (placement.at !== undefined) insertions.push({ at: placement.at, line: placement.line });
192
192
  else newTables.set(placement.table, [...(newTables.get(placement.table) || []), placement.line]);
@@ -204,11 +204,11 @@ function planCodexConfig({ file, scope, managedResources = [], desiredResources
204
204
  }
205
205
  let content = nextLines.join('\n');
206
206
  if (content && !content.endsWith('\n')) content += '\n';
207
- const invalid = newViolation({ file, original, content, identities: changes.map((change: any) => change.identity) });
207
+ const invalid = newViolation({ file, original, content, identities: changes.map((change ) => change.identity) });
208
208
  if (invalid) return { ok: false, status: 'conflict', file, original, changes: [], conflicts: [invalid], managedResources };
209
- const nextManagedResources = managedResources.filter((resource: any) => !isOwnedRecord(resource, scope) || desired.has(resource.identity));
209
+ const nextManagedResources = managedResources.filter((resource ) => !isOwnedRecord(resource, scope) || desired.has(resource.identity));
210
210
  for (const resource of desired.values()) {
211
- const index = nextManagedResources.findIndex((item: any) => isOwnedRecord(item, scope) && item.identity === resource.identity);
211
+ const index = nextManagedResources.findIndex((item ) => isOwnedRecord(item, scope) && item.identity === resource.identity);
212
212
  const updated = { ...resource, target: 'codex', scope, kind: CONFIG_KIND, fingerprint: fingerprint(resource.value) };
213
213
  if (index >= 0) nextManagedResources[index] = updated;
214
214
  else nextManagedResources.push(updated);
@@ -221,17 +221,17 @@ function planCodexConfig({ file, scope, managedResources = [], desiredResources
221
221
  * it was the file's last table, the blank line the table append puts before a new header goes too.
222
222
  * A table that also receives a created key, or whose header line carries a comment, is kept.
223
223
  * Headers come from the parser's own records, never from re-reading lines. */
224
- function dropEmptiedTables(lines: any[], changes: any[], headerRecords: any[]): void {
225
- const tableOf = (identity: string) => identity.split('.').slice(0, -1).join('.');
226
- const removedTables = new Map<number, string>(changes.filter((change) => change.type === 'remove').map((change): [number, string] => [change.line, tableOf(change.identity)]));
227
- const createdTables = new Set<string>(changes.filter((change) => change.type === 'create').map((change) => tableOf(change.identity)));
228
- const headers: number[] = headerRecords.map((header) => header.line);
224
+ function dropEmptiedTables(lines , changes , headerRecords ) {
225
+ const tableOf = (identity ) => identity.split('.').slice(0, -1).join('.');
226
+ const removedTables = new Map (changes.filter((change) => change.type === 'remove').map((change) => [change.line, tableOf(change.identity)]));
227
+ const createdTables = new Set (changes.filter((change) => change.type === 'create').map((change) => tableOf(change.identity)));
228
+ const headers = headerRecords.map((header) => header.line);
229
229
  headers.forEach((start, position) => {
230
230
  const end = headers[position + 1] ?? lines.length;
231
231
  if (stripComment(lines[start]) !== lines[start]) return;
232
- const body = lines.slice(start + 1, end).map((_: any, offset: number) => start + 1 + offset);
232
+ const body = lines.slice(start + 1, end).map((_ , offset ) => start + 1 + offset);
233
233
  const removedHere = body.filter((index) => removedTables.has(index));
234
- if (!removedHere.length || removedHere.some((index) => createdTables.has(removedTables.get(index) as string))) return;
234
+ if (!removedHere.length || removedHere.some((index) => createdTables.has(removedTables.get(index) ))) return;
235
235
  if (body.some((index) => !removedTables.has(index) && lines[index] !== null && lines[index].trim() !== '')) return;
236
236
  lines[start] = null;
237
237
  for (const index of removedHere) lines[index] = null;
@@ -241,7 +241,7 @@ function dropEmptiedTables(lines: any[], changes: any[], headerRecords: any[]):
241
241
 
242
242
  /** Replace a file through a sibling temporary file, keeping the existing file's permission bits so
243
243
  * a config kept private stays so. A file this creates gets the process's default mode. */
244
- function atomicWrite(file: string, content: string, fsImpl: any = fs): void {
244
+ function atomicWrite(file , content , fsImpl = fs) {
245
245
  fsImpl.mkdirSync(path.dirname(file), { recursive: true });
246
246
  const mode = fsImpl.existsSync(file) ? fsImpl.statSync(file).mode & 0o7777 : null;
247
247
  const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
@@ -254,7 +254,7 @@ function atomicWrite(file: string, content: string, fsImpl: any = fs): void {
254
254
  }
255
255
  }
256
256
 
257
- function applyCodexConfig(plan: any, { dryRun = false, fsImpl = fs }: { dryRun?: boolean, fsImpl?: any } = {}): any {
257
+ function applyCodexConfig(plan , { dryRun = false, fsImpl = fs } = {}) {
258
258
  if (!plan.ok) return { ...plan, applied: false };
259
259
  if (dryRun || plan.status === 'unchanged') return { ...plan, applied: false };
260
260
  // A removal that leaves nothing behind deletes the file rather than leaving it empty.
@@ -263,7 +263,7 @@ function applyCodexConfig(plan: any, { dryRun = false, fsImpl = fs }: { dryRun?:
263
263
  return { ...plan, applied: true };
264
264
  }
265
265
 
266
- function reconcileCodexConfig(options: any): any {
266
+ function reconcileCodexConfig(options ) {
267
267
  const file = options.file ?? configPath(options);
268
268
  const plan = planCodexConfig({ ...options, file });
269
269
  return applyCodexConfig(plan, options);
@@ -7,19 +7,19 @@
7
7
  // that are not executable. Trust is reported as a prerequisite rather than being bypassed.
8
8
  const fs = require('node:fs');
9
9
  const path = require('node:path');
10
- const { verifyHookCommands } = require('../hook-commands.ts');
11
- const { mergeHooks } = require('../../helper/settings-merge.ts');
12
- const { atomicWrite } = require('./config.ts');
10
+ const { verifyHookCommands } = require('../hook-commands.js');
11
+ const { mergeHooks } = require('../../helper/settings-merge.js');
12
+ const { atomicWrite } = require('./config.js');
13
13
 
14
14
  const SUPPORTED_EVENTS = new Set([
15
15
  'PreToolUse', 'PermissionRequest', 'PostToolUse', 'PreCompact', 'PostCompact',
16
16
  'UserPromptSubmit', 'SubagentStop', 'Stop', 'SessionStart', 'SubagentStart', 'SessionEnd',
17
17
  ]);
18
18
 
19
- function validationError(errors: string[], message: string): void { errors.push(message); }
19
+ function validationError(errors , message ) { errors.push(message); }
20
20
 
21
- function validateHooksConfig(config: any): { ok: boolean, errors: string[], events: string[] } {
22
- const errors: string[] = [];
21
+ function validateHooksConfig(config ) {
22
+ const errors = [];
23
23
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
24
24
  return { ok: false, errors: ['Hook configuration must be an object'], events: [] };
25
25
  }
@@ -35,7 +35,7 @@ function validateHooksConfig(config: any): { ok: boolean, errors: string[], even
35
35
  validationError(errors, `Hook event '${event}' requires a non-empty array of matcher groups`);
36
36
  continue;
37
37
  }
38
- groups.forEach((group: any, groupIndex: number) => {
38
+ groups.forEach((group , groupIndex ) => {
39
39
  if (!group || typeof group !== 'object' || Array.isArray(group)) {
40
40
  validationError(errors, `Hook event '${event}' group ${groupIndex} must be an object`);
41
41
  return;
@@ -47,7 +47,7 @@ function validateHooksConfig(config: any): { ok: boolean, errors: string[], even
47
47
  validationError(errors, `Hook event '${event}' group ${groupIndex} requires a non-empty hooks array`);
48
48
  return;
49
49
  }
50
- group.hooks.forEach((handler: any, handlerIndex: number) => {
50
+ group.hooks.forEach((handler , handlerIndex ) => {
51
51
  const label = `Hook event '${event}' group ${groupIndex} handler ${handlerIndex}`;
52
52
  if (!handler || typeof handler !== 'object' || Array.isArray(handler)) {
53
53
  validationError(errors, `${label} must be an object`);
@@ -73,9 +73,9 @@ function validateHooksConfig(config: any): { ok: boolean, errors: string[], even
73
73
  }
74
74
 
75
75
  /** Maps Claude guardrail intent, never fabricating a Codex equivalent for unavailable events. */
76
- function classifyClaudeGuardrails(claudeHooks: any = {}): { supported: any[], unsupported: any[] } {
77
- const supported: any[] = [];
78
- const unsupported: any[] = [];
76
+ function classifyClaudeGuardrails(claudeHooks = {}) {
77
+ const supported = [];
78
+ const unsupported = [];
79
79
  for (const [event, groups] of Object.entries(claudeHooks || {})) {
80
80
  const item = { sourceEvent: event, targetEvent: event, groups: Array.isArray(groups) ? groups.length : 0 };
81
81
  if (SUPPORTED_EVENTS.has(event)) supported.push(item);
@@ -84,7 +84,7 @@ function classifyClaudeGuardrails(claudeHooks: any = {}): { supported: any[], un
84
84
  return { supported, unsupported };
85
85
  }
86
86
 
87
- function destinationFor(context: any = {}): string {
87
+ function destinationFor(context = {}) {
88
88
  // Preferred: the declared path resolved by the adapter (harnesses.json "paths".hooksFile).
89
89
  if (context.paths?.hooksFile) return context.paths.hooksFile;
90
90
  // Legacy derivation for direct callers passing raw scope inputs; kept byte-compatible with the
@@ -102,20 +102,20 @@ function destinationFor(context: any = {}): string {
102
102
  throw new Error('A hook destination or Codex scope is required');
103
103
  }
104
104
 
105
- function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext = {}, trusted = false, fsImpl = fs }: any = {}): any {
105
+ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext = {}, trusted = false, fsImpl = fs } = {}) {
106
106
  let desired = config;
107
107
  try {
108
108
  if (!desired && sourceFile) desired = JSON.parse(fsImpl.readFileSync(sourceFile, 'utf8'));
109
- } catch (error: any) {
109
+ } catch (error ) {
110
110
  return { ok: false, status: 'malformed', changes: [], errors: [`Could not parse ${sourceFile}: ${error.message}`] };
111
111
  }
112
112
  const schema = validateHooksConfig(desired);
113
113
  let destination;
114
- try { destination = destinationFor(destinationContext); } catch (error: any) {
114
+ try { destination = destinationFor(destinationContext); } catch (error ) {
115
115
  return { ok: false, status: 'invalid-request', changes: [], errors: [...schema.errors, error.message] };
116
116
  }
117
117
  const commands = verifyHookCommands(desired || {}, { scriptsDir: sourceHooksDir, trusted, fsImpl });
118
- const errors = [...schema.errors, ...commands.checks.filter((check: any) => !check.ok).map((check: any) => `${check.event}: ${check.reason}${check.script ? ` (${check.script})` : ''}`)];
118
+ const errors = [...schema.errors, ...commands.checks.filter((check ) => !check.ok).map((check ) => `${check.event}: ${check.reason}${check.script ? ` (${check.script})` : ''}`)];
119
119
  let mergedDesired = desired;
120
120
  const original = fsImpl.existsSync(destination) ? fsImpl.readFileSync(destination, 'utf8') : '';
121
121
  if (original.trim()) {
@@ -136,19 +136,19 @@ function planCodexHooks({ config, sourceFile, sourceHooksDir, destinationContext
136
136
  || path.join(path.dirname(destination), 'hooks');
137
137
  // The scripts deployCodexHooks copies beside hooks.json, so a backup of this change holds them too.
138
138
  const companionTargets = [...new Set([
139
- ...commands.checks.map((check: any) => path.join(destinationHooksDir, check.script)),
140
- ...shippedHookScripts(sourceHooksDir, fsImpl).map((name: string) => path.join(destinationHooksDir, name)),
139
+ ...commands.checks.map((check ) => path.join(destinationHooksDir, check.script)),
140
+ ...shippedHookScripts(sourceHooksDir, fsImpl).map((name ) => path.join(destinationHooksDir, name)),
141
141
  ])].sort();
142
142
  return { ok: true, status: changed ? 'change' : 'unchanged', destination, original, content,
143
143
  changes: changed ? [{ type: fsImpl.existsSync(destination) ? 'update' : 'create', file: destination, companionTargets }] : [],
144
144
  errors: [], commands, trust: commands.trust, scriptsDir: sourceHooksDir, destinationHooksDir };
145
145
  }
146
146
 
147
- function deployCodexHooks(plan: any, { dryRun = false, fsImpl = fs }: { dryRun?: boolean, fsImpl?: any } = {}): any {
147
+ function deployCodexHooks(plan , { dryRun = false, fsImpl = fs } = {}) {
148
148
  if (!plan.ok) return { ...plan, applied: false };
149
149
  if (dryRun || plan.status === 'unchanged') return { ...plan, applied: false };
150
150
  atomicWrite(plan.destination, plan.content, fsImpl);
151
- const deployed = new Set<string>();
151
+ const deployed = new Set ();
152
152
  for (const check of plan.commands.checks) {
153
153
  const target = path.join(plan.destinationHooksDir, check.script);
154
154
  fsImpl.mkdirSync(path.dirname(target), { recursive: true });
@@ -174,16 +174,16 @@ function deployCodexHooks(plan: any, { dryRun = false, fsImpl = fs }: { dryRun?:
174
174
 
175
175
  /** Every file deployCodexHooks copies beside hooks.json: each regular, non-JSON file in the
176
176
  * shipped scripts directory (the command-named scripts are among them). */
177
- function shippedHookScripts(scriptsDir: any, fsImpl: any = fs): string[] {
177
+ function shippedHookScripts(scriptsDir , fsImpl = fs) {
178
178
  if (!scriptsDir || !fsImpl.existsSync(scriptsDir)) return [];
179
179
  return fsImpl.readdirSync(scriptsDir)
180
- .filter((name: string) => !name.endsWith('.json') && fsImpl.statSync(path.join(scriptsDir, name)).isFile());
180
+ .filter((name ) => !name.endsWith('.json') && fsImpl.statSync(path.join(scriptsDir, name)).isFile());
181
181
  }
182
182
 
183
183
  /** Undo deployCodexHooks' script copies. The ledger records only hooks.json, so ownership of a
184
184
  * script is proven by its bytes: one still identical to the shipped source is deleted, one the
185
185
  * user edited or added stays. The directory goes only once it is empty. */
186
- function removeCodexHookScripts({ scriptsDir, destinationHooksDir }: { scriptsDir?: string, destinationHooksDir?: string } = {}, { dryRun = false, fsImpl = fs }: { dryRun?: boolean, fsImpl?: any } = {}): { removed: number } {
186
+ function removeCodexHookScripts({ scriptsDir, destinationHooksDir } = {}, { dryRun = false, fsImpl = fs } = {}) {
187
187
  if (!destinationHooksDir || !fsImpl.existsSync(destinationHooksDir)) return { removed: 0 };
188
188
  let removed = 0;
189
189
  for (const name of shippedHookScripts(scriptsDir, fsImpl)) {