agent-docs-kit 2.1.0__py3-none-any.whl
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.
- agent_docs_kit-2.1.0.dist-info/METADATA +299 -0
- agent_docs_kit-2.1.0.dist-info/RECORD +40 -0
- agent_docs_kit-2.1.0.dist-info/WHEEL +4 -0
- agent_docs_kit-2.1.0.dist-info/entry_points.txt +3 -0
- agent_docs_kit-2.1.0.dist-info/licenses/LICENSE +21 -0
- living_docs_cli/__init__.py +686 -0
- living_docs_cli/__main__.py +3 -0
- living_docs_cli/assets/fumadocs-starter/app/docs/[[...slug]]/page.tsx +44 -0
- living_docs_cli/assets/fumadocs-starter/app/docs/layout.tsx +12 -0
- living_docs_cli/assets/fumadocs-starter/app/global.css +450 -0
- living_docs_cli/assets/fumadocs-starter/app/layout.tsx +15 -0
- living_docs_cli/assets/fumadocs-starter/app/page.tsx +5 -0
- living_docs_cli/assets/fumadocs-starter/components/living-docs/index.tsx +185 -0
- living_docs_cli/assets/fumadocs-starter/components/mdx.tsx +27 -0
- living_docs_cli/assets/fumadocs-starter/content/docs/architecture.mdx +55 -0
- living_docs_cli/assets/fumadocs-starter/content/docs/components.mdx +132 -0
- living_docs_cli/assets/fumadocs-starter/content/docs/glossary.mdx +16 -0
- living_docs_cli/assets/fumadocs-starter/content/docs/index.mdx +72 -0
- living_docs_cli/assets/fumadocs-starter/content/docs/meta.json +4 -0
- living_docs_cli/assets/fumadocs-starter/lib/layout.shared.ts +7 -0
- living_docs_cli/assets/fumadocs-starter/lib/source.ts +7 -0
- living_docs_cli/assets/fumadocs-starter/next-env.d.ts +4 -0
- living_docs_cli/assets/fumadocs-starter/next.config.mjs +10 -0
- living_docs_cli/assets/fumadocs-starter/package.json +27 -0
- living_docs_cli/assets/fumadocs-starter/source.config.ts +34 -0
- living_docs_cli/assets/fumadocs-starter/tsconfig.json +23 -0
- living_docs_cli/assets/project/.living-docs/scripts/check.mjs +72 -0
- living_docs_cli/assets/project/.living-docs/scripts/create-doc.mjs +88 -0
- living_docs_cli/assets/project/.living-docs/scripts/glossary.mjs +107 -0
- living_docs_cli/assets/project/.living-docs/templates/architecture.mdx +51 -0
- living_docs_cli/assets/project/.living-docs/templates/change.mdx +40 -0
- living_docs_cli/assets/project/.living-docs/templates/glossary.mdx +15 -0
- living_docs_cli/assets/project/.living-docs/templates/plan.mdx +54 -0
- living_docs_cli/assets/styles/atlas.css +450 -0
- living_docs_cli/assets/workflow-skills/living-docs-architecture/SKILL.md +55 -0
- living_docs_cli/assets/workflow-skills/living-docs-change/SKILL.md +62 -0
- living_docs_cli/assets/workflow-skills/living-docs-check/SKILL.md +32 -0
- living_docs_cli/assets/workflow-skills/living-docs-glossary/SKILL.md +30 -0
- living_docs_cli/assets/workflow-skills/living-docs-plan/SKILL.md +55 -0
- living_docs_cli/assets/workflow-skills/living-docs-write/SKILL.md +46 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
|
2
|
+
import type { MDXComponents } from 'mdx/types';
|
|
3
|
+
import {
|
|
4
|
+
ArchMap,
|
|
5
|
+
ChangeMeta,
|
|
6
|
+
DocsHero,
|
|
7
|
+
FlowSteps,
|
|
8
|
+
SkillGrid,
|
|
9
|
+
StateFlow,
|
|
10
|
+
TermGrid,
|
|
11
|
+
} from './living-docs';
|
|
12
|
+
|
|
13
|
+
export function getMDXComponents(components?: MDXComponents) {
|
|
14
|
+
return {
|
|
15
|
+
...defaultMdxComponents,
|
|
16
|
+
ArchMap,
|
|
17
|
+
ChangeMeta,
|
|
18
|
+
DocsHero,
|
|
19
|
+
FlowSteps,
|
|
20
|
+
SkillGrid,
|
|
21
|
+
StateFlow,
|
|
22
|
+
TermGrid,
|
|
23
|
+
...components,
|
|
24
|
+
} satisfies MDXComponents;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useMDXComponents = getMDXComponents;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "System Architecture"
|
|
3
|
+
description: "Current cross-domain architecture."
|
|
4
|
+
domain: "system"
|
|
5
|
+
type: "architecture"
|
|
6
|
+
status: "active"
|
|
7
|
+
date: "__TODAY__"
|
|
8
|
+
terms:
|
|
9
|
+
- name: "Domain"
|
|
10
|
+
description: "A documentation area that maps to a subsystem, product surface, or ownership boundary."
|
|
11
|
+
- name: "Change Record"
|
|
12
|
+
description: "An immutable page that records a shipped change, its motivation, and verification."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# System Architecture
|
|
16
|
+
|
|
17
|
+
Use this page for cross-domain architecture. Keep it current; put history in change records.
|
|
18
|
+
|
|
19
|
+
<ArchMap
|
|
20
|
+
tiers={[
|
|
21
|
+
{
|
|
22
|
+
title: 'Documentation source',
|
|
23
|
+
boxes: [
|
|
24
|
+
{ title: 'MDX pages', body: 'Human and agent editable source files.', tone: 'blue' },
|
|
25
|
+
{ title: 'Frontmatter', body: 'Structured metadata for domains, types, dates, and terms.' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: 'Presentation',
|
|
30
|
+
boxes: [
|
|
31
|
+
{ title: 'Fumadocs', body: 'Renders navigation, search-ready pages, and MDX components.', tone: 'green' },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: 'Agent workflow',
|
|
36
|
+
boxes: [
|
|
37
|
+
{ title: 'living-docs skills', body: 'Create architecture, change, plan, glossary, and check workflows.', tone: 'violet' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
/>
|
|
42
|
+
|
|
43
|
+
## Maintenance Rules
|
|
44
|
+
|
|
45
|
+
- Update architecture pages after meaningful system changes.
|
|
46
|
+
- Create change records for shipped behavior, contracts, or architecture changes.
|
|
47
|
+
- Run the glossary generator when terms change.
|
|
48
|
+
- Run the check gate before committing docs changes.
|
|
49
|
+
|
|
50
|
+
<TermGrid
|
|
51
|
+
items={[
|
|
52
|
+
{ name: 'Domain', description: 'A documentation area that maps to a subsystem, product surface, or ownership boundary.' },
|
|
53
|
+
{ name: 'Change Record', description: 'An immutable page that records a shipped change, its motivation, and verification.' },
|
|
54
|
+
]}
|
|
55
|
+
/>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Components"
|
|
3
|
+
description: "Reusable MDX components for architecture, flow, state, change, and glossary docs."
|
|
4
|
+
type: "guide"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Components
|
|
8
|
+
|
|
9
|
+
Use these MDX components directly in living-docs pages. Prefer components for
|
|
10
|
+
common documentation shapes, and use mermaid only when the diagram needs custom
|
|
11
|
+
graph syntax such as sequence diagrams, complex branches, or ER diagrams.
|
|
12
|
+
|
|
13
|
+
## Architecture Map
|
|
14
|
+
|
|
15
|
+
Use `<ArchMap />` for layered system structure.
|
|
16
|
+
|
|
17
|
+
```mdx
|
|
18
|
+
<ArchMap
|
|
19
|
+
tiers={[
|
|
20
|
+
{
|
|
21
|
+
title: 'Entry points',
|
|
22
|
+
boxes: [
|
|
23
|
+
{ title: 'Web app', body: 'User-facing route or shell.', tone: 'blue' },
|
|
24
|
+
{ title: 'API', body: 'Server boundary and contract.', tone: 'green' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: 'Core services',
|
|
29
|
+
boxes: [
|
|
30
|
+
{ title: 'Domain service', body: 'Business rules and orchestration.' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
]}
|
|
34
|
+
/>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
<ArchMap
|
|
38
|
+
tiers={[
|
|
39
|
+
{
|
|
40
|
+
title: 'Entry points',
|
|
41
|
+
boxes: [
|
|
42
|
+
{ title: 'Web app', body: 'User-facing route or shell.', tone: 'blue' },
|
|
43
|
+
{ title: 'API', body: 'Server boundary and contract.', tone: 'green' },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: 'Core services',
|
|
48
|
+
boxes: [
|
|
49
|
+
{ title: 'Domain service', body: 'Business rules and orchestration.' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
]}
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
## Flow Steps
|
|
56
|
+
|
|
57
|
+
Use `<FlowSteps />` for request paths, execution flows, or operational runbooks.
|
|
58
|
+
|
|
59
|
+
```mdx
|
|
60
|
+
<FlowSteps
|
|
61
|
+
title="Request flow"
|
|
62
|
+
steps={[
|
|
63
|
+
{ title: 'Receive input', body: 'Validate payload and identify the domain.', tone: 'blue' },
|
|
64
|
+
{ title: 'Run service', body: 'Execute the domain action and persist state.', tone: 'green' },
|
|
65
|
+
{ title: 'Return result', body: 'Send user-visible status and telemetry.', tone: 'amber' },
|
|
66
|
+
]}
|
|
67
|
+
/>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
<FlowSteps
|
|
71
|
+
title="Request flow"
|
|
72
|
+
steps={[
|
|
73
|
+
{ title: 'Receive input', body: 'Validate payload and identify the domain.', tone: 'blue' },
|
|
74
|
+
{ title: 'Run service', body: 'Execute the domain action and persist state.', tone: 'green' },
|
|
75
|
+
{ title: 'Return result', body: 'Send user-visible status and telemetry.', tone: 'amber' },
|
|
76
|
+
]}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
## State Flow
|
|
80
|
+
|
|
81
|
+
Use `<StateFlow />` for lifecycle states and simple state machines.
|
|
82
|
+
|
|
83
|
+
```mdx
|
|
84
|
+
<StateFlow
|
|
85
|
+
title="Document lifecycle"
|
|
86
|
+
states={[
|
|
87
|
+
{ name: 'draft', description: 'Plan or work in progress.', tone: 'amber' },
|
|
88
|
+
{ name: 'active', description: 'Current runtime truth.', tone: 'green' },
|
|
89
|
+
{ name: 'archived', description: 'Kept for history.' },
|
|
90
|
+
]}
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
<StateFlow
|
|
95
|
+
title="Document lifecycle"
|
|
96
|
+
states={[
|
|
97
|
+
{ name: 'draft', description: 'Plan or work in progress.', tone: 'amber' },
|
|
98
|
+
{ name: 'active', description: 'Current runtime truth.', tone: 'green' },
|
|
99
|
+
{ name: 'archived', description: 'Kept for history.' },
|
|
100
|
+
]}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
## Change Metadata
|
|
104
|
+
|
|
105
|
+
Use `<ChangeMeta />` at the top of change records.
|
|
106
|
+
|
|
107
|
+
```mdx
|
|
108
|
+
<ChangeMeta date="2026-06-17" source="commit or PR" tests="npm run build" />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
<ChangeMeta date="2026-06-17" source="commit or PR" tests="npm run build" />
|
|
112
|
+
|
|
113
|
+
## Terms
|
|
114
|
+
|
|
115
|
+
Use `<TermGrid />` for page-local vocabulary. The generated glossary is still
|
|
116
|
+
built from frontmatter `terms`, so keep the frontmatter updated too.
|
|
117
|
+
|
|
118
|
+
```mdx
|
|
119
|
+
<TermGrid
|
|
120
|
+
items={[
|
|
121
|
+
{ name: 'Domain', description: 'A subsystem or business area.' },
|
|
122
|
+
{ name: 'Change record', description: 'Immutable record of shipped behavior.' },
|
|
123
|
+
]}
|
|
124
|
+
/>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
<TermGrid
|
|
128
|
+
items={[
|
|
129
|
+
{ name: 'Domain', description: 'A subsystem or business area.' },
|
|
130
|
+
{ name: 'Change record', description: 'Immutable record of shipped behavior.' },
|
|
131
|
+
]}
|
|
132
|
+
/>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Glossary"
|
|
3
|
+
description: "Generated term index for living-docs"
|
|
4
|
+
type: "glossary"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Glossary
|
|
8
|
+
|
|
9
|
+
Generated from MDX frontmatter `terms`.
|
|
10
|
+
|
|
11
|
+
<TermGrid
|
|
12
|
+
items={[
|
|
13
|
+
{ name: "Change Record", description: "An immutable page that records a shipped change, its motivation, and verification.", meta: "system · architecture.mdx" },
|
|
14
|
+
{ name: "Domain", description: "A documentation area that maps to a subsystem, product surface, or ownership boundary.", meta: "system · architecture.mdx" },
|
|
15
|
+
]}
|
|
16
|
+
/>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Overview"
|
|
3
|
+
description: "A living documentation system powered by Fumadocs and MDX."
|
|
4
|
+
type: "guide"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<DocsHero
|
|
8
|
+
title="__PROJECT_NAME__ Documentation"
|
|
9
|
+
description="A skills-first documentation workspace for architecture, shipped changes, future plans, and shared vocabulary."
|
|
10
|
+
primaryLabel="Open architecture"
|
|
11
|
+
primaryHref="/docs/architecture"
|
|
12
|
+
secondaryLabel="View glossary"
|
|
13
|
+
secondaryHref="/docs/glossary"
|
|
14
|
+
/>
|
|
15
|
+
|
|
16
|
+
This documentation system is managed by living-docs. Use MDX as the source of truth and Fumadocs as the presentation layer.
|
|
17
|
+
|
|
18
|
+
Reusable documentation components are available in [Components](/docs/components).
|
|
19
|
+
|
|
20
|
+
## Structure
|
|
21
|
+
|
|
22
|
+
- Architecture pages describe current system shape.
|
|
23
|
+
- Change pages record what changed and why.
|
|
24
|
+
- Plan pages capture future design directions.
|
|
25
|
+
- The glossary is generated from frontmatter `terms`.
|
|
26
|
+
|
|
27
|
+
## Skills
|
|
28
|
+
|
|
29
|
+
Use these project-local skills from your agent:
|
|
30
|
+
|
|
31
|
+
<SkillGrid
|
|
32
|
+
items={[
|
|
33
|
+
{
|
|
34
|
+
name: 'living-docs-write',
|
|
35
|
+
body: 'Route general documentation requests to the right architecture, change, plan, glossary, or validation workflow.',
|
|
36
|
+
tone: 'violet',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'living-docs-architecture',
|
|
40
|
+
body: 'Capture the current system shape, contracts, runtime paths, and diagrams.',
|
|
41
|
+
tone: 'blue',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'living-docs-change',
|
|
45
|
+
body: 'Record shipped behavior changes with source files, validation, and follow-up notes.',
|
|
46
|
+
tone: 'green',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'living-docs-plan',
|
|
50
|
+
body: 'Draft future design directions before they become committed change records.',
|
|
51
|
+
tone: 'amber',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'living-docs-glossary',
|
|
55
|
+
body: 'Regenerate shared vocabulary from frontmatter terms across the docs.',
|
|
56
|
+
tone: 'violet',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'living-docs-check',
|
|
60
|
+
body: 'Validate MDX health, required diagrams, generated glossary, and managed docs metadata.',
|
|
61
|
+
tone: 'red',
|
|
62
|
+
},
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
The skills use these project-local scripts:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
node .living-docs/scripts/create-doc.mjs change api auth-flow "Auth Flow Update"
|
|
70
|
+
node .living-docs/scripts/glossary.mjs
|
|
71
|
+
node .living-docs/scripts/check.mjs
|
|
72
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__-docs",
|
|
3
|
+
"private": true,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "next dev",
|
|
6
|
+
"build": "next build",
|
|
7
|
+
"start": "next start",
|
|
8
|
+
"typecheck": "fumadocs-mdx && tsc --noEmit",
|
|
9
|
+
"docs:check": "node ../.living-docs/scripts/check.mjs",
|
|
10
|
+
"docs:glossary": "node ../.living-docs/scripts/glossary.mjs"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"fumadocs-core": "^16.10.3",
|
|
14
|
+
"fumadocs-mdx": "^15.0.12",
|
|
15
|
+
"fumadocs-ui": "^16.10.3",
|
|
16
|
+
"next": "^16.2.9",
|
|
17
|
+
"react": "^19.2.7",
|
|
18
|
+
"react-dom": "^19.2.7",
|
|
19
|
+
"zod": "^4.4.3"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^25.9.3",
|
|
23
|
+
"@types/react": "^19.2.17",
|
|
24
|
+
"@types/react-dom": "^19.2.3",
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
|
|
2
|
+
import { pageSchema, metaSchema } from 'fumadocs-core/source/schema';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
export const docs = defineDocs({
|
|
6
|
+
dir: 'content/docs',
|
|
7
|
+
docs: {
|
|
8
|
+
schema: pageSchema.extend({
|
|
9
|
+
domain: z.string().optional(),
|
|
10
|
+
type: z.enum(['guide', 'architecture', 'change', 'plan', 'glossary']).default('guide'),
|
|
11
|
+
status: z.enum(['draft', 'active', 'shipped', 'deprecated']).optional(),
|
|
12
|
+
date: z.string().optional(),
|
|
13
|
+
source: z.string().optional(),
|
|
14
|
+
tests: z.string().optional(),
|
|
15
|
+
terms: z
|
|
16
|
+
.array(
|
|
17
|
+
z.object({
|
|
18
|
+
name: z.string(),
|
|
19
|
+
description: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
)
|
|
22
|
+
.default([]),
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
meta: {
|
|
26
|
+
schema: metaSchema,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
mdxOptions: {
|
|
32
|
+
providerImportSource: '@/components/mdx',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "es2022"],
|
|
5
|
+
"allowJs": false,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".source/**/*.ts", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
|
|
22
|
+
"exclude": ["node_modules"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const root = process.cwd();
|
|
6
|
+
const configPath = join(root, '.living-docs', 'config.json');
|
|
7
|
+
const errors = [];
|
|
8
|
+
|
|
9
|
+
function walk(dir) {
|
|
10
|
+
const results = [];
|
|
11
|
+
for (const name of readdirSync(dir)) {
|
|
12
|
+
const path = join(dir, name);
|
|
13
|
+
const stat = statSync(path);
|
|
14
|
+
if (stat.isDirectory()) results.push(...walk(path));
|
|
15
|
+
if (stat.isFile() && path.endsWith('.mdx')) results.push(path);
|
|
16
|
+
}
|
|
17
|
+
return results;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function frontmatter(text) {
|
|
21
|
+
if (!text.startsWith('---\n')) return '';
|
|
22
|
+
const end = text.indexOf('\n---', 4);
|
|
23
|
+
return end === -1 ? '' : text.slice(4, end);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function scalar(fm, key) {
|
|
27
|
+
const match = fm.match(new RegExp(`^${key}:\\s*["']?([^"'\\n]+)["']?\\s*$`, 'm'));
|
|
28
|
+
return match?.[1]?.trim() || '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasDiagram(text) {
|
|
32
|
+
return ['<ArchMap', '<FlowSteps', '<StateFlow', '```mermaid'].some((marker) => text.includes(marker));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!existsSync(configPath)) {
|
|
36
|
+
errors.push('missing .living-docs/config.json');
|
|
37
|
+
} else {
|
|
38
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
39
|
+
const contentDir = join(root, config.contentDir || 'docs/content/docs');
|
|
40
|
+
if (!existsSync(contentDir)) {
|
|
41
|
+
errors.push(`missing content directory: ${relative(root, contentDir)}`);
|
|
42
|
+
} else {
|
|
43
|
+
const files = walk(contentDir);
|
|
44
|
+
if (files.length === 0) errors.push('no MDX files found');
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const rel = relative(root, file);
|
|
47
|
+
const text = readFileSync(file, 'utf8');
|
|
48
|
+
const fm = frontmatter(text);
|
|
49
|
+
const type = scalar(fm, 'type');
|
|
50
|
+
if (!scalar(fm, 'title')) errors.push(`${rel}: missing title`);
|
|
51
|
+
if (rel.includes('/changes/') && type !== 'change') errors.push(`${rel}: changes pages must set type: change`);
|
|
52
|
+
if (rel.includes('/plans/') && type !== 'plan') errors.push(`${rel}: plan pages must set type: plan`);
|
|
53
|
+
if ((type === 'architecture' || type === 'change') && !hasDiagram(text)) {
|
|
54
|
+
errors.push(`${rel}: architecture/change docs need <ArchMap />, <FlowSteps />, <StateFlow />, or mermaid`);
|
|
55
|
+
}
|
|
56
|
+
if ((type === 'architecture' || type === 'change' || type === 'plan') && !fm.includes('terms:') && !text.includes('<TermGrid')) {
|
|
57
|
+
errors.push(`${rel}: managed docs should define terms or render <TermGrid />`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!existsSync(join(contentDir, 'glossary.mdx'))) {
|
|
61
|
+
errors.push('missing glossary.mdx; run node .living-docs/scripts/glossary.mjs');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (errors.length) {
|
|
67
|
+
console.error(`living-docs check failed (${errors.length} issue(s))`);
|
|
68
|
+
for (const error of errors) console.error(` - ${error}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('living-docs check passed');
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
const configPath = join(root, '.living-docs', 'config.json');
|
|
8
|
+
|
|
9
|
+
function fail(message) {
|
|
10
|
+
console.error(`Error: ${message}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!existsSync(configPath)) {
|
|
15
|
+
fail('missing .living-docs/config.json; run living-docs init first');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
19
|
+
const contentDir = join(root, config.contentDir || 'docs/content/docs');
|
|
20
|
+
const templatesDir = join(root, '.living-docs', 'templates');
|
|
21
|
+
|
|
22
|
+
function slugify(input) {
|
|
23
|
+
return input
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.trim()
|
|
26
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
27
|
+
.replace(/^-+|-+$/g, '') || 'doc';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function titleize(input) {
|
|
31
|
+
return input
|
|
32
|
+
.replace(/[-_]+/g, ' ')
|
|
33
|
+
.replace(/\s+/g, ' ')
|
|
34
|
+
.trim()
|
|
35
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function render(template, values) {
|
|
39
|
+
let out = template;
|
|
40
|
+
for (const [key, value] of Object.entries(values)) {
|
|
41
|
+
out = out.replaceAll(`__${key}__`, value);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeNew(path, content) {
|
|
47
|
+
if (existsSync(path)) {
|
|
48
|
+
fail(`refusing to overwrite existing file: ${path}`);
|
|
49
|
+
}
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
writeFileSync(path, content);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const [kind, domainArg, topicArg, ...titleParts] = process.argv.slice(2);
|
|
55
|
+
if (!kind || !['architecture', 'change', 'plan'].includes(kind)) {
|
|
56
|
+
fail('usage: create-doc.mjs <architecture|change|plan> <domain> [topic-slug] [title]');
|
|
57
|
+
}
|
|
58
|
+
if (!domainArg) {
|
|
59
|
+
fail('domain is required');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const domain = slugify(domainArg);
|
|
63
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
64
|
+
const topic = topicArg ? slugify(topicArg) : domain;
|
|
65
|
+
const title = titleParts.length ? titleParts.join(' ') : titleize(topic);
|
|
66
|
+
const source = process.env.LIVING_DOCS_SOURCE || 'TODO';
|
|
67
|
+
const tests = process.env.LIVING_DOCS_TESTS || 'TODO';
|
|
68
|
+
|
|
69
|
+
let templateName = `${kind}.mdx`;
|
|
70
|
+
let outPath;
|
|
71
|
+
if (kind === 'architecture') {
|
|
72
|
+
outPath = join(contentDir, domain, 'architecture.mdx');
|
|
73
|
+
} else if (kind === 'change') {
|
|
74
|
+
outPath = join(contentDir, domain, 'changes', `${today}-${topic}.mdx`);
|
|
75
|
+
} else {
|
|
76
|
+
outPath = join(contentDir, domain, 'plans', `${topic}.mdx`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const template = readFileSync(join(templatesDir, templateName), 'utf8');
|
|
80
|
+
writeNew(outPath, render(template, {
|
|
81
|
+
TITLE: title,
|
|
82
|
+
DOMAIN: domain,
|
|
83
|
+
DATE: today,
|
|
84
|
+
SOURCE: source,
|
|
85
|
+
TESTS: tests,
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
console.log(outPath);
|