@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.
- package/CHANGELOG.md +1145 -0
- package/README.md +422 -227
- package/assets/doxbrix-preview.css +77 -15
- package/contracts/agent-events-v1.schema.json +1 -0
- package/contracts/coverage-v1.schema.json +59 -0
- package/contracts/drift-v1.schema.json +1 -0
- package/contracts/evaluation-v1.schema.json +62 -0
- package/contracts/quality-codes-v1.json +1 -0
- package/contracts/quality-report-v1.schema.json +1 -0
- package/contracts/validation-v1.schema.json +1 -0
- package/dist/agent-failure.d.ts +36 -0
- package/dist/agent-failure.js +98 -0
- package/dist/agent-isolation.d.ts +17 -0
- package/dist/agent-isolation.js +94 -0
- package/dist/agent-log.d.ts +197 -0
- package/dist/agent-log.js +1094 -0
- package/dist/agent-process.d.ts +63 -0
- package/dist/agent-process.js +128 -0
- package/dist/agent-reply.d.ts +47 -0
- package/dist/agent-reply.js +315 -0
- package/dist/agents.d.ts +24 -5
- package/dist/agents.js +60 -9
- package/dist/api-coverage.d.ts +11 -0
- package/dist/api-coverage.js +72 -0
- package/dist/application-probe.d.ts +28 -0
- package/dist/application-probe.js +93 -0
- package/dist/application-url.d.ts +14 -0
- package/dist/application-url.js +26 -0
- package/dist/args.js +15 -0
- package/dist/artifact-deploy.d.ts +9 -0
- package/dist/artifact-deploy.js +28 -8
- package/dist/assets.d.ts +72 -0
- package/dist/assets.js +382 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +38 -9
- package/dist/author.d.ts +206 -4
- package/dist/author.js +1650 -52
- package/dist/authoring-batches.d.ts +190 -0
- package/dist/authoring-batches.js +489 -0
- package/dist/authoring-postpass.d.ts +79 -0
- package/dist/authoring-postpass.js +1289 -0
- package/dist/authoring-progress.d.ts +95 -0
- package/dist/authoring-progress.js +269 -0
- package/dist/autosync.d.ts +56 -0
- package/dist/autosync.js +442 -0
- package/dist/batch-artifacts.d.ts +81 -0
- package/dist/batch-artifacts.js +412 -0
- package/dist/batch-limits.d.ts +37 -0
- package/dist/batch-limits.js +80 -0
- package/dist/branding.d.ts +41 -0
- package/dist/branding.js +229 -0
- package/dist/bundle-upload.d.ts +26 -0
- package/dist/bundle-upload.js +99 -0
- package/dist/capture-auth.d.ts +95 -0
- package/dist/capture-auth.js +204 -0
- package/dist/capture.d.ts +48 -0
- package/dist/capture.js +20 -1
- package/dist/cli.js +839 -32
- package/dist/content-links.d.ts +10 -0
- package/dist/content-links.js +49 -0
- package/dist/contract-validation.d.ts +4 -0
- package/dist/contract-validation.js +25 -0
- package/dist/coverage-actions.d.ts +8 -0
- package/dist/coverage-actions.js +71 -0
- package/dist/coverage-resolutions.d.ts +16 -0
- package/dist/coverage-resolutions.js +58 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.js +288 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +122 -0
- package/dist/deploy-credentials.d.ts +7 -0
- package/dist/deploy-credentials.js +76 -0
- package/dist/deploy-targets/github-pages.d.ts +3 -0
- package/dist/deploy-targets/github-pages.js +100 -0
- package/dist/deploy-targets/index.d.ts +8 -0
- package/dist/deploy-targets/index.js +78 -0
- package/dist/deploy-targets/netlify.d.ts +3 -0
- package/dist/deploy-targets/netlify.js +49 -0
- package/dist/deploy-targets/types.d.ts +32 -0
- package/dist/deploy-targets/types.js +2 -0
- package/dist/deploy-targets/vercel.d.ts +3 -0
- package/dist/deploy-targets/vercel.js +95 -0
- package/dist/deploy.d.ts +9 -0
- package/dist/deploy.js +87 -13
- package/dist/deterministic-capture.d.ts +90 -0
- package/dist/deterministic-capture.js +435 -0
- package/dist/direct-edit.d.ts +40 -0
- package/dist/direct-edit.js +159 -0
- package/dist/docs-crawl.d.ts +86 -0
- package/dist/docs-crawl.js +536 -0
- package/dist/docs-site.d.ts +49 -0
- package/dist/docs-site.js +233 -0
- package/dist/doctor.js +8 -0
- package/dist/documentation-collections.d.ts +23 -0
- package/dist/documentation-collections.js +191 -0
- package/dist/documentation-plan.d.ts +253 -0
- package/dist/documentation-plan.js +2563 -0
- package/dist/doxbrix-build.d.ts +19 -0
- package/dist/doxbrix-build.js +167 -0
- package/dist/doxbrix-markdown.d.ts +9 -0
- package/dist/doxbrix-markdown.js +115 -14
- package/dist/drift.d.ts +10 -0
- package/dist/drift.js +164 -0
- package/dist/evaluation.d.ts +46 -0
- package/dist/evaluation.js +113 -0
- package/dist/evidence-pack.d.ts +47 -0
- package/dist/evidence-pack.js +358 -0
- package/dist/evidence.d.ts +25 -0
- package/dist/evidence.js +175 -0
- package/dist/fs.d.ts +8 -2
- package/dist/fs.js +41 -11
- package/dist/generator-api.d.ts +89 -0
- package/dist/generator-preflight.d.ts +27 -0
- package/dist/generator-preflight.js +105 -0
- package/dist/generator-runtime.d.ts +7 -0
- package/dist/generator-runtime.js +17 -1
- package/dist/generators.d.ts +24 -2
- package/dist/generators.js +54 -1
- package/dist/git-delivery.d.ts +17 -0
- package/dist/git-delivery.js +123 -0
- package/dist/globs.d.ts +16 -0
- package/dist/globs.js +65 -0
- package/dist/glossary.d.ts +26 -0
- package/dist/glossary.js +179 -0
- package/dist/history.d.ts +106 -0
- package/dist/history.js +600 -0
- package/dist/html-markdown.d.ts +46 -0
- package/dist/html-markdown.js +423 -0
- package/dist/interactive.js +3 -0
- package/dist/job-events.d.ts +74 -0
- package/dist/job-events.js +377 -0
- package/dist/keep-awake.d.ts +50 -0
- package/dist/keep-awake.js +123 -0
- package/dist/local-source-snapshot.d.ts +20 -0
- package/dist/local-source-snapshot.js +61 -0
- package/dist/mintlify-detect.d.ts +3 -0
- package/dist/mintlify-detect.js +18 -0
- package/dist/mintlify-import.d.ts +75 -0
- package/dist/mintlify-import.js +190 -0
- package/dist/navigation.d.ts +98 -0
- package/dist/navigation.js +310 -0
- package/dist/openapi.d.ts +60 -0
- package/dist/openapi.js +439 -0
- package/dist/page-editor-bridge.d.ts +3 -0
- package/dist/page-editor-bridge.js +109 -0
- package/dist/page-editor-preview.d.ts +10 -0
- package/dist/page-editor-preview.js +55 -0
- package/dist/page-extension.d.ts +9 -0
- package/dist/page-extension.js +15 -0
- package/dist/page-metadata.d.ts +28 -0
- package/dist/page-metadata.js +166 -0
- package/dist/page-operations.d.ts +34 -0
- package/dist/page-operations.js +215 -0
- package/dist/page-routes.d.ts +4 -0
- package/dist/page-routes.js +61 -0
- package/dist/pages.d.ts +20 -0
- package/dist/pages.js +184 -0
- package/dist/plan-generator.d.ts +3 -0
- package/dist/plan-generator.js +21 -0
- package/dist/plan-navigation.d.ts +11 -0
- package/dist/plan-navigation.js +30 -0
- package/dist/planning-captures.d.ts +20 -0
- package/dist/planning-captures.js +143 -0
- package/dist/planning-research.d.ts +135 -0
- package/dist/planning-research.js +472 -0
- package/dist/planning-triage.d.ts +23 -0
- package/dist/planning-triage.js +131 -0
- package/dist/preview.d.ts +24 -0
- package/dist/preview.js +280 -29
- package/dist/project-detect.d.ts +36 -0
- package/dist/project-detect.js +251 -0
- package/dist/project-import.d.ts +54 -0
- package/dist/project-import.js +157 -0
- package/dist/project-lock.d.ts +6 -0
- package/dist/project-lock.js +96 -0
- package/dist/project-registry.d.ts +25 -0
- package/dist/project-registry.js +79 -0
- package/dist/project.d.ts +35 -3
- package/dist/project.js +388 -38
- package/dist/prompts.d.ts +9 -0
- package/dist/prompts.js +25 -0
- package/dist/proposal-replay.d.ts +32 -0
- package/dist/proposal-replay.js +99 -0
- package/dist/quality-claims.d.ts +8 -0
- package/dist/quality-claims.js +168 -0
- package/dist/quality-config.d.ts +5 -0
- package/dist/quality-config.js +84 -0
- package/dist/quality-contract.d.ts +37 -0
- package/dist/quality-contract.js +46 -0
- package/dist/quality-examples.d.ts +4 -0
- package/dist/quality-examples.js +233 -0
- package/dist/quality-gates.d.ts +16 -0
- package/dist/quality-gates.js +192 -0
- package/dist/quality-links.d.ts +7 -0
- package/dist/quality-links.js +149 -0
- package/dist/quality-lint.d.ts +6 -0
- package/dist/quality-lint.js +124 -0
- package/dist/quality-rendered.d.ts +11 -0
- package/dist/quality-rendered.js +222 -0
- package/dist/quality-schema.d.ts +3 -0
- package/dist/quality-schema.js +71 -0
- package/dist/release-notes.d.ts +44 -0
- package/dist/release-notes.js +183 -0
- package/dist/remote-monitor.d.ts +16 -0
- package/dist/remote-monitor.js +74 -0
- package/dist/remote-source.d.ts +34 -0
- package/dist/remote-source.js +426 -0
- package/dist/review-diff.d.ts +82 -0
- package/dist/review-diff.js +400 -0
- package/dist/review-learning.d.ts +11 -0
- package/dist/review-learning.js +60 -0
- package/dist/review-render.d.ts +38 -0
- package/dist/review-render.js +224 -0
- package/dist/review-report.d.ts +9 -0
- package/dist/review-report.js +89 -0
- package/dist/review-ui.d.ts +14 -0
- package/dist/review-ui.js +1248 -0
- package/dist/schedule.d.ts +78 -0
- package/dist/schedule.js +480 -0
- package/dist/screen-capture-provider.d.ts +52 -0
- package/dist/screen-capture-provider.js +218 -0
- package/dist/screenshot-workflow.d.ts +167 -0
- package/dist/screenshot-workflow.js +1237 -0
- package/dist/settings.d.ts +1 -1
- package/dist/settings.js +95 -7
- package/dist/site-export.d.ts +18 -0
- package/dist/site-export.js +87 -0
- package/dist/source-connectors.d.ts +33 -0
- package/dist/source-connectors.js +268 -0
- package/dist/source-discovery.d.ts +132 -0
- package/dist/source-discovery.js +823 -0
- package/dist/source-intelligence.d.ts +9 -0
- package/dist/source-intelligence.js +306 -0
- package/dist/sync-review.d.ts +28 -0
- package/dist/sync-review.js +264 -0
- package/dist/sync-runs.d.ts +192 -0
- package/dist/sync-runs.js +2244 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.js +298 -32
- package/dist/text-diff.d.ts +9 -0
- package/dist/text-diff.js +59 -0
- package/dist/types.d.ts +946 -1
- package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
- package/dist/ui/assets/index-BHBYU2aG.css +1 -0
- package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
- package/dist/ui/index.html +18 -0
- package/dist/ui-server.d.ts +83 -0
- package/dist/ui-server.js +3532 -0
- package/dist/usage-budget.d.ts +28 -0
- package/dist/usage-budget.js +90 -0
- package/dist/validation.d.ts +25 -1
- package/dist/validation.js +312 -26
- package/dist/workspace-tools.d.ts +54 -0
- package/dist/workspace-tools.js +123 -0
- package/docs/agent-compatibility.md +49 -32
- package/docs/ci-and-automation.md +105 -66
- package/docs/doxbrix-http-api.md +8 -1
- package/docs/existing-documentation.md +80 -0
- package/docs/generation-performance.md +108 -0
- package/docs/generator-authoring.md +68 -5
- package/docs/generator-selection.md +50 -13
- package/docs/mintlify-import.md +71 -0
- package/docs/openapi-security.md +25 -0
- package/docs/project-format.md +331 -36
- package/docs/release-quality.md +158 -0
- package/docs/releasing.md +72 -0
- package/docs/review-workflows.md +51 -0
- package/docs/security-model.md +102 -39
- package/docs/troubleshooting.md +226 -93
- package/package.json +46 -15
- package/scripts/test-auto-screenshot.mjs +172 -0
- package/skills/doxloop-authoring/SKILL.md +242 -361
- package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
- package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
- package/skills/doxloop-authoring/references/page-depth.md +169 -0
- package/skills/doxloop-authoring/references/project-format.md +106 -7
- package/skills/doxloop-authoring/references/quality.md +10 -0
- package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
- package/skills/doxloop-authoring/references/screenshots.md +149 -235
- package/skills/doxloop-authoring/references/workflows.md +76 -0
- package/skills/doxloop-doxbrix/SKILL.md +38 -22
- package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
- package/skills/doxloop-doxbrix/references/components.md +30 -3
- package/skills/doxloop-doxbrix/references/manifest.md +3 -2
- package/vendor/doxbrix-import/LICENSE +202 -0
- package/vendor/doxbrix-import/README.md +17 -0
- package/vendor/doxbrix-import/UPSTREAM.json +47 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
- package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
- package/vendor/doxbrix-import/dist/docs/import.js +147 -0
- package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
- package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
- package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
- package/vendor/doxbrix-import/dist/docs/project.js +77 -0
- package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
- package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
- package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
- package/vendor/doxbrix-import/dist/importer.js +1567 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
- package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
- package/vendor/doxbrix-import/dist/safe-path.js +47 -0
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/args.d.ts.map +0 -1
- package/dist/args.js.map +0 -1
- package/dist/artifact-deploy.d.ts.map +0 -1
- package/dist/artifact-deploy.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/author.d.ts.map +0 -1
- package/dist/author.js.map +0 -1
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/deploy.d.ts.map +0 -1
- package/dist/deploy.js.map +0 -1
- package/dist/deployment-visibility.d.ts.map +0 -1
- package/dist/deployment-visibility.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doxbrix-markdown.d.ts.map +0 -1
- package/dist/doxbrix-markdown.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/fs.js.map +0 -1
- package/dist/generator-api.d.ts.map +0 -1
- package/dist/generator-api.js.map +0 -1
- package/dist/generator-manager.d.ts.map +0 -1
- package/dist/generator-manager.js.map +0 -1
- package/dist/generator-runtime.d.ts.map +0 -1
- package/dist/generator-runtime.js.map +0 -1
- package/dist/generators.d.ts.map +0 -1
- package/dist/generators.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/preview.d.ts.map +0 -1
- package/dist/preview.js.map +0 -1
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js.map +0 -1
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DoxbrixBuildOptions {
|
|
2
|
+
root: string;
|
|
3
|
+
outDir?: string;
|
|
4
|
+
/** Origin-relative mount point, for example `/repository`. */
|
|
5
|
+
basePath?: string;
|
|
6
|
+
/** Public origin used for canonical URLs and sitemap entries. */
|
|
7
|
+
siteUrl?: string;
|
|
8
|
+
clean?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface StaticBuildResult {
|
|
11
|
+
outputDir: string;
|
|
12
|
+
pages: number;
|
|
13
|
+
assets: number;
|
|
14
|
+
basePath: string;
|
|
15
|
+
siteUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Build the built-in Doxbrix reader into portable static files. */
|
|
18
|
+
export declare function buildDoxbrixStaticSite(options: DoxbrixBuildOptions): Promise<StaticBuildResult>;
|
|
19
|
+
//# sourceMappingURL=doxbrix-build.d.ts.map
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { readRedirects } from './page-operations.js';
|
|
2
|
+
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import matter from 'gray-matter';
|
|
6
|
+
import { renderMarkdown } from './doxbrix-markdown.js';
|
|
7
|
+
import { DoxloopError } from './errors.js';
|
|
8
|
+
import { listFiles, resolveContainedDirectory } from './fs.js';
|
|
9
|
+
import { loadQualityConfig } from './quality-config.js';
|
|
10
|
+
import { reverifyClaims } from './quality-claims.js';
|
|
11
|
+
import { loadPages, loadProject, loadSiteConfig, pageId, ROOT_CONTENT_IGNORED_DIRECTORIES, } from './project.js';
|
|
12
|
+
import { doxbrixDocument, firstSitePage, redirectTarget } from './preview.js';
|
|
13
|
+
const PACKAGE_ROOT = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
14
|
+
const DOXBRIX_CSS = resolve(PACKAGE_ROOT, 'assets', 'doxbrix-preview.css');
|
|
15
|
+
const ASSET_EXTENSIONS = new Set([
|
|
16
|
+
'.avif', '.bmp', '.eot', '.gif', '.ico', '.jpeg', '.jpg', '.mov', '.mp3',
|
|
17
|
+
'.mp4', '.ogg', '.otf', '.pdf', '.png', '.svg', '.ttf', '.wav', '.webm',
|
|
18
|
+
'.webp', '.woff', '.woff2',
|
|
19
|
+
]);
|
|
20
|
+
/** Build the built-in Doxbrix reader into portable static files. */
|
|
21
|
+
export async function buildDoxbrixStaticSite(options) {
|
|
22
|
+
const root = resolve(options.root);
|
|
23
|
+
const project = await loadProject(root);
|
|
24
|
+
if (project.generator !== 'doxbrix') {
|
|
25
|
+
throw new DoxloopError('The built-in static builder only supports Doxbrix projects.');
|
|
26
|
+
}
|
|
27
|
+
const outputDir = resolve(options.outDir ?? join(root, 'build'));
|
|
28
|
+
if (outputDir === root) {
|
|
29
|
+
throw new DoxloopError('Doxbrix build output cannot replace the project directory.');
|
|
30
|
+
}
|
|
31
|
+
const contentRoot = await resolveContainedDirectory(root, project.contentDir, 'Build content directory', { allowRoot: true });
|
|
32
|
+
if (options.clean !== false)
|
|
33
|
+
await rm(outputDir, { recursive: true, force: true });
|
|
34
|
+
await mkdir(join(outputDir, '__doxloop'), { recursive: true });
|
|
35
|
+
const [paths, site, css, qualityConfig, verification] = await Promise.all([
|
|
36
|
+
loadPages(root, project),
|
|
37
|
+
loadSiteConfig(root, project),
|
|
38
|
+
readFile(DOXBRIX_CSS, 'utf8'),
|
|
39
|
+
loadQualityConfig(root),
|
|
40
|
+
loadQualityConfig(root).then((config) => config.readerVerification?.enabled ? reverifyClaims(root, project, false).then((result) => result.metadata) : undefined),
|
|
41
|
+
]);
|
|
42
|
+
if (paths.length === 0)
|
|
43
|
+
throw new DoxloopError('Doxbrix static build found no pages.');
|
|
44
|
+
const basePath = normalizeBasePath(options.basePath);
|
|
45
|
+
const siteUrl = publicSiteUrl(options.siteUrl, site.site);
|
|
46
|
+
const search = [];
|
|
47
|
+
const sitemap = [];
|
|
48
|
+
const ids = new Set(paths.map((path) => pageId(contentRoot, path)));
|
|
49
|
+
const navigationHome = firstSitePage(site);
|
|
50
|
+
const firstId = navigationHome && ids.has(navigationHome) ? navigationHome : pageId(contentRoot, paths[0]);
|
|
51
|
+
for (const path of paths) {
|
|
52
|
+
const id = pageId(contentRoot, path);
|
|
53
|
+
const parsed = matter(await readFile(path, 'utf8'));
|
|
54
|
+
const title = text(parsed.data.title) || labelFromId(id);
|
|
55
|
+
const description = text(parsed.data.description);
|
|
56
|
+
const href = `${basePath}/${id}`;
|
|
57
|
+
const canonical = text(parsed.data.canonical) || (siteUrl ? new URL(`${id}/`, trailingSlash(siteUrl)).toString() : undefined);
|
|
58
|
+
const socialImage = resolvePublicReference(text(parsed.data.socialImage), siteUrl);
|
|
59
|
+
const verificationEntry = qualityConfig.readerVerification?.enabled
|
|
60
|
+
? verification?.pages[relative(root, path).replace(/\\/g, '/')]
|
|
61
|
+
: undefined;
|
|
62
|
+
const html = doxbrixDocument({
|
|
63
|
+
site,
|
|
64
|
+
title,
|
|
65
|
+
...(description ? { description } : {}),
|
|
66
|
+
...(canonical ? { canonical } : {}),
|
|
67
|
+
...(socialImage ? { socialImage } : {}),
|
|
68
|
+
current: id,
|
|
69
|
+
rendered: renderMarkdown(parsed.content),
|
|
70
|
+
...(verificationEntry ? { verification: verificationEntry } : {}),
|
|
71
|
+
basePath,
|
|
72
|
+
liveReload: false,
|
|
73
|
+
});
|
|
74
|
+
const pageOutput = join(outputDir, ...id.split('/'), 'index.html');
|
|
75
|
+
await mkdir(dirname(pageOutput), { recursive: true });
|
|
76
|
+
await writeFile(pageOutput, html, 'utf8');
|
|
77
|
+
if (id.endsWith('/index'))
|
|
78
|
+
await writeFile(join(outputDir, ...id.split('/').slice(0, -1), 'index.html'), html, 'utf8');
|
|
79
|
+
search.push({ title, description: description ?? '', href, text: markdownSearchText(parsed.content) });
|
|
80
|
+
if (siteUrl)
|
|
81
|
+
sitemap.push(new URL(`${id}/`, trailingSlash(siteUrl)).toString());
|
|
82
|
+
}
|
|
83
|
+
// Write last so an unlisted index page cannot replace the navigation homepage.
|
|
84
|
+
await cp(join(outputDir, ...firstId.split('/'), 'index.html'), join(outputDir, 'index.html'));
|
|
85
|
+
const builtPages = new Map([...ids].map((id) => [id, id]));
|
|
86
|
+
for (const [from, to] of Object.entries(await readRedirects(root))) {
|
|
87
|
+
// A redirect stub is only worth writing when the page it points at exists in this build.
|
|
88
|
+
if (to !== '/' && !redirectTarget(to, site, builtPages))
|
|
89
|
+
continue;
|
|
90
|
+
const target = `${basePath}${to}`;
|
|
91
|
+
const output = join(outputDir, from.replace(/^\//, ''), 'index.html');
|
|
92
|
+
await mkdir(dirname(output), { recursive: true });
|
|
93
|
+
await writeFile(output, `<!doctype html><meta charset="utf-8"><meta http-equiv="refresh" content="0; url=${target}"><link rel="canonical" href="${target}"><a href="${target}">This page has moved</a>`, 'utf8');
|
|
94
|
+
}
|
|
95
|
+
await writeFile(join(outputDir, '__doxloop', 'doxbrix.css'), css, 'utf8');
|
|
96
|
+
await writeFile(join(outputDir, '__doxloop', 'search-index'), JSON.stringify(search), 'utf8');
|
|
97
|
+
await writeFile(join(outputDir, '__doxloop', 'search-index.json'), `${JSON.stringify(search, null, 2)}\n`, 'utf8');
|
|
98
|
+
await writeFile(join(outputDir, 'sitemap.xml'), sitemapXml(sitemap), 'utf8');
|
|
99
|
+
await writeFile(join(outputDir, 'robots.txt'), robotsTxt(siteUrl), 'utf8');
|
|
100
|
+
let assets = 0;
|
|
101
|
+
const ignoredDirectories = new Set(ROOT_CONTENT_IGNORED_DIRECTORIES);
|
|
102
|
+
ignoredDirectories.add(relative(contentRoot, outputDir).split(/[\\/]/)[0] || 'build');
|
|
103
|
+
for (const asset of await listFiles(contentRoot, ASSET_EXTENSIONS, { ignoredDirectories })) {
|
|
104
|
+
const destination = join(outputDir, relative(contentRoot, asset));
|
|
105
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
106
|
+
await cp(asset, destination);
|
|
107
|
+
assets++;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
outputDir,
|
|
111
|
+
pages: paths.length,
|
|
112
|
+
assets,
|
|
113
|
+
basePath,
|
|
114
|
+
...(siteUrl ? { siteUrl } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function publicSiteUrl(explicit, site) {
|
|
118
|
+
const configured = explicit ?? process.env.DOXLOOP_SITE_URL ?? text(site?.url);
|
|
119
|
+
if (!configured)
|
|
120
|
+
return undefined;
|
|
121
|
+
let url;
|
|
122
|
+
try {
|
|
123
|
+
url = new URL(configured);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
throw new DoxloopError(`Invalid static site URL: ${configured}`);
|
|
127
|
+
}
|
|
128
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
129
|
+
throw new DoxloopError('Static site URL must be an HTTP(S) URL without credentials, query, or fragment.');
|
|
130
|
+
}
|
|
131
|
+
return url.toString();
|
|
132
|
+
}
|
|
133
|
+
function resolvePublicReference(value, siteUrl) {
|
|
134
|
+
if (!value)
|
|
135
|
+
return undefined;
|
|
136
|
+
if (/^https?:\/\//i.test(value) || !siteUrl)
|
|
137
|
+
return value;
|
|
138
|
+
return new URL(value.replace(/^\//, ''), trailingSlash(siteUrl)).toString();
|
|
139
|
+
}
|
|
140
|
+
function normalizeBasePath(value = '') {
|
|
141
|
+
const trimmed = value.trim();
|
|
142
|
+
if (!trimmed || trimmed === '/')
|
|
143
|
+
return '';
|
|
144
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(trimmed) || trimmed.startsWith('//')) {
|
|
145
|
+
throw new DoxloopError('Static build base path must be an origin-relative URL path.');
|
|
146
|
+
}
|
|
147
|
+
const normalized = `/${trimmed.replace(/^\/+|\/+$/g, '')}`;
|
|
148
|
+
if (normalized.split('/').some((part) => part === '..')) {
|
|
149
|
+
throw new DoxloopError('Static build base path cannot leave its URL root.');
|
|
150
|
+
}
|
|
151
|
+
return normalized;
|
|
152
|
+
}
|
|
153
|
+
function sitemapXml(urls) {
|
|
154
|
+
const body = urls.map((url) => ` <url><loc>${xml(url)}</loc></url>`).join('\n');
|
|
155
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${body}${body ? '\n' : ''}</urlset>\n`;
|
|
156
|
+
}
|
|
157
|
+
function robotsTxt(siteUrl) {
|
|
158
|
+
return `User-agent: *\nAllow: /\n${siteUrl ? `Sitemap: ${new URL('sitemap.xml', trailingSlash(siteUrl)).toString()}\n` : ''}`;
|
|
159
|
+
}
|
|
160
|
+
function trailingSlash(value) { return value.endsWith('/') ? value : `${value}/`; }
|
|
161
|
+
function text(value) { return typeof value === 'string' && value.trim() ? value.trim() : undefined; }
|
|
162
|
+
function xml(value) { return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); }
|
|
163
|
+
function labelFromId(id) { return id.split('/').at(-1).replace(/[-_]+/g, ' ').replace(/\b\w/g, (letter) => letter.toUpperCase()); }
|
|
164
|
+
function markdownSearchText(markdown) {
|
|
165
|
+
return markdown.replace(/```[\s\S]*?```/g, ' ').replace(/`([^`]+)`/g, '$1').replace(/!\[[^\]]*\]\([^)]+\)/g, ' ').replace(/\[([^\]]+)\]\([^)]+\)/g, '$1').replace(/<[^>]+>/g, ' ').replace(/[#>*_~|=-]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 12_000);
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=doxbrix-build.js.map
|
|
@@ -16,5 +16,14 @@ export interface RenderResult {
|
|
|
16
16
|
html: string;
|
|
17
17
|
toc: TocEntry[];
|
|
18
18
|
}
|
|
19
|
+
export interface EditableBlock {
|
|
20
|
+
start: number;
|
|
21
|
+
end: number;
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
/** Map editable rendered regions to exact source spans, including repeated blocks and CRLF. */
|
|
25
|
+
export declare function renderEditableMarkdown(md: string): RenderResult & {
|
|
26
|
+
blocks: EditableBlock[];
|
|
27
|
+
};
|
|
19
28
|
export declare function renderMarkdown(md: string): RenderResult;
|
|
20
29
|
//# sourceMappingURL=doxbrix-markdown.d.ts.map
|
package/dist/doxbrix-markdown.js
CHANGED
|
@@ -18,6 +18,53 @@ const uid = () => `t${(uidCounter++).toString(36)}`;
|
|
|
18
18
|
let tocEntries = [];
|
|
19
19
|
const slugCounts = new Map();
|
|
20
20
|
let renderDepth = 0;
|
|
21
|
+
let editableSource;
|
|
22
|
+
let editableBlocks = [];
|
|
23
|
+
let originalEditableSource = '';
|
|
24
|
+
let editableOffsets = [];
|
|
25
|
+
let editableCursor = 0;
|
|
26
|
+
let editScope;
|
|
27
|
+
/** Map editable rendered regions to exact source spans, including repeated blocks and CRLF. */
|
|
28
|
+
export function renderEditableMarkdown(md) {
|
|
29
|
+
originalEditableSource = md;
|
|
30
|
+
editableSource = md.replace(/\r\n/g, '\n');
|
|
31
|
+
editableOffsets = [];
|
|
32
|
+
for (let i = 0; i < md.length; i++) {
|
|
33
|
+
editableOffsets.push(i);
|
|
34
|
+
if (md[i] === '\r' && md[i + 1] === '\n')
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
editableOffsets.push(md.length);
|
|
38
|
+
editableCursor = 0;
|
|
39
|
+
editScope = undefined;
|
|
40
|
+
editableBlocks = [];
|
|
41
|
+
try {
|
|
42
|
+
return { ...renderMarkdown(md), blocks: [...editableBlocks] };
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
editableSource = undefined;
|
|
46
|
+
editableBlocks = [];
|
|
47
|
+
editableOffsets = [];
|
|
48
|
+
editScope = undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function editAttributes(text, code = false) {
|
|
52
|
+
if (!editableSource || !text.trim())
|
|
53
|
+
return '';
|
|
54
|
+
const from = editScope?.cursor ?? 0;
|
|
55
|
+
const start = editableSource.indexOf(text, from);
|
|
56
|
+
if (start < 0 || (editScope ? start + text.length > editScope.end : editableSource.indexOf(text, start + 1) !== -1))
|
|
57
|
+
return '';
|
|
58
|
+
if (editScope)
|
|
59
|
+
editScope.cursor = start + text.length;
|
|
60
|
+
const sourceStart = editableOffsets[start];
|
|
61
|
+
const sourceEnd = editableOffsets[start + text.length];
|
|
62
|
+
if (editableBlocks.some((block) => sourceStart < block.end && sourceEnd > block.start))
|
|
63
|
+
return '';
|
|
64
|
+
const original = originalEditableSource.slice(sourceStart, sourceEnd);
|
|
65
|
+
editableBlocks.push({ start: sourceStart, end: sourceEnd, text: original });
|
|
66
|
+
return ` data-edit-start="${sourceStart}" data-edit-end="${sourceEnd}" data-edit-format="${code ? 'plain' : 'markdown'}" data-edit-source="${escAttr(original)}"`;
|
|
67
|
+
}
|
|
21
68
|
export function renderMarkdown(md) {
|
|
22
69
|
uidCounter = 0;
|
|
23
70
|
tocEntries = [];
|
|
@@ -417,7 +464,7 @@ function codeBlock(code, lang) {
|
|
|
417
464
|
catch {
|
|
418
465
|
highlighted = esc(code);
|
|
419
466
|
}
|
|
420
|
-
return `<div class="dp-code-block dp-code-theme-auto dp-code-theme-light"><div class="dp-code-header"><span class="dp-code-lang">${esc(lang || 'plaintext')}</span><button class="dp-code-copy-btn" type="button" aria-label="Copy code">${copyIcon()}<span>Copy</span></button></div><pre class="dp-code-pre dp-hl-auto dp-hl-light"><code>${highlighted}</code></pre></div>`;
|
|
467
|
+
return `<div class="dp-code-block dp-code-theme-auto dp-code-theme-light"><div class="dp-code-header"><span class="dp-code-lang">${esc(lang || 'plaintext')}</span><button class="dp-code-copy-btn" type="button" aria-label="Copy code">${copyIcon()}<span>Copy</span></button></div><pre class="dp-code-pre dp-hl-auto dp-hl-light"><code${editAttributes(code, true)}>${highlighted}</code></pre></div>`;
|
|
421
468
|
}
|
|
422
469
|
function copyIcon() {
|
|
423
470
|
return '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
@@ -675,7 +722,26 @@ function responseExample(_props, inner) {
|
|
|
675
722
|
// Markdown chunk renderer.
|
|
676
723
|
// ---------------------------------------------------------------------------
|
|
677
724
|
function renderMarkdownChunk(md, top) {
|
|
725
|
+
const previous = editScope;
|
|
726
|
+
const start = editableSource?.indexOf(md, editableCursor) ?? -1;
|
|
727
|
+
editScope = start >= 0 ? { start, end: start + md.length, cursor: start } : undefined;
|
|
728
|
+
if (start >= 0)
|
|
729
|
+
editableCursor = start + md.length;
|
|
730
|
+
try {
|
|
731
|
+
return renderMarkdownLines(md, top);
|
|
732
|
+
}
|
|
733
|
+
finally {
|
|
734
|
+
editScope = previous;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function renderMarkdownLines(md, top) {
|
|
678
738
|
const lines = md.split('\n');
|
|
739
|
+
const lineOffsets = [];
|
|
740
|
+
let offset = 0;
|
|
741
|
+
for (const line of lines) {
|
|
742
|
+
lineOffsets.push(offset);
|
|
743
|
+
offset += line.length + 1;
|
|
744
|
+
}
|
|
679
745
|
const out = [];
|
|
680
746
|
let i = 0;
|
|
681
747
|
let guard = 0;
|
|
@@ -685,10 +751,14 @@ function renderMarkdownChunk(md, top) {
|
|
|
685
751
|
break;
|
|
686
752
|
}
|
|
687
753
|
const line = lines[i];
|
|
688
|
-
|
|
754
|
+
if (editScope)
|
|
755
|
+
editScope.cursor = editScope.start + lineOffsets[i];
|
|
756
|
+
const fence = /^\s*(```+|~~~+)\s*([^\s`~]*)(?:\s+.*)?$/.exec(line);
|
|
689
757
|
if (fence) {
|
|
690
758
|
const lang = fence[2] ?? '';
|
|
691
759
|
const body = [];
|
|
760
|
+
if (editScope)
|
|
761
|
+
editScope.cursor = editScope.start + lineOffsets[i] + line.length + 1;
|
|
692
762
|
i++;
|
|
693
763
|
while (i < lines.length && !/^\s*(```|~~~)\s*$/.test(lines[i]))
|
|
694
764
|
body.push(lines[i++]);
|
|
@@ -712,8 +782,10 @@ function renderMarkdownChunk(md, top) {
|
|
|
712
782
|
i++;
|
|
713
783
|
continue;
|
|
714
784
|
}
|
|
715
|
-
const h =
|
|
785
|
+
const h = /^\s*(#{1,6})\s+(.*)$/.exec(line);
|
|
716
786
|
if (h) {
|
|
787
|
+
if (editScope)
|
|
788
|
+
editScope.cursor += line.indexOf(h[2], h[1].length);
|
|
717
789
|
out.push(heading(h[1].length, h[2], top));
|
|
718
790
|
i++;
|
|
719
791
|
continue;
|
|
@@ -742,10 +814,14 @@ function renderMarkdownChunk(md, top) {
|
|
|
742
814
|
if (/^\s*([-*+]|\d+\.)\s+/.test(line)) {
|
|
743
815
|
const ordered = /^\s*\d+\.\s+/.test(line);
|
|
744
816
|
const items = [];
|
|
817
|
+
const itemStarts = [];
|
|
745
818
|
while (i < lines.length && /^\s*([-*+]|\d+\.)\s+/.test(lines[i])) {
|
|
746
|
-
|
|
819
|
+
const item = lines[i].replace(/^\s*([-*+]|\d+\.)\s+/, '');
|
|
820
|
+
itemStarts.push((editScope?.start ?? 0) + lineOffsets[i] + lines[i].length - item.length);
|
|
821
|
+
items.push(item);
|
|
822
|
+
i++;
|
|
747
823
|
}
|
|
748
|
-
out.push(renderList(items, ordered));
|
|
824
|
+
out.push(renderList(items, ordered, itemStarts));
|
|
749
825
|
continue;
|
|
750
826
|
}
|
|
751
827
|
// Lone-image paragraph -> block image (matches the reader's image block).
|
|
@@ -761,8 +837,11 @@ function renderMarkdownChunk(md, top) {
|
|
|
761
837
|
!/^\s*(#{1,6}\s|>\s?|```|~~~|<!--|<\/[A-Za-z]|([-*+]|\d+\.)\s+)/.test(lines[i])) {
|
|
762
838
|
para.push(lines[i++]);
|
|
763
839
|
}
|
|
840
|
+
// A malformed block opener must still advance, preserving text below it.
|
|
841
|
+
if (!para.length)
|
|
842
|
+
para.push(lines[i++]);
|
|
764
843
|
if (para.length)
|
|
765
|
-
out.push(`<p class="dp-p">${inline(para.join(' '))}</p>`);
|
|
844
|
+
out.push(`<p class="dp-p"${editAttributes(para.join('\n'))}>${inline(para.join(' '))}</p>`);
|
|
766
845
|
}
|
|
767
846
|
return out.join('\n');
|
|
768
847
|
}
|
|
@@ -772,31 +851,39 @@ function heading(level, text, top) {
|
|
|
772
851
|
if (top && (lvl === 2 || lvl === 3 || lvl === 4)) {
|
|
773
852
|
tocEntries.push({ id, level: lvl, title: stripInline(text) });
|
|
774
853
|
}
|
|
775
|
-
return `<h${lvl} id="${escAttr(id)}" class="dp-h${lvl}">${inline(text)}</h${lvl}>`;
|
|
854
|
+
return `<h${lvl} id="${escAttr(id)}" class="dp-h${lvl}"${editAttributes(text)}>${inline(text)}</h${lvl}>`;
|
|
776
855
|
}
|
|
777
|
-
function renderList(items, ordered) {
|
|
856
|
+
function renderList(items, ordered, starts) {
|
|
778
857
|
const isTask = items.some((it) => /^\[[ xX]\]\s+/.test(it));
|
|
779
858
|
if (isTask) {
|
|
780
859
|
const lis = items
|
|
781
|
-
.map((it) => {
|
|
860
|
+
.map((it, index) => {
|
|
861
|
+
if (editScope)
|
|
862
|
+
editScope.cursor = starts[index];
|
|
782
863
|
const m = /^\[([ xX])\]\s+(.*)$/.exec(it);
|
|
783
864
|
const checked = m && m[1] !== ' ';
|
|
784
865
|
const icon = `<span class="dp-task-check-icon${checked ? ' checked' : ''}"></span>`;
|
|
785
866
|
const txt = m ? m[2] : it;
|
|
867
|
+
if (editScope)
|
|
868
|
+
editScope.cursor += it.length - txt.length;
|
|
786
869
|
const style = checked ? ' style="text-decoration:line-through;color:#9ca3af"' : '';
|
|
787
|
-
return `<li class="dp-task-item">${icon}<span${style}>${inline(txt)}</span></li>`;
|
|
870
|
+
return `<li class="dp-task-item">${icon}<span${style}${editAttributes(txt)}>${inline(txt)}</span></li>`;
|
|
788
871
|
})
|
|
789
872
|
.join('');
|
|
790
873
|
return `<ul class="dp-task-list">${lis}</ul>`;
|
|
791
874
|
}
|
|
792
875
|
const tag = ordered ? 'ol' : 'ul';
|
|
793
876
|
const cls = ordered ? 'dp-ol' : 'dp-ul';
|
|
794
|
-
const lis = items.map((it) =>
|
|
877
|
+
const lis = items.map((it, index) => {
|
|
878
|
+
if (editScope)
|
|
879
|
+
editScope.cursor = starts[index];
|
|
880
|
+
return `<li${editAttributes(it)}>${inline(it)}</li>`;
|
|
881
|
+
}).join('');
|
|
795
882
|
return `<${tag} class="${cls}">${lis}</${tag}>`;
|
|
796
883
|
}
|
|
797
884
|
function renderTable(header, rows) {
|
|
798
|
-
const head = `<tr>${header.map((c) => `<th>${inline(c)}</th>`).join('')}</tr>`;
|
|
799
|
-
const body = rows.map((r) => `<tr>${r.map((c) => `<td>${inline(c)}</td>`).join('')}</tr>`).join('');
|
|
885
|
+
const head = `<tr>${header.map((c) => `<th${editAttributes(c)}>${inline(c)}</th>`).join('')}</tr>`;
|
|
886
|
+
const body = rows.map((r) => `<tr>${r.map((c) => `<td${editAttributes(c)}>${inline(c)}</td>`).join('')}</tr>`).join('');
|
|
800
887
|
return `<div class="dp-table-wrap"><table class="dp-table"><thead>${head}</thead><tbody>${body}</tbody></table></div>`;
|
|
801
888
|
}
|
|
802
889
|
// ---------------------------------------------------------------------------
|
|
@@ -809,7 +896,7 @@ function inline(text) {
|
|
|
809
896
|
const html = tag === 'Badge'
|
|
810
897
|
? `<span class="dp-inline-badge">${esc(str(props.text) || (body ?? '').trim())}</span>`
|
|
811
898
|
: `<span class="dp-inline-badge">${esc(iconGlyph(str(props.name)))}</span>`;
|
|
812
|
-
tokens.push(html);
|
|
899
|
+
tokens.push(editableSource !== undefined ? `<span contenteditable="false" data-md-atom="${escAttr(_m)}">${html}</span>` : html);
|
|
813
900
|
return `${TOK_OPEN}${tokens.length - 1}${TOK_CLOSE}`;
|
|
814
901
|
});
|
|
815
902
|
s = esc(s);
|
|
@@ -821,6 +908,20 @@ function inline(text) {
|
|
|
821
908
|
s = s.replace(/~~([^~]+)~~/g, '<s>$1</s>');
|
|
822
909
|
s = s.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
|
|
823
910
|
s = s.replace(/(^|[^_\w])_([^_]+)_/g, '$1<em>$2</em>');
|
|
911
|
+
if (editableSource !== undefined) {
|
|
912
|
+
const links = [...text.matchAll(/(?<!!)\[([^\]]+)\](\([^)\s]+[^)]*\))/g)];
|
|
913
|
+
let linkIndex = 0;
|
|
914
|
+
s = s.replace(/<a class="df-inline-link"[^>]*>/g, (tag) => {
|
|
915
|
+
const link = links[linkIndex++];
|
|
916
|
+
return link ? tag.replace(/>$/, ` data-md-suffix="${escAttr(']' + link[2])}">`) : tag;
|
|
917
|
+
});
|
|
918
|
+
const images = [...text.matchAll(/!\[([^\]]*)\]\([^)\s]+[^)]*\)/g)];
|
|
919
|
+
let imageIndex = 0;
|
|
920
|
+
s = s.replace(/<img [^>]*\/>/g, (tag) => {
|
|
921
|
+
const source = images[imageIndex++]?.[0];
|
|
922
|
+
return source ? `<span contenteditable="false" data-md-atom="${escAttr(source)}">${tag}</span>` : tag;
|
|
923
|
+
});
|
|
924
|
+
}
|
|
824
925
|
if (tokens.length) {
|
|
825
926
|
s = s.replace(new RegExp(`${TOK_OPEN}(\\d+)${TOK_CLOSE}`, 'g'), (_m, k) => tokens[Number(k)] ?? '');
|
|
826
927
|
}
|
package/dist/drift.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DoxloopProject, DriftResult, SourceChange } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Answer "which documentation pages no longer match the product?" without
|
|
4
|
+
* starting an agent. Detection is deterministic and safe to run on a schedule
|
|
5
|
+
* or in continuous integration.
|
|
6
|
+
*/
|
|
7
|
+
export declare function computeDrift(root: string, project: DoxloopProject): Promise<DriftResult>;
|
|
8
|
+
export declare function computeDriftFromChanges(root: string, project: DoxloopProject, changes: SourceChange[]): Promise<DriftResult>;
|
|
9
|
+
export declare function formatDrift(result: DriftResult): string;
|
|
10
|
+
//# sourceMappingURL=drift.d.ts.map
|
package/dist/drift.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { pagesForChange, readEvidenceMap } from './evidence.js';
|
|
2
|
+
import { isWatchedPath } from './globs.js';
|
|
3
|
+
import { changedSourcePaths, collectSourceChanges, readSyncState } from './sync.js';
|
|
4
|
+
/**
|
|
5
|
+
* Answer "which documentation pages no longer match the product?" without
|
|
6
|
+
* starting an agent. Detection is deterministic and safe to run on a schedule
|
|
7
|
+
* or in continuous integration.
|
|
8
|
+
*/
|
|
9
|
+
export async function computeDrift(root, project) {
|
|
10
|
+
const changes = await collectSourceChanges(root, project.sources);
|
|
11
|
+
return computeDriftFromChanges(root, project, changes);
|
|
12
|
+
}
|
|
13
|
+
export async function computeDriftFromChanges(root, project, changes) {
|
|
14
|
+
const map = await readEvidenceMap(root);
|
|
15
|
+
const notes = [];
|
|
16
|
+
const sources = [];
|
|
17
|
+
const reasonsByPage = new Map();
|
|
18
|
+
let unresolved = false;
|
|
19
|
+
for (const change of changes) {
|
|
20
|
+
const rawPaths = changedSourcePaths(change);
|
|
21
|
+
const changedPaths = rawPaths.filter((path) => isWatchedPath(path, project.sync.watch, project.sync.ignore));
|
|
22
|
+
sources.push({
|
|
23
|
+
name: change.name,
|
|
24
|
+
path: change.path,
|
|
25
|
+
kind: change.kind,
|
|
26
|
+
changedPaths,
|
|
27
|
+
filteredPaths: rawPaths.length - changedPaths.length,
|
|
28
|
+
...('baseline' in change ? { baseline: change.baseline } : {}),
|
|
29
|
+
...('head' in change ? { head: change.head } : {}),
|
|
30
|
+
...(change.scope ? { scope: change.scope } : {}),
|
|
31
|
+
});
|
|
32
|
+
const note = unresolvedNote(change);
|
|
33
|
+
if (note) {
|
|
34
|
+
notes.push(note);
|
|
35
|
+
unresolved = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (changedPaths.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
if (!map) {
|
|
41
|
+
notes.push(`Source "${change.name}" changed, but no evidence map exists yet, so affected pages cannot be named. Run \`doxloop update\` to build one.`);
|
|
42
|
+
unresolved = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const matched = pagesForChange(map, change.name, changedPaths);
|
|
46
|
+
for (const [page, paths] of matched) {
|
|
47
|
+
const reasons = reasonsByPage.get(page) ?? [];
|
|
48
|
+
reasons.push({
|
|
49
|
+
source: change.name,
|
|
50
|
+
paths,
|
|
51
|
+
...('baseline' in change ? { baseline: change.baseline } : {}),
|
|
52
|
+
...('head' in change ? { head: change.head } : {}),
|
|
53
|
+
});
|
|
54
|
+
reasonsByPage.set(page, reasons);
|
|
55
|
+
}
|
|
56
|
+
const attributed = new Set([...matched.values()].flat());
|
|
57
|
+
const unattributed = changedPaths.filter((path) => !attributed.has(path));
|
|
58
|
+
if (unattributed.length > 0) {
|
|
59
|
+
notes.push(`${unattributed.length} changed file${unattributed.length === 1 ? '' : 's'} in "${change.name}" ${unattributed.length === 1 ? 'is' : 'are'} not referenced by any page.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (map && project.sync.maxVerificationAgeDays) {
|
|
63
|
+
const state = await readSyncState(root);
|
|
64
|
+
const threshold = project.sync.maxVerificationAgeDays;
|
|
65
|
+
const severity = project.sync.maxVerificationAgeSeverity ?? 'warn';
|
|
66
|
+
for (const [page, evidence] of Object.entries(map.pages)) {
|
|
67
|
+
for (const entry of evidence.sources) {
|
|
68
|
+
const record = state.sources[entry.source];
|
|
69
|
+
const explicit = evidence.verifiedOn?.[entry.source];
|
|
70
|
+
const revisionMatches = record && evidence.verifiedAt?.[entry.source] && [record.commit, record.contentFingerprint].includes(evidence.verifiedAt[entry.source]);
|
|
71
|
+
const verifiedOn = explicit ?? (revisionMatches ? record.recordedAt : undefined);
|
|
72
|
+
const ageDays = verifiedOn ? Math.floor((Date.now() - Date.parse(verifiedOn)) / 86_400_000) : Number.POSITIVE_INFINITY;
|
|
73
|
+
if (ageDays <= threshold)
|
|
74
|
+
continue;
|
|
75
|
+
const ageLabel = Number.isFinite(ageDays) ? `${ageDays} days` : 'an unknown amount of time';
|
|
76
|
+
if (severity === 'warn') {
|
|
77
|
+
notes.push(`Page "${page}" was last verified against "${entry.source}" ${ageLabel} ago; policy is ${threshold} days.`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const reasons = reasonsByPage.get(page) ?? [];
|
|
81
|
+
reasons.push({ source: entry.source, paths: [], kind: 'max-age', ...(Number.isFinite(ageDays) ? { ageDays } : {}) });
|
|
82
|
+
reasonsByPage.set(page, reasons);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const pages = [...reasonsByPage.entries()]
|
|
87
|
+
.map(([page, reasons]) => stalePage(page, reasons, map))
|
|
88
|
+
.sort((left, right) => left.page.localeCompare(right.page));
|
|
89
|
+
return {
|
|
90
|
+
status: pages.length > 0 ? 'stale' : unresolved ? 'unknown' : 'current',
|
|
91
|
+
pages,
|
|
92
|
+
trackedPages: map ? Object.keys(map.pages).length : 0,
|
|
93
|
+
sources,
|
|
94
|
+
evidenceMap: map ? 'present' : 'missing',
|
|
95
|
+
notes,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function stalePage(page, reasons, map) {
|
|
99
|
+
const recorded = map?.pages[page]?.verifiedAt;
|
|
100
|
+
const verifiedAt = recorded
|
|
101
|
+
? recorded[reasons[0]?.source ?? '']
|
|
102
|
+
: undefined;
|
|
103
|
+
return { page, reasons, ...(verifiedAt ? { verifiedAt } : {}) };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Conditions where drift cannot be decided from local state. These are
|
|
107
|
+
* reported rather than silently treated as "documentation is current".
|
|
108
|
+
*/
|
|
109
|
+
function unresolvedNote(change) {
|
|
110
|
+
switch (change.kind) {
|
|
111
|
+
case 'missing-path':
|
|
112
|
+
return `Source "${change.name}" is configured as ${change.path}, which does not exist.`;
|
|
113
|
+
case 'not-git':
|
|
114
|
+
return `Source "${change.name}" is not a Git repository, so changes cannot be compared.`;
|
|
115
|
+
case 'no-baseline':
|
|
116
|
+
return `Source "${change.name}" has no sync baseline yet. Accepting the next update records one.`;
|
|
117
|
+
case 'baseline-lost':
|
|
118
|
+
return `The recorded baseline for source "${change.name}" no longer exists, so changes cannot be compared.`;
|
|
119
|
+
case 'spec-remote':
|
|
120
|
+
return `The API specification "${change.name}" is remote and is not compared locally. The next update checks it.`;
|
|
121
|
+
default:
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function formatDrift(result) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
if (result.status === 'stale') {
|
|
128
|
+
lines.push(`Documentation drift: ${result.pages.length} page${result.pages.length === 1 ? '' : 's'} stale`, '');
|
|
129
|
+
for (const page of result.pages) {
|
|
130
|
+
lines.push(` ${page.page}`);
|
|
131
|
+
for (const reason of page.reasons) {
|
|
132
|
+
if (reason.kind === 'max-age') {
|
|
133
|
+
lines.push(` stale because verification is ${reason.ageDays === undefined ? 'undated' : `${reason.ageDays} days old`} for source "${reason.source}"`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const because = reason.paths.length === 0
|
|
137
|
+
? `source "${reason.source}" changed`
|
|
138
|
+
: `${reason.paths.slice(0, 5).join(', ')}${reason.paths.length > 5 ? `, and ${reason.paths.length - 5} more` : ''} changed`;
|
|
139
|
+
lines.push(` stale because ${because}${reason.head ? ` (${reason.head.slice(0, 12)})` : ''}`);
|
|
140
|
+
}
|
|
141
|
+
if (page.verifiedAt) {
|
|
142
|
+
lines.push(` last verified ${page.verifiedAt.slice(0, 12)}`);
|
|
143
|
+
}
|
|
144
|
+
lines.push('');
|
|
145
|
+
}
|
|
146
|
+
const current = Math.max(result.trackedPages - result.pages.length, 0);
|
|
147
|
+
if (current > 0)
|
|
148
|
+
lines.push(` ${current} other tracked page${current === 1 ? '' : 's'} current`, '');
|
|
149
|
+
}
|
|
150
|
+
else if (result.status === 'current') {
|
|
151
|
+
lines.push('Documentation is current with the recorded source baseline.', '');
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
lines.push('Documentation drift could not be determined.', '');
|
|
155
|
+
}
|
|
156
|
+
for (const note of result.notes)
|
|
157
|
+
lines.push(` Note: ${note}`);
|
|
158
|
+
if (result.notes.length > 0)
|
|
159
|
+
lines.push('');
|
|
160
|
+
if (result.status === 'stale')
|
|
161
|
+
lines.push('Fix with: doxloop update');
|
|
162
|
+
return lines.join('\n').trimEnd();
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=drift.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const EVALUATION_CONTRACT_VERSION: "1.0.0";
|
|
2
|
+
export declare const EVALUATION_BASELINE_FILE: string;
|
|
3
|
+
export interface EvaluationMetric {
|
|
4
|
+
id: string;
|
|
5
|
+
score: number;
|
|
6
|
+
weight: number;
|
|
7
|
+
detail: string;
|
|
8
|
+
}
|
|
9
|
+
export interface EvaluationReport {
|
|
10
|
+
schemaVersion: 1;
|
|
11
|
+
contractVersion: '1.0.0';
|
|
12
|
+
generatedAt: string;
|
|
13
|
+
mode: 'generation' | 'update';
|
|
14
|
+
project: string;
|
|
15
|
+
score: number;
|
|
16
|
+
metrics: EvaluationMetric[];
|
|
17
|
+
durationMs?: number;
|
|
18
|
+
usage?: {
|
|
19
|
+
inputTokens?: number;
|
|
20
|
+
outputTokens?: number;
|
|
21
|
+
costUsd?: number;
|
|
22
|
+
};
|
|
23
|
+
reviewer: {
|
|
24
|
+
accepted: number;
|
|
25
|
+
revised: number;
|
|
26
|
+
rejected: number;
|
|
27
|
+
};
|
|
28
|
+
regression?: {
|
|
29
|
+
baselineScore: number;
|
|
30
|
+
delta: number;
|
|
31
|
+
threshold: number;
|
|
32
|
+
blocked: boolean;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare function evaluateWorkspace(root: string, options: {
|
|
36
|
+
mode: 'generation' | 'update';
|
|
37
|
+
before?: string;
|
|
38
|
+
expectedChangedPages?: string[];
|
|
39
|
+
maximumPages?: number;
|
|
40
|
+
durationMs?: number;
|
|
41
|
+
usage?: EvaluationReport['usage'];
|
|
42
|
+
regressionThreshold?: number;
|
|
43
|
+
}): Promise<EvaluationReport>;
|
|
44
|
+
export declare function approveEvaluationBaseline(root: string, report: EvaluationReport): Promise<void>;
|
|
45
|
+
export declare function formatEvaluation(report: EvaluationReport): string;
|
|
46
|
+
//# sourceMappingURL=evaluation.d.ts.map
|