concept-atlas-dense-explain 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/bin/cli.mjs +143 -0
  2. package/package.json +15 -2
  3. package/skill/SKILL.md +19 -92
  4. package/template/content/scroll-reading-demo.mdx +31 -0
  5. package/{skill/assets/template → template}/package.json +1 -1
  6. package/template/scripts/build.mjs +43 -0
  7. package/{skill/assets/template → template}/scripts/validate-content.mjs +48 -48
  8. package/template/scroll.html +12 -0
  9. package/{skill/assets/template → template}/src/components/MDXComponents.jsx +85 -47
  10. package/{skill/assets/template → template}/src/main.jsx +2 -2
  11. package/{skill/assets/template → template}/src/model/normalize-content.js +10 -4
  12. package/template/src/scroll-main.jsx +35 -0
  13. package/{skill/assets/template → template}/src/styles/concept-explain.css +3045 -2600
  14. package/{skill/assets/template → template}/src/views/NodeExplorer.jsx +668 -668
  15. package/{skill/assets/template → template}/src/views/RelationGraph.jsx +723 -723
  16. package/{skill/assets/template → template}/vite.config.js +6 -6
  17. package/bin/install.mjs +0 -17
  18. package/skill/assets/template/scripts/build.mjs +0 -37
  19. package/skill/references/components.md +0 -74
  20. package/skill/references/prompting.md +0 -90
  21. /package/{skill/assets/template → template}/content/compile-runtime.mdx +0 -0
  22. /package/{skill/assets/template → template}/index.html +0 -0
  23. /package/{skill/assets/template → template}/scripts/clean-temp.mjs +0 -0
  24. /package/{skill/assets/template → template}/src/app/App.jsx +0 -0
  25. /package/{skill/assets/template → template}/src/components/index.js +0 -0
  26. /package/{skill/assets/template → template}/src/model/concept-schema.js +0 -0
  27. /package/{skill/assets/template → template}/src/model/relation-types.js +0 -0
package/bin/cli.mjs ADDED
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ import { access, constants, mkdir, readFile, rm, rename, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { build } from 'vite';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
+ const templateRoot = path.join(packageRoot, 'template');
9
+ const args = process.argv.slice(2);
10
+
11
+ function usage() {
12
+ console.log('Usage:');
13
+ console.log(' npx concept-atlas-dense-explain <input.mdx> [--mode atlas|scroll] [-o output.html] [--force]');
14
+ console.log(' npx concept-atlas-dense-explain render <input.mdx> [--mode atlas|scroll] [-o output.html] [--force]');
15
+ console.log(' npx concept-atlas-dense-explain create <output.mdx> [--mode atlas|scroll] [--force]');
16
+ }
17
+
18
+ async function exists(filePath) {
19
+ try { await access(filePath, constants.F_OK); return true; } catch { return false; }
20
+ }
21
+
22
+ function flagValue(flags, names) {
23
+ for (const name of names) {
24
+ const index = flags.indexOf(name);
25
+ if (index >= 0) return flags[index + 1];
26
+ }
27
+ return null;
28
+ }
29
+
30
+ const command = ['help', 'create', 'new', 'render'].includes(args[0]) ? args.shift() : 'render';
31
+
32
+ if (command === 'help') { usage(); process.exit(0); }
33
+
34
+ if (command === 'create' || command === 'new') {
35
+ const output = args[0] ? path.resolve(args[0]) : null;
36
+ const mode = flagValue(args, ['--mode']) || 'atlas';
37
+ if (!output || path.extname(output).toLowerCase() !== '.mdx' || !['atlas', 'scroll'].includes(mode)) {
38
+ usage();
39
+ process.exit(1);
40
+ }
41
+ if (await exists(output) && !args.includes('--force')) {
42
+ console.error(`Refusing to overwrite ${output}; pass --force to replace it.`);
43
+ process.exit(1);
44
+ }
45
+ await mkdir(path.dirname(output), { recursive: true });
46
+ const template = mode === 'atlas' ? `\
47
+ <ExplainPage id="topic-id" title="主题名称" summary="用一句话说明这个主题解决什么问题。">
48
+ <ConceptGraph root="root-node">
49
+ <ConceptNode id="root-node" title="核心概念" level="L0" summary="给读者建立整体认知。">
50
+ <Overview>先用直觉解释这个主题是什么,以及为什么值得理解。</Overview>
51
+ <Definition>给出准确、可检查的定义。</Definition>
52
+ <Mechanism>说明输入如何经过关键步骤,产生什么输出或结果。</Mechanism>
53
+ <Boundary>说明适用范围、限制条件和容易混淆的反例。</Boundary>
54
+ <Children><ConceptRef id="first-branch" /><ConceptRef id="second-branch" /></Children>
55
+ </ConceptNode>
56
+
57
+ <ConceptNode id="first-branch" title="第一条关键分支" level="L1" parent="root-node">
58
+ <Overview>解释第一个重要组成部分。</Overview>
59
+ <Example title="典型例子">填写一个具体例子,帮助读者验证理解。</Example>
60
+ </ConceptNode>
61
+
62
+ <ConceptNode id="second-branch" title="第二条关键分支" level="L1" parent="root-node">
63
+ <Overview>解释第二个重要组成部分。</Overview>
64
+ <Boundary>填写它的边界、代价或常见误区。</Boundary>
65
+ </ConceptNode>
66
+
67
+ <Relation from="first-branch" to="second-branch" type="depends-on" label="依赖" />
68
+ </ConceptGraph>
69
+ </ExplainPage>
70
+ ` : `\
71
+ <ScrollDocument>
72
+ <ScrollHeader title="主题名称">用一两句话说明主题、背景和读者应该带走的判断。</ScrollHeader>
73
+
74
+ <ScrollSection title="先建立整体认知">
75
+ <ScrollProse>先解释主题是什么、解决什么问题,以及它和相邻概念的区别。</ScrollProse>
76
+ <Insight title="核心判断">填写这篇文章最重要、最值得记住的一句话。</Insight>
77
+ </ScrollSection>
78
+
79
+ <ScrollSection title="解释关键机制">
80
+ <ScrollProse>按输入、步骤、输出的顺序解释过程,不要只罗列名词。</ScrollProse>
81
+ <Flow title="处理流程" steps={[{title:'输入',description:'原始条件或数据'},{title:'处理',description:'关键变化或判断'},{title:'输出',description:'结果与可观察证据'}]} />
82
+ </ScrollSection>
83
+
84
+ <ScrollSection title="边界与实践">
85
+ <ScrollGrid columns="2">
86
+ <Boundary>填写适用范围、限制条件和反例。</Boundary>
87
+ <Example title="典型案例">填写一个能验证前文解释的具体案例。</Example>
88
+ </ScrollGrid>
89
+ </ScrollSection>
90
+ </ScrollDocument>
91
+ `;
92
+ await writeFile(output, template, 'utf8');
93
+ console.log(`Created ${mode} MDX template: ${output}`);
94
+ process.exit(0);
95
+ }
96
+
97
+ if (args[0] && args[0].toLowerCase() === 'render') args.shift();
98
+ const input = args[0] ? path.resolve(args[0]) : null;
99
+ const modeFlag = flagValue(args, ['--mode']);
100
+ const output = path.resolve(flagValue(args, ['-o', '--output']) || (input ? input.replace(/\.mdx$/i, '.html') : ''));
101
+ const force = args.includes('--force');
102
+
103
+ if (!input || path.extname(input).toLowerCase() !== '.mdx' || !(await exists(input))) {
104
+ console.error('Provide an existing .mdx input file.');
105
+ usage();
106
+ process.exit(1);
107
+ }
108
+
109
+ const source = await readFile(input, 'utf8');
110
+ const mode = modeFlag || (/<ScrollDocument\b/.test(source) ? 'scroll' : /<ExplainPage\b|<ConceptGraph\b/.test(source) ? 'atlas' : null);
111
+ if (!mode || !['atlas', 'scroll'].includes(mode)) {
112
+ console.error('Could not detect the MDX carrier; choose --mode atlas or --mode scroll.');
113
+ process.exit(1);
114
+ }
115
+ if (await exists(output) && !force) {
116
+ console.error(`Refusing to overwrite ${output}; pass --force to replace it.`);
117
+ process.exit(1);
118
+ }
119
+ await mkdir(path.dirname(output), { recursive: true });
120
+
121
+ const templateEntry = mode === 'atlas' ? 'index.html' : 'scroll.html';
122
+ const generatedEntry = path.join(path.dirname(output), templateEntry);
123
+
124
+ try {
125
+ await build({
126
+ root: templateRoot,
127
+ configFile: path.join(templateRoot, 'vite.config.js'),
128
+ resolve: { alias: { '@concept-atlas/content': input } },
129
+ build: {
130
+ outDir: path.dirname(output),
131
+ emptyOutDir: false,
132
+ rollupOptions: { input: path.join(templateRoot, templateEntry) },
133
+ },
134
+ });
135
+ if (generatedEntry !== output) {
136
+ await rm(output, { force: true });
137
+ await rename(generatedEntry, output);
138
+ }
139
+ console.log(`Built ${mode} HTML: ${output}`);
140
+ } catch (error) {
141
+ console.error('Build failed:', error);
142
+ process.exit(1);
143
+ }
package/package.json CHANGED
@@ -1,15 +1,28 @@
1
1
  {
2
2
  "name": "concept-atlas-dense-explain",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Portable dense-explanation skill and MDX concept atlas template",
5
5
  "type": "module",
6
6
  "bin": {
7
- "concept-atlas-dense-explain": "bin/install.mjs"
7
+ "concept-atlas-dense-explain": "bin/cli.mjs"
8
8
  },
9
9
  "files": [
10
10
  "bin",
11
+ "template",
11
12
  "skill"
12
13
  ],
14
+ "dependencies": {
15
+ "@mdx-js/rollup": "^3.0.1",
16
+ "@vitejs/plugin-react": "^4.3.4",
17
+ "clsx": "^2.1.1",
18
+ "d3": "^7.9.0",
19
+ "lucide-react": "^0.475.0",
20
+ "mermaid": "^11.17.2",
21
+ "react": "^18.3.1",
22
+ "react-dom": "^18.3.1",
23
+ "vite": "^5.4.14",
24
+ "vite-plugin-singlefile": "^2.1.0"
25
+ },
13
26
  "keywords": [
14
27
  "codex",
15
28
  "skill",
package/skill/SKILL.md CHANGED
@@ -1,98 +1,25 @@
1
- ---
2
- name: concept-atlas-dense-explain
3
- description: Build structured explanation webpages with the bundled React/Vite/MDX components. Use this skill whenever the user asks for a technical explanation, concept map, layered knowledge page, interactive explanation, component-based knowledge page, or webpage resembling the Concept Atlas exemplar. Before generating anything, ask whether the user wants (A) a scrollable long-form page assembled from semantic components or (B) the full Concept Atlas interactive framework with concept navigation and relation graph. Do not choose the output mode silently.
4
- ---
5
-
6
- # Concept Atlas Dense Explain
7
-
8
- This is an Agent Skills-compatible skill. Keep the instructions and generated content portable across Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Cline, GitHub Copilot, and other agents that support `SKILL.md`.
9
-
10
- Use the bundled components and, when selected, the complete template to turn knowledge into a structured explanation. The required deliverable is a built webpage, normally `dist/index.html`; it may be a scrollable component page or a full interactive Concept Atlas.
1
+ ---
2
+ name: concept-atlas-dense-explain
3
+ description: Use the Concept Atlas npm CLI to generate AI-editable MDX starters and compile standalone HTML from semantic MDX when a technical explanation, concept map, layered knowledge page, or interactive explanation is requested.
4
+ ---
11
5
 
12
- ## Mandatory mode selection
6
+ # Concept Atlas Dense Explain
13
7
 
14
- Before writing content, determine the output mode. If the user has not already chosen one, ask exactly one concise clarification:
8
+ Use the `concept-atlas-dense-explain` npm CLI. This skill is intentionally lightweight: do not copy implementation files from the skill directory or recreate the React/Vite application manually.
15
9
 
16
- > 你希望生成哪种形式?A. 使用语义组件组装的滚动型讲解网页;B. 使用完整 Concept Atlas 框架的交互式网页(概念树、节点下钻、关系图)。如果没有偏好,我推荐 B。
10
+ ## Workflow
17
11
 
18
- Do not start implementation until the user chooses A or B. If the user explicitly requests both, create separate outputs only when the workspace and scope support them.
12
+ 1. Choose the carrier before writing any MDX. A (continuous reading) uses a document-flow MDX structure; B (interactive Concept Atlas) uses a concept-graph MDX structure. Recommend B when the reader needs concept navigation or a relation graph; recommend A for a conventional HTML reading flow with denser horizontal component grids.
13
+ 2. When a starting point is useful, create a single MDX file with `npx concept-atlas-dense-explain create <file>.mdx --mode atlas|scroll`, then rewrite it with the AI.
14
+ 3. For B, write semantic MDX to the user-selected `.mdx` file. Include one `L0`, multiple `L1` branches, useful depth, `Children`/`ConceptRef`, and labeled `Relation`s. This content renders through the interactive atlas entry.
15
+ 4. For A, write semantic MDX to the user-selected `.mdx` file with `ScrollDocument`, `ScrollHeader`, `ScrollSection`, `ScrollProse`, and `ScrollGrid`. Do not wrap it in `ExplainPage`, `ConceptGraph`, or `ConceptNode`. This content renders through the continuous-reader entry.
16
+ 5. Do not try to make one MDX file serve both carriers. The template ships both sample entries for reference, but they represent distinct authoring formats.
17
+ 6. Compile directly with `npx concept-atlas-dense-explain <file>.mdx --mode atlas|scroll`. The output is a standalone `.html` beside the MDX unless `-o` is provided. The mode can be detected from the top-level carrier, but specify it when the file is ambiguous.
18
+ 7. Report the selected mode, output path, and limitations. Do not claim interactions that were not verified.
19
19
 
20
- ### Mode A — Scrollable component page
20
+ ## Content rules
21
21
 
22
- Use the existing React/Vite shell and compose the page from supported semantic and presentation components. A natural vertical reading layout is valid in this mode. It does not require a 15–25 node concept graph or relation graph unless the user asks for those features. Keep content semantic and avoid handwritten CSS, SVG, or replacement app code.
23
-
24
- ### Mode B Interactive Concept Atlas
25
-
26
- Use the complete bundled template. The page must contain one `L0` root, multiple `L1` branches, lower-level nodes where useful, `Children` / `ConceptRef` navigation, and labeled cross-branch `Relation`s. The template provides the three-column node explorer, graph view, search, filters, URL node state, keyboard navigation, and responsive behavior.
27
-
28
- ## Mandatory preflight and mode-specific fallback rule
29
-
30
- Before generating content, resolve the directory containing this `SKILL.md` and verify that these bundled files are readable:
31
-
32
- - `assets/template/content/compile-runtime.mdx`
33
- - `assets/template/src/app/App.jsx`
34
- - `assets/template/src/components/MDXComponents.jsx`
35
- - `assets/template/src/model/normalize-content.js`
36
- - `references/components.md`
37
-
38
- Read the component contract before writing. For Mode B, also read the exemplar and verify the complete template. If the bundled template is unavailable, report that Mode B cannot be completed and offer Mode A only after the user agrees. Never silently downgrade Mode B to a long page or custom mockup.
39
-
40
- For Mode B, the template is the implementation boundary: do not edit `src/`, styles, `index.html`, Vite configuration, or package dependencies for a content task; replace only the MDX content after copying the template. For Mode A, use the host project's existing shell and do not introduce a replacement application unless requested.
41
-
42
- ## Step 0 — Read the exemplar before writing Mode B content
43
-
44
- For Mode B, read [assets/template/content/compile-runtime.mdx](assets/template/content/compile-runtime.mdx) in full first. It is the **reference standard**, not just a file to overwrite. Study its shape before generating anything:
45
-
46
- - 1 `L0` root, ~5 `L1` branches, 2–3 children per branch, descending to `L3`/`L4` where the topic deserves it.
47
- - Every `title` is a claim ("ABI 破坏:能链接,不等于能调用"), and every `summary` is a one-sentence judgment.
48
- - Important nodes carry 3–6 semantic components, not one lone paragraph.
49
- - Claims are backed by concrete commands, numbers, or named artifacts (`readelf -Ws`, `nm -C`, `objdump -dr`, `.bss`, ASLR).
50
- - Cross-branch meaning is encoded as labeled `Relation`s, not prose.
51
-
52
- ## Workflow
53
-
54
- 1. Ask for and record Mode A or Mode B before implementation.
55
- 2. Read [references/components.md](references/components.md); for Mode B, resolve and copy the complete `assets/template` directory and read the exemplar first.
56
- 3. Mode A: create the requested scrollable content using semantic components and the existing shell. Mode B: replace only the template content MDX, preserving React, CSS, graph, and build files.
57
- 4. Mode B: model one `L0` root, then `L1` structure, `L2` mechanisms, and optional `L3/L4` boundaries or failures. Add `parent`/`Children` and labeled cross-branch `Relation`s.
58
- 5. In either mode, keep content semantic: do not encode layout, coordinates, CSS, or SVG in MDX. Use supported model and presentation components where they clarify structure.
59
- 6. Run the relevant validator, then `npm install` and `npm run build`. If validation or build fails, revise the content rather than returning an unverified substitute.
60
- 7. Mode B: verify node switching, graph mode, search, URL `#node=...`, keyboard navigation, and responsive layout. Return the built output path and state which mode was used.
61
-
62
- ## Delivery summary
63
-
64
- After generation, briefly explain:
65
-
66
- - which mode the user selected (`A` scrollable component page or `B` interactive Concept Atlas);
67
- - what the skill contributed (semantic components, concept tree, relations, or template interactions);
68
- - the output path and the main ways to inspect or continue editing it.
69
-
70
- Keep this summary concise. Do not merely list files or claim interactive features that were not built.
71
-
72
- ## Content rules
73
-
74
- - Start with a one- or two-sentence core judgment.
75
- - Use tables/flows for comparison and causality; use short paragraphs only for explanation.
76
- - Every dense block should contain a conclusion, evidence, or limitation.
77
- - Prefer 2–4 columns on wide screens and natural stacking on narrow screens.
78
- - Do not invent unsupported components or relation types. Read [references/components.md](references/components.md) for the supported semantic API.
79
- - Mode B should aim for the exemplar's scale: roughly 1 root, 4–5 `L1` branches, ~15–25 nodes total. A thin graph is a failure in Mode B, but is not a requirement for Mode A.
80
-
81
- ## Self-check before finishing
82
-
83
- Run this checklist and revise until every item passes. If an item fails, fix the content instead of adding decoration.
84
-
85
- - [ ] The user selected Mode A or Mode B before implementation.
86
- - [ ] Every key section or node has a claim, summary, or clear reading purpose.
87
- - [ ] Mode B has one `L0`, multiple `L1`s, useful depth, concrete evidence, and labeled whitelisted relations.
88
- - [ ] First screen shows the core claim plus 3–5 key facts; long detail lives in `Details` / `Tabs` when appropriate.
89
- - [ ] No CSS, coordinates, SVG, or layout instructions appear in the MDX.
90
- - [ ] Mode B changes only the template's MDX content; no replacement JSX or custom vertical article page was created.
91
- - [ ] The relevant validator passes before the build.
92
- - [ ] Mode B builds and node switching, graph mode, search, `#node=...`, and keyboard navigation all work.
93
-
94
- ## Portable template
95
-
96
- The self-contained React/Vite/MDX implementation is in [assets/template](assets/template). It is required for Mode B and optional for Mode A when the host project already provides an equivalent shell. For Mode B, copy it into a project, replace `content/compile-runtime.mdx` **after studying it**, run `npm install`, then `npm run build`. The template has no dependency on the source repository's absolute paths.
97
-
98
- For the component contract, relation whitelist, and level semantics, read [references/components.md](references/components.md). For the exemplar breakdown, density targets, and prompt wording, read [references/prompting.md](references/prompting.md).
22
+ - Keep MDX semantic; do not write CSS, coordinates, SVG, or replacement application code.
23
+ - Give important nodes a claim-like title, a one-sentence summary, evidence, and meaningful components.
24
+ - Use only components and relation types supported by the built-in renderer.
25
+ - If the CLI or npm registry is unavailable, report the blocker instead of copying the implementation into the skill.
@@ -0,0 +1,31 @@
1
+ <ScrollDocument>
2
+ <ScrollHeader title="用概念模型组织一次技术判断">同一套语义组件可以脱离图谱和节点面板,按传统文档流连续阅读。正文保持高信息密度,模型在需要比较、推导或收敛时占满可用宽度。</ScrollHeader>
3
+
4
+ <ScrollSection title="先识别推理结构">
5
+ <ScrollProse>模型不是内容的装饰。它们分别处理并列要素、二维定位、变量关系、论证层级和筛选过程。先确认关系形状,读者才能用最短路径验证结论。</ScrollProse>
6
+ <ScrollGrid columns="3">
7
+ <FrameworkModel title="对象" type="elements" elements={[{title:'系统',description:'明确正在判断什么'},{title:'边界',description:'确定讨论范围'}]} />
8
+ <FrameworkModel title="约束" type="elements" elements={[{title:'时间',description:'决策窗口与反馈周期'},{title:'资源',description:'人力、预算和技术限制'}]} />
9
+ <FrameworkModel title="证据" type="elements" elements={[{title:'行为',description:'真实使用与任务结果'},{title:'数据',description:'可重复检查的观察'}]} />
10
+ </ScrollGrid>
11
+ </ScrollSection>
12
+
13
+ <ScrollSection title="在二维关系中定位选择">
14
+ <ScrollProse>当候选方案同时受两个变量约束时,矩阵比线性列表更容易暴露优先级。</ScrollProse>
15
+ <ScrollGrid columns="2">
16
+ <MatrixModel title="影响 / 成本矩阵" xLabel="实施成本" yLabel="预期影响" cells={[{title:'优先投入',description:'高影响 / 低成本',tone:'success'},{title:'审慎评估',description:'高影响 / 高成本',tone:'warn'},{title:'快速验证',description:'低影响 / 低成本'},{title:'暂缓处理',description:'低影响 / 高成本',tone:'danger'}]} />
17
+ <Stack gap="md"><Insight title="先做什么">优先验证高影响、低成本的选择,再为高成本方案准备证据。</Insight><Callout title="阅读判断">矩阵只帮助定位,不替代成本估算或影响验证。</Callout><NoteGrid notes={[{title:'输入',content:'候选方案与约束'},{title:'输出',content:'下一轮验证顺序'}]} /></Stack>
18
+ </ScrollGrid>
19
+ </ScrollSection>
20
+
21
+ <ScrollSection title="把假设压缩为可讨论的关系" wide>
22
+ <ScrollGrid columns="3"><FormulaModel title="用户价值" formula="用户价值 = 新体验 - 旧体验 - 替换成本" variables={[{symbol:'新体验',description:'方案带来的增量收益'},{symbol:'旧体验',description:'现有替代方案的价值'},{symbol:'替换成本',description:'学习、迁移和风险'}]} /><DecisionMatrix title="需要确认" headers={['变量','检查']} rows={[['新体验','是否改善核心任务'],['旧体验','能否满足需求'],['替换成本','迁移是否可接受']]} /><FailureMode symptom="价值无法被感知" cause="只描述新功能,不解释替换路径" evidence="试用后仍回到旧方案" remedy="缩短迁移步骤并验证关键任务" /></ScrollGrid>
23
+ </ScrollSection>
24
+
25
+ <ScrollSection title="组织证据,再收敛到行动"><ScrollGrid columns="3"><PyramidModel title="论证层级" levels={[{title:'结论',description:'读者需要带走的判断'},{title:'理由',description:'支撑判断的关键分组'},{title:'证据',description:'事实、数据与案例'}]} /><FunnelModel title="决策过程" steps={[{title:'收集',description:'汇集候选信息'},{title:'筛选',description:'排除不满足约束的项'},{title:'验证',description:'检查关键假设'},{title:'行动',description:'形成下一步决策'}]} /><Tradeoff title="输出质量" options={[{name:'压缩结论',benefit:'阅读快',cost:'上下文少',when:'总览'},{name:'保留证据',benefit:'可追溯',cost:'阅读长',when:'关键决策'}]} /></ScrollGrid></ScrollSection>
26
+
27
+ <ScrollSection title="保留可追溯性">
28
+ <Evidence command="npm run validate" observes="确认内容结构完整,再把关键结论连接回原始证据。" />
29
+ <Insight title="核心原则">选择组件是为了让关系更容易被检验,不是为了把阅读页面做成一组不同样式的卡片。</Insight>
30
+ </ScrollSection>
31
+ </ScrollDocument>
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "version": "0.1.0",
5
5
  "type": "module",
6
- "scripts": { "dev": "vite", "validate": "node scripts/validate-content.mjs", "build": "npm run validate && node scripts/build.mjs", "clean:temp": "node scripts/clean-temp.mjs" },
6
+ "scripts": { "dev": "vite", "validate": "node scripts/validate-content.mjs", "build": "npm run validate && node scripts/build.mjs", "clean:temp": "node scripts/clean-temp.mjs" },
7
7
  "dependencies": {
8
8
  "@mdx-js/rollup": "^3.0.1",
9
9
  "clsx": "^2.1.1",
@@ -0,0 +1,43 @@
1
+ import { build } from 'vite';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const rootDir = path.resolve(__dirname, '..');
9
+
10
+ async function runBuild() {
11
+ console.log('🚀 开始构建 Concept Atlas 知识讲解页面...');
12
+
13
+ try {
14
+ // 确保 dist 目录存在
15
+ const distDir = path.resolve(rootDir, 'dist');
16
+ if (!fs.existsSync(distDir)) {
17
+ fs.mkdirSync(distDir, { recursive: true });
18
+ }
19
+
20
+ const mode = process.env.CONCEPT_ATLAS_MODE;
21
+ const carriers = mode === 'atlas' ? ['index.html'] : mode === 'scroll' ? ['scroll.html'] : ['index.html', 'scroll.html'];
22
+
23
+ // vite-plugin-singlefile supports one HTML input per build. Build each
24
+ // requested carrier separately so every output remains a standalone file.
25
+ for (const [index, entry] of carriers.entries()) {
26
+ await build({
27
+ root: rootDir,
28
+ build: {
29
+ outDir: 'dist',
30
+ emptyOutDir: index === 0,
31
+ rollupOptions: { input: path.resolve(rootDir, entry) },
32
+ }
33
+ });
34
+ }
35
+
36
+ console.log(`✅ 构建成功!产物已生成到 ${carriers.map(entry => `dist/${entry}`).join(' 和 ')}。`);
37
+ } catch (err) {
38
+ console.error('❌ 构建失败:', err);
39
+ process.exit(1);
40
+ }
41
+ }
42
+
43
+ runBuild();
@@ -1,48 +1,48 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const scriptDir = path.dirname(fileURLToPath(import.meta.url));
6
- const rootDir = path.resolve(scriptDir, '..');
7
- const contentPath = path.join(rootDir, 'content', 'compile-runtime.mdx');
8
- const source = fs.readFileSync(contentPath, 'utf8');
9
- const failures = [];
10
- const nodePattern = /<ConceptNode\b([^>]*)>([\s\S]*?)<\/ConceptNode>/g;
11
- const relationPattern = /<Relation\b([^>]*)\/>/g;
12
- const attributes = text => Object.fromEntries([...text.matchAll(/([\w-]+)=(?:"([^"]*)"|'([^']*)')/g)].map(match => [match[1], match[2] ?? match[3] ?? '']));
13
- const nodes = [...source.matchAll(nodePattern)].map(match => ({ ...attributes(match[1]), body: match[2] }));
14
- const relations = [...source.matchAll(relationPattern)].map(match => attributes(match[1]));
15
- const ids = new Set(nodes.map(node => node.id));
16
- const allowedRelations = new Set(['prerequisite', 'causes', 'produces', 'uses', 'implements', 'contrasts', 'depends-on', 'exception-of', 'precedes']);
17
-
18
- if (!source.includes('<ExplainPage')) failures.push('missing ExplainPage');
19
- if (!source.includes('<ConceptGraph')) failures.push('missing ConceptGraph');
20
- if (nodes.filter(node => node.level === 'L0').length !== 1) failures.push('expected exactly one L0 root');
21
- if (nodes.filter(node => node.level === 'L1').length < 4) failures.push('expected at least four L1 branches');
22
- if (nodes.length < 15) failures.push(`expected at least 15 nodes, found ${nodes.length}`);
23
- if (!nodes.some(node => ['L3', 'L4'].includes(node.level))) failures.push('expected L3 or L4 depth');
24
- if (relations.length < 8) failures.push(`expected at least 8 relations, found ${relations.length}`);
25
-
26
- for (const node of nodes) {
27
- if (!node.id) failures.push('node is missing id');
28
- if (!node.title) failures.push(`${node.id || '<unknown>'} is missing title`);
29
- if (!node.summary) failures.push(`${node.id || '<unknown>'} is missing summary`);
30
- if (node.parent && !ids.has(node.parent)) failures.push(`${node.id} references missing parent ${node.parent}`);
31
- if (!/(<(Definition|Mechanism|Example|Evidence|Boundary|Details|Callout|Counterexample|Glossary|Input|Output)\b)/.test(node.body)) {
32
- failures.push(`${node.id} lacks a core semantic component`);
33
- }
34
- }
35
-
36
- for (const relation of relations) {
37
- if (!ids.has(relation.from) || !ids.has(relation.to)) failures.push(`relation references missing node: ${relation.from} -> ${relation.to}`);
38
- if (!allowedRelations.has(relation.type)) failures.push(`unsupported relation type: ${relation.type}`);
39
- if (!relation.label) failures.push(`relation ${relation.from} -> ${relation.to} is missing label`);
40
- }
41
-
42
- if (failures.length) {
43
- console.error('Concept Atlas content validation failed:');
44
- for (const failure of failures) console.error(`- ${failure}`);
45
- process.exit(1);
46
- }
47
-
48
- console.log(`Concept Atlas content validation passed (${nodes.length} nodes, ${relations.length} relations).`);
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
6
+ const rootDir = path.resolve(scriptDir, '..');
7
+ const contentPath = path.join(rootDir, 'content', 'compile-runtime.mdx');
8
+ const source = fs.readFileSync(contentPath, 'utf8');
9
+ const failures = [];
10
+ const nodePattern = /<ConceptNode\b([^>]*)>([\s\S]*?)<\/ConceptNode>/g;
11
+ const relationPattern = /<Relation\b([^>]*)\/>/g;
12
+ const attributes = text => Object.fromEntries([...text.matchAll(/([\w-]+)=(?:"([^"]*)"|'([^']*)')/g)].map(match => [match[1], match[2] ?? match[3] ?? '']));
13
+ const nodes = [...source.matchAll(nodePattern)].map(match => ({ ...attributes(match[1]), body: match[2] }));
14
+ const relations = [...source.matchAll(relationPattern)].map(match => attributes(match[1]));
15
+ const ids = new Set(nodes.map(node => node.id));
16
+ const allowedRelations = new Set(['prerequisite', 'causes', 'produces', 'uses', 'implements', 'contrasts', 'depends-on', 'exception-of', 'precedes']);
17
+
18
+ if (!source.includes('<ExplainPage')) failures.push('missing ExplainPage');
19
+ if (!source.includes('<ConceptGraph')) failures.push('missing ConceptGraph');
20
+ if (nodes.filter(node => node.level === 'L0').length !== 1) failures.push('expected exactly one L0 root');
21
+ if (nodes.filter(node => node.level === 'L1').length < 4) failures.push('expected at least four L1 branches');
22
+ if (nodes.length < 15) failures.push(`expected at least 15 nodes, found ${nodes.length}`);
23
+ if (!nodes.some(node => ['L3', 'L4'].includes(node.level))) failures.push('expected L3 or L4 depth');
24
+ if (relations.length < 8) failures.push(`expected at least 8 relations, found ${relations.length}`);
25
+
26
+ for (const node of nodes) {
27
+ if (!node.id) failures.push('node is missing id');
28
+ if (!node.title) failures.push(`${node.id || '<unknown>'} is missing title`);
29
+ if (!node.summary) failures.push(`${node.id || '<unknown>'} is missing summary`);
30
+ if (node.parent && !ids.has(node.parent)) failures.push(`${node.id} references missing parent ${node.parent}`);
31
+ if (!/(<(Definition|Mechanism|Example|Evidence|Boundary|Details|Callout|Counterexample|Glossary|Input|Output)\b)/.test(node.body)) {
32
+ failures.push(`${node.id} lacks a core semantic component`);
33
+ }
34
+ }
35
+
36
+ for (const relation of relations) {
37
+ if (!ids.has(relation.from) || !ids.has(relation.to)) failures.push(`relation references missing node: ${relation.from} -> ${relation.to}`);
38
+ if (!allowedRelations.has(relation.type)) failures.push(`unsupported relation type: ${relation.type}`);
39
+ if (!relation.label) failures.push(`relation ${relation.from} -> ${relation.to} is missing label`);
40
+ }
41
+
42
+ if (failures.length) {
43
+ console.error('Concept Atlas content validation failed:');
44
+ for (const failure of failures) console.error(`- ${failure}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ console.log(`Concept Atlas content validation passed (${nodes.length} nodes, ${relations.length} relations).`);
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN" data-carrier="scroll">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Concept Atlas · 连续阅读示例</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/scroll-main.jsx"></script>
11
+ </body>
12
+ </html>