@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
package/dist/generators.js
CHANGED
|
@@ -3,11 +3,35 @@ import { join, resolve } from 'node:path';
|
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { DoxloopError, UsageError } from './errors.js';
|
|
5
5
|
import { GENERATOR_API_VERSION, } from './generator-api.js';
|
|
6
|
+
export const GENERATOR_TIERS = {
|
|
7
|
+
full: {
|
|
8
|
+
label: 'Full',
|
|
9
|
+
description: 'Navigation, components, and diagrams are validated and documented; preview and strict build are exercised in CI.',
|
|
10
|
+
},
|
|
11
|
+
supported: {
|
|
12
|
+
label: 'Supported',
|
|
13
|
+
description: 'Nested navigation is validated where the configuration is readable and reported as unverified otherwise; authoring syntax is documented.',
|
|
14
|
+
},
|
|
15
|
+
basic: {
|
|
16
|
+
label: 'Basic',
|
|
17
|
+
description: 'Scaffold, preview, and file checks work; navigation checks are limited to what the scaffold owns.',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export const GENERATOR_TOOLCHAINS = {
|
|
21
|
+
node: { label: 'Node.js 20.12+', detail: 'Node.js with npm, pnpm, or yarn on the PATH.' },
|
|
22
|
+
python: { label: 'Python 3.9+', detail: 'python3 with the venv and pip modules; Doxloop creates .doxloop/venv on first preview.' },
|
|
23
|
+
hugo: { label: 'Hugo', detail: 'The hugo binary on the PATH (extended edition recommended).' },
|
|
24
|
+
ruby: { label: 'Ruby + Bundler', detail: 'ruby and bundle on the PATH; Doxloop installs gems under .doxloop/bundle on first preview.' },
|
|
25
|
+
};
|
|
6
26
|
export const GENERATOR_CATALOG = [
|
|
7
27
|
{
|
|
8
28
|
id: 'doxbrix',
|
|
9
29
|
displayName: 'Doxbrix',
|
|
10
30
|
skillName: 'doxloop-doxbrix',
|
|
31
|
+
buildCommand: 'doxloop export --out build',
|
|
32
|
+
outputDir: 'build',
|
|
33
|
+
tier: 'full',
|
|
34
|
+
toolchain: [],
|
|
11
35
|
},
|
|
12
36
|
{
|
|
13
37
|
id: 'docusaurus',
|
|
@@ -16,6 +40,8 @@ export const GENERATOR_CATALOG = [
|
|
|
16
40
|
skillName: 'doxloop-docusaurus',
|
|
17
41
|
buildCommand: 'npm run build',
|
|
18
42
|
outputDir: 'build',
|
|
43
|
+
tier: 'full',
|
|
44
|
+
toolchain: ['node'],
|
|
19
45
|
},
|
|
20
46
|
{
|
|
21
47
|
id: 'mkdocs',
|
|
@@ -24,6 +50,8 @@ export const GENERATOR_CATALOG = [
|
|
|
24
50
|
skillName: 'doxloop-mkdocs',
|
|
25
51
|
buildCommand: 'mkdocs build --strict',
|
|
26
52
|
outputDir: 'site',
|
|
53
|
+
tier: 'full',
|
|
54
|
+
toolchain: ['python'],
|
|
27
55
|
},
|
|
28
56
|
{
|
|
29
57
|
id: 'sphinx',
|
|
@@ -32,6 +60,8 @@ export const GENERATOR_CATALOG = [
|
|
|
32
60
|
skillName: 'doxloop-sphinx',
|
|
33
61
|
buildCommand: 'sphinx-build -W -b html docs _build/html',
|
|
34
62
|
outputDir: '_build/html',
|
|
63
|
+
tier: 'supported',
|
|
64
|
+
toolchain: ['python'],
|
|
35
65
|
},
|
|
36
66
|
{
|
|
37
67
|
id: 'hugo',
|
|
@@ -40,6 +70,8 @@ export const GENERATOR_CATALOG = [
|
|
|
40
70
|
skillName: 'doxloop-hugo',
|
|
41
71
|
buildCommand: 'hugo --minify',
|
|
42
72
|
outputDir: 'public',
|
|
73
|
+
tier: 'supported',
|
|
74
|
+
toolchain: ['hugo'],
|
|
43
75
|
},
|
|
44
76
|
{
|
|
45
77
|
id: 'vitepress',
|
|
@@ -48,6 +80,8 @@ export const GENERATOR_CATALOG = [
|
|
|
48
80
|
skillName: 'doxloop-vitepress',
|
|
49
81
|
buildCommand: 'npm run docs:build',
|
|
50
82
|
outputDir: 'docs/.vitepress/dist',
|
|
83
|
+
tier: 'supported',
|
|
84
|
+
toolchain: ['node'],
|
|
51
85
|
},
|
|
52
86
|
{
|
|
53
87
|
id: 'markdoc',
|
|
@@ -56,6 +90,8 @@ export const GENERATOR_CATALOG = [
|
|
|
56
90
|
skillName: 'doxloop-markdoc',
|
|
57
91
|
buildCommand: 'npm run build',
|
|
58
92
|
outputDir: 'dist',
|
|
93
|
+
tier: 'basic',
|
|
94
|
+
toolchain: ['node'],
|
|
59
95
|
},
|
|
60
96
|
{
|
|
61
97
|
id: 'nextra',
|
|
@@ -64,6 +100,8 @@ export const GENERATOR_CATALOG = [
|
|
|
64
100
|
skillName: 'doxloop-nextra',
|
|
65
101
|
buildCommand: 'npm run build',
|
|
66
102
|
outputDir: 'out',
|
|
103
|
+
tier: 'basic',
|
|
104
|
+
toolchain: ['node'],
|
|
67
105
|
},
|
|
68
106
|
{
|
|
69
107
|
id: 'starlight',
|
|
@@ -72,6 +110,8 @@ export const GENERATOR_CATALOG = [
|
|
|
72
110
|
skillName: 'doxloop-starlight',
|
|
73
111
|
buildCommand: 'npm run build',
|
|
74
112
|
outputDir: 'dist',
|
|
113
|
+
tier: 'supported',
|
|
114
|
+
toolchain: ['node'],
|
|
75
115
|
},
|
|
76
116
|
{
|
|
77
117
|
id: 'jekyll',
|
|
@@ -80,6 +120,8 @@ export const GENERATOR_CATALOG = [
|
|
|
80
120
|
skillName: 'doxloop-jekyll',
|
|
81
121
|
buildCommand: 'bundle exec jekyll build --strict_front_matter',
|
|
82
122
|
outputDir: '_site',
|
|
123
|
+
tier: 'basic',
|
|
124
|
+
toolchain: ['ruby'],
|
|
83
125
|
},
|
|
84
126
|
{
|
|
85
127
|
id: 'static',
|
|
@@ -88,6 +130,8 @@ export const GENERATOR_CATALOG = [
|
|
|
88
130
|
skillName: 'doxloop-static',
|
|
89
131
|
buildCommand: 'npm run build',
|
|
90
132
|
outputDir: 'site',
|
|
133
|
+
tier: 'basic',
|
|
134
|
+
toolchain: ['node'],
|
|
91
135
|
},
|
|
92
136
|
];
|
|
93
137
|
export function generatorCatalogEntry(id) {
|
|
@@ -156,6 +200,9 @@ export async function installedGeneratorEntries(root) {
|
|
|
156
200
|
installed: entry.id === 'doxbrix' ||
|
|
157
201
|
(entry.packageName !== undefined &&
|
|
158
202
|
resolveGeneratorPackage(root, entry.packageName) !== undefined),
|
|
203
|
+
tierLabel: GENERATOR_TIERS[entry.tier].label,
|
|
204
|
+
tierDescription: GENERATOR_TIERS[entry.tier].description,
|
|
205
|
+
toolchainLabels: entry.toolchain.map((id) => GENERATOR_TOOLCHAINS[id].label),
|
|
159
206
|
})));
|
|
160
207
|
}
|
|
161
208
|
function validateAdapter(value, expectedId, packageName) {
|
|
@@ -175,6 +222,9 @@ function validateAdapter(value, expectedId, packageName) {
|
|
|
175
222
|
!adapter.authoring ||
|
|
176
223
|
typeof adapter.authoring.skillName !== 'string' ||
|
|
177
224
|
typeof adapter.authoring.skillDirectory !== 'string' ||
|
|
225
|
+
(adapter.planning !== undefined &&
|
|
226
|
+
(!Array.isArray(adapter.planning.navigationFiles) ||
|
|
227
|
+
!adapter.planning.navigationFiles.every((file) => typeof file === 'string'))) ||
|
|
178
228
|
!adapter.project ||
|
|
179
229
|
!Array.isArray(adapter.project.pageExtensions) ||
|
|
180
230
|
!Array.isArray(adapter.project.gitignore) ||
|
|
@@ -188,7 +238,10 @@ function validateAdapter(value, expectedId, packageName) {
|
|
|
188
238
|
typeof adapter.validate !== 'function' ||
|
|
189
239
|
(adapter.resolveLocalAsset !== undefined &&
|
|
190
240
|
typeof adapter.resolveLocalAsset !== 'function') ||
|
|
191
|
-
(adapter.readPage !== undefined && typeof adapter.readPage !== 'function')
|
|
241
|
+
(adapter.readPage !== undefined && typeof adapter.readPage !== 'function') ||
|
|
242
|
+
(adapter.writeNavigation !== undefined &&
|
|
243
|
+
typeof adapter.writeNavigation !== 'function') ||
|
|
244
|
+
(adapter.renderPage !== undefined && typeof adapter.renderPage !== 'function')) {
|
|
192
245
|
throw incompatible(packageName, 'does not satisfy the generator adapter contract');
|
|
193
246
|
}
|
|
194
247
|
return adapter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface GitDeliveryResult {
|
|
2
|
+
schemaVersion: 1;
|
|
3
|
+
proposalId: string;
|
|
4
|
+
branch: string;
|
|
5
|
+
baseBranch: string;
|
|
6
|
+
commit: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
compareUrl?: string;
|
|
9
|
+
pullRequestCommand?: string;
|
|
10
|
+
pushedAt?: string;
|
|
11
|
+
pullRequestUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Push a previously prepared branch and optionally ask GitHub CLI to open its pull request. */
|
|
14
|
+
export declare function publishProposalBranch(root: string, proposalId: string, createPullRequest: boolean): Promise<GitDeliveryResult>;
|
|
15
|
+
/** Materialize a reviewed proposal on an isolated local Git branch. */
|
|
16
|
+
export declare function createProposalBranch(root: string, proposalId: string, requestedBranch?: string): Promise<GitDeliveryResult>;
|
|
17
|
+
//# sourceMappingURL=git-delivery.d.ts.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { DoxloopError } from './errors.js';
|
|
7
|
+
import { ensureGitignoreEntries, pathExists } from './fs.js';
|
|
8
|
+
import { readSyncRun, runWorkspace } from './sync-runs.js';
|
|
9
|
+
const execute = promisify(execFile);
|
|
10
|
+
/** Push a previously prepared branch and optionally ask GitHub CLI to open its pull request. */
|
|
11
|
+
export async function publishProposalBranch(root, proposalId, createPullRequest) {
|
|
12
|
+
const path = join(root, '.doxloop', 'deliveries', `${proposalId}.json`);
|
|
13
|
+
if (!(await pathExists(path)))
|
|
14
|
+
throw new DoxloopError('Prepare the proposal branch before publishing it.');
|
|
15
|
+
let delivery;
|
|
16
|
+
try {
|
|
17
|
+
delivery = JSON.parse(await readFile(path, 'utf8'));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new DoxloopError('The saved proposal delivery record is unreadable.');
|
|
21
|
+
}
|
|
22
|
+
if (delivery.schemaVersion !== 1 || delivery.proposalId !== proposalId || !delivery.branch || !delivery.commit)
|
|
23
|
+
throw new DoxloopError('The saved proposal delivery record is invalid.');
|
|
24
|
+
const current = (await git(root, ['rev-parse', delivery.branch])).trim();
|
|
25
|
+
if (current !== delivery.commit)
|
|
26
|
+
throw new DoxloopError('The prepared delivery branch changed after review. Prepare a new branch before publishing.');
|
|
27
|
+
if (!(await optionalGit(root, ['remote', 'get-url', 'origin'])))
|
|
28
|
+
throw new DoxloopError('Add a Git remote named origin before publishing this branch.');
|
|
29
|
+
await git(root, ['push', '--set-upstream', 'origin', delivery.branch]);
|
|
30
|
+
let pullRequestUrl = delivery.pullRequestUrl;
|
|
31
|
+
if (createPullRequest && !pullRequestUrl) {
|
|
32
|
+
try {
|
|
33
|
+
pullRequestUrl = (await execute('gh', ['pr', 'create', '--fill', '--base', delivery.baseBranch, '--head', delivery.branch], { cwd: root, maxBuffer: 2_000_000 })).stdout.trim();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new DoxloopError(`The branch was pushed, but GitHub CLI could not create the pull request: ${error instanceof Error ? error.message : String(error)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
delivery = { ...delivery, pushedAt: new Date().toISOString(), ...(pullRequestUrl ? { pullRequestUrl } : {}) };
|
|
40
|
+
await writeFile(path, `${JSON.stringify(delivery, null, 2)}\n`, 'utf8');
|
|
41
|
+
return delivery;
|
|
42
|
+
}
|
|
43
|
+
/** Materialize a reviewed proposal on an isolated local Git branch. */
|
|
44
|
+
export async function createProposalBranch(root, proposalId, requestedBranch) {
|
|
45
|
+
const run = await readSyncRun(root, proposalId);
|
|
46
|
+
if (!['awaiting-review', 'partially-applied'].includes(run.status))
|
|
47
|
+
throw new DoxloopError('Only a reviewable proposal can be prepared as a Git branch.');
|
|
48
|
+
if (!(await pathExists(join(root, '.git'))))
|
|
49
|
+
throw new DoxloopError('Pull-request delivery requires the documentation project to be a Git repository.');
|
|
50
|
+
const branch = requestedBranch?.trim() || `doxloop/${proposalId}`;
|
|
51
|
+
await git(root, ['check-ref-format', '--branch', branch]);
|
|
52
|
+
const baseBranch = (await git(root, ['branch', '--show-current'])).trim() || 'HEAD';
|
|
53
|
+
const parent = await mkdtemp(join(tmpdir(), 'doxloop-delivery-'));
|
|
54
|
+
const worktree = join(parent, 'worktree');
|
|
55
|
+
try {
|
|
56
|
+
await git(root, ['worktree', 'add', '--detach', worktree, 'HEAD']);
|
|
57
|
+
await git(worktree, ['checkout', '-b', branch]);
|
|
58
|
+
for (const change of run.changes) {
|
|
59
|
+
const target = contained(worktree, change.path);
|
|
60
|
+
if (change.kind === 'deleted')
|
|
61
|
+
await rm(target, { force: true, recursive: true });
|
|
62
|
+
else {
|
|
63
|
+
const source = contained(runWorkspace(root, proposalId), change.path);
|
|
64
|
+
await mkdir(dirname(target), { recursive: true });
|
|
65
|
+
await cp(source, target, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
await git(worktree, ['add', '--all']);
|
|
69
|
+
const status = await git(worktree, ['status', '--porcelain']);
|
|
70
|
+
if (!status.trim())
|
|
71
|
+
throw new DoxloopError('The proposal does not produce a Git change against the current documentation branch.');
|
|
72
|
+
await git(worktree, ['-c', 'user.name=Doxloop', '-c', 'user.email=doxloop@local', 'commit', '-m', `docs: apply ${proposalId}`]);
|
|
73
|
+
const commit = (await git(worktree, ['rev-parse', 'HEAD'])).trim();
|
|
74
|
+
const remote = await optionalGit(root, ['remote', 'get-url', 'origin']);
|
|
75
|
+
const compareUrl = remote ? githubCompareUrl(remote.trim(), baseBranch, branch) : undefined;
|
|
76
|
+
const result = {
|
|
77
|
+
schemaVersion: 1,
|
|
78
|
+
proposalId,
|
|
79
|
+
branch,
|
|
80
|
+
baseBranch,
|
|
81
|
+
commit,
|
|
82
|
+
createdAt: new Date().toISOString(),
|
|
83
|
+
...(compareUrl ? { compareUrl } : {}),
|
|
84
|
+
};
|
|
85
|
+
await ensureGitignoreEntries(root, ['.doxloop/deliveries/']);
|
|
86
|
+
const directory = join(root, '.doxloop', 'deliveries');
|
|
87
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
88
|
+
await writeFile(join(directory, `${proposalId}.json`), `${JSON.stringify(result, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await optionalGit(root, ['worktree', 'remove', '--force', worktree]);
|
|
93
|
+
await rm(parent, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function git(cwd, args) {
|
|
97
|
+
try {
|
|
98
|
+
return (await execute('git', args, { cwd, maxBuffer: 2_000_000 })).stdout;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw new DoxloopError(error instanceof Error ? error.message : String(error));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function optionalGit(cwd, args) {
|
|
105
|
+
try {
|
|
106
|
+
return (await execute('git', args, { cwd, maxBuffer: 2_000_000 })).stdout;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function contained(root, candidate) {
|
|
113
|
+
const path = resolve(root, candidate);
|
|
114
|
+
const rel = relative(resolve(root), path);
|
|
115
|
+
if (rel === '..' || rel.startsWith(`..${sep}`))
|
|
116
|
+
throw new DoxloopError(`Proposal path leaves the Git worktree: ${candidate}`);
|
|
117
|
+
return path;
|
|
118
|
+
}
|
|
119
|
+
function githubCompareUrl(remote, base, branch) {
|
|
120
|
+
const match = /github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?$/.exec(remote);
|
|
121
|
+
return match ? `https://github.com/${match[1]}/${match[2]}/compare/${encodeURIComponent(base)}...${encodeURIComponent(branch)}?expand=1` : undefined;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=git-delivery.js.map
|
package/dist/globs.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match a project-relative POSIX path against a glob pattern.
|
|
3
|
+
*
|
|
4
|
+
* Supported syntax is deliberately small: `*` inside one segment, `**` across
|
|
5
|
+
* segments, and `?` for one character. A pattern without a slash matches the
|
|
6
|
+
* file name at any depth, which is what people expect from `.gitignore` and
|
|
7
|
+
* keeps `pnpm-lock.yaml` or `*.test.ts` working without a leading `**\/`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function matchesGlob(path: string, pattern: string): boolean;
|
|
10
|
+
export declare function matchesAnyGlob(path: string, patterns: readonly string[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Decide whether a changed source path is worth documenting attention.
|
|
13
|
+
* `ignore` always wins so a broad `watch` entry stays easy to narrow.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isWatchedPath(path: string, watch: readonly string[], ignore: readonly string[]): boolean;
|
|
16
|
+
//# sourceMappingURL=globs.d.ts.map
|
package/dist/globs.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const SEGMENT = '[^/]';
|
|
2
|
+
/**
|
|
3
|
+
* Match a project-relative POSIX path against a glob pattern.
|
|
4
|
+
*
|
|
5
|
+
* Supported syntax is deliberately small: `*` inside one segment, `**` across
|
|
6
|
+
* segments, and `?` for one character. A pattern without a slash matches the
|
|
7
|
+
* file name at any depth, which is what people expect from `.gitignore` and
|
|
8
|
+
* keeps `pnpm-lock.yaml` or `*.test.ts` working without a leading `**\/`.
|
|
9
|
+
*/
|
|
10
|
+
export function matchesGlob(path, pattern) {
|
|
11
|
+
const normalizedPath = normalize(path);
|
|
12
|
+
const normalizedPattern = normalize(pattern);
|
|
13
|
+
if (normalizedPattern === '')
|
|
14
|
+
return false;
|
|
15
|
+
const effective = normalizedPattern.includes('/')
|
|
16
|
+
? normalizedPattern
|
|
17
|
+
: `**/${normalizedPattern}`;
|
|
18
|
+
return globRegExp(effective).test(normalizedPath);
|
|
19
|
+
}
|
|
20
|
+
export function matchesAnyGlob(path, patterns) {
|
|
21
|
+
return patterns.some((pattern) => matchesGlob(path, pattern));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Decide whether a changed source path is worth documenting attention.
|
|
25
|
+
* `ignore` always wins so a broad `watch` entry stays easy to narrow.
|
|
26
|
+
*/
|
|
27
|
+
export function isWatchedPath(path, watch, ignore) {
|
|
28
|
+
if (matchesAnyGlob(path, ignore))
|
|
29
|
+
return false;
|
|
30
|
+
if (watch.length === 0)
|
|
31
|
+
return true;
|
|
32
|
+
return matchesAnyGlob(path, watch);
|
|
33
|
+
}
|
|
34
|
+
function normalize(value) {
|
|
35
|
+
return value.trim().split('\\').join('/').replace(/^\.\//, '').replace(/\/+$/, '');
|
|
36
|
+
}
|
|
37
|
+
function globRegExp(pattern) {
|
|
38
|
+
let source = '^';
|
|
39
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
40
|
+
const character = pattern[index];
|
|
41
|
+
if (character === '*') {
|
|
42
|
+
const doubled = pattern[index + 1] === '*';
|
|
43
|
+
if (doubled) {
|
|
44
|
+
// `a/**/b` must also match `a/b`, so the separator is part of the group.
|
|
45
|
+
if (pattern[index + 2] === '/') {
|
|
46
|
+
source += `(?:${SEGMENT}+/)*`;
|
|
47
|
+
index += 2;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
source += '.*';
|
|
51
|
+
index += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
source += `${SEGMENT}*`;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (character === '?') {
|
|
58
|
+
source += SEGMENT;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
source += character.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
62
|
+
}
|
|
63
|
+
return new RegExp(`${source}$`);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=globs.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A glossary page generated from the documentation brief's terminology map,
|
|
3
|
+
* so the terms a team curates in Settings become a reader-facing page without
|
|
4
|
+
* an agent run. The page carries a marker so regenerating it never overwrites
|
|
5
|
+
* a glossary a person wrote by hand.
|
|
6
|
+
*/
|
|
7
|
+
export declare const GLOSSARY_MARKER = "doxloop:glossary";
|
|
8
|
+
export interface GlossaryTerm {
|
|
9
|
+
term: string;
|
|
10
|
+
definition: string;
|
|
11
|
+
}
|
|
12
|
+
export interface GlossaryState {
|
|
13
|
+
/** Terms from the project brief, merged with the current plan's terminology. */
|
|
14
|
+
terms: GlossaryTerm[];
|
|
15
|
+
/** Project-relative path of the glossary page when one exists. */
|
|
16
|
+
page?: string;
|
|
17
|
+
/** True when the existing page was generated by Doxloop and can be regenerated. */
|
|
18
|
+
generated: boolean;
|
|
19
|
+
/** How many terms came from the current documentation plan rather than the brief. */
|
|
20
|
+
fromPlan: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function readGlossary(root: string): Promise<GlossaryState>;
|
|
23
|
+
export declare function generateGlossaryPage(root: string, raw: unknown): Promise<GlossaryState>;
|
|
24
|
+
export declare function readerDefinition(definition: string): string;
|
|
25
|
+
export declare function renderGlossary(terms: GlossaryTerm[], format: 'markdown' | 'rst' | 'html', productTitle: string): string;
|
|
26
|
+
//# sourceMappingURL=glossary.d.ts.map
|
package/dist/glossary.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { applyDirectEdit } from './direct-edit.js';
|
|
4
|
+
import { DoxloopError } from './errors.js';
|
|
5
|
+
import { EVIDENCE_MAP_FILE, readEvidenceMap, writeEvidenceMap } from './evidence.js';
|
|
6
|
+
import { pathExists, readJson, resolveContainedDirectory } from './fs.js';
|
|
7
|
+
import { loadGeneratorAdapter } from './generators.js';
|
|
8
|
+
import { appendDoxbrixNavigationPage } from './navigation.js';
|
|
9
|
+
import { loadProject, pageExtensions, relativePath, siteConfigPath } from './project.js';
|
|
10
|
+
/**
|
|
11
|
+
* A glossary page generated from the documentation brief's terminology map,
|
|
12
|
+
* so the terms a team curates in Settings become a reader-facing page without
|
|
13
|
+
* an agent run. The page carries a marker so regenerating it never overwrites
|
|
14
|
+
* a glossary a person wrote by hand.
|
|
15
|
+
*/
|
|
16
|
+
export const GLOSSARY_MARKER = 'doxloop:glossary';
|
|
17
|
+
export async function readGlossary(root) {
|
|
18
|
+
const project = await loadProject(root);
|
|
19
|
+
const brief = termsFromRecord(project.documentation.terminology);
|
|
20
|
+
const known = new Set(brief.map((entry) => entry.term.toLowerCase()));
|
|
21
|
+
const planned = (await planTerminology(root)).filter((entry) => !known.has(entry.term.toLowerCase()));
|
|
22
|
+
const location = await glossaryLocation(root, project);
|
|
23
|
+
const exists = await pathExists(location.absolute);
|
|
24
|
+
const generated = exists ? (await readFile(location.absolute, 'utf8')).includes(GLOSSARY_MARKER) : false;
|
|
25
|
+
return {
|
|
26
|
+
terms: [...brief, ...planned].sort(byTerm),
|
|
27
|
+
...(exists ? { page: location.path } : {}),
|
|
28
|
+
generated,
|
|
29
|
+
fromPlan: planned.length,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function generateGlossaryPage(root, raw) {
|
|
33
|
+
const body = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
34
|
+
const project = await loadProject(root);
|
|
35
|
+
const state = await readGlossary(root);
|
|
36
|
+
const terms = Array.isArray(body.terms) ? parseTerms(body.terms) : state.terms;
|
|
37
|
+
if (terms.length === 0)
|
|
38
|
+
throw new DoxloopError('Add at least one term under Settings → Audience and voice before generating a glossary.', 2);
|
|
39
|
+
const location = await glossaryLocation(root, project);
|
|
40
|
+
if (state.page && !state.generated && body.replace !== true) {
|
|
41
|
+
throw new DoxloopError(`${state.page} already exists and was not generated by Doxloop. Choose "Replace" to overwrite it.`);
|
|
42
|
+
}
|
|
43
|
+
const adapter = project.generator === 'doxbrix' ? undefined : await loadGeneratorAdapter(root, project);
|
|
44
|
+
const content = renderGlossary(terms, location.format, project.title);
|
|
45
|
+
const navigationFile = project.generator === 'doxbrix'
|
|
46
|
+
? relativePath(root, await siteConfigPath(root, project))
|
|
47
|
+
: adapter?.planning?.navigationFiles[0];
|
|
48
|
+
const files = [location.path, EVIDENCE_MAP_FILE, ...(navigationFile ? [navigationFile] : [])];
|
|
49
|
+
await applyDirectEdit(root, {
|
|
50
|
+
kind: 'glossary',
|
|
51
|
+
requestText: `Generated the glossary from ${terms.length} term${terms.length === 1 ? '' : 's'}`,
|
|
52
|
+
files,
|
|
53
|
+
pagesChanged: 1,
|
|
54
|
+
apply: async () => {
|
|
55
|
+
await writeFile(location.absolute, content, 'utf8');
|
|
56
|
+
if (project.generator === 'doxbrix') {
|
|
57
|
+
await appendDoxbrixNavigationPage(root, project, { file: 'glossary', title: 'Glossary', icon: 'book', section: 'Reference' });
|
|
58
|
+
}
|
|
59
|
+
else if (adapter?.writeNavigation) {
|
|
60
|
+
await adapter.writeNavigation({
|
|
61
|
+
root,
|
|
62
|
+
contentRoot: location.contentRoot,
|
|
63
|
+
project,
|
|
64
|
+
action: 'add',
|
|
65
|
+
page: { path: location.contentRelative, title: 'Glossary', section: 'Reference' },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const evidence = (await readEvidenceMap(root)) ?? { schemaVersion: 1, pages: {} };
|
|
69
|
+
evidence.pages[location.path] = {
|
|
70
|
+
sources: [],
|
|
71
|
+
confidence: 'inferred',
|
|
72
|
+
claims: terms.map((entry) => `${entry.term}: ${entry.definition}`),
|
|
73
|
+
};
|
|
74
|
+
await writeEvidenceMap(root, evidence);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
return readGlossary(root);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Plan terminology doubles as a writer style sheet ("use this capitalization",
|
|
81
|
+
* "use X instead of Y"). Readers need only the defining part, so style clauses
|
|
82
|
+
* are dropped, and an entry that is nothing but a style note is left out.
|
|
83
|
+
*/
|
|
84
|
+
const STYLE_CLAUSE = /^(use|prefer|avoid|do not|don't|never|always|write|spell|capitali[sz]e|keep)\b|\bcapitali[sz]ation\b|\binstead of\b/i;
|
|
85
|
+
export function readerDefinition(definition) {
|
|
86
|
+
const clauses = definition.trim().split(/;\s+|(?<=\.)\s+/).map((clause) => clause.trim()).filter(Boolean);
|
|
87
|
+
const kept = clauses.filter((clause) => !STYLE_CLAUSE.test(clause));
|
|
88
|
+
if (kept.length === 0)
|
|
89
|
+
return '';
|
|
90
|
+
const text = kept.join(' ').replace(/[;,]\s*$/, '');
|
|
91
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
92
|
+
}
|
|
93
|
+
/** "app state" reads as a heading when capitalised; code-like terms ("onChange", "x.y") keep their exact spelling. */
|
|
94
|
+
function headingTerm(term) {
|
|
95
|
+
return /^[a-z][a-z -]*$/.test(term) ? term[0].toUpperCase() + term.slice(1) : term;
|
|
96
|
+
}
|
|
97
|
+
export function renderGlossary(terms, format, productTitle) {
|
|
98
|
+
const sorted = [...terms]
|
|
99
|
+
.map((entry) => ({ term: entry.term, definition: readerDefinition(entry.definition) }))
|
|
100
|
+
.filter((entry) => entry.definition)
|
|
101
|
+
.sort(byTerm);
|
|
102
|
+
const description = `Definitions of the terms used across the ${productTitle} documentation.`;
|
|
103
|
+
if (format === 'rst') {
|
|
104
|
+
const entries = sorted.map((entry) => ` ${entry.term}\n ${entry.definition.replace(/\n+/g, ' ')}`).join('\n\n');
|
|
105
|
+
return `.. ${GLOSSARY_MARKER}\n\nGlossary\n========\n\n${description}\n\n.. glossary::\n :sorted:\n\n${entries}\n`;
|
|
106
|
+
}
|
|
107
|
+
if (format === 'html') {
|
|
108
|
+
const items = sorted.map((entry) => ` <dt id="${slug(entry.term)}">${escapeHtml(entry.term)}</dt>\n <dd>${escapeHtml(entry.definition)}</dd>`).join('\n');
|
|
109
|
+
return `<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8">\n <title>Glossary</title>\n <meta name="description" content="${escapeHtml(description)}">\n <!-- ${GLOSSARY_MARKER} -->\n </head>\n <body>\n <main>\n <h1>Glossary</h1>\n <p>${escapeHtml(description)}</p>\n <dl>\n${items}\n </dl>\n </main>\n </body>\n</html>\n`;
|
|
110
|
+
}
|
|
111
|
+
const sections = sorted.map((entry) => `## ${headingTerm(entry.term)}\n\n${entry.definition.trim()}\n`).join('\n');
|
|
112
|
+
return `---\ntitle: "Glossary"\ndescription: ${JSON.stringify(description)}\nicon: book\n---\n\n<!-- ${GLOSSARY_MARKER} -->\n\n${description} Each entry gives the meaning used throughout these pages.\n\n${sections}`;
|
|
113
|
+
}
|
|
114
|
+
async function glossaryLocation(root, project) {
|
|
115
|
+
const contentRoot = await resolveContainedDirectory(root, project.contentDir, 'Documentation content directory', {
|
|
116
|
+
allowRoot: project.generator === 'doxbrix',
|
|
117
|
+
});
|
|
118
|
+
const adapter = project.generator === 'doxbrix' ? undefined : await loadGeneratorAdapter(root, project);
|
|
119
|
+
const format = adapter?.project.contentFormat ?? 'markdown';
|
|
120
|
+
const extensions = [...(await pageExtensions(root, project))];
|
|
121
|
+
const extension = project.generator === 'doxbrix'
|
|
122
|
+
? '.mdx'
|
|
123
|
+
: format === 'rst'
|
|
124
|
+
? '.rst'
|
|
125
|
+
: format === 'html'
|
|
126
|
+
? '.html'
|
|
127
|
+
: extensions.find((entry) => entry === '.md') ?? extensions[0] ?? '.md';
|
|
128
|
+
const contentRelative = `glossary${extension}`;
|
|
129
|
+
const absolute = join(contentRoot, contentRelative);
|
|
130
|
+
return { absolute, path: relativePath(root, absolute), contentRelative, contentRoot, format };
|
|
131
|
+
}
|
|
132
|
+
async function planTerminology(root) {
|
|
133
|
+
const path = join(root, '.doxloop', 'documentation-plan.json');
|
|
134
|
+
if (!(await pathExists(path)))
|
|
135
|
+
return [];
|
|
136
|
+
try {
|
|
137
|
+
const plan = await readJson(path);
|
|
138
|
+
return termsFromRecord(plan.terminology);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function termsFromRecord(value) {
|
|
145
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
146
|
+
return [];
|
|
147
|
+
return Object.entries(value)
|
|
148
|
+
.filter((entry) => typeof entry[1] === 'string' && entry[0].trim() !== '' && entry[1].trim() !== '')
|
|
149
|
+
.map(([term, definition]) => ({ term: term.trim(), definition: definition.trim() }));
|
|
150
|
+
}
|
|
151
|
+
function parseTerms(raw) {
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
const terms = [];
|
|
154
|
+
for (const entry of raw) {
|
|
155
|
+
const item = entry && typeof entry === 'object' ? entry : {};
|
|
156
|
+
const term = typeof item.term === 'string' ? item.term.trim() : '';
|
|
157
|
+
const definition = typeof item.definition === 'string' ? item.definition.trim() : '';
|
|
158
|
+
if (!term || !definition)
|
|
159
|
+
continue;
|
|
160
|
+
if (term.length > 80 || definition.length > 1000)
|
|
161
|
+
throw new DoxloopError(`"${term.slice(0, 40)}" is too long for a glossary entry.`, 2);
|
|
162
|
+
const key = term.toLowerCase();
|
|
163
|
+
if (seen.has(key))
|
|
164
|
+
continue;
|
|
165
|
+
seen.add(key);
|
|
166
|
+
terms.push({ term, definition });
|
|
167
|
+
}
|
|
168
|
+
return terms;
|
|
169
|
+
}
|
|
170
|
+
function byTerm(left, right) {
|
|
171
|
+
return left.term.localeCompare(right.term, undefined, { sensitivity: 'base' });
|
|
172
|
+
}
|
|
173
|
+
function slug(value) {
|
|
174
|
+
return value.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '') || 'term';
|
|
175
|
+
}
|
|
176
|
+
function escapeHtml(value) {
|
|
177
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=glossary.js.map
|