@cakemail-org/cakemail-cli 1.5.0 → 2.0.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/.claude/settings.local.json +12 -0
- package/.env.example +40 -0
- package/.env.test.example +45 -0
- package/CHANGELOG.md +1031 -0
- package/README.md +319 -15
- package/audit-formats.js +128 -0
- package/cakemail.rb +20 -0
- package/dist/cli.js +27 -10
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -6
- package/dist/client.js.map +1 -1
- package/dist/commands/account.js +1 -1
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/attributes.js +1 -1
- package/dist/commands/attributes.js.map +1 -1
- package/dist/commands/campaigns.d.ts.map +1 -1
- package/dist/commands/campaigns.js +103 -8
- package/dist/commands/campaigns.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +63 -4
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contacts.d.ts.map +1 -1
- package/dist/commands/contacts.js +91 -12
- package/dist/commands/contacts.js.map +1 -1
- package/dist/commands/emails.js +1 -1
- package/dist/commands/emails.js.map +1 -1
- package/dist/commands/interests.d.ts +5 -0
- package/dist/commands/interests.d.ts.map +1 -0
- package/dist/commands/interests.js +172 -0
- package/dist/commands/interests.js.map +1 -0
- package/dist/commands/lists.d.ts.map +1 -1
- package/dist/commands/lists.js +6 -8
- package/dist/commands/lists.js.map +1 -1
- package/dist/commands/logs.d.ts +5 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +237 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +1 -1
- package/dist/commands/segments.js +1 -1
- package/dist/commands/segments.js.map +1 -1
- package/dist/commands/senders.d.ts.map +1 -1
- package/dist/commands/senders.js +11 -8
- package/dist/commands/senders.js.map +1 -1
- package/dist/commands/suppressed.js +1 -1
- package/dist/commands/suppressed.js.map +1 -1
- package/dist/commands/tags.d.ts +5 -0
- package/dist/commands/tags.d.ts.map +1 -0
- package/dist/commands/tags.js +124 -0
- package/dist/commands/tags.js.map +1 -0
- package/dist/commands/templates.js +1 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/commands/transactional-templates.d.ts +5 -0
- package/dist/commands/transactional-templates.d.ts.map +1 -0
- package/dist/commands/transactional-templates.js +354 -0
- package/dist/commands/transactional-templates.js.map +1 -0
- package/dist/commands/webhooks.js +1 -1
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/utils/auth.d.ts +8 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +39 -11
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/config-file.d.ts +7 -0
- package/dist/utils/config-file.d.ts.map +1 -1
- package/dist/utils/config-file.js +15 -0
- package/dist/utils/config-file.js.map +1 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -4
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/errors.js +1 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/list-defaults.d.ts +33 -0
- package/dist/utils/list-defaults.d.ts.map +1 -0
- package/dist/utils/list-defaults.js +52 -0
- package/dist/utils/list-defaults.js.map +1 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +36 -13
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +32 -4
- package/dist/utils/progress.js.map +1 -1
- package/dist/utils/spinner.d.ts +17 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +43 -0
- package/dist/utils/spinner.js.map +1 -0
- package/docs/DOCUMENTATION-STANDARD.md +1068 -0
- package/docs/README.md +161 -0
- package/docs/developer/ARCHITECTURE.md +516 -0
- package/docs/developer/AUTH.md +204 -0
- package/docs/developer/CONTRIBUTING.md +227 -0
- package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
- package/docs/developer/PROJECT_INDEX.md +365 -0
- package/docs/planning/API_COVERAGE.md +1045 -0
- package/docs/planning/BACKLOG.md +1159 -0
- package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
- package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
- package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
- package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
- package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
- package/docs/planning/cakemail-profile-system-plan.md +1121 -0
- package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
- package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
- package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
- package/docs/user-manual/.obsidian/app.json +1 -0
- package/docs/user-manual/.obsidian/appearance.json +1 -0
- package/docs/user-manual/.obsidian/core-plugins.json +33 -0
- package/docs/user-manual/.obsidian/workspace.json +167 -0
- package/docs/user-manual/01-getting-started/01-installation.md +214 -0
- package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
- package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
- package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
- package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
- package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
- package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
- package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
- package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
- package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
- package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
- package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
- package/docs/user-manual/03-email-operations/01-senders.md +490 -0
- package/docs/user-manual/03-email-operations/02-templates.md +444 -0
- package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
- package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
- package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
- package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
- package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
- package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
- package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
- package/docs/user-manual/05-contact-management/01-lists.md +836 -0
- package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
- package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
- package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
- package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
- package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
- package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
- package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
- package/docs/user-manual/07-integrations/02-automation.md +326 -0
- package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
- package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
- package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
- package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
- package/docs/user-manual/09-command-reference/01-config.md +776 -0
- package/docs/user-manual/09-command-reference/02-account.md +652 -0
- package/docs/user-manual/09-command-reference/03-lists.md +958 -0
- package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
- package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
- package/docs/user-manual/09-command-reference/06-segments.md +894 -0
- package/docs/user-manual/09-command-reference/07-senders.md +803 -0
- package/docs/user-manual/09-command-reference/08-templates.md +818 -0
- package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
- package/docs/user-manual/09-command-reference/10-emails.md +807 -0
- package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
- package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
- package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
- package/docs/user-manual/09-command-reference/14-interests.md +630 -0
- package/docs/user-manual/09-command-reference/15-tags.md +584 -0
- package/docs/user-manual/09-command-reference/16-logs.md +656 -0
- package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
- package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
- package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
- package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
- package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
- package/docs/user-manual/11-appendix/04-faq.md +484 -0
- package/docs/user-manual/11-appendix/05-glossary.md +250 -0
- package/docs/user-manual/README.md +0 -0
- package/package.json +13 -47
- package/src/cli.ts +125 -0
- package/src/client.ts +16 -0
- package/src/commands/account.ts +267 -0
- package/src/commands/accounts.ts +78 -0
- package/src/commands/actions.ts +249 -0
- package/src/commands/attributes.ts +139 -0
- package/src/commands/campaign-blueprints.ts +106 -0
- package/src/commands/campaigns.ts +469 -0
- package/src/commands/config.ts +77 -0
- package/src/commands/contacts.ts +612 -0
- package/src/commands/custom-attributes.ts +127 -0
- package/src/commands/dkims.ts +117 -0
- package/src/commands/domains.ts +82 -0
- package/src/commands/email-apis.ts +569 -0
- package/src/commands/emails.ts +197 -0
- package/src/commands/forms.ts +283 -0
- package/src/commands/interests.ts +155 -0
- package/src/commands/links.ts +38 -0
- package/src/commands/lists.ts +406 -0
- package/src/commands/logos.ts +71 -0
- package/src/commands/logs.ts +386 -0
- package/src/commands/reports.ts +306 -0
- package/src/commands/segments.ts +158 -0
- package/src/commands/senders.ts +204 -0
- package/src/commands/sub-accounts.ts +271 -0
- package/src/commands/suppressed-emails.ts +234 -0
- package/src/commands/suppressed.ts +198 -0
- package/src/commands/system-emails.ts +85 -0
- package/src/commands/tags.ts +146 -0
- package/src/commands/tasks.ts +116 -0
- package/src/commands/templates.ts +189 -0
- package/src/commands/tokens.ts +83 -0
- package/src/commands/transactional-emails.ts +374 -0
- package/src/commands/transactional-templates.ts +385 -0
- package/src/commands/users.ts +506 -0
- package/src/commands/webhooks.ts +172 -0
- package/src/commands/workflow-blueprints.ts +123 -0
- package/src/commands/workflows.ts +265 -0
- package/src/types/profile.ts +93 -0
- package/src/utils/auth.ts +272 -0
- package/src/utils/config-file.ts +96 -0
- package/src/utils/config.ts +134 -0
- package/src/utils/confirm.ts +32 -0
- package/src/utils/defaults.ts +99 -0
- package/src/utils/errors.ts +116 -0
- package/src/utils/interactive.ts +91 -0
- package/src/utils/list-defaults.ts +74 -0
- package/src/utils/output.ts +190 -0
- package/src/utils/progress.ts +320 -0
- package/src/utils/spinner.ts +22 -0
- package/tests/IMPLEMENTATION_STATUS.md +258 -0
- package/tests/PTY_SETUP.md +118 -0
- package/tests/PTY_TESTING_GUIDE.md +507 -0
- package/tests/README.md +244 -0
- package/tests/fixtures/api-responses/campaigns.json +34 -0
- package/tests/fixtures/test-config.json +13 -0
- package/tests/helpers/cli-runner.ts +128 -0
- package/tests/helpers/mock-server.ts +301 -0
- package/tests/helpers/pty-runner.ts +181 -0
- package/tests/integration/campaigns-real-api.test.ts +196 -0
- package/tests/integration/setup-integration.ts +50 -0
- package/tests/pty/campaigns.test.ts +241 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +28 -0
|
@@ -0,0 +1,1517 @@
|
|
|
1
|
+
# Kenogami: Bidirectional Knowledge Graph System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Kenogami is a knowledge-base system where the **knowledge graph becomes the source of truth** rather than just a derivative of articles. This inverts the traditional flow from "Articles → Knowledge Graph" to "Knowledge Graph ↔ Articles" with bidirectional synchronization.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [Traditional vs Bidirectional Flow](#traditional-vs-bidirectional-flow)
|
|
12
|
+
2. [Architectural Changes](#architectural-changes)
|
|
13
|
+
3. [Knowledge Graph as Source of Truth](#knowledge-graph-as-source-of-truth)
|
|
14
|
+
4. [Article Generation from Graph](#article-generation-from-graph)
|
|
15
|
+
5. [Synchronization Strategies](#synchronization-strategies)
|
|
16
|
+
6. [Conflict Resolution](#conflict-resolution)
|
|
17
|
+
7. [Implementation Design](#implementation-design)
|
|
18
|
+
8. [User Workflows](#user-workflows)
|
|
19
|
+
9. [Integration with AI Testing](#integration-with-ai-testing)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Traditional vs Bidirectional Flow
|
|
24
|
+
|
|
25
|
+
### Traditional Flow (Current AI Testing Design)
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
┌─────────────────┐
|
|
29
|
+
│ Articles │ (Source of Truth)
|
|
30
|
+
│ (Markdown) │
|
|
31
|
+
└────────┬────────┘
|
|
32
|
+
│
|
|
33
|
+
│ Parse & Extract
|
|
34
|
+
▼
|
|
35
|
+
┌─────────────────┐
|
|
36
|
+
│ Knowledge Graph │ (Derived Data)
|
|
37
|
+
│ (Read-Only) │
|
|
38
|
+
└────────┬────────┘
|
|
39
|
+
│
|
|
40
|
+
│ Query
|
|
41
|
+
▼
|
|
42
|
+
┌─────────────────┐
|
|
43
|
+
│ AI Testing │
|
|
44
|
+
│ Validation │
|
|
45
|
+
└─────────────────┘
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Problems:**
|
|
49
|
+
- ❌ Duplicate content across articles (same command documented in multiple places)
|
|
50
|
+
- ❌ Inconsistent formatting and terminology
|
|
51
|
+
- ❌ Hard to maintain relationships between concepts
|
|
52
|
+
- ❌ No single source of truth for facts
|
|
53
|
+
- ❌ Articles can drift out of sync with each other
|
|
54
|
+
|
|
55
|
+
### Bidirectional Flow (Kenogami Approach)
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
59
|
+
│ Knowledge Graph (KG) │
|
|
60
|
+
│ (SOURCE OF TRUTH) │
|
|
61
|
+
│ │
|
|
62
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
63
|
+
│ │ Command │───▶│Parameter │ │ Concept │ │
|
|
64
|
+
│ │ Node │ │ Node │ │ Node │ │
|
|
65
|
+
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
66
|
+
│ │ │ │
|
|
67
|
+
│ │ hasExample │ relatesTo │
|
|
68
|
+
│ ▼ ▼ │
|
|
69
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
70
|
+
│ │ Example │ │ Error │ │Workflow │ │
|
|
71
|
+
│ │ Node │ │ Node │ │ Node │ │
|
|
72
|
+
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
73
|
+
└───────┬──────────────────────────────────┬──────────────────┘
|
|
74
|
+
│ │
|
|
75
|
+
│ Generate │ Extract/Update
|
|
76
|
+
▼ │
|
|
77
|
+
┌─────────────────┐ │
|
|
78
|
+
│ Articles │◀───────────────────────┘
|
|
79
|
+
│ (Markdown) │
|
|
80
|
+
│ (Generated) │
|
|
81
|
+
└────────┬────────┘
|
|
82
|
+
│
|
|
83
|
+
│ Test Against
|
|
84
|
+
▼
|
|
85
|
+
┌─────────────────┐
|
|
86
|
+
│ AI Testing │
|
|
87
|
+
│ Validation │
|
|
88
|
+
└─────────────────┘
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Benefits:**
|
|
92
|
+
- ✅ Single source of truth (the graph)
|
|
93
|
+
- ✅ Automatic consistency across all articles
|
|
94
|
+
- ✅ Relationships are first-class entities
|
|
95
|
+
- ✅ Generate multiple article views from same data
|
|
96
|
+
- ✅ Update once, reflect everywhere
|
|
97
|
+
- ✅ Track changes at the fact level, not document level
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Architectural Changes
|
|
102
|
+
|
|
103
|
+
### System Components
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
107
|
+
│ Kenogami System │
|
|
108
|
+
└─────────────────────────────────────────────────────────────┘
|
|
109
|
+
│
|
|
110
|
+
┌─────────────────────┼─────────────────────┐
|
|
111
|
+
│ │ │
|
|
112
|
+
▼ ▼ ▼
|
|
113
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
114
|
+
│ Graph │ │ Article │ │Sync Engine │
|
|
115
|
+
│ Editor │ │ Generator │ │ │
|
|
116
|
+
│ │ │ │ │ │
|
|
117
|
+
│ - Add nodes │ │ - Templates │ │ - Diff │
|
|
118
|
+
│ - Edit facts │ │ - Rendering │ │ - Merge │
|
|
119
|
+
│ - Relations │ │ - Formatting │ │ - Conflict │
|
|
120
|
+
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
|
121
|
+
│ │ │
|
|
122
|
+
│ │ │
|
|
123
|
+
└─────────────────────┼─────────────────────┘
|
|
124
|
+
│
|
|
125
|
+
▼
|
|
126
|
+
┌─────────────────┐
|
|
127
|
+
│ Knowledge Graph │
|
|
128
|
+
│ (Neo4j or │
|
|
129
|
+
│ similar DB) │
|
|
130
|
+
└─────────────────┘
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Data Flow Changes
|
|
134
|
+
|
|
135
|
+
**Old Flow: Article → Graph**
|
|
136
|
+
```typescript
|
|
137
|
+
// Traditional extraction
|
|
138
|
+
const articles = readMarkdownFiles('./docs');
|
|
139
|
+
const knowledgeGraph = await extractKnowledge(articles);
|
|
140
|
+
// Graph is derivative, articles are source
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**New Flow: Graph → Articles**
|
|
144
|
+
```typescript
|
|
145
|
+
// Graph-first approach
|
|
146
|
+
const knowledgeGraph = loadKnowledgeGraph(); // Source of truth
|
|
147
|
+
const articles = await generateArticles(knowledgeGraph, templates);
|
|
148
|
+
// Articles are derivative, graph is source
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Hybrid Flow: Graph ↔ Articles (Recommended)**
|
|
152
|
+
```typescript
|
|
153
|
+
// Bidirectional sync
|
|
154
|
+
const knowledgeGraph = loadKnowledgeGraph();
|
|
155
|
+
|
|
156
|
+
// When articles are edited by humans:
|
|
157
|
+
const articleChanges = detectChanges('./docs');
|
|
158
|
+
await syncArticlesToGraph(articleChanges, knowledgeGraph);
|
|
159
|
+
|
|
160
|
+
// When graph is edited programmatically:
|
|
161
|
+
const graphChanges = detectGraphChanges(knowledgeGraph);
|
|
162
|
+
await syncGraphToArticles(graphChanges, './docs', templates);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Knowledge Graph as Source of Truth
|
|
168
|
+
|
|
169
|
+
### Graph Schema Design
|
|
170
|
+
|
|
171
|
+
**Node Types:**
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
type KnowledgeNode =
|
|
175
|
+
| CommandNode
|
|
176
|
+
| ConceptNode
|
|
177
|
+
| ParameterNode
|
|
178
|
+
| ExampleNode
|
|
179
|
+
| ErrorNode
|
|
180
|
+
| WorkflowNode
|
|
181
|
+
| TutorialNode
|
|
182
|
+
| FAQNode;
|
|
183
|
+
|
|
184
|
+
interface CommandNode {
|
|
185
|
+
id: string;
|
|
186
|
+
type: 'command';
|
|
187
|
+
name: string; // "cakemail campaigns list"
|
|
188
|
+
category: string; // "campaigns"
|
|
189
|
+
description: string;
|
|
190
|
+
version_added: string; // "1.3.0"
|
|
191
|
+
version_deprecated?: string;
|
|
192
|
+
|
|
193
|
+
// Metadata
|
|
194
|
+
aliases: string[];
|
|
195
|
+
platform: 'cli' | 'web' | 'mobile' | 'api';
|
|
196
|
+
authentication_required: boolean;
|
|
197
|
+
|
|
198
|
+
// Relationships (defined as edges)
|
|
199
|
+
// → hasParameter → ParameterNode
|
|
200
|
+
// → hasExample → ExampleNode
|
|
201
|
+
// → canThrow → ErrorNode
|
|
202
|
+
// → partOfWorkflow → WorkflowNode
|
|
203
|
+
// → relatedTo → CommandNode
|
|
204
|
+
// → documentedIn → ArticleNode
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface ParameterNode {
|
|
208
|
+
id: string;
|
|
209
|
+
type: 'parameter';
|
|
210
|
+
name: string; // "status"
|
|
211
|
+
flag: string; // "-s, --status"
|
|
212
|
+
description: string;
|
|
213
|
+
value_type: 'string' | 'number' | 'boolean' | 'enum';
|
|
214
|
+
required: boolean;
|
|
215
|
+
default_value?: any;
|
|
216
|
+
enum_values?: string[];
|
|
217
|
+
|
|
218
|
+
// Relationships
|
|
219
|
+
// ← hasParameter ← CommandNode
|
|
220
|
+
// → hasExample → ExampleNode
|
|
221
|
+
// → validates → ValidationRuleNode
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
interface ExampleNode {
|
|
225
|
+
id: string;
|
|
226
|
+
type: 'example';
|
|
227
|
+
title: string;
|
|
228
|
+
command: string; // Full command string
|
|
229
|
+
description: string;
|
|
230
|
+
expected_output: string | object;
|
|
231
|
+
expected_exit_code: number;
|
|
232
|
+
platform: 'cli' | 'web' | 'mobile';
|
|
233
|
+
|
|
234
|
+
// Relationships
|
|
235
|
+
// ← hasExample ← CommandNode
|
|
236
|
+
// → producesOutput → OutputNode
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface ConceptNode {
|
|
240
|
+
id: string;
|
|
241
|
+
type: 'concept';
|
|
242
|
+
name: string; // "OAuth Authentication"
|
|
243
|
+
description: string;
|
|
244
|
+
category: 'core' | 'advanced' | 'reference';
|
|
245
|
+
|
|
246
|
+
// Relationships
|
|
247
|
+
// → relatedTo → ConceptNode
|
|
248
|
+
// → implements → CommandNode
|
|
249
|
+
// → prerequisiteFor → ConceptNode
|
|
250
|
+
// → documentedIn → ArticleNode
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interface WorkflowNode {
|
|
254
|
+
id: string;
|
|
255
|
+
type: 'workflow';
|
|
256
|
+
name: string; // "Create and Send Campaign"
|
|
257
|
+
description: string;
|
|
258
|
+
steps: WorkflowStep[];
|
|
259
|
+
|
|
260
|
+
// Relationships
|
|
261
|
+
// → hasStep → CommandNode (ordered)
|
|
262
|
+
// → requires → ConceptNode
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface ArticleNode {
|
|
266
|
+
id: string;
|
|
267
|
+
type: 'article';
|
|
268
|
+
title: string;
|
|
269
|
+
slug: string; // "campaigns-management"
|
|
270
|
+
path: string; // "docs/user-manual/campaigns.md"
|
|
271
|
+
category: string;
|
|
272
|
+
template: 'command_reference' | 'tutorial' | 'concept' | 'faq';
|
|
273
|
+
last_generated: Date;
|
|
274
|
+
last_human_edit?: Date;
|
|
275
|
+
|
|
276
|
+
// Relationships
|
|
277
|
+
// ← documentedIn ← CommandNode
|
|
278
|
+
// ← documentedIn ← ConceptNode
|
|
279
|
+
// → includes → (any node type)
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Relationship Types:**
|
|
284
|
+
|
|
285
|
+
```cypher
|
|
286
|
+
// Command → Parameter
|
|
287
|
+
(:Command)-[:HAS_PARAMETER {position: 1, required: true}]->(:Parameter)
|
|
288
|
+
|
|
289
|
+
// Command → Example
|
|
290
|
+
(:Command)-[:HAS_EXAMPLE {order: 1}]->(:Example)
|
|
291
|
+
|
|
292
|
+
// Command → Error
|
|
293
|
+
(:Command)-[:CAN_THROW {condition: "invalid list ID"}]->(:Error)
|
|
294
|
+
|
|
295
|
+
// Command → Command (related)
|
|
296
|
+
(:Command)-[:RELATED_TO {similarity: 0.8}]->(:Command)
|
|
297
|
+
|
|
298
|
+
// Command → Workflow
|
|
299
|
+
(:Command)-[:PART_OF_WORKFLOW {step: 2}]->(:Workflow)
|
|
300
|
+
|
|
301
|
+
// Concept → Concept (prerequisite)
|
|
302
|
+
(:Concept)-[:PREREQUISITE_FOR]->(:Concept)
|
|
303
|
+
|
|
304
|
+
// Article → Content (what it documents)
|
|
305
|
+
(:Article)-[:DOCUMENTS {section: "Usage"}]->(:Command)
|
|
306
|
+
|
|
307
|
+
// Temporal relationships
|
|
308
|
+
(:Command)-[:SUPERSEDES]->(:Command) // v2 command replaces v1
|
|
309
|
+
(:Command)-[:DEPRECATED_IN {version: "2.0.0"}]->(:Version)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Graph Query Examples
|
|
313
|
+
|
|
314
|
+
**Find all commands in a category:**
|
|
315
|
+
```cypher
|
|
316
|
+
MATCH (c:Command {category: 'campaigns'})
|
|
317
|
+
RETURN c.name, c.description
|
|
318
|
+
ORDER BY c.name
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Find command with all related information:**
|
|
322
|
+
```cypher
|
|
323
|
+
MATCH (c:Command {name: 'cakemail campaigns list'})
|
|
324
|
+
OPTIONAL MATCH (c)-[:HAS_PARAMETER]->(p:Parameter)
|
|
325
|
+
OPTIONAL MATCH (c)-[:HAS_EXAMPLE]->(e:Example)
|
|
326
|
+
OPTIONAL MATCH (c)-[:CAN_THROW]->(err:Error)
|
|
327
|
+
OPTIONAL MATCH (c)-[:RELATED_TO]->(related:Command)
|
|
328
|
+
RETURN c,
|
|
329
|
+
collect(DISTINCT p) as parameters,
|
|
330
|
+
collect(DISTINCT e) as examples,
|
|
331
|
+
collect(DISTINCT err) as errors,
|
|
332
|
+
collect(DISTINCT related) as related_commands
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Find all articles that need regeneration:**
|
|
336
|
+
```cypher
|
|
337
|
+
// Find articles where documented commands have changed
|
|
338
|
+
MATCH (a:Article)-[:DOCUMENTS]->(c:Command)
|
|
339
|
+
WHERE c.updated_at > a.last_generated
|
|
340
|
+
RETURN a.path, a.title, count(c) as outdated_commands
|
|
341
|
+
ORDER BY outdated_commands DESC
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Find workflow for a goal:**
|
|
345
|
+
```cypher
|
|
346
|
+
MATCH (w:Workflow {name: 'Create and Send Campaign'})
|
|
347
|
+
MATCH (w)-[s:HAS_STEP]->(c:Command)
|
|
348
|
+
RETURN c.name, s.step
|
|
349
|
+
ORDER BY s.step
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Article Generation from Graph
|
|
355
|
+
|
|
356
|
+
### Template System
|
|
357
|
+
|
|
358
|
+
**Article Templates:**
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
interface ArticleTemplate {
|
|
362
|
+
type: 'command_reference' | 'tutorial' | 'concept' | 'faq';
|
|
363
|
+
structure: TemplateSection[];
|
|
364
|
+
renderingRules: RenderingRule[];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
interface CommandReferenceTemplate extends ArticleTemplate {
|
|
368
|
+
type: 'command_reference';
|
|
369
|
+
structure: [
|
|
370
|
+
{ section: 'title', source: 'command.name' },
|
|
371
|
+
{ section: 'description', source: 'command.description' },
|
|
372
|
+
{ section: 'usage', generator: 'renderUsage' },
|
|
373
|
+
{ section: 'parameters', generator: 'renderParameters' },
|
|
374
|
+
{ section: 'examples', generator: 'renderExamples' },
|
|
375
|
+
{ section: 'errors', generator: 'renderErrors' },
|
|
376
|
+
{ section: 'related', generator: 'renderRelated' }
|
|
377
|
+
];
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Template Example (Command Reference):**
|
|
382
|
+
|
|
383
|
+
```handlebars
|
|
384
|
+
{{!-- templates/command-reference.hbs --}}
|
|
385
|
+
# {{command.name}}
|
|
386
|
+
|
|
387
|
+
{{command.description}}
|
|
388
|
+
|
|
389
|
+
## Usage
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
{{command.name}} {{#each parameters}}{{renderParameter this}}{{/each}}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Parameters
|
|
396
|
+
|
|
397
|
+
{{#each parameters}}
|
|
398
|
+
### {{flag}}
|
|
399
|
+
|
|
400
|
+
{{description}}
|
|
401
|
+
|
|
402
|
+
- **Type:** `{{value_type}}`
|
|
403
|
+
- **Required:** {{#if required}}Yes{{else}}No{{/if}}
|
|
404
|
+
{{#if default_value}}
|
|
405
|
+
- **Default:** `{{default_value}}`
|
|
406
|
+
{{/if}}
|
|
407
|
+
{{#if enum_values}}
|
|
408
|
+
- **Valid values:** {{#each enum_values}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}
|
|
409
|
+
{{/if}}
|
|
410
|
+
|
|
411
|
+
{{/each}}
|
|
412
|
+
|
|
413
|
+
## Examples
|
|
414
|
+
|
|
415
|
+
{{#each examples}}
|
|
416
|
+
### {{title}}
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
{{command}}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
{{description}}
|
|
423
|
+
|
|
424
|
+
**Expected output:**
|
|
425
|
+
```{{#if (isJSON expected_output)}}json{{else}}text{{/if}}
|
|
426
|
+
{{expected_output}}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
{{/each}}
|
|
430
|
+
|
|
431
|
+
## Error Handling
|
|
432
|
+
|
|
433
|
+
{{#each errors}}
|
|
434
|
+
### {{error_message}}
|
|
435
|
+
|
|
436
|
+
{{description}}
|
|
437
|
+
|
|
438
|
+
**Exit code:** {{exit_code}}
|
|
439
|
+
|
|
440
|
+
{{#if suggestion}}
|
|
441
|
+
💡 **Tip:** {{suggestion}}
|
|
442
|
+
{{/if}}
|
|
443
|
+
|
|
444
|
+
{{/each}}
|
|
445
|
+
|
|
446
|
+
## Related Commands
|
|
447
|
+
|
|
448
|
+
{{#each related_commands}}
|
|
449
|
+
- [`{{name}}`]({{link}}) - {{description}}
|
|
450
|
+
{{/each}}
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
*Last generated: {{generated_at}}*
|
|
455
|
+
*Source: Knowledge Graph v{{graph_version}}*
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Article Generator
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
class ArticleGenerator {
|
|
462
|
+
constructor(
|
|
463
|
+
private knowledgeGraph: KnowledgeGraph,
|
|
464
|
+
private templates: TemplateRegistry
|
|
465
|
+
) {}
|
|
466
|
+
|
|
467
|
+
async generateArticle(articleId: string): Promise<GeneratedArticle> {
|
|
468
|
+
// 1. Load article node from graph
|
|
469
|
+
const articleNode = await this.knowledgeGraph.getNode(articleId);
|
|
470
|
+
|
|
471
|
+
// 2. Query all related content
|
|
472
|
+
const content = await this.queryArticleContent(articleNode);
|
|
473
|
+
|
|
474
|
+
// 3. Select template
|
|
475
|
+
const template = this.templates.get(articleNode.template);
|
|
476
|
+
|
|
477
|
+
// 4. Render article
|
|
478
|
+
const markdown = await this.renderTemplate(template, content);
|
|
479
|
+
|
|
480
|
+
// 5. Add metadata
|
|
481
|
+
const frontmatter = this.generateFrontmatter(articleNode, content);
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
path: articleNode.path,
|
|
485
|
+
content: `${frontmatter}\n\n${markdown}`,
|
|
486
|
+
metadata: {
|
|
487
|
+
generated_at: new Date(),
|
|
488
|
+
source_nodes: content.nodes.map(n => n.id),
|
|
489
|
+
template: articleNode.template
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async queryArticleContent(articleNode: ArticleNode): Promise<ArticleContent> {
|
|
495
|
+
// Query based on article type
|
|
496
|
+
switch (articleNode.template) {
|
|
497
|
+
case 'command_reference':
|
|
498
|
+
return this.queryCommandReference(articleNode);
|
|
499
|
+
case 'tutorial':
|
|
500
|
+
return this.queryTutorial(articleNode);
|
|
501
|
+
case 'concept':
|
|
502
|
+
return this.queryConcept(articleNode);
|
|
503
|
+
case 'faq':
|
|
504
|
+
return this.queryFAQ(articleNode);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async queryCommandReference(articleNode: ArticleNode): Promise<ArticleContent> {
|
|
509
|
+
// Cypher query to get all commands documented in this article
|
|
510
|
+
const query = `
|
|
511
|
+
MATCH (a:Article {id: $articleId})-[:DOCUMENTS]->(c:Command)
|
|
512
|
+
OPTIONAL MATCH (c)-[:HAS_PARAMETER]->(p:Parameter)
|
|
513
|
+
OPTIONAL MATCH (c)-[:HAS_EXAMPLE]->(e:Example)
|
|
514
|
+
OPTIONAL MATCH (c)-[:CAN_THROW]->(err:Error)
|
|
515
|
+
OPTIONAL MATCH (c)-[:RELATED_TO]->(rel:Command)
|
|
516
|
+
RETURN c,
|
|
517
|
+
collect(DISTINCT p) as parameters,
|
|
518
|
+
collect(DISTINCT e) as examples,
|
|
519
|
+
collect(DISTINCT err) as errors,
|
|
520
|
+
collect(DISTINCT rel) as related
|
|
521
|
+
`;
|
|
522
|
+
|
|
523
|
+
const result = await this.knowledgeGraph.query(query, { articleId: articleNode.id });
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
commands: result.map(r => ({
|
|
527
|
+
...r.c,
|
|
528
|
+
parameters: r.parameters,
|
|
529
|
+
examples: r.examples,
|
|
530
|
+
errors: r.errors,
|
|
531
|
+
related: r.related
|
|
532
|
+
}))
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
generateFrontmatter(articleNode: ArticleNode, content: ArticleContent): string {
|
|
537
|
+
return `---
|
|
538
|
+
title: ${articleNode.title}
|
|
539
|
+
category: ${articleNode.category}
|
|
540
|
+
template: ${articleNode.template}
|
|
541
|
+
generated_at: ${new Date().toISOString()}
|
|
542
|
+
graph_version: ${this.knowledgeGraph.version}
|
|
543
|
+
source_nodes: [${content.nodes.map(n => n.id).join(', ')}]
|
|
544
|
+
---`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Regeneration Strategies
|
|
550
|
+
|
|
551
|
+
**Strategy 1: Full Regeneration**
|
|
552
|
+
```typescript
|
|
553
|
+
// Regenerate all articles from scratch
|
|
554
|
+
async function regenerateAllArticles() {
|
|
555
|
+
const articles = await knowledgeGraph.getAllNodes('Article');
|
|
556
|
+
|
|
557
|
+
for (const article of articles) {
|
|
558
|
+
const generated = await articleGenerator.generateArticle(article.id);
|
|
559
|
+
await writeFile(generated.path, generated.content);
|
|
560
|
+
|
|
561
|
+
// Update article node with generation timestamp
|
|
562
|
+
await knowledgeGraph.updateNode(article.id, {
|
|
563
|
+
last_generated: new Date()
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Strategy 2: Selective Regeneration (Efficient)**
|
|
570
|
+
```typescript
|
|
571
|
+
// Only regenerate articles affected by graph changes
|
|
572
|
+
async function regenerateChangedArticles(changedNodeIds: string[]) {
|
|
573
|
+
// Find all articles that document the changed nodes
|
|
574
|
+
const query = `
|
|
575
|
+
MATCH (a:Article)-[:DOCUMENTS*1..2]->(n)
|
|
576
|
+
WHERE n.id IN $changedNodeIds
|
|
577
|
+
RETURN DISTINCT a
|
|
578
|
+
`;
|
|
579
|
+
|
|
580
|
+
const affectedArticles = await knowledgeGraph.query(query, { changedNodeIds });
|
|
581
|
+
|
|
582
|
+
for (const article of affectedArticles) {
|
|
583
|
+
await articleGenerator.generateArticle(article.id);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**Strategy 3: Incremental Updates (Advanced)**
|
|
589
|
+
```typescript
|
|
590
|
+
// Update only changed sections of articles
|
|
591
|
+
async function incrementalUpdate(nodeId: string, changes: NodeChanges) {
|
|
592
|
+
const affectedArticles = await findArticlesDocumenting(nodeId);
|
|
593
|
+
|
|
594
|
+
for (const article of affectedArticles) {
|
|
595
|
+
// Parse existing article
|
|
596
|
+
const existingContent = await readFile(article.path);
|
|
597
|
+
const ast = parseMarkdown(existingContent);
|
|
598
|
+
|
|
599
|
+
// Find section that documents this node
|
|
600
|
+
const section = findSectionForNode(ast, nodeId);
|
|
601
|
+
|
|
602
|
+
// Regenerate only that section
|
|
603
|
+
const newSection = await generateSection(nodeId, changes);
|
|
604
|
+
|
|
605
|
+
// Replace section in AST
|
|
606
|
+
ast.replace(section, newSection);
|
|
607
|
+
|
|
608
|
+
// Write back
|
|
609
|
+
await writeFile(article.path, renderMarkdown(ast));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Synchronization Strategies
|
|
617
|
+
|
|
618
|
+
### Two-Way Sync Architecture
|
|
619
|
+
|
|
620
|
+
```
|
|
621
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
622
|
+
│ Sync Coordinator │
|
|
623
|
+
└──────────────────────────────────────────────────────────────┘
|
|
624
|
+
│
|
|
625
|
+
┌─────────────────────┼─────────────────────┐
|
|
626
|
+
│ │ │
|
|
627
|
+
▼ ▼ ▼
|
|
628
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
629
|
+
│ Change │ │ Conflict │ │ Merge │
|
|
630
|
+
│ Detector │ │ Resolver │ │ Engine │
|
|
631
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Change Detection
|
|
635
|
+
|
|
636
|
+
**Article Changes (Human Edits):**
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
class ArticleChangeDetector {
|
|
640
|
+
async detectChanges(articlePath: string): Promise<ArticleChanges> {
|
|
641
|
+
// 1. Parse current article
|
|
642
|
+
const currentContent = await readFile(articlePath);
|
|
643
|
+
const currentAST = parseMarkdown(currentContent);
|
|
644
|
+
|
|
645
|
+
// 2. Get last known state from graph
|
|
646
|
+
const articleNode = await this.getArticleNodeByPath(articlePath);
|
|
647
|
+
const lastGenerated = articleNode.last_generated;
|
|
648
|
+
|
|
649
|
+
// 3. Load version from last generation
|
|
650
|
+
const lastContent = await this.getGeneratedVersion(articleNode.id, lastGenerated);
|
|
651
|
+
const lastAST = parseMarkdown(lastContent);
|
|
652
|
+
|
|
653
|
+
// 4. Diff the two versions
|
|
654
|
+
const diff = diffAST(lastAST, currentAST);
|
|
655
|
+
|
|
656
|
+
// 5. Classify changes
|
|
657
|
+
return this.classifyChanges(diff, articleNode);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
classifyChanges(diff: ASTDiff, articleNode: ArticleNode): ArticleChanges {
|
|
661
|
+
const changes: ArticleChanges = {
|
|
662
|
+
structural: [], // New sections, removed sections
|
|
663
|
+
factual: [], // Changed facts (command names, parameters, etc.)
|
|
664
|
+
editorial: [], // Wording, examples, explanations
|
|
665
|
+
metadata: [] // Frontmatter changes
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
for (const change of diff.changes) {
|
|
669
|
+
if (this.isStructuralChange(change)) {
|
|
670
|
+
changes.structural.push(change);
|
|
671
|
+
} else if (this.isFactualChange(change)) {
|
|
672
|
+
changes.factual.push(change);
|
|
673
|
+
} else {
|
|
674
|
+
changes.editorial.push(change);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return changes;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
isFactualChange(change: Change): boolean {
|
|
682
|
+
// Factual changes affect the knowledge graph
|
|
683
|
+
// Examples: command names, parameter types, exit codes
|
|
684
|
+
return (
|
|
685
|
+
change.affects('command.name') ||
|
|
686
|
+
change.affects('parameter.type') ||
|
|
687
|
+
change.affects('example.command') ||
|
|
688
|
+
change.affects('error.exit_code')
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**Graph Changes (Programmatic Updates):**
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
class GraphChangeDetector {
|
|
698
|
+
async detectChanges(): Promise<GraphChanges> {
|
|
699
|
+
// Use graph's built-in change tracking
|
|
700
|
+
const query = `
|
|
701
|
+
MATCH (n)
|
|
702
|
+
WHERE n.updated_at > $lastCheck
|
|
703
|
+
RETURN n, labels(n) as type
|
|
704
|
+
`;
|
|
705
|
+
|
|
706
|
+
const changedNodes = await this.knowledgeGraph.query(query, {
|
|
707
|
+
lastCheck: this.lastCheckpoint
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
nodes: changedNodes,
|
|
712
|
+
affectedArticles: await this.findAffectedArticles(changedNodes)
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async findAffectedArticles(changedNodes: Node[]): Promise<ArticleNode[]> {
|
|
717
|
+
const nodeIds = changedNodes.map(n => n.id);
|
|
718
|
+
|
|
719
|
+
const query = `
|
|
720
|
+
MATCH (a:Article)-[:DOCUMENTS*1..3]->(n)
|
|
721
|
+
WHERE n.id IN $nodeIds
|
|
722
|
+
RETURN DISTINCT a
|
|
723
|
+
`;
|
|
724
|
+
|
|
725
|
+
return this.knowledgeGraph.query(query, { nodeIds });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Sync Directions
|
|
731
|
+
|
|
732
|
+
**Direction 1: Article → Graph (Human edits articles)**
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
class ArticleToGraphSync {
|
|
736
|
+
async syncArticleChanges(changes: ArticleChanges): Promise<SyncResult> {
|
|
737
|
+
const updates: GraphUpdate[] = [];
|
|
738
|
+
|
|
739
|
+
// 1. Apply structural changes
|
|
740
|
+
for (const change of changes.structural) {
|
|
741
|
+
if (change.type === 'section_added') {
|
|
742
|
+
// Extract new content and create graph nodes
|
|
743
|
+
const newNodes = await this.extractNodesFromSection(change.section);
|
|
744
|
+
updates.push({ type: 'create', nodes: newNodes });
|
|
745
|
+
} else if (change.type === 'section_removed') {
|
|
746
|
+
// Mark nodes as deprecated or remove relationships
|
|
747
|
+
updates.push({ type: 'deprecate', nodes: change.affectedNodes });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// 2. Apply factual changes
|
|
752
|
+
for (const change of changes.factual) {
|
|
753
|
+
const node = await this.findNodeForChange(change);
|
|
754
|
+
updates.push({
|
|
755
|
+
type: 'update',
|
|
756
|
+
nodeId: node.id,
|
|
757
|
+
field: change.field,
|
|
758
|
+
oldValue: change.oldValue,
|
|
759
|
+
newValue: change.newValue
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// 3. Editorial changes don't affect graph
|
|
764
|
+
// They're preserved as article-specific styling
|
|
765
|
+
|
|
766
|
+
// 4. Apply all updates to graph
|
|
767
|
+
return this.applyUpdates(updates);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async extractNodesFromSection(section: MarkdownSection): Promise<Node[]> {
|
|
771
|
+
// Use LLM to extract structured data from markdown
|
|
772
|
+
const prompt = `
|
|
773
|
+
Extract structured knowledge from this documentation section:
|
|
774
|
+
|
|
775
|
+
${section.content}
|
|
776
|
+
|
|
777
|
+
Identify:
|
|
778
|
+
- Commands (name, description, parameters)
|
|
779
|
+
- Examples (command, expected output)
|
|
780
|
+
- Concepts (name, description, relationships)
|
|
781
|
+
- Errors (message, exit code, suggestions)
|
|
782
|
+
|
|
783
|
+
Return as JSON matching our graph schema.
|
|
784
|
+
`;
|
|
785
|
+
|
|
786
|
+
const extracted = await this.llm.complete(prompt, { format: 'json' });
|
|
787
|
+
return this.convertToGraphNodes(extracted);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
**Direction 2: Graph → Articles (Programmatic updates to graph)**
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
class GraphToArticleSync {
|
|
796
|
+
async syncGraphChanges(changes: GraphChanges): Promise<SyncResult> {
|
|
797
|
+
const affectedArticles = changes.affectedArticles;
|
|
798
|
+
|
|
799
|
+
for (const article of affectedArticles) {
|
|
800
|
+
// Check for conflicts (has article been edited since last generation?)
|
|
801
|
+
const hasHumanEdits = await this.hasHumanEdits(article);
|
|
802
|
+
|
|
803
|
+
if (hasHumanEdits) {
|
|
804
|
+
// Conflict! Needs resolution
|
|
805
|
+
await this.raiseConflict({
|
|
806
|
+
article: article.path,
|
|
807
|
+
graphChanges: changes.nodes,
|
|
808
|
+
humanEdits: await this.getHumanEdits(article)
|
|
809
|
+
});
|
|
810
|
+
} else {
|
|
811
|
+
// Safe to regenerate
|
|
812
|
+
await this.regenerateArticle(article);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return { success: true, conflicts: this.conflicts };
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async hasHumanEdits(article: ArticleNode): Promise<boolean> {
|
|
820
|
+
// Compare file modification time with last_generated timestamp
|
|
821
|
+
const fileStats = await stat(article.path);
|
|
822
|
+
return fileStats.mtime > article.last_generated;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
## Conflict Resolution
|
|
830
|
+
|
|
831
|
+
### Conflict Scenarios
|
|
832
|
+
|
|
833
|
+
**Scenario 1: Simultaneous Edits**
|
|
834
|
+
```
|
|
835
|
+
Graph: Command parameter "status" changed from optional to required
|
|
836
|
+
Article: Human added example showing command without "status" parameter
|
|
837
|
+
|
|
838
|
+
Conflict: Example no longer valid
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
**Scenario 2: Divergent Facts**
|
|
842
|
+
```
|
|
843
|
+
Graph: Command exit code is 1 on error
|
|
844
|
+
Article: Human documented exit code as 0 (incorrect)
|
|
845
|
+
|
|
846
|
+
Conflict: Which is correct?
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
**Scenario 3: Structure vs Content**
|
|
850
|
+
```
|
|
851
|
+
Graph: Command deprecated, marked for removal
|
|
852
|
+
Article: Human added extensive tutorial about the command
|
|
853
|
+
|
|
854
|
+
Conflict: Keep tutorial or remove?
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### Resolution Strategies
|
|
858
|
+
|
|
859
|
+
**Strategy 1: Graph Wins (Default for Facts)**
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
class GraphWinsResolver implements ConflictResolver {
|
|
863
|
+
resolve(conflict: Conflict): Resolution {
|
|
864
|
+
// For factual conflicts, graph is source of truth
|
|
865
|
+
if (conflict.type === 'factual') {
|
|
866
|
+
return {
|
|
867
|
+
action: 'use_graph',
|
|
868
|
+
reasoning: 'Knowledge graph is authoritative for facts',
|
|
869
|
+
preserveHumanEdit: false
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return { action: 'escalate' };
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
**Strategy 2: Human Wins (For Editorial)**
|
|
879
|
+
|
|
880
|
+
```typescript
|
|
881
|
+
class HumanWinsResolver implements ConflictResolver {
|
|
882
|
+
resolve(conflict: Conflict): Resolution {
|
|
883
|
+
// For editorial changes, preserve human intent
|
|
884
|
+
if (conflict.type === 'editorial') {
|
|
885
|
+
return {
|
|
886
|
+
action: 'use_article',
|
|
887
|
+
reasoning: 'Human editorial improvements should be preserved',
|
|
888
|
+
syncBackToGraph: false
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return { action: 'escalate' };
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
**Strategy 3: Merge (Best Effort)**
|
|
898
|
+
|
|
899
|
+
```typescript
|
|
900
|
+
class MergeResolver implements ConflictResolver {
|
|
901
|
+
async resolve(conflict: Conflict): Promise<Resolution> {
|
|
902
|
+
// Use AI to attempt intelligent merge
|
|
903
|
+
const prompt = `
|
|
904
|
+
There is a conflict between the knowledge graph and article:
|
|
905
|
+
|
|
906
|
+
Graph says: ${conflict.graphVersion}
|
|
907
|
+
Article says: ${conflict.articleVersion}
|
|
908
|
+
|
|
909
|
+
The conflict type is: ${conflict.type}
|
|
910
|
+
|
|
911
|
+
Can these be merged? If so, how?
|
|
912
|
+
If not, which should take precedence and why?
|
|
913
|
+
`;
|
|
914
|
+
|
|
915
|
+
const resolution = await this.llm.complete(prompt);
|
|
916
|
+
return this.parseResolution(resolution);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
**Strategy 4: Three-Way Merge (Most Robust)**
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
class ThreeWayMergeResolver implements ConflictResolver {
|
|
925
|
+
resolve(conflict: Conflict): Resolution {
|
|
926
|
+
// Compare:
|
|
927
|
+
// 1. Last generated version (base)
|
|
928
|
+
// 2. Current graph version
|
|
929
|
+
// 3. Current article version
|
|
930
|
+
|
|
931
|
+
const base = conflict.lastGeneratedVersion;
|
|
932
|
+
const graph = conflict.graphVersion;
|
|
933
|
+
const article = conflict.articleVersion;
|
|
934
|
+
|
|
935
|
+
if (graph === base && article !== base) {
|
|
936
|
+
// Only article changed → use article
|
|
937
|
+
return { action: 'use_article', syncBackToGraph: true };
|
|
938
|
+
} else if (article === base && graph !== base) {
|
|
939
|
+
// Only graph changed → use graph
|
|
940
|
+
return { action: 'use_graph' };
|
|
941
|
+
} else {
|
|
942
|
+
// Both changed → conflict!
|
|
943
|
+
return {
|
|
944
|
+
action: 'manual_review',
|
|
945
|
+
versions: { base, graph, article },
|
|
946
|
+
suggestedResolution: this.suggestMerge(base, graph, article)
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
### Conflict UI
|
|
954
|
+
|
|
955
|
+
```typescript
|
|
956
|
+
// Interactive conflict resolution interface
|
|
957
|
+
class ConflictResolutionUI {
|
|
958
|
+
async presentConflict(conflict: Conflict): Promise<Resolution> {
|
|
959
|
+
console.log(`
|
|
960
|
+
┌─────────────────────────────────────────────────────────┐
|
|
961
|
+
│ 🚨 CONFLICT DETECTED │
|
|
962
|
+
├─────────────────────────────────────────────────────────┤
|
|
963
|
+
│ File: ${conflict.article.path} │
|
|
964
|
+
│ Type: ${conflict.type} │
|
|
965
|
+
│ │
|
|
966
|
+
│ ❓ What should we do? │
|
|
967
|
+
└─────────────────────────────────────────────────────────┘
|
|
968
|
+
|
|
969
|
+
📊 Graph Version (Programmatic):
|
|
970
|
+
${highlight(conflict.graphVersion)}
|
|
971
|
+
|
|
972
|
+
✏️ Article Version (Human Edit):
|
|
973
|
+
${highlight(conflict.articleVersion)}
|
|
974
|
+
|
|
975
|
+
🔀 Options:
|
|
976
|
+
1) Use graph version (discard human edit)
|
|
977
|
+
2) Use article version (update graph to match)
|
|
978
|
+
3) Merge both (AI-assisted)
|
|
979
|
+
4) Edit manually
|
|
980
|
+
5) Skip for now
|
|
981
|
+
|
|
982
|
+
Choice:
|
|
983
|
+
`);
|
|
984
|
+
|
|
985
|
+
const choice = await this.prompt();
|
|
986
|
+
return this.handleChoice(choice, conflict);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
---
|
|
992
|
+
|
|
993
|
+
## Implementation Design
|
|
994
|
+
|
|
995
|
+
### Database Schema (Neo4j Example)
|
|
996
|
+
|
|
997
|
+
```cypher
|
|
998
|
+
// Create constraints
|
|
999
|
+
CREATE CONSTRAINT command_id IF NOT EXISTS
|
|
1000
|
+
FOR (c:Command) REQUIRE c.id IS UNIQUE;
|
|
1001
|
+
|
|
1002
|
+
CREATE CONSTRAINT article_path IF NOT EXISTS
|
|
1003
|
+
FOR (a:Article) REQUIRE a.path IS UNIQUE;
|
|
1004
|
+
|
|
1005
|
+
// Create indexes for common queries
|
|
1006
|
+
CREATE INDEX command_name IF NOT EXISTS
|
|
1007
|
+
FOR (c:Command) ON (c.name);
|
|
1008
|
+
|
|
1009
|
+
CREATE INDEX article_category IF NOT EXISTS
|
|
1010
|
+
FOR (a:Article) ON (a.category);
|
|
1011
|
+
|
|
1012
|
+
// Full-text search
|
|
1013
|
+
CREATE FULLTEXT INDEX article_content IF NOT EXISTS
|
|
1014
|
+
FOR (a:Article) ON EACH [a.title, a.description];
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
### Core System Components
|
|
1018
|
+
|
|
1019
|
+
**Component 1: Knowledge Graph Manager**
|
|
1020
|
+
|
|
1021
|
+
```typescript
|
|
1022
|
+
class KnowledgeGraphManager {
|
|
1023
|
+
constructor(private neo4j: Neo4jDriver) {}
|
|
1024
|
+
|
|
1025
|
+
// CRUD operations
|
|
1026
|
+
async createNode(type: string, properties: any): Promise<Node> {
|
|
1027
|
+
const query = `
|
|
1028
|
+
CREATE (n:${type} $properties)
|
|
1029
|
+
SET n.id = randomUUID(),
|
|
1030
|
+
n.created_at = datetime(),
|
|
1031
|
+
n.updated_at = datetime()
|
|
1032
|
+
RETURN n
|
|
1033
|
+
`;
|
|
1034
|
+
return this.neo4j.run(query, { properties });
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
async updateNode(id: string, updates: any): Promise<Node> {
|
|
1038
|
+
const query = `
|
|
1039
|
+
MATCH (n {id: $id})
|
|
1040
|
+
SET n += $updates,
|
|
1041
|
+
n.updated_at = datetime()
|
|
1042
|
+
RETURN n
|
|
1043
|
+
`;
|
|
1044
|
+
return this.neo4j.run(query, { id, updates });
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
async createRelationship(
|
|
1048
|
+
fromId: string,
|
|
1049
|
+
toId: string,
|
|
1050
|
+
type: string,
|
|
1051
|
+
properties?: any
|
|
1052
|
+
): Promise<Relationship> {
|
|
1053
|
+
const query = `
|
|
1054
|
+
MATCH (from {id: $fromId}), (to {id: $toId})
|
|
1055
|
+
CREATE (from)-[r:${type} $properties]->(to)
|
|
1056
|
+
RETURN r
|
|
1057
|
+
`;
|
|
1058
|
+
return this.neo4j.run(query, { fromId, toId, properties });
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Versioning
|
|
1062
|
+
async createSnapshot(label: string): Promise<Snapshot> {
|
|
1063
|
+
const query = `
|
|
1064
|
+
MATCH (n)
|
|
1065
|
+
WITH collect(n) as nodes
|
|
1066
|
+
CREATE (s:Snapshot {
|
|
1067
|
+
id: randomUUID(),
|
|
1068
|
+
label: $label,
|
|
1069
|
+
created_at: datetime(),
|
|
1070
|
+
node_count: size(nodes)
|
|
1071
|
+
})
|
|
1072
|
+
RETURN s
|
|
1073
|
+
`;
|
|
1074
|
+
return this.neo4j.run(query, { label });
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
async restoreSnapshot(snapshotId: string): Promise<void> {
|
|
1078
|
+
// Restore graph to previous snapshot state
|
|
1079
|
+
// Implementation depends on versioning strategy
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
**Component 2: Sync Coordinator**
|
|
1085
|
+
|
|
1086
|
+
```typescript
|
|
1087
|
+
class SyncCoordinator {
|
|
1088
|
+
constructor(
|
|
1089
|
+
private graphManager: KnowledgeGraphManager,
|
|
1090
|
+
private articleGenerator: ArticleGenerator,
|
|
1091
|
+
private changeDetector: ChangeDetector,
|
|
1092
|
+
private conflictResolver: ConflictResolver
|
|
1093
|
+
) {}
|
|
1094
|
+
|
|
1095
|
+
async syncAll(direction: 'graph_to_articles' | 'articles_to_graph' | 'bidirectional') {
|
|
1096
|
+
if (direction === 'graph_to_articles' || direction === 'bidirectional') {
|
|
1097
|
+
await this.syncGraphToArticles();
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
if (direction === 'articles_to_graph' || direction === 'bidirectional') {
|
|
1101
|
+
await this.syncArticlesToGraph();
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
async syncGraphToArticles(): Promise<SyncResult> {
|
|
1106
|
+
// 1. Detect graph changes
|
|
1107
|
+
const graphChanges = await this.changeDetector.detectGraphChanges();
|
|
1108
|
+
|
|
1109
|
+
if (graphChanges.length === 0) {
|
|
1110
|
+
return { status: 'up_to_date' };
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// 2. Find affected articles
|
|
1114
|
+
const affectedArticles = await this.findAffectedArticles(graphChanges);
|
|
1115
|
+
|
|
1116
|
+
// 3. For each article, check for conflicts
|
|
1117
|
+
const results = [];
|
|
1118
|
+
for (const article of affectedArticles) {
|
|
1119
|
+
const hasConflict = await this.checkForConflicts(article, graphChanges);
|
|
1120
|
+
|
|
1121
|
+
if (hasConflict) {
|
|
1122
|
+
const resolution = await this.conflictResolver.resolve(hasConflict);
|
|
1123
|
+
results.push(await this.applyResolution(resolution));
|
|
1124
|
+
} else {
|
|
1125
|
+
// No conflict, regenerate
|
|
1126
|
+
results.push(await this.articleGenerator.generateArticle(article.id));
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
return { status: 'synced', results };
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async syncArticlesToGraph(): Promise<SyncResult> {
|
|
1134
|
+
// 1. Detect article changes
|
|
1135
|
+
const articleChanges = await this.changeDetector.detectArticleChanges();
|
|
1136
|
+
|
|
1137
|
+
if (articleChanges.length === 0) {
|
|
1138
|
+
return { status: 'up_to_date' };
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// 2. Extract graph updates from changes
|
|
1142
|
+
const graphUpdates = [];
|
|
1143
|
+
for (const change of articleChanges) {
|
|
1144
|
+
if (change.type === 'factual') {
|
|
1145
|
+
const update = await this.extractGraphUpdate(change);
|
|
1146
|
+
graphUpdates.push(update);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// 3. Apply updates to graph
|
|
1151
|
+
for (const update of graphUpdates) {
|
|
1152
|
+
await this.graphManager.applyUpdate(update);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return { status: 'synced', updates: graphUpdates.length };
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
**Component 3: Article Generator (Enhanced)**
|
|
1161
|
+
|
|
1162
|
+
```typescript
|
|
1163
|
+
class SmartArticleGenerator extends ArticleGenerator {
|
|
1164
|
+
// Preserve human editorial changes during regeneration
|
|
1165
|
+
async generateArticlePreservingEdits(
|
|
1166
|
+
articleId: string,
|
|
1167
|
+
humanEdits: EditorialChange[]
|
|
1168
|
+
): Promise<GeneratedArticle> {
|
|
1169
|
+
// 1. Generate fresh article from graph
|
|
1170
|
+
const generatedArticle = await super.generateArticle(articleId);
|
|
1171
|
+
|
|
1172
|
+
// 2. Parse both versions
|
|
1173
|
+
const generatedAST = parseMarkdown(generatedArticle.content);
|
|
1174
|
+
const currentContent = await readFile(articleId);
|
|
1175
|
+
const currentAST = parseMarkdown(currentContent);
|
|
1176
|
+
|
|
1177
|
+
// 3. Identify editorial sections to preserve
|
|
1178
|
+
const preserveSections = this.identifyEditorialSections(currentAST, humanEdits);
|
|
1179
|
+
|
|
1180
|
+
// 4. Merge: use generated for facts, current for editorial
|
|
1181
|
+
const mergedAST = this.mergeASTs(generatedAST, currentAST, preserveSections);
|
|
1182
|
+
|
|
1183
|
+
// 5. Render back to markdown
|
|
1184
|
+
return {
|
|
1185
|
+
...generatedArticle,
|
|
1186
|
+
content: renderMarkdown(mergedAST)
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
identifyEditorialSections(ast: MarkdownAST, edits: EditorialChange[]): Section[] {
|
|
1191
|
+
// Find sections that contain purely editorial content
|
|
1192
|
+
// (no factual data from graph)
|
|
1193
|
+
return ast.sections.filter(section =>
|
|
1194
|
+
this.isPurelyEditorial(section) &&
|
|
1195
|
+
this.wasEditedByHuman(section, edits)
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
mergeASTs(
|
|
1200
|
+
generated: MarkdownAST,
|
|
1201
|
+
current: MarkdownAST,
|
|
1202
|
+
preserveSections: Section[]
|
|
1203
|
+
): MarkdownAST {
|
|
1204
|
+
const merged = { ...generated };
|
|
1205
|
+
|
|
1206
|
+
for (const section of preserveSections) {
|
|
1207
|
+
// Replace generated section with human version
|
|
1208
|
+
const sectionIndex = merged.sections.findIndex(s =>
|
|
1209
|
+
s.heading === section.heading
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
if (sectionIndex >= 0) {
|
|
1213
|
+
merged.sections[sectionIndex] = section;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
return merged;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
---
|
|
1223
|
+
|
|
1224
|
+
## User Workflows
|
|
1225
|
+
|
|
1226
|
+
### Workflow 1: Technical Writer Updates Documentation
|
|
1227
|
+
|
|
1228
|
+
**Before Kenogami (Traditional):**
|
|
1229
|
+
```
|
|
1230
|
+
1. Writer edits campaigns.md
|
|
1231
|
+
2. Writer edits getting-started.md
|
|
1232
|
+
3. Writer edits command-reference.md
|
|
1233
|
+
4. (Oops, forgot to update examples in tutorial.md)
|
|
1234
|
+
5. (Oops, parameter description inconsistent across files)
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
**With Kenogami (Graph-First):**
|
|
1238
|
+
```
|
|
1239
|
+
1. Writer opens Kenogami UI
|
|
1240
|
+
2. Writer finds "campaigns create" command node
|
|
1241
|
+
3. Writer edits parameter description in ONE place
|
|
1242
|
+
4. Clicks "Regenerate affected articles" (4 articles updated automatically)
|
|
1243
|
+
5. Reviews changes, approves
|
|
1244
|
+
6. All articles now consistent ✅
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
### Workflow 2: Developer Adds New Command
|
|
1248
|
+
|
|
1249
|
+
**Before:**
|
|
1250
|
+
```
|
|
1251
|
+
1. Developer implements `campaigns schedule` command
|
|
1252
|
+
2. Developer documents it in campaigns.ts JSDoc
|
|
1253
|
+
3. (Forgets to update user manual)
|
|
1254
|
+
4. (User reads outdated docs, reports "missing feature")
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
**With Kenogami:**
|
|
1258
|
+
```
|
|
1259
|
+
1. Developer implements `campaigns schedule` command
|
|
1260
|
+
2. Developer adds to knowledge graph (via script or UI):
|
|
1261
|
+
- Command node
|
|
1262
|
+
- Parameter nodes
|
|
1263
|
+
- Example nodes
|
|
1264
|
+
- Related command links
|
|
1265
|
+
3. Run: `kenogami sync --direction graph-to-articles`
|
|
1266
|
+
4. All relevant articles auto-updated ✅
|
|
1267
|
+
- Command reference updated
|
|
1268
|
+
- Tutorial examples added
|
|
1269
|
+
- Related commands section updated
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
### Workflow 3: Resolving Conflicts
|
|
1273
|
+
|
|
1274
|
+
**Scenario:**
|
|
1275
|
+
```
|
|
1276
|
+
Developer: Marks command as deprecated in graph
|
|
1277
|
+
Writer: Just wrote extensive tutorial about that command
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
**Resolution:**
|
|
1281
|
+
```
|
|
1282
|
+
1. Kenogami detects conflict during sync
|
|
1283
|
+
2. Shows side-by-side comparison:
|
|
1284
|
+
|
|
1285
|
+
Graph: status = "deprecated_in_v2.0"
|
|
1286
|
+
Article: Contains 500-word tutorial (recently edited)
|
|
1287
|
+
|
|
1288
|
+
3. Presents options:
|
|
1289
|
+
a) Keep tutorial, add deprecation notice
|
|
1290
|
+
b) Remove tutorial
|
|
1291
|
+
c) Move tutorial to "Legacy Commands" section
|
|
1292
|
+
|
|
1293
|
+
4. Writer chooses (a)
|
|
1294
|
+
5. Kenogami:
|
|
1295
|
+
- Preserves tutorial content
|
|
1296
|
+
- Adds deprecation notice at top
|
|
1297
|
+
- Updates all links to point to replacement command
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
---
|
|
1301
|
+
|
|
1302
|
+
## Integration with AI Testing
|
|
1303
|
+
|
|
1304
|
+
### Bidirectional Benefit for Testing
|
|
1305
|
+
|
|
1306
|
+
**Testing Flow with Kenogami:**
|
|
1307
|
+
|
|
1308
|
+
```
|
|
1309
|
+
┌──────────────────────────────────────────────────────────┐
|
|
1310
|
+
│ 1. Knowledge Graph (Source of Truth) │
|
|
1311
|
+
│ - Commands, parameters, expected outputs │
|
|
1312
|
+
└────────────────┬─────────────────────────────────────────┘
|
|
1313
|
+
│
|
|
1314
|
+
┌───────┴────────┐
|
|
1315
|
+
│ │
|
|
1316
|
+
▼ ▼
|
|
1317
|
+
┌──────────────┐ ┌──────────────┐
|
|
1318
|
+
│ Articles │ │ Test Cases │
|
|
1319
|
+
│ (Generated) │ │ (Generated) │
|
|
1320
|
+
└──────┬───────┘ └──────┬───────┘
|
|
1321
|
+
│ │
|
|
1322
|
+
│ │
|
|
1323
|
+
└────────┬────────┘
|
|
1324
|
+
▼
|
|
1325
|
+
┌─────────────────┐
|
|
1326
|
+
│ AI Testing │
|
|
1327
|
+
│ Validation │
|
|
1328
|
+
└────────┬─────────┘
|
|
1329
|
+
│
|
|
1330
|
+
▼
|
|
1331
|
+
┌─────────────────┐
|
|
1332
|
+
│ Test Results │
|
|
1333
|
+
│ (Pass/Fail) │
|
|
1334
|
+
└────────┬─────────┘
|
|
1335
|
+
│
|
|
1336
|
+
│ If fail: Update graph OR article
|
|
1337
|
+
▼
|
|
1338
|
+
┌─────────────────┐
|
|
1339
|
+
│ Feedback Loop │
|
|
1340
|
+
│ to Graph │
|
|
1341
|
+
└──────────────────┘
|
|
1342
|
+
```
|
|
1343
|
+
|
|
1344
|
+
**Enhanced Test Generation:**
|
|
1345
|
+
|
|
1346
|
+
```typescript
|
|
1347
|
+
class KenogamiTestGenerator extends TestCaseGenerator {
|
|
1348
|
+
constructor(private knowledgeGraph: KnowledgeGraph) {
|
|
1349
|
+
super();
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
async generateTestsFromGraph(): Promise<TestCase[]> {
|
|
1353
|
+
// Query graph for all commands
|
|
1354
|
+
const commands = await this.knowledgeGraph.query(`
|
|
1355
|
+
MATCH (c:Command)
|
|
1356
|
+
OPTIONAL MATCH (c)-[:HAS_PARAMETER]->(p:Parameter)
|
|
1357
|
+
OPTIONAL MATCH (c)-[:HAS_EXAMPLE]->(e:Example)
|
|
1358
|
+
RETURN c, collect(p) as parameters, collect(e) as examples
|
|
1359
|
+
`);
|
|
1360
|
+
|
|
1361
|
+
// Generate test cases directly from graph
|
|
1362
|
+
return commands.map(cmd => ({
|
|
1363
|
+
id: `TEST-${cmd.c.id}`,
|
|
1364
|
+
command: this.buildCommandString(cmd.c, cmd.parameters),
|
|
1365
|
+
expectedBehavior: {
|
|
1366
|
+
exitCode: 0,
|
|
1367
|
+
output: cmd.examples[0]?.expected_output,
|
|
1368
|
+
schema: this.generateSchemaFromParameters(cmd.parameters)
|
|
1369
|
+
}
|
|
1370
|
+
}));
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
async feedbackFailureToGraph(testResult: TestResult): Promise<void> {
|
|
1374
|
+
if (!testResult.passed) {
|
|
1375
|
+
// Analyze failure
|
|
1376
|
+
const analysis = await this.analyzeFailure(testResult);
|
|
1377
|
+
|
|
1378
|
+
if (analysis.type === 'documentation_issue') {
|
|
1379
|
+
// Update graph with correct information
|
|
1380
|
+
await this.knowledgeGraph.updateNode(analysis.nodeId, {
|
|
1381
|
+
[analysis.field]: analysis.correctValue,
|
|
1382
|
+
updated_by: 'ai_testing',
|
|
1383
|
+
updated_reason: analysis.reason
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
// Trigger article regeneration
|
|
1387
|
+
await this.syncCoordinator.syncGraphToArticles();
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
**Test Results Feed Back to Graph:**
|
|
1395
|
+
|
|
1396
|
+
```typescript
|
|
1397
|
+
// When tests discover undocumented behavior
|
|
1398
|
+
class TestResultAnalyzer {
|
|
1399
|
+
async analyzeAndUpdateGraph(results: TestResult[]): Promise<GraphUpdate[]> {
|
|
1400
|
+
const updates = [];
|
|
1401
|
+
|
|
1402
|
+
for (const result of results) {
|
|
1403
|
+
if (result.passed && result.confidence < 0.8) {
|
|
1404
|
+
// Test passed but with low confidence - might be undocumented behavior
|
|
1405
|
+
const analysis = await this.llm.analyze(`
|
|
1406
|
+
Test passed but documentation was unclear:
|
|
1407
|
+
|
|
1408
|
+
Command: ${result.testCase.command}
|
|
1409
|
+
Expected: ${result.testCase.expectedBehavior}
|
|
1410
|
+
Actual: ${result.actualOutput}
|
|
1411
|
+
|
|
1412
|
+
Should we update the knowledge graph with this information?
|
|
1413
|
+
`);
|
|
1414
|
+
|
|
1415
|
+
if (analysis.should_update) {
|
|
1416
|
+
updates.push({
|
|
1417
|
+
nodeId: result.testCase.commandId,
|
|
1418
|
+
field: analysis.field,
|
|
1419
|
+
newValue: analysis.value,
|
|
1420
|
+
source: 'ai_testing_discovery'
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
if (!result.passed && result.actualBehaviorIsCorrect) {
|
|
1426
|
+
// Test failed because documentation is wrong
|
|
1427
|
+
updates.push({
|
|
1428
|
+
nodeId: result.testCase.commandId,
|
|
1429
|
+
field: result.mismatchField,
|
|
1430
|
+
oldValue: result.expected,
|
|
1431
|
+
newValue: result.actual,
|
|
1432
|
+
source: 'ai_testing_correction'
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Apply updates to graph
|
|
1438
|
+
for (const update of updates) {
|
|
1439
|
+
await this.knowledgeGraph.applyUpdate(update);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return updates;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
---
|
|
1448
|
+
|
|
1449
|
+
## Summary: Impact on Flow
|
|
1450
|
+
|
|
1451
|
+
### Before Kenogami (Traditional)
|
|
1452
|
+
|
|
1453
|
+
```
|
|
1454
|
+
Articles → Extract → Knowledge Graph → Generate Tests → Validate
|
|
1455
|
+
|
|
1456
|
+
Problems:
|
|
1457
|
+
- Articles can be inconsistent
|
|
1458
|
+
- Changes require manual updates in multiple places
|
|
1459
|
+
- Graph is read-only, can't improve from testing
|
|
1460
|
+
- Documentation drift over time
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
### With Kenogami (Bidirectional)
|
|
1464
|
+
|
|
1465
|
+
```
|
|
1466
|
+
┌─ Generate → Articles
|
|
1467
|
+
│
|
|
1468
|
+
Knowledge Graph ────┼─ Generate → Test Cases
|
|
1469
|
+
▲ │
|
|
1470
|
+
│ └─ Generate → API Specs
|
|
1471
|
+
│
|
|
1472
|
+
└─ Updates from: Test Results, Human Edits, Code Analysis
|
|
1473
|
+
|
|
1474
|
+
Benefits:
|
|
1475
|
+
✅ Single source of truth (the graph)
|
|
1476
|
+
✅ Automatic consistency
|
|
1477
|
+
✅ Test results improve documentation
|
|
1478
|
+
✅ Multi-format output (articles, tests, specs) always in sync
|
|
1479
|
+
✅ Changes propagate everywhere automatically
|
|
1480
|
+
```
|
|
1481
|
+
|
|
1482
|
+
### Key Architectural Shifts
|
|
1483
|
+
|
|
1484
|
+
| Aspect | Traditional | Kenogami |
|
|
1485
|
+
|--------|-------------|----------|
|
|
1486
|
+
| **Source of Truth** | Articles | Knowledge Graph |
|
|
1487
|
+
| **Article Creation** | Manual writing | Generated from graph |
|
|
1488
|
+
| **Updates** | Edit articles | Edit graph → regenerate |
|
|
1489
|
+
| **Consistency** | Manual effort | Automatic |
|
|
1490
|
+
| **Test Generation** | From articles | From graph (more reliable) |
|
|
1491
|
+
| **Test Feedback** | Manual fix docs | Auto-update graph |
|
|
1492
|
+
| **Multi-Format** | Duplicate content | Single source, multiple outputs |
|
|
1493
|
+
| **Version Control** | File-based | Graph snapshots + files |
|
|
1494
|
+
|
|
1495
|
+
### Migration Path
|
|
1496
|
+
|
|
1497
|
+
```
|
|
1498
|
+
Phase 1: Build graph from existing articles (one-time)
|
|
1499
|
+
Phase 2: Validate graph completeness
|
|
1500
|
+
Phase 3: Generate articles from graph (verify output)
|
|
1501
|
+
Phase 4: Enable bidirectional sync (with conflicts)
|
|
1502
|
+
Phase 5: Full graph-first workflow
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
---
|
|
1506
|
+
|
|
1507
|
+
## Conclusion
|
|
1508
|
+
|
|
1509
|
+
**Kenogami transforms documentation from static files into a living knowledge system** where:
|
|
1510
|
+
|
|
1511
|
+
1. **Knowledge Graph is authoritative** - Facts live in one place
|
|
1512
|
+
2. **Articles are generated views** - Consistent, up-to-date, multi-format
|
|
1513
|
+
3. **Bidirectional sync** - Accept edits from both humans and automation
|
|
1514
|
+
4. **Testing drives documentation** - Failed tests improve the graph
|
|
1515
|
+
5. **Scale effortlessly** - Add CLI, Web, Mobile docs without duplication
|
|
1516
|
+
|
|
1517
|
+
This architecture is **ideal for AI testing** because the knowledge graph provides a **single, queryable, version-controlled source of truth** that both generates documentation AND test cases, creating a self-improving system.
|