@doxbrix/doxloop 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/CHANGELOG.md +1145 -0
  2. package/README.md +422 -227
  3. package/assets/doxbrix-preview.css +77 -15
  4. package/contracts/agent-events-v1.schema.json +1 -0
  5. package/contracts/coverage-v1.schema.json +59 -0
  6. package/contracts/drift-v1.schema.json +1 -0
  7. package/contracts/evaluation-v1.schema.json +62 -0
  8. package/contracts/quality-codes-v1.json +1 -0
  9. package/contracts/quality-report-v1.schema.json +1 -0
  10. package/contracts/validation-v1.schema.json +1 -0
  11. package/dist/agent-failure.d.ts +36 -0
  12. package/dist/agent-failure.js +98 -0
  13. package/dist/agent-isolation.d.ts +17 -0
  14. package/dist/agent-isolation.js +94 -0
  15. package/dist/agent-log.d.ts +197 -0
  16. package/dist/agent-log.js +1094 -0
  17. package/dist/agent-process.d.ts +63 -0
  18. package/dist/agent-process.js +128 -0
  19. package/dist/agent-reply.d.ts +47 -0
  20. package/dist/agent-reply.js +315 -0
  21. package/dist/agents.d.ts +24 -5
  22. package/dist/agents.js +60 -9
  23. package/dist/api-coverage.d.ts +11 -0
  24. package/dist/api-coverage.js +72 -0
  25. package/dist/application-probe.d.ts +28 -0
  26. package/dist/application-probe.js +93 -0
  27. package/dist/application-url.d.ts +14 -0
  28. package/dist/application-url.js +26 -0
  29. package/dist/args.js +15 -0
  30. package/dist/artifact-deploy.d.ts +9 -0
  31. package/dist/artifact-deploy.js +28 -8
  32. package/dist/assets.d.ts +72 -0
  33. package/dist/assets.js +382 -0
  34. package/dist/auth.d.ts +10 -0
  35. package/dist/auth.js +38 -9
  36. package/dist/author.d.ts +206 -4
  37. package/dist/author.js +1650 -52
  38. package/dist/authoring-batches.d.ts +190 -0
  39. package/dist/authoring-batches.js +489 -0
  40. package/dist/authoring-postpass.d.ts +79 -0
  41. package/dist/authoring-postpass.js +1289 -0
  42. package/dist/authoring-progress.d.ts +95 -0
  43. package/dist/authoring-progress.js +269 -0
  44. package/dist/autosync.d.ts +56 -0
  45. package/dist/autosync.js +442 -0
  46. package/dist/batch-artifacts.d.ts +81 -0
  47. package/dist/batch-artifacts.js +412 -0
  48. package/dist/batch-limits.d.ts +37 -0
  49. package/dist/batch-limits.js +80 -0
  50. package/dist/branding.d.ts +41 -0
  51. package/dist/branding.js +229 -0
  52. package/dist/bundle-upload.d.ts +26 -0
  53. package/dist/bundle-upload.js +99 -0
  54. package/dist/capture-auth.d.ts +95 -0
  55. package/dist/capture-auth.js +204 -0
  56. package/dist/capture.d.ts +48 -0
  57. package/dist/capture.js +20 -1
  58. package/dist/cli.js +839 -32
  59. package/dist/content-links.d.ts +10 -0
  60. package/dist/content-links.js +49 -0
  61. package/dist/contract-validation.d.ts +4 -0
  62. package/dist/contract-validation.js +25 -0
  63. package/dist/coverage-actions.d.ts +8 -0
  64. package/dist/coverage-actions.js +71 -0
  65. package/dist/coverage-resolutions.d.ts +16 -0
  66. package/dist/coverage-resolutions.js +58 -0
  67. package/dist/db.d.ts +37 -0
  68. package/dist/db.js +288 -0
  69. package/dist/demo.d.ts +12 -0
  70. package/dist/demo.js +122 -0
  71. package/dist/deploy-credentials.d.ts +7 -0
  72. package/dist/deploy-credentials.js +76 -0
  73. package/dist/deploy-targets/github-pages.d.ts +3 -0
  74. package/dist/deploy-targets/github-pages.js +100 -0
  75. package/dist/deploy-targets/index.d.ts +8 -0
  76. package/dist/deploy-targets/index.js +78 -0
  77. package/dist/deploy-targets/netlify.d.ts +3 -0
  78. package/dist/deploy-targets/netlify.js +49 -0
  79. package/dist/deploy-targets/types.d.ts +32 -0
  80. package/dist/deploy-targets/types.js +2 -0
  81. package/dist/deploy-targets/vercel.d.ts +3 -0
  82. package/dist/deploy-targets/vercel.js +95 -0
  83. package/dist/deploy.d.ts +9 -0
  84. package/dist/deploy.js +87 -13
  85. package/dist/deterministic-capture.d.ts +90 -0
  86. package/dist/deterministic-capture.js +435 -0
  87. package/dist/direct-edit.d.ts +40 -0
  88. package/dist/direct-edit.js +159 -0
  89. package/dist/docs-crawl.d.ts +86 -0
  90. package/dist/docs-crawl.js +536 -0
  91. package/dist/docs-site.d.ts +49 -0
  92. package/dist/docs-site.js +233 -0
  93. package/dist/doctor.js +8 -0
  94. package/dist/documentation-collections.d.ts +23 -0
  95. package/dist/documentation-collections.js +191 -0
  96. package/dist/documentation-plan.d.ts +253 -0
  97. package/dist/documentation-plan.js +2563 -0
  98. package/dist/doxbrix-build.d.ts +19 -0
  99. package/dist/doxbrix-build.js +167 -0
  100. package/dist/doxbrix-markdown.d.ts +9 -0
  101. package/dist/doxbrix-markdown.js +115 -14
  102. package/dist/drift.d.ts +10 -0
  103. package/dist/drift.js +164 -0
  104. package/dist/evaluation.d.ts +46 -0
  105. package/dist/evaluation.js +113 -0
  106. package/dist/evidence-pack.d.ts +47 -0
  107. package/dist/evidence-pack.js +358 -0
  108. package/dist/evidence.d.ts +25 -0
  109. package/dist/evidence.js +175 -0
  110. package/dist/fs.d.ts +8 -2
  111. package/dist/fs.js +41 -11
  112. package/dist/generator-api.d.ts +89 -0
  113. package/dist/generator-preflight.d.ts +27 -0
  114. package/dist/generator-preflight.js +105 -0
  115. package/dist/generator-runtime.d.ts +7 -0
  116. package/dist/generator-runtime.js +17 -1
  117. package/dist/generators.d.ts +24 -2
  118. package/dist/generators.js +54 -1
  119. package/dist/git-delivery.d.ts +17 -0
  120. package/dist/git-delivery.js +123 -0
  121. package/dist/globs.d.ts +16 -0
  122. package/dist/globs.js +65 -0
  123. package/dist/glossary.d.ts +26 -0
  124. package/dist/glossary.js +179 -0
  125. package/dist/history.d.ts +106 -0
  126. package/dist/history.js +600 -0
  127. package/dist/html-markdown.d.ts +46 -0
  128. package/dist/html-markdown.js +423 -0
  129. package/dist/interactive.js +3 -0
  130. package/dist/job-events.d.ts +74 -0
  131. package/dist/job-events.js +377 -0
  132. package/dist/keep-awake.d.ts +50 -0
  133. package/dist/keep-awake.js +123 -0
  134. package/dist/local-source-snapshot.d.ts +20 -0
  135. package/dist/local-source-snapshot.js +61 -0
  136. package/dist/mintlify-detect.d.ts +3 -0
  137. package/dist/mintlify-detect.js +18 -0
  138. package/dist/mintlify-import.d.ts +75 -0
  139. package/dist/mintlify-import.js +190 -0
  140. package/dist/navigation.d.ts +98 -0
  141. package/dist/navigation.js +310 -0
  142. package/dist/openapi.d.ts +60 -0
  143. package/dist/openapi.js +439 -0
  144. package/dist/page-editor-bridge.d.ts +3 -0
  145. package/dist/page-editor-bridge.js +109 -0
  146. package/dist/page-editor-preview.d.ts +10 -0
  147. package/dist/page-editor-preview.js +55 -0
  148. package/dist/page-extension.d.ts +9 -0
  149. package/dist/page-extension.js +15 -0
  150. package/dist/page-metadata.d.ts +28 -0
  151. package/dist/page-metadata.js +166 -0
  152. package/dist/page-operations.d.ts +34 -0
  153. package/dist/page-operations.js +215 -0
  154. package/dist/page-routes.d.ts +4 -0
  155. package/dist/page-routes.js +61 -0
  156. package/dist/pages.d.ts +20 -0
  157. package/dist/pages.js +184 -0
  158. package/dist/plan-generator.d.ts +3 -0
  159. package/dist/plan-generator.js +21 -0
  160. package/dist/plan-navigation.d.ts +11 -0
  161. package/dist/plan-navigation.js +30 -0
  162. package/dist/planning-captures.d.ts +20 -0
  163. package/dist/planning-captures.js +143 -0
  164. package/dist/planning-research.d.ts +135 -0
  165. package/dist/planning-research.js +472 -0
  166. package/dist/planning-triage.d.ts +23 -0
  167. package/dist/planning-triage.js +131 -0
  168. package/dist/preview.d.ts +24 -0
  169. package/dist/preview.js +280 -29
  170. package/dist/project-detect.d.ts +36 -0
  171. package/dist/project-detect.js +251 -0
  172. package/dist/project-import.d.ts +54 -0
  173. package/dist/project-import.js +157 -0
  174. package/dist/project-lock.d.ts +6 -0
  175. package/dist/project-lock.js +96 -0
  176. package/dist/project-registry.d.ts +25 -0
  177. package/dist/project-registry.js +79 -0
  178. package/dist/project.d.ts +35 -3
  179. package/dist/project.js +388 -38
  180. package/dist/prompts.d.ts +9 -0
  181. package/dist/prompts.js +25 -0
  182. package/dist/proposal-replay.d.ts +32 -0
  183. package/dist/proposal-replay.js +99 -0
  184. package/dist/quality-claims.d.ts +8 -0
  185. package/dist/quality-claims.js +168 -0
  186. package/dist/quality-config.d.ts +5 -0
  187. package/dist/quality-config.js +84 -0
  188. package/dist/quality-contract.d.ts +37 -0
  189. package/dist/quality-contract.js +46 -0
  190. package/dist/quality-examples.d.ts +4 -0
  191. package/dist/quality-examples.js +233 -0
  192. package/dist/quality-gates.d.ts +16 -0
  193. package/dist/quality-gates.js +192 -0
  194. package/dist/quality-links.d.ts +7 -0
  195. package/dist/quality-links.js +149 -0
  196. package/dist/quality-lint.d.ts +6 -0
  197. package/dist/quality-lint.js +124 -0
  198. package/dist/quality-rendered.d.ts +11 -0
  199. package/dist/quality-rendered.js +222 -0
  200. package/dist/quality-schema.d.ts +3 -0
  201. package/dist/quality-schema.js +71 -0
  202. package/dist/release-notes.d.ts +44 -0
  203. package/dist/release-notes.js +183 -0
  204. package/dist/remote-monitor.d.ts +16 -0
  205. package/dist/remote-monitor.js +74 -0
  206. package/dist/remote-source.d.ts +34 -0
  207. package/dist/remote-source.js +426 -0
  208. package/dist/review-diff.d.ts +82 -0
  209. package/dist/review-diff.js +400 -0
  210. package/dist/review-learning.d.ts +11 -0
  211. package/dist/review-learning.js +60 -0
  212. package/dist/review-render.d.ts +38 -0
  213. package/dist/review-render.js +224 -0
  214. package/dist/review-report.d.ts +9 -0
  215. package/dist/review-report.js +89 -0
  216. package/dist/review-ui.d.ts +14 -0
  217. package/dist/review-ui.js +1248 -0
  218. package/dist/schedule.d.ts +78 -0
  219. package/dist/schedule.js +480 -0
  220. package/dist/screen-capture-provider.d.ts +52 -0
  221. package/dist/screen-capture-provider.js +218 -0
  222. package/dist/screenshot-workflow.d.ts +167 -0
  223. package/dist/screenshot-workflow.js +1237 -0
  224. package/dist/settings.d.ts +1 -1
  225. package/dist/settings.js +95 -7
  226. package/dist/site-export.d.ts +18 -0
  227. package/dist/site-export.js +87 -0
  228. package/dist/source-connectors.d.ts +33 -0
  229. package/dist/source-connectors.js +268 -0
  230. package/dist/source-discovery.d.ts +132 -0
  231. package/dist/source-discovery.js +823 -0
  232. package/dist/source-intelligence.d.ts +9 -0
  233. package/dist/source-intelligence.js +306 -0
  234. package/dist/sync-review.d.ts +28 -0
  235. package/dist/sync-review.js +264 -0
  236. package/dist/sync-runs.d.ts +192 -0
  237. package/dist/sync-runs.js +2244 -0
  238. package/dist/sync.d.ts +35 -0
  239. package/dist/sync.js +298 -32
  240. package/dist/text-diff.d.ts +9 -0
  241. package/dist/text-diff.js +59 -0
  242. package/dist/types.d.ts +946 -1
  243. package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
  244. package/dist/ui/assets/index-BHBYU2aG.css +1 -0
  245. package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
  246. package/dist/ui/index.html +18 -0
  247. package/dist/ui-server.d.ts +83 -0
  248. package/dist/ui-server.js +3532 -0
  249. package/dist/usage-budget.d.ts +28 -0
  250. package/dist/usage-budget.js +90 -0
  251. package/dist/validation.d.ts +25 -1
  252. package/dist/validation.js +312 -26
  253. package/dist/workspace-tools.d.ts +54 -0
  254. package/dist/workspace-tools.js +123 -0
  255. package/docs/agent-compatibility.md +49 -32
  256. package/docs/ci-and-automation.md +105 -66
  257. package/docs/doxbrix-http-api.md +8 -1
  258. package/docs/existing-documentation.md +80 -0
  259. package/docs/generation-performance.md +108 -0
  260. package/docs/generator-authoring.md +68 -5
  261. package/docs/generator-selection.md +50 -13
  262. package/docs/mintlify-import.md +71 -0
  263. package/docs/openapi-security.md +25 -0
  264. package/docs/project-format.md +331 -36
  265. package/docs/release-quality.md +158 -0
  266. package/docs/releasing.md +72 -0
  267. package/docs/review-workflows.md +51 -0
  268. package/docs/security-model.md +102 -39
  269. package/docs/troubleshooting.md +226 -93
  270. package/package.json +46 -15
  271. package/scripts/test-auto-screenshot.mjs +172 -0
  272. package/skills/doxloop-authoring/SKILL.md +242 -361
  273. package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
  274. package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
  275. package/skills/doxloop-authoring/references/page-depth.md +169 -0
  276. package/skills/doxloop-authoring/references/project-format.md +106 -7
  277. package/skills/doxloop-authoring/references/quality.md +10 -0
  278. package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
  279. package/skills/doxloop-authoring/references/screenshots.md +149 -235
  280. package/skills/doxloop-authoring/references/workflows.md +76 -0
  281. package/skills/doxloop-doxbrix/SKILL.md +38 -22
  282. package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
  283. package/skills/doxloop-doxbrix/references/components.md +30 -3
  284. package/skills/doxloop-doxbrix/references/manifest.md +3 -2
  285. package/vendor/doxbrix-import/LICENSE +202 -0
  286. package/vendor/doxbrix-import/README.md +17 -0
  287. package/vendor/doxbrix-import/UPSTREAM.json +47 -0
  288. package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
  289. package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
  290. package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
  291. package/vendor/doxbrix-import/dist/docs/import.js +147 -0
  292. package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
  293. package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
  294. package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
  295. package/vendor/doxbrix-import/dist/docs/project.js +77 -0
  296. package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
  297. package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
  298. package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
  299. package/vendor/doxbrix-import/dist/importer.js +1567 -0
  300. package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
  301. package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
  302. package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
  303. package/vendor/doxbrix-import/dist/safe-path.js +47 -0
  304. package/dist/agents.d.ts.map +0 -1
  305. package/dist/agents.js.map +0 -1
  306. package/dist/args.d.ts.map +0 -1
  307. package/dist/args.js.map +0 -1
  308. package/dist/artifact-deploy.d.ts.map +0 -1
  309. package/dist/artifact-deploy.js.map +0 -1
  310. package/dist/auth.d.ts.map +0 -1
  311. package/dist/auth.js.map +0 -1
  312. package/dist/author.d.ts.map +0 -1
  313. package/dist/author.js.map +0 -1
  314. package/dist/capture.d.ts.map +0 -1
  315. package/dist/capture.js.map +0 -1
  316. package/dist/cli.d.ts.map +0 -1
  317. package/dist/cli.js.map +0 -1
  318. package/dist/deploy.d.ts.map +0 -1
  319. package/dist/deploy.js.map +0 -1
  320. package/dist/deployment-visibility.d.ts.map +0 -1
  321. package/dist/deployment-visibility.js.map +0 -1
  322. package/dist/doctor.d.ts.map +0 -1
  323. package/dist/doctor.js.map +0 -1
  324. package/dist/doxbrix-markdown.d.ts.map +0 -1
  325. package/dist/doxbrix-markdown.js.map +0 -1
  326. package/dist/errors.d.ts.map +0 -1
  327. package/dist/errors.js.map +0 -1
  328. package/dist/fs.d.ts.map +0 -1
  329. package/dist/fs.js.map +0 -1
  330. package/dist/generator-api.d.ts.map +0 -1
  331. package/dist/generator-api.js.map +0 -1
  332. package/dist/generator-manager.d.ts.map +0 -1
  333. package/dist/generator-manager.js.map +0 -1
  334. package/dist/generator-runtime.d.ts.map +0 -1
  335. package/dist/generator-runtime.js.map +0 -1
  336. package/dist/generators.d.ts.map +0 -1
  337. package/dist/generators.js.map +0 -1
  338. package/dist/interactive.d.ts.map +0 -1
  339. package/dist/interactive.js.map +0 -1
  340. package/dist/preview.d.ts.map +0 -1
  341. package/dist/preview.js.map +0 -1
  342. package/dist/progress.d.ts.map +0 -1
  343. package/dist/progress.js.map +0 -1
  344. package/dist/project.d.ts.map +0 -1
  345. package/dist/project.js.map +0 -1
  346. package/dist/prompts.d.ts.map +0 -1
  347. package/dist/prompts.js.map +0 -1
  348. package/dist/settings.d.ts.map +0 -1
  349. package/dist/settings.js.map +0 -1
  350. package/dist/sync.d.ts.map +0 -1
  351. package/dist/sync.js.map +0 -1
  352. package/dist/types.d.ts.map +0 -1
  353. package/dist/types.js.map +0 -1
  354. package/dist/validation.d.ts.map +0 -1
  355. package/dist/validation.js.map +0 -1
  356. package/dist/version.d.ts.map +0 -1
  357. package/dist/version.js.map +0 -1
@@ -0,0 +1,377 @@
1
+ const EVENT_PREFIX = 'DOXLOOP_EVENT ';
2
+ /**
3
+ * Every line the control center keeps for a job carries the local wall-clock
4
+ * time it arrived, so a reader can see which step took how long. Stage and
5
+ * outcome events already carry their own `at` and stay machine-readable.
6
+ */
7
+ const JOB_LINE_STAMP = /^\d{2}:\d{2}:\d{2} /;
8
+ export function formatJobLineStamp(at = new Date()) {
9
+ const pad = (value) => String(value).padStart(2, '0');
10
+ return `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}`;
11
+ }
12
+ /** Prefix a log line with the time it arrived; event lines and already stamped lines pass through. */
13
+ export function stampJobLine(line, at = new Date()) {
14
+ if (line.startsWith(EVENT_PREFIX) || JOB_LINE_STAMP.test(line))
15
+ return line;
16
+ return `${formatJobLineStamp(at)} ${line}`;
17
+ }
18
+ export function stripJobLineStamp(line) {
19
+ return line.replace(JOB_LINE_STAMP, '');
20
+ }
21
+ export function jobOutcomeLine(outcome, at = new Date().toISOString()) {
22
+ return `${EVENT_PREFIX}${JSON.stringify({ schemaVersion: 1, type: 'outcome', ...outcome, at })}`;
23
+ }
24
+ /** Report a job outcome to a control center that started this CLI; a terminal already saw the text. */
25
+ export function emitJobOutcome(outcome) {
26
+ if (process.stdout.isTTY)
27
+ return;
28
+ process.stdout.write(`${jobOutcomeLine(outcome)}\n`);
29
+ }
30
+ export function parseJobOutcomeLine(line) {
31
+ if (!line.startsWith(EVENT_PREFIX))
32
+ return undefined;
33
+ try {
34
+ const parsed = JSON.parse(line.slice(EVENT_PREFIX.length));
35
+ const raw = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
36
+ return raw?.schemaVersion === 1 && raw.type === 'outcome' ? parseJobOutcome(raw) : undefined;
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
42
+ export function parseJobOutcome(raw) {
43
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
44
+ return undefined;
45
+ const outcome = raw;
46
+ if (outcome.kind !== 'sync' || typeof outcome.message !== 'string')
47
+ return undefined;
48
+ if (!['current', 'stale', 'proposal', 'skipped', 'failed', 'unknown'].includes(String(outcome.status)))
49
+ return undefined;
50
+ return {
51
+ kind: 'sync',
52
+ status: outcome.status,
53
+ message: outcome.message,
54
+ ...(typeof outcome.pages === 'number' && Number.isInteger(outcome.pages) && outcome.pages >= 0 ? { pages: outcome.pages } : {}),
55
+ ...(typeof outcome.proposalId === 'string' ? { proposalId: outcome.proposalId } : {}),
56
+ };
57
+ }
58
+ /** Serialize one stage event as the line the UI server reads from a CLI job. */
59
+ export function workflowStageLine(id, label, status, progress, at = new Date().toISOString()) {
60
+ return `${EVENT_PREFIX}${JSON.stringify({ schemaVersion: 1, type: 'stage', id, label, status, ...(progress ? { progress } : {}), at })}`;
61
+ }
62
+ /**
63
+ * Report a workflow stage. A CLI run started by the control center streams
64
+ * machine-readable events; a person watching a terminal gets a plain line.
65
+ */
66
+ export function emitWorkflowStage(id, label, status, progress) {
67
+ if (process.stdout.isTTY) {
68
+ if (status === 'pending')
69
+ return;
70
+ const mark = status === 'running' ? '…' : status === 'completed' ? '✓' : '✗';
71
+ process.stdout.write(`${mark} ${label}${formatStageProgress(progress)}\n`);
72
+ return;
73
+ }
74
+ process.stdout.write(`${workflowStageLine(id, label, status, progress)}\n`);
75
+ }
76
+ export function formatStageProgress(progress) {
77
+ if (!progress)
78
+ return '';
79
+ return progress.total !== undefined ? ` (${progress.done} of ${progress.total})` : ` (${progress.done})`;
80
+ }
81
+ export function applyWorkflowStageLine(stages, line) {
82
+ if (!line.startsWith(EVENT_PREFIX))
83
+ return;
84
+ let raw;
85
+ try {
86
+ const parsed = JSON.parse(line.slice(EVENT_PREFIX.length));
87
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
88
+ return;
89
+ raw = parsed;
90
+ }
91
+ catch {
92
+ return;
93
+ }
94
+ if (raw.schemaVersion !== 1 || raw.type !== 'stage' || typeof raw.id !== 'string' || typeof raw.label !== 'string' || typeof raw.at !== 'string')
95
+ return;
96
+ if (!['pending', 'running', 'completed', 'failed'].includes(String(raw.status)))
97
+ return;
98
+ const status = raw.status;
99
+ const progress = parseStageProgress(raw.progress);
100
+ const existing = stages.find((stage) => stage.id === raw.id);
101
+ if (existing) {
102
+ // A stage announced up front never moves backwards once work started on it.
103
+ if (status === 'pending')
104
+ return;
105
+ existing.label = raw.label;
106
+ existing.status = status;
107
+ if (progress)
108
+ existing.progress = progress;
109
+ if (status === 'running')
110
+ existing.startedAt ??= raw.at;
111
+ else
112
+ existing.finishedAt = raw.at;
113
+ }
114
+ else {
115
+ stages.push({
116
+ id: raw.id,
117
+ label: raw.label,
118
+ status,
119
+ ...(progress ? { progress } : {}),
120
+ ...(status === 'running' ? { startedAt: raw.at } : status === 'pending' ? {} : { finishedAt: raw.at }),
121
+ });
122
+ }
123
+ }
124
+ export function finishWorkflowStages(stages, status, at = new Date().toISOString()) {
125
+ for (const stage of stages) {
126
+ if (stage.status !== 'running')
127
+ continue;
128
+ stage.status = status;
129
+ stage.finishedAt = at;
130
+ }
131
+ }
132
+ export function parseWorkflowStage(raw) {
133
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
134
+ return undefined;
135
+ const stage = raw;
136
+ if (typeof stage.id !== 'string' || typeof stage.label !== 'string' || !['pending', 'running', 'completed', 'failed'].includes(String(stage.status)))
137
+ return undefined;
138
+ const progress = parseStageProgress(stage.progress);
139
+ return {
140
+ id: stage.id,
141
+ label: stage.label,
142
+ status: stage.status,
143
+ ...(typeof stage.startedAt === 'string' ? { startedAt: stage.startedAt } : {}),
144
+ ...(typeof stage.finishedAt === 'string' ? { finishedAt: stage.finishedAt } : {}),
145
+ ...(progress ? { progress } : {}),
146
+ };
147
+ }
148
+ function parseStageProgress(raw) {
149
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
150
+ return undefined;
151
+ const progress = raw;
152
+ if (typeof progress.done !== 'number' || !Number.isInteger(progress.done) || progress.done < 0)
153
+ return undefined;
154
+ const total = progress.total;
155
+ if (total !== undefined && (typeof total !== 'number' || !Number.isInteger(total) || total < 0))
156
+ return undefined;
157
+ return { done: progress.done, ...(typeof total === 'number' ? { total } : {}) };
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Job log hygiene: what the control center shows in "Live activity".
161
+ const ANSI_ESCAPE = /\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)|\u001b[@-Z\\-_]/g;
162
+ /** An optional `[task] ` prefix a parallel research session puts on its lines. */
163
+ const SESSION_PREFIX = /^(\[[^\]\s]{1,40}\] )?/;
164
+ /** A Rust `tracing` line from Codex's own internals: timestamp, level, module, message. */
165
+ const CODEX_TRACE = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z?\s+(ERROR|WARN|INFO|DEBUG|TRACE)\s+(codex[\w:]*)\s*:\s*(.*)$/;
166
+ export function stripAnsi(value) {
167
+ return value.replace(ANSI_ESCAPE, '');
168
+ }
169
+ function compactLine(value, maximum) {
170
+ const single = value.replace(/\s+/g, ' ').trim();
171
+ return single.length > maximum ? `${single.slice(0, maximum - 1)}…` : single;
172
+ }
173
+ /** Condense one of Codex's internal tool-router errors, which Codex retries itself. */
174
+ function codexTraceSummary(level, module, message) {
175
+ const text = message.replace(/^error=/, '');
176
+ if (module.includes('tools::router') || module.includes('tools')) {
177
+ if (/apply_patch verification failed/i.test(text))
178
+ return 'Codex edit retried (patch did not apply)';
179
+ const rejected = /^patch rejected:\s*(.*)$/i.exec(text);
180
+ if (rejected)
181
+ return `Codex edit rejected (${compactLine(rejected[1] ?? '', 120)})`;
182
+ const command = /^exec_command failed for `([^`]*)/i.exec(text);
183
+ if (command)
184
+ return `Codex command could not run (${compactLine((command[1] ?? '').replace(/^\/bin\/\w+ -lc /, ''), 100)})`;
185
+ }
186
+ if (level === 'DEBUG' || level === 'TRACE' || level === 'INFO')
187
+ return undefined;
188
+ return `Codex ${level === 'WARN' ? 'warning' : 'error'}: ${compactLine(text, 200)}`;
189
+ }
190
+ /**
191
+ * Clean one raw output line for the job log, or drop it: ANSI colour codes
192
+ * are stripped, Codex's "Reading additional input from stdin..." notice is
193
+ * dropped, and Codex's internal tracing lines are condensed to one readable
194
+ * sentence. Event lines pass through untouched.
195
+ */
196
+ export function cleanJobOutputLine(line) {
197
+ if (line.startsWith(EVENT_PREFIX))
198
+ return line;
199
+ const stripped = stripAnsi(line).replace(/\s+$/, '');
200
+ if (!stripped.trim())
201
+ return undefined;
202
+ const prefix = SESSION_PREFIX.exec(stripped)?.[1] ?? '';
203
+ const body = stripped.slice(prefix.length);
204
+ if (/^Reading (?:additional )?(?:input|prompt) from stdin\.*$/i.test(body.trim()))
205
+ return undefined;
206
+ const trace = CODEX_TRACE.exec(body.trim());
207
+ if (trace) {
208
+ const summary = codexTraceSummary(trace[1], trace[2], trace[3] ?? '');
209
+ return summary ? `${prefix}${summary}` : undefined;
210
+ }
211
+ return stripped;
212
+ }
213
+ export function cleanJobOutputLines(lines) {
214
+ return lines.flatMap((line) => {
215
+ const cleaned = cleanJobOutputLine(line);
216
+ return cleaned === undefined ? [] : [cleaned];
217
+ });
218
+ }
219
+ const REPLY_TAG = /^<(doxloop-(?:plan-patch|plan|brief|review|triage))>(.*)$/;
220
+ const SESSION_END = /^(?:Claude|Codex|Gemini) (?:finished|stopped|session started)\b/;
221
+ /** A line a pretty-printed JSON document can contain on its own. */
222
+ const JSON_LINE = /^\s*(?:[{}[\]],?|"(?:[^"\\]|\\.)*"\s*:.*|"(?:[^"\\]|\\.)*",?|-?\d[\d.eE+-]*,?|true,?|false,?|null,?|[{[].*)$/;
223
+ /** Most lines a hidden block may hold before the filter gives up on it. */
224
+ const MAX_HIDDEN_LINES = 50_000;
225
+ function jsonDepthChange(line) {
226
+ let depth = 0;
227
+ let inString = false;
228
+ for (let index = 0; index < line.length; index += 1) {
229
+ const character = line[index];
230
+ if (inString) {
231
+ if (character === '\\')
232
+ index += 1;
233
+ else if (character === '"')
234
+ inString = false;
235
+ continue;
236
+ }
237
+ if (character === '"')
238
+ inString = true;
239
+ else if (character === '{' || character === '[')
240
+ depth += 1;
241
+ else if (character === '}' || character === ']')
242
+ depth -= 1;
243
+ }
244
+ return depth;
245
+ }
246
+ function replySummary(block, complete) {
247
+ const body = block.lines.join('\n');
248
+ let parsed;
249
+ try {
250
+ const start = body.search(/[{[]/);
251
+ parsed = start >= 0 ? JSON.parse(body.slice(start)) : undefined;
252
+ }
253
+ catch {
254
+ parsed = undefined;
255
+ }
256
+ const record = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
257
+ const count = (key) => Array.isArray(record?.[key]) ? record[key].length : undefined;
258
+ const hidden = `${block.lines.length} line${block.lines.length === 1 ? '' : 's'} in the full log`;
259
+ const incomplete = complete ? '' : ', incomplete';
260
+ if (block.tag === 'doxloop-plan') {
261
+ const pages = count('pages');
262
+ return pages !== undefined ? `Plan reply received: ${pages} page${pages === 1 ? '' : 's'}` : `Plan reply received (${hidden}${incomplete})`;
263
+ }
264
+ if (block.tag === 'doxloop-plan-patch') {
265
+ const pages = count('pages');
266
+ return `Plan changes received${pages !== undefined ? `: ${pages} page${pages === 1 ? '' : 's'}` : ` (${hidden}${incomplete})`}`;
267
+ }
268
+ if (block.tag === 'doxloop-brief')
269
+ return `Research brief received (${hidden}${incomplete})`;
270
+ if (block.tag === 'doxloop-review') {
271
+ const findings = count('findings');
272
+ const score = typeof record?.score === 'number' ? record.score : undefined;
273
+ return `Review reply received${score !== undefined ? `: ${score}/100` : ''}${findings !== undefined ? `, ${findings} finding${findings === 1 ? '' : 's'}` : ''}${score === undefined && findings === undefined ? ` (${hidden}${incomplete})` : ''}`;
274
+ }
275
+ if (block.tag === 'doxloop-triage')
276
+ return 'Request triage received';
277
+ return `Structured reply hidden (${hidden}${incomplete})`;
278
+ }
279
+ /**
280
+ * Keeps an agent's machine-readable replies out of the live activity view:
281
+ * a `<doxloop-plan>` block or a pretty-printed JSON document becomes one
282
+ * summary line ("Plan reply received: 62 pages") instead of hundreds of
283
+ * lines that push the useful ones out of the window. Parallel sessions are
284
+ * tracked by their `[task] ` prefix. The full job log keeps the raw reply.
285
+ */
286
+ export class JobLogDisplayFilter {
287
+ blocks = new Map();
288
+ push(lines) {
289
+ return lines.flatMap((line) => this.line(line));
290
+ }
291
+ /** Summaries for blocks still open when the job ends. */
292
+ finish() {
293
+ const output = [];
294
+ for (const [key, block] of this.blocks)
295
+ output.push(`${key}${replySummary(block, false)}`);
296
+ this.blocks.clear();
297
+ return output;
298
+ }
299
+ line(raw) {
300
+ if (raw.startsWith(EVENT_PREFIX))
301
+ return [raw];
302
+ const stamp = /^\d{2}:\d{2}:\d{2} /.exec(raw)?.[0] ?? '';
303
+ const unstamped = raw.slice(stamp.length);
304
+ const prefix = SESSION_PREFIX.exec(unstamped)?.[1] ?? '';
305
+ const text = unstamped.slice(prefix.length);
306
+ const block = this.blocks.get(prefix);
307
+ if (block)
308
+ return this.continueBlock(block, prefix, stamp, text, raw);
309
+ const tag = REPLY_TAG.exec(text.trim());
310
+ if (tag) {
311
+ const opened = { kind: 'tag', tag: tag[1], lines: [], raw: [], depth: 0, strays: 0 };
312
+ const rest = tag[2] ?? '';
313
+ const close = rest.indexOf(`</${tag[1]}>`);
314
+ if (close >= 0) {
315
+ if (rest.slice(0, close).trim())
316
+ opened.lines.push(rest.slice(0, close));
317
+ return [`${stamp}${prefix}${replySummary(opened, true)}`];
318
+ }
319
+ if (rest.trim())
320
+ opened.lines.push(rest);
321
+ this.blocks.set(prefix, opened);
322
+ return [];
323
+ }
324
+ const trimmed = text.trim();
325
+ if (trimmed === '{' || trimmed === '[' || /^```json\s*$/i.test(trimmed)) {
326
+ const fenced = trimmed.startsWith('```');
327
+ this.blocks.set(prefix, { kind: 'json', lines: fenced ? [] : [trimmed], raw: [raw], depth: fenced ? 0 : 1, strays: 0, ...(fenced ? { tag: 'fence' } : {}) });
328
+ return [];
329
+ }
330
+ return [raw];
331
+ }
332
+ continueBlock(block, prefix, stamp, text, raw) {
333
+ const trimmed = text.trim();
334
+ const close = block.kind === 'tag' ? `</${block.tag}>` : undefined;
335
+ if (close && trimmed.includes(close)) {
336
+ const before = trimmed.slice(0, trimmed.indexOf(close));
337
+ if (before.trim())
338
+ block.lines.push(before);
339
+ this.blocks.delete(prefix);
340
+ return [`${stamp}${prefix}${replySummary(block, true)}`];
341
+ }
342
+ if (block.tag === 'fence' && trimmed === '```') {
343
+ this.blocks.delete(prefix);
344
+ return [`${stamp}${prefix}${replySummary({ ...block, tag: undefined }, true)}`];
345
+ }
346
+ // A session that ended (or a truncated reply) must not swallow what follows.
347
+ if (SESSION_END.test(trimmed) || block.lines.length >= MAX_HIDDEN_LINES) {
348
+ this.blocks.delete(prefix);
349
+ return [`${stamp}${prefix}${replySummary(block.tag === 'fence' ? { ...block, tag: undefined } : block, false)}`, raw];
350
+ }
351
+ if (block.kind === 'json' && block.tag !== 'fence') {
352
+ if (!JSON_LINE.test(text)) {
353
+ block.strays += 1;
354
+ if (block.strays > 3) {
355
+ this.blocks.delete(prefix);
356
+ return [`${stamp}${prefix}${replySummary(block, false)}`, raw];
357
+ }
358
+ return [raw];
359
+ }
360
+ block.strays = 0;
361
+ block.lines.push(trimmed);
362
+ block.raw.push(raw);
363
+ block.depth += jsonDepthChange(trimmed);
364
+ if (block.depth <= 0) {
365
+ this.blocks.delete(prefix);
366
+ // A short object is readable as it stands.
367
+ if (block.lines.length <= 3)
368
+ return block.raw;
369
+ return [`${stamp}${prefix}${replySummary(block, true)}`];
370
+ }
371
+ return [];
372
+ }
373
+ block.lines.push(trimmed);
374
+ return [];
375
+ }
376
+ }
377
+ //# sourceMappingURL=job-events.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Keeps the computer from idle-sleeping while documentation jobs run: laptop
3
+ * sleep used to kill authoring batches mid-session. One helper process is
4
+ * held while any job needs it (reference-counted across concurrent jobs) and
5
+ * released when the last one ends or the server exits.
6
+ *
7
+ * macOS: `caffeinate -i -w <server pid>` (also exits on its own if the
8
+ * server dies). Linux: `systemd-inhibit --what=idle:sleep … sleep infinity`
9
+ * when systemd-inhibit is installed. Elsewhere, or with
10
+ * DOXLOOP_KEEP_AWAKE=0, nothing happens.
11
+ */
12
+ import { type ChildProcess } from 'node:child_process';
13
+ export interface KeepAwakeOptions {
14
+ platform?: NodeJS.Platform;
15
+ env?: NodeJS.ProcessEnv;
16
+ pid?: number;
17
+ spawn?: (command: string, args: string[], options: {
18
+ stdio: 'ignore';
19
+ }) => ChildProcess;
20
+ /** Whether a command is on PATH; replaced in tests. */
21
+ hasCommand?: (command: string) => boolean;
22
+ /** Register process-exit cleanup (off in tests). */
23
+ registerExitHook?: boolean;
24
+ }
25
+ export declare function keepAwakeCommand(options?: Pick<KeepAwakeOptions, 'platform' | 'env' | 'pid' | 'hasCommand'>): {
26
+ command: string;
27
+ args: string[];
28
+ } | undefined;
29
+ export declare class KeepAwake {
30
+ private readonly options;
31
+ private holders;
32
+ private child;
33
+ private exitHooked;
34
+ constructor(options?: KeepAwakeOptions);
35
+ /** Number of jobs currently holding the computer awake. */
36
+ get count(): number;
37
+ /** Whether a keep-awake helper process is running. */
38
+ get active(): boolean;
39
+ /**
40
+ * Hold the computer awake until the returned release function is called.
41
+ * Releasing twice is harmless; the helper stops when the last holder releases.
42
+ */
43
+ acquire(): () => void;
44
+ /** Stop the helper regardless of holders (process exit). */
45
+ stop(): void;
46
+ private start;
47
+ }
48
+ /** The UI server's shared keep-awake. */
49
+ export declare const keepAwake: KeepAwake;
50
+ //# sourceMappingURL=keep-awake.d.ts.map
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Keeps the computer from idle-sleeping while documentation jobs run: laptop
3
+ * sleep used to kill authoring batches mid-session. One helper process is
4
+ * held while any job needs it (reference-counted across concurrent jobs) and
5
+ * released when the last one ends or the server exits.
6
+ *
7
+ * macOS: `caffeinate -i -w <server pid>` (also exits on its own if the
8
+ * server dies). Linux: `systemd-inhibit --what=idle:sleep … sleep infinity`
9
+ * when systemd-inhibit is installed. Elsewhere, or with
10
+ * DOXLOOP_KEEP_AWAKE=0, nothing happens.
11
+ */
12
+ import { spawn as nodeSpawn } from 'node:child_process';
13
+ import { accessSync, constants } from 'node:fs';
14
+ import { delimiter, join } from 'node:path';
15
+ export function keepAwakeCommand(options = {}) {
16
+ const env = options.env ?? process.env;
17
+ if (env.DOXLOOP_KEEP_AWAKE === '0' || env.DOXLOOP_KEEP_AWAKE === 'false')
18
+ return undefined;
19
+ const platform = options.platform ?? process.platform;
20
+ const hasCommand = options.hasCommand ?? ((command) => commandOnPath(command, env));
21
+ if (platform === 'darwin') {
22
+ return { command: 'caffeinate', args: ['-i', '-w', String(options.pid ?? process.pid)] };
23
+ }
24
+ if (platform === 'linux' && hasCommand('systemd-inhibit')) {
25
+ return {
26
+ command: 'systemd-inhibit',
27
+ args: ['--what=idle:sleep', '--who=Doxloop', '--why=Doxloop is generating documentation', '--mode=block', 'sleep', 'infinity'],
28
+ };
29
+ }
30
+ return undefined;
31
+ }
32
+ function commandOnPath(command, env) {
33
+ for (const directory of (env.PATH ?? '').split(delimiter)) {
34
+ if (!directory)
35
+ continue;
36
+ try {
37
+ accessSync(join(directory, command), constants.X_OK);
38
+ return true;
39
+ }
40
+ catch {
41
+ // Keep looking.
42
+ }
43
+ }
44
+ return false;
45
+ }
46
+ export class KeepAwake {
47
+ options;
48
+ holders = 0;
49
+ child;
50
+ exitHooked = false;
51
+ constructor(options = {}) {
52
+ this.options = options;
53
+ }
54
+ /** Number of jobs currently holding the computer awake. */
55
+ get count() {
56
+ return this.holders;
57
+ }
58
+ /** Whether a keep-awake helper process is running. */
59
+ get active() {
60
+ return this.child !== undefined;
61
+ }
62
+ /**
63
+ * Hold the computer awake until the returned release function is called.
64
+ * Releasing twice is harmless; the helper stops when the last holder releases.
65
+ */
66
+ acquire() {
67
+ this.holders += 1;
68
+ if (this.holders === 1)
69
+ this.start();
70
+ let released = false;
71
+ return () => {
72
+ if (released)
73
+ return;
74
+ released = true;
75
+ this.holders = Math.max(0, this.holders - 1);
76
+ if (this.holders === 0)
77
+ this.stop();
78
+ };
79
+ }
80
+ /** Stop the helper regardless of holders (process exit). */
81
+ stop() {
82
+ const child = this.child;
83
+ this.child = undefined;
84
+ if (child && child.exitCode === null && child.signalCode === null) {
85
+ try {
86
+ child.kill('SIGTERM');
87
+ }
88
+ catch {
89
+ // Already gone.
90
+ }
91
+ }
92
+ }
93
+ start() {
94
+ const command = keepAwakeCommand(this.options);
95
+ if (!command)
96
+ return;
97
+ let child;
98
+ try {
99
+ child = (this.options.spawn ?? nodeSpawn)(command.command, command.args, { stdio: 'ignore' });
100
+ }
101
+ catch {
102
+ return;
103
+ }
104
+ this.child = child;
105
+ // A missing binary or a helper that exits early just means no keep-awake.
106
+ child.once('error', () => {
107
+ if (this.child === child)
108
+ this.child = undefined;
109
+ });
110
+ child.once('exit', () => {
111
+ if (this.child === child)
112
+ this.child = undefined;
113
+ });
114
+ child.unref?.();
115
+ if (!this.exitHooked && this.options.registerExitHook !== false) {
116
+ this.exitHooked = true;
117
+ process.once('exit', () => this.stop());
118
+ }
119
+ }
120
+ }
121
+ /** The UI server's shared keep-awake. */
122
+ export const keepAwake = new KeepAwake();
123
+ //# sourceMappingURL=keep-awake.js.map
@@ -0,0 +1,20 @@
1
+ import type { SourceBinding } from './types.js';
2
+ export interface LocalSourceSnapshot {
3
+ /** The sources with every copied path replaced by its snapshot. */
4
+ sources: SourceBinding[];
5
+ copied: Array<{
6
+ name: string;
7
+ path: string;
8
+ files: number;
9
+ }>;
10
+ }
11
+ /**
12
+ * Copy each local source folder into a throwaway snapshot beside the managed
13
+ * remote snapshots, so an agent that cannot be denied write access to extra
14
+ * directories (Gemini) never touches the user's real checkout. Sources that
15
+ * are already managed snapshots, remote, inside the project, or missing are
16
+ * left as they are. A snapshot is keyed by content, so an unchanged source is
17
+ * reused rather than copied again.
18
+ */
19
+ export declare function snapshotLocalSources(root: string, sources: SourceBinding[]): Promise<LocalSourceSnapshot>;
20
+ //# sourceMappingURL=local-source-snapshot.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { cp, mkdir, mkdtemp, rename, rm } from 'node:fs/promises';
3
+ import { dirname, join, relative, resolve, sep, isAbsolute } from 'node:path';
4
+ import { pathExists } from './fs.js';
5
+ import { sourceKind } from './project.js';
6
+ import { localSourceManifest } from './sync.js';
7
+ /**
8
+ * Copy each local source folder into a throwaway snapshot beside the managed
9
+ * remote snapshots, so an agent that cannot be denied write access to extra
10
+ * directories (Gemini) never touches the user's real checkout. Sources that
11
+ * are already managed snapshots, remote, inside the project, or missing are
12
+ * left as they are. A snapshot is keyed by content, so an unchanged source is
13
+ * reused rather than copied again.
14
+ */
15
+ export async function snapshotLocalSources(root, sources) {
16
+ const projectRoot = resolve(root);
17
+ const projectId = createHash('sha256').update(projectRoot).digest('hex').slice(0, 12);
18
+ const copied = [];
19
+ const rewritten = [];
20
+ for (const source of sources) {
21
+ const sourcePath = resolve(projectRoot, source.path);
22
+ if (sourceKind(source) !== 'directory' ||
23
+ source.remote ||
24
+ sourcePath.split(sep).includes('.doxloop-sources') ||
25
+ !isOutside(projectRoot, sourcePath) ||
26
+ !(await pathExists(sourcePath))) {
27
+ rewritten.push(source);
28
+ continue;
29
+ }
30
+ const manifest = await localSourceManifest(sourcePath);
31
+ const destination = join(dirname(projectRoot), '.doxloop-sources', projectId, safeName(source.name), `local-${manifest.fingerprint.slice(0, 12)}`);
32
+ if (!(await pathExists(destination))) {
33
+ const parent = dirname(destination);
34
+ await mkdir(parent, { recursive: true });
35
+ const temporary = await mkdtemp(join(parent, '.copy-'));
36
+ try {
37
+ for (const file of Object.keys(manifest.files)) {
38
+ const target = join(temporary, file);
39
+ await mkdir(dirname(target), { recursive: true });
40
+ await cp(join(sourcePath, file), target, { dereference: false, errorOnExist: false, force: true });
41
+ }
42
+ await rename(temporary, destination);
43
+ }
44
+ catch (error) {
45
+ await rm(temporary, { recursive: true, force: true });
46
+ throw error;
47
+ }
48
+ }
49
+ copied.push({ name: source.name, path: destination, files: Object.keys(manifest.files).length });
50
+ rewritten.push({ ...source, path: destination });
51
+ }
52
+ return { sources: rewritten, copied };
53
+ }
54
+ function isOutside(projectRoot, path) {
55
+ const rel = relative(projectRoot, path);
56
+ return rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
57
+ }
58
+ function safeName(value) {
59
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'source';
60
+ }
61
+ //# sourceMappingURL=local-source-snapshot.js.map
@@ -0,0 +1,3 @@
1
+ /** Detection never treats Mintlify as a generator that can be adopted in place. */
2
+ export declare function mintlifyMarker(root: string): Promise<string | undefined>;
3
+ //# sourceMappingURL=mintlify-detect.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ /** Detection never treats Mintlify as a generator that can be adopted in place. */
4
+ export async function mintlifyMarker(root) {
5
+ for (const name of ['docs.json', 'mint.json']) {
6
+ try {
7
+ const config = JSON.parse(await readFile(join(root, name), 'utf8'));
8
+ // Existing Doxbrix projects also have a legacy title + navigation-array format.
9
+ if (name === 'docs.json' && typeof config?.title === 'string' && Array.isArray(config.navigation))
10
+ continue;
11
+ if (config && config.navigation && !Array.isArray(config.spaces))
12
+ return name;
13
+ }
14
+ catch { /* Not a Mintlify configuration. */ }
15
+ }
16
+ return undefined;
17
+ }
18
+ //# sourceMappingURL=mintlify-detect.js.map