@rarusoft/dendrite-wiki 0.1.0-alpha.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 (74) hide show
  1. package/README.md +79 -0
  2. package/dist/api-extractor/extract.js +269 -0
  3. package/dist/api-extractor/language-extractor.js +15 -0
  4. package/dist/api-extractor/python-extractor.js +358 -0
  5. package/dist/api-extractor/render.js +195 -0
  6. package/dist/api-extractor/tree-sitter-extractor.js +1079 -0
  7. package/dist/api-extractor/types.js +11 -0
  8. package/dist/api-extractor/typescript-extractor.js +50 -0
  9. package/dist/api-extractor/walk.js +178 -0
  10. package/dist/api-reference.js +438 -0
  11. package/dist/benchmark-events.js +129 -0
  12. package/dist/benchmark.js +270 -0
  13. package/dist/binder-export.js +381 -0
  14. package/dist/canonical-target.js +168 -0
  15. package/dist/chart-insert.js +377 -0
  16. package/dist/chart-prompts.js +414 -0
  17. package/dist/context-cache.js +98 -0
  18. package/dist/contradicts-shipped-memory.js +232 -0
  19. package/dist/diff-context.js +142 -0
  20. package/dist/doctor.js +220 -0
  21. package/dist/generated-docs.js +219 -0
  22. package/dist/i18n.js +71 -0
  23. package/dist/index.js +49 -0
  24. package/dist/librarian.js +255 -0
  25. package/dist/maintenance-actions.js +244 -0
  26. package/dist/maintenance-inbox.js +842 -0
  27. package/dist/maintenance-runner.js +62 -0
  28. package/dist/page-drift.js +225 -0
  29. package/dist/page-inbox.js +168 -0
  30. package/dist/report-export.js +339 -0
  31. package/dist/review-bridge.js +1386 -0
  32. package/dist/search-index.js +199 -0
  33. package/dist/store.js +1617 -0
  34. package/dist/telemetry-defaults.js +44 -0
  35. package/dist/telemetry-report.js +263 -0
  36. package/dist/telemetry.js +544 -0
  37. package/dist/wiki-synthesis.js +901 -0
  38. package/package.json +35 -0
  39. package/src/api-extractor/extract.ts +333 -0
  40. package/src/api-extractor/language-extractor.ts +37 -0
  41. package/src/api-extractor/python-extractor.ts +380 -0
  42. package/src/api-extractor/render.ts +267 -0
  43. package/src/api-extractor/tree-sitter-extractor.ts +1210 -0
  44. package/src/api-extractor/types.ts +41 -0
  45. package/src/api-extractor/typescript-extractor.ts +56 -0
  46. package/src/api-extractor/walk.ts +209 -0
  47. package/src/api-reference.ts +552 -0
  48. package/src/benchmark-events.ts +216 -0
  49. package/src/benchmark.ts +376 -0
  50. package/src/binder-export.ts +437 -0
  51. package/src/canonical-target.ts +192 -0
  52. package/src/chart-insert.ts +478 -0
  53. package/src/chart-prompts.ts +417 -0
  54. package/src/context-cache.ts +129 -0
  55. package/src/contradicts-shipped-memory.ts +311 -0
  56. package/src/diff-context.ts +187 -0
  57. package/src/doctor.ts +260 -0
  58. package/src/generated-docs.ts +316 -0
  59. package/src/i18n.ts +106 -0
  60. package/src/index.ts +59 -0
  61. package/src/librarian.ts +331 -0
  62. package/src/maintenance-actions.ts +314 -0
  63. package/src/maintenance-inbox.ts +1132 -0
  64. package/src/maintenance-runner.ts +85 -0
  65. package/src/page-drift.ts +292 -0
  66. package/src/page-inbox.ts +254 -0
  67. package/src/report-export.ts +392 -0
  68. package/src/review-bridge.ts +1729 -0
  69. package/src/search-index.ts +266 -0
  70. package/src/store.ts +2171 -0
  71. package/src/telemetry-defaults.ts +50 -0
  72. package/src/telemetry-report.ts +365 -0
  73. package/src/telemetry.ts +757 -0
  74. package/src/wiki-synthesis.ts +1307 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Maintenance action executor — the verb side of the Review Board.
3
+ *
4
+ * Each finding in the maintenance inbox carries one or more *actions* the operator can
5
+ * execute against it: apply a memory→wiki promotion, archive a stale guidance file,
6
+ * snooze a page-drift finding, promote a recurring observation cluster to a draft memory,
7
+ * forget a contradicted memory, etc. This module dispatches those verbs against the
8
+ * underlying stores (memory-store, page-drift-snoozes, maintenance-inbox) and produces an
9
+ * `ExecutedMaintenanceAction` artifact that the review bridge surfaces in the Review
10
+ * Board's "Done" overlay.
11
+ *
12
+ * Every action that mutates files records an undoable artifact under `local-data/` so the
13
+ * operator can roll back. Apply-actions ask for confirmation through the Decision Modal
14
+ * before they run; this module trusts the upstream confirmation gate and just executes.
15
+ */
16
+ // Side-effect import: registers WikiCanonicalTarget on the brain DI surface.
17
+ // Any consumer that loads this module (server, CLI, tests, eval-mode subprocess
18
+ // invocations) auto-wires the wiki adapter for brain promotion.
19
+ import './canonical-target.js';
20
+ import { findMaintenanceInboxAction } from './maintenance-inbox.js';
21
+ import { applyProjectMemoryPromotion, draftProjectMemoryPromotion } from '@rarusoft/dendrite-memory';
22
+ import { forgetProjectMemory, promoteMemoryToSkill, rememberProjectMemory, reviewProjectMemories } from '@rarusoft/dendrite-memory';
23
+ import { detectRawObservationClusters, snoozePageDrift } from '@rarusoft/dendrite-memory';
24
+ import { applyWikiProposal, archiveGuidanceFile, editPageSummary, insertH1FromSlug, lintWikiPages, listWikiProposals, readWikiPage, writeWikiProposalPages } from './store.js';
25
+ export async function executeMaintenanceAction(actionId, options = {}) {
26
+ const [findings, proposals, memoryReview, observationClusters] = await Promise.all([
27
+ lintWikiPages(),
28
+ listWikiProposals(),
29
+ reviewProjectMemories(),
30
+ detectRawObservationClusters()
31
+ ]);
32
+ const resolved = await findMaintenanceInboxAction(actionId, findings, proposals, {
33
+ memoryFindings: memoryReview.findings,
34
+ observationClusters
35
+ });
36
+ if (!resolved) {
37
+ throw new Error(`Unknown maintenance action: ${actionId}`);
38
+ }
39
+ if (!resolved.action.available) {
40
+ throw new Error(resolved.action.reason ?? `Maintenance action is not currently available: ${actionId}`);
41
+ }
42
+ let result;
43
+ let resultKind;
44
+ let resultSummary;
45
+ switch (resolved.action.tool) {
46
+ case 'wiki_read': {
47
+ const text = await readWikiPage(readStringArgument(resolved.action, 'slug'));
48
+ resultKind = 'wiki-page-text';
49
+ resultSummary = `Read wiki page: ${readStringArgument(resolved.action, 'slug')}.`;
50
+ result = { text };
51
+ break;
52
+ }
53
+ case 'wiki_write_proposals': {
54
+ const pages = await writeWikiProposalPages();
55
+ resultKind = 'proposal-review-pages';
56
+ resultSummary =
57
+ pages.length === 0
58
+ ? 'No proposal review pages needed refresh.'
59
+ : `Wrote ${formatCount(pages.length, 'proposal review page')}.`;
60
+ result = { pages };
61
+ break;
62
+ }
63
+ case 'wiki_apply_proposal': {
64
+ const applyResult = await applyWikiProposal(readStringArgument(resolved.action, 'reviewSlug'));
65
+ resultKind = 'applied-proposal';
66
+ resultSummary = `Applied ${applyResult.proposalKind} proposal ${applyResult.reviewSlug} and updated ${formatCount(applyResult.updatedPaths.length, 'path')}.`;
67
+ result = applyResult;
68
+ break;
69
+ }
70
+ case 'wiki_proposals': {
71
+ const activeProposals = await listWikiProposals();
72
+ resultKind = 'proposal-list';
73
+ resultSummary = `Found ${formatCount(activeProposals.length, 'active proposal')}.`;
74
+ result = { proposals: activeProposals };
75
+ break;
76
+ }
77
+ case 'wiki_lint': {
78
+ const activeFindings = await lintWikiPages();
79
+ resultKind = 'lint-findings';
80
+ resultSummary = `Found ${formatCount(activeFindings.length, 'active lint finding')}.`;
81
+ result = { findings: activeFindings };
82
+ break;
83
+ }
84
+ case 'memory_forget': {
85
+ const mode = readForgetModeArgument(resolved.action, 'mode');
86
+ const forgetResult = await forgetProjectMemory(readStringArgument(resolved.action, 'id'), mode);
87
+ resultKind = 'forgotten-project-memory';
88
+ resultSummary = forgetResult.removed
89
+ ? `${mode === 'delete' ? 'Deleted' : 'Archived'} ${formatCount(1, 'project-local memory')}.`
90
+ : `Project-local memory ${forgetResult.id} was already absent.`;
91
+ result = forgetResult;
92
+ break;
93
+ }
94
+ case 'memory_promote': {
95
+ const memoryIds = readStringArrayArgument(resolved.action, 'memoryIds');
96
+ const options = {
97
+ targetPage: readOptionalStringArgument(resolved.action, 'targetPage'),
98
+ sectionHeading: readOptionalStringArgument(resolved.action, 'sectionHeading')
99
+ };
100
+ const mode = readStringArgument(resolved.action, 'mode');
101
+ if (mode === 'apply') {
102
+ const applyResult = await applyProjectMemoryPromotion(memoryIds, options);
103
+ resultKind = 'applied-memory-promotion';
104
+ resultSummary = applyResult.applied
105
+ ? `Applied a wiki promotion for ${formatCount(memoryIds.length, 'project-local memory')}.`
106
+ : `Skipped wiki promotion for ${formatCount(memoryIds.length, 'project-local memory')} because the target page was unchanged.`;
107
+ result = applyResult;
108
+ break;
109
+ }
110
+ const draft = await draftProjectMemoryPromotion(memoryIds, options);
111
+ resultKind = 'drafted-memory-promotion';
112
+ resultSummary = `Drafted a wiki promotion for ${formatCount(memoryIds.length, 'project-local memory')}.`;
113
+ result = draft;
114
+ break;
115
+ }
116
+ case 'memory_promote_skill': {
117
+ // The inbox surfaces this action with no explicit scope, so promoteMemoryToSkill
118
+ // re-runs inferSkillScopeFromMemory at apply time. The source memory is auto-
119
+ // superseded so the skill-promotion-ready finding stops re-flagging.
120
+ const memoryId = readStringArgument(resolved.action, 'memoryId');
121
+ const promotion = await promoteMemoryToSkill(memoryId);
122
+ resultKind = 'promoted-memory-to-skill';
123
+ resultSummary = promotion.inferredScope
124
+ ? `Promoted memory ${memoryId} into a skill using inferred scope; source memory marked superseded.`
125
+ : `Promoted memory ${memoryId} into a skill; source memory marked superseded.`;
126
+ result = promotion;
127
+ break;
128
+ }
129
+ case 'memory_remember': {
130
+ // Currently only used by the 'create-memory-from-cluster' action hint. The
131
+ // surfaced text is a TEMPLATE — operator is expected to immediately edit the
132
+ // memory body to capture the actual lesson. The template wording in the
133
+ // inbox makes that contract explicit.
134
+ const text = readStringArgument(resolved.action, 'text');
135
+ const tags = resolved.action.arguments.tags ? readStringArrayArgument(resolved.action, 'tags') : undefined;
136
+ const sources = resolved.action.arguments.sources ? readStringArrayArgument(resolved.action, 'sources') : undefined;
137
+ const relatedFiles = resolved.action.arguments.relatedFiles
138
+ ? readStringArrayArgument(resolved.action, 'relatedFiles')
139
+ : undefined;
140
+ const created = await rememberProjectMemory({
141
+ text,
142
+ kind: 'lesson',
143
+ tags,
144
+ sources,
145
+ relatedFiles
146
+ });
147
+ resultKind = 'remembered-from-cluster';
148
+ resultSummary = `Created draft memory ${created.id} from observation cluster — operator should now edit the text to capture the actual lesson.`;
149
+ result = { record: created };
150
+ break;
151
+ }
152
+ case 'wiki_snooze_page_drift': {
153
+ const slug = readStringArgument(resolved.action, 'slug');
154
+ const days = readOptionalNumberArgument(resolved.action, 'days') ?? 30;
155
+ const reason = readOptionalStringArgument(resolved.action, 'reason') ?? 'Operator acknowledged page-drift signal as noise';
156
+ const snooze = await snoozePageDrift(slug, { days, reason });
157
+ resultKind = 'snoozed-page-drift';
158
+ resultSummary = `Snoozed page-drift for ${slug} until ${snooze.snoozedUntil.slice(0, 10)} (${days} day${days === 1 ? '' : 's'}).`;
159
+ // Snooze is local-data only; the snoozes file changes but no canonical wiki content does.
160
+ result = { snooze, updatedPaths: ['local-data/page-drift-snoozes.json'] };
161
+ break;
162
+ }
163
+ case 'wiki_insert_h1': {
164
+ const slug = readStringArgument(resolved.action, 'slug');
165
+ const inserted = await insertH1FromSlug(slug);
166
+ resultKind = 'inserted-h1';
167
+ resultSummary = inserted
168
+ ? `Inserted H1 heading into ${slug}.md derived from the slug.`
169
+ : `Skipped H1 insertion for ${slug} because the page already has an H1.`;
170
+ result = { slug, inserted, updatedPaths: inserted ? [`docs/wiki/${slug}.md`] : [] };
171
+ break;
172
+ }
173
+ case 'wiki_archive_guidance': {
174
+ const targetPath = readStringArgument(resolved.action, 'path');
175
+ const moveResult = await archiveGuidanceFile(targetPath);
176
+ resultKind = 'archived-guidance-file';
177
+ resultSummary = moveResult.moved
178
+ ? `Archived ${moveResult.from} → ${moveResult.to}.`
179
+ : `${moveResult.from} is already under an archive directory; no move performed.`;
180
+ result = { ...moveResult, updatedPaths: moveResult.moved ? [moveResult.from, moveResult.to] : [] };
181
+ break;
182
+ }
183
+ case 'wiki_edit_summary': {
184
+ const slug = readStringArgument(resolved.action, 'slug');
185
+ // Prefer the operator's draft from the inline editor when supplied; fall back to the
186
+ // stored argument (which is empty when the action was surfaced from buildLintActions —
187
+ // the editor is expected to fill it before submission).
188
+ const storedNewFirstParagraph = typeof resolved.action.arguments.newFirstParagraph === 'string'
189
+ ? resolved.action.arguments.newFirstParagraph
190
+ : '';
191
+ const newFirstParagraph = (options.summaryDraft ?? '').trim() || storedNewFirstParagraph;
192
+ if (!newFirstParagraph.trim()) {
193
+ throw new Error('wiki_edit_summary requires a non-empty newFirstParagraph (supply via the inline editor or summaryDraft).');
194
+ }
195
+ const editResult = await editPageSummary(slug, newFirstParagraph);
196
+ resultKind = 'edited-page-summary';
197
+ resultSummary = editResult.changed
198
+ ? `Rewrote first paragraph of ${slug}.md (was ${editResult.previousSummary.length} chars, now ${editResult.newSummary.length} chars).`
199
+ : `${slug}.md first paragraph already matches the supplied text; no change written.`;
200
+ result = { ...editResult, updatedPaths: editResult.changed ? [`docs/wiki/${slug}.md`] : [] };
201
+ break;
202
+ }
203
+ }
204
+ return {
205
+ actionId,
206
+ action: resolved.action,
207
+ source: resolved.source,
208
+ resultKind,
209
+ resultSummary,
210
+ result
211
+ };
212
+ }
213
+ function formatCount(count, singular, plural = `${singular}s`) {
214
+ return `${count} ${count === 1 ? singular : plural}`;
215
+ }
216
+ function readStringArgument(action, key) {
217
+ const value = action.arguments[key];
218
+ if (typeof value !== 'string') {
219
+ throw new Error(`Maintenance action ${action.id} is missing string argument ${key}.`);
220
+ }
221
+ return value;
222
+ }
223
+ function readOptionalStringArgument(action, key) {
224
+ const value = action.arguments[key];
225
+ return typeof value === 'string' ? value : undefined;
226
+ }
227
+ function readOptionalNumberArgument(action, key) {
228
+ const value = action.arguments[key];
229
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
230
+ }
231
+ function readStringArrayArgument(action, key) {
232
+ const value = action.arguments[key];
233
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
234
+ throw new Error(`Maintenance action ${action.id} is missing string-array argument ${key}.`);
235
+ }
236
+ return value;
237
+ }
238
+ function readForgetModeArgument(action, key) {
239
+ const value = readStringArgument(action, key);
240
+ if (value !== 'archive' && value !== 'delete') {
241
+ throw new Error(`Maintenance action ${action.id} has unsupported forget mode ${value}.`);
242
+ }
243
+ return value;
244
+ }