total-recall-brain 3.46.2 → 3.47.1

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 (89) hide show
  1. package/bin/total-recall.mjs +5 -0
  2. package/frontend/dist/assets/index-CpsWeV4X.css +1 -0
  3. package/frontend/dist/assets/{index-D1goIPjC.js → index-poecbHSl.js} +100 -100
  4. package/frontend/dist/brand/total-recall-lockup-transparent.png +0 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/dist/sw.js +1 -1
  7. package/frontend/src/App.tsx +15 -81
  8. package/frontend/src/components/DashboardOverview.tsx +164 -0
  9. package/frontend/src/components/brand/BrandMark.tsx +8 -6
  10. package/frontend/src/components/plugins/PluginWorkspace.tsx +112 -57
  11. package/frontend/src/components/workspace/PluginHome.tsx +3 -3
  12. package/frontend/src/index.css +43 -11
  13. package/package.json +1 -1
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +4 -4
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -2
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +30 -5
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/skills/auth/SKILL.md +35 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +494 -79
  19. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +4 -4
  20. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +3 -2
  21. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +33 -8
  22. package/scaffold/.agent/skills/total-recall/plugins/automations/skills/automations/SKILL.md +29 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/dream-consolidation.md +17 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  25. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -2
  26. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +31 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/code-quality/skills/code-quality/SKILL.md +27 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +17 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  32. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -2
  33. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +24 -4
  34. package/scaffold/.agent/skills/total-recall/plugins/embeddings/skills/embeddings/SKILL.md +25 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
  36. package/scaffold/.agent/skills/total-recall/plugins/git/package.json +3 -2
  37. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +300 -58
  38. package/scaffold/.agent/skills/total-recall/plugins/git/skills/git/SKILL.md +36 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +5 -33
  40. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-fleet-ops.md +16 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-publish.md +16 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
  43. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -2
  44. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +47 -14
  45. package/scaffold/.agent/skills/total-recall/plugins/mesh/skills/mesh/SKILL.md +36 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +6 -3
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +1 -1
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +5 -2
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +4 -2
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +28 -9
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +7 -1
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-selection.mjs +59 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +3 -3
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +9 -3
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +50 -6
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/session-controls.md +16 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +175 -17
  58. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
  59. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -2
  60. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +242 -31
  61. package/scaffold/.agent/skills/total-recall/plugins/openwiki/skills/openwiki/SKILL.md +32 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +41 -15
  63. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +29 -22
  64. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +20 -33
  65. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +42 -49
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/markdown-links.mjs +112 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +3 -2
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +219 -65
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +2 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +21 -3
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +51 -2
  72. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +4 -4
  73. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  74. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +22 -5
  75. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +3 -2
  76. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +169 -37
  77. package/scaffold/.agent/skills/total-recall/plugins/ssss/skills/ssss/SKILL.md +30 -0
  78. package/src/cli/context.mjs +6 -2
  79. package/src/cli/plugin/create.mjs +2 -1
  80. package/src/cli/plugin/monetization.mjs +66 -32
  81. package/src/cli/plugin/search.mjs +25 -3
  82. package/src/cli/preflight.mjs +40 -6
  83. package/src/core/context-compactor.mjs +2 -0
  84. package/src/core/gate-mode.mjs +13 -24
  85. package/src/core/plugin-distribution.mjs +35 -1
  86. package/src/core/preflight-gate.mjs +28 -8
  87. package/src/core/preflight-receipt.mjs +11 -0
  88. package/src/server/routes/vault.mjs +1 -0
  89. package/frontend/dist/assets/index-BRYE1jmv.css +0 -1
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
3
  "id": "openwiki",
4
4
  "name": "OpenWiki",
5
- "version": "0.2.0",
5
+ "version": "0.4.1",
6
6
  "description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall.",
7
7
  "license": "MIT",
8
8
  "runtime": {
@@ -17,10 +17,11 @@
17
17
  "command": "watch"
18
18
  },
19
19
  {
20
- "intent": "Auto-refresh OpenWiki documentation for repositories with new commits",
21
- "schedule": "*/30 * * * *",
20
+ "intent": "Refresh OpenWiki documentation nightly for repositories with real code changes, on the selected node",
21
+ "schedule": "20 1-5 * * *",
22
22
  "command": "auto-refresh",
23
- "timeout_ms": 600000
23
+ "timeout_ms": 600000,
24
+ "placement": "selected-node"
24
25
  }
25
26
  ],
26
27
  "branding": {
@@ -71,7 +72,11 @@
71
72
  ]
72
73
  },
73
74
  "gate": {
74
- "tasks": ["Search or read a repository's wiki", "Generate or refresh wiki pages", "Ingest documentation into memory"],
75
+ "tasks": [
76
+ "Search or read a repository's wiki",
77
+ "Generate or refresh wiki pages",
78
+ "Ingest documentation into memory"
79
+ ],
75
80
  "owned_commands": []
76
81
  },
77
82
  "cli": {
@@ -84,8 +89,19 @@
84
89
  "effect": "read",
85
90
  "output": "json",
86
91
  "args": [
87
- { "name": "task", "label": "Task description", "type": "string", "positional": true, "required": true },
88
- { "name": "json", "label": "JSON output", "type": "boolean", "flag": "--json" }
92
+ {
93
+ "name": "task",
94
+ "label": "Task description",
95
+ "type": "string",
96
+ "positional": true,
97
+ "required": true
98
+ },
99
+ {
100
+ "name": "json",
101
+ "label": "JSON output",
102
+ "type": "boolean",
103
+ "flag": "--json"
104
+ }
89
105
  ]
90
106
  },
91
107
  {
@@ -94,11 +110,37 @@
94
110
  "effect": "read",
95
111
  "output": "json",
96
112
  "args": [
97
- { "name": "query", "label": "Search terms", "type": "string", "positional": true, "required": true },
98
- { "name": "allVaults", "label": "Search all registered vaults", "type": "boolean", "flag": "--all-vaults" },
99
- { "name": "node", "label": "Mesh node", "type": "string", "flag": "--node" },
100
- { "name": "limit", "label": "Maximum results", "type": "number", "flag": "--limit" },
101
- { "name": "json", "label": "JSON output", "type": "boolean", "flag": "--json" }
113
+ {
114
+ "name": "query",
115
+ "label": "Search terms",
116
+ "type": "string",
117
+ "positional": true,
118
+ "required": true
119
+ },
120
+ {
121
+ "name": "allVaults",
122
+ "label": "Search all registered vaults",
123
+ "type": "boolean",
124
+ "flag": "--all-vaults"
125
+ },
126
+ {
127
+ "name": "node",
128
+ "label": "Mesh node",
129
+ "type": "string",
130
+ "flag": "--node"
131
+ },
132
+ {
133
+ "name": "limit",
134
+ "label": "Maximum results",
135
+ "type": "number",
136
+ "flag": "--limit"
137
+ },
138
+ {
139
+ "name": "json",
140
+ "label": "JSON output",
141
+ "type": "boolean",
142
+ "flag": "--json"
143
+ }
102
144
  ]
103
145
  },
104
146
  {
@@ -107,10 +149,35 @@
107
149
  "effect": "read",
108
150
  "output": "json",
109
151
  "args": [
110
- { "name": "allVaults", "label": "Inspect all registered vaults", "type": "boolean", "flag": "--all-vaults" },
111
- { "name": "node", "label": "Mesh node", "type": "string", "flag": "--node" },
112
- { "name": "format", "label": "Graph format", "type": "choice", "options": ["json", "mermaid", "dot"], "flag": "--format" },
113
- { "name": "json", "label": "JSON output", "type": "boolean", "flag": "--json" }
152
+ {
153
+ "name": "allVaults",
154
+ "label": "Inspect all registered vaults",
155
+ "type": "boolean",
156
+ "flag": "--all-vaults"
157
+ },
158
+ {
159
+ "name": "node",
160
+ "label": "Mesh node",
161
+ "type": "string",
162
+ "flag": "--node"
163
+ },
164
+ {
165
+ "name": "format",
166
+ "label": "Graph format",
167
+ "type": "choice",
168
+ "options": [
169
+ "json",
170
+ "mermaid",
171
+ "dot"
172
+ ],
173
+ "flag": "--format"
174
+ },
175
+ {
176
+ "name": "json",
177
+ "label": "JSON output",
178
+ "type": "boolean",
179
+ "flag": "--json"
180
+ }
114
181
  ]
115
182
  },
116
183
  {
@@ -120,11 +187,36 @@
120
187
  "output": "json",
121
188
  "timeout_ms": 600000,
122
189
  "args": [
123
- { "name": "repo", "label": "Registered repository", "type": "string", "flag": "--repo" },
124
- { "name": "node", "label": "Mesh node", "type": "string", "flag": "--node" },
125
- { "name": "force", "label": "Regenerate existing pages", "type": "boolean", "flag": "--force" },
126
- { "name": "dryRun", "label": "Describe generation without writing", "type": "boolean", "flag": "--dry-run" },
127
- { "name": "json", "label": "JSON output", "type": "boolean", "flag": "--json" }
190
+ {
191
+ "name": "repo",
192
+ "label": "Registered repository",
193
+ "type": "string",
194
+ "flag": "--repo"
195
+ },
196
+ {
197
+ "name": "node",
198
+ "label": "Mesh node",
199
+ "type": "string",
200
+ "flag": "--node"
201
+ },
202
+ {
203
+ "name": "force",
204
+ "label": "Regenerate existing pages",
205
+ "type": "boolean",
206
+ "flag": "--force"
207
+ },
208
+ {
209
+ "name": "dryRun",
210
+ "label": "Describe generation without writing",
211
+ "type": "boolean",
212
+ "flag": "--dry-run"
213
+ },
214
+ {
215
+ "name": "json",
216
+ "label": "JSON output",
217
+ "type": "boolean",
218
+ "flag": "--json"
219
+ }
128
220
  ]
129
221
  },
130
222
  {
@@ -133,11 +225,44 @@
133
225
  "effect": "write",
134
226
  "output": "json",
135
227
  "args": [
136
- { "name": "action", "label": "Credential action", "type": "choice", "options": ["status", "set"], "positional": true },
137
- { "name": "provider", "label": "Credential provider", "type": "choice", "options": ["openrouter", "google"], "positional": true },
138
- { "name": "value", "label": "Secret value through stdin", "type": "string", "secret": true },
139
- { "name": "stdin", "label": "Read secret from stdin", "type": "boolean", "flag": "--stdin" },
140
- { "name": "json", "label": "JSON output", "type": "boolean", "flag": "--json" }
228
+ {
229
+ "name": "action",
230
+ "label": "Credential action",
231
+ "type": "choice",
232
+ "options": [
233
+ "status",
234
+ "set"
235
+ ],
236
+ "positional": true
237
+ },
238
+ {
239
+ "name": "provider",
240
+ "label": "Credential provider",
241
+ "type": "choice",
242
+ "options": [
243
+ "openrouter",
244
+ "google"
245
+ ],
246
+ "positional": true
247
+ },
248
+ {
249
+ "name": "value",
250
+ "label": "Secret value through stdin",
251
+ "type": "string",
252
+ "secret": true
253
+ },
254
+ {
255
+ "name": "stdin",
256
+ "label": "Read secret from stdin",
257
+ "type": "boolean",
258
+ "flag": "--stdin"
259
+ },
260
+ {
261
+ "name": "json",
262
+ "label": "JSON output",
263
+ "type": "boolean",
264
+ "flag": "--json"
265
+ }
141
266
  ]
142
267
  },
143
268
  {
@@ -146,9 +271,24 @@
146
271
  "effect": "read",
147
272
  "output": "json",
148
273
  "args": [
149
- { "name": "allVaults", "label": "Inspect all registered vaults", "type": "boolean", "flag": "--all-vaults" },
150
- { "name": "node", "label": "Mesh node", "type": "string", "flag": "--node" },
151
- { "name": "json", "label": "JSON output", "type": "boolean", "flag": "--json" }
274
+ {
275
+ "name": "allVaults",
276
+ "label": "Inspect all registered vaults",
277
+ "type": "boolean",
278
+ "flag": "--all-vaults"
279
+ },
280
+ {
281
+ "name": "node",
282
+ "label": "Mesh node",
283
+ "type": "string",
284
+ "flag": "--node"
285
+ },
286
+ {
287
+ "name": "json",
288
+ "label": "JSON output",
289
+ "type": "boolean",
290
+ "flag": "--json"
291
+ }
152
292
  ]
153
293
  },
154
294
  {
@@ -268,7 +408,7 @@
268
408
  },
269
409
  {
270
410
  "name": "refresh",
271
- "description": "Report which registered projects' generated OpenWiki pages are stale; --apply regenerates them (openwiki/ only)",
411
+ "description": "Report which registered projects' generated OpenWiki pages are stale; --apply updates existing wikis (openwiki/ only), never creates one, and skips excluded repositories",
272
412
  "timeout_ms": 600000,
273
413
  "effect": "write",
274
414
  "output": "json",
@@ -291,6 +431,12 @@
291
431
  "label": "Force regeneration",
292
432
  "type": "boolean"
293
433
  },
434
+ {
435
+ "name": "budgetMs",
436
+ "flag": "--budget-ms",
437
+ "label": "Start no generation that cannot finish within this many milliseconds",
438
+ "type": "number"
439
+ },
294
440
  {
295
441
  "name": "json",
296
442
  "flag": "--json",
@@ -422,6 +568,57 @@
422
568
  "type": "boolean"
423
569
  }
424
570
  ]
571
+ },
572
+ {
573
+ "name": "schedule",
574
+ "description": "Show or choose the node that runs the nightly wiki refresh (task auto-refresh)",
575
+ "effect": "write",
576
+ "output": "json",
577
+ "args": [
578
+ {
579
+ "name": "node",
580
+ "flag": "--node",
581
+ "label": "Mesh hostname, or self for this machine",
582
+ "type": "string"
583
+ },
584
+ {
585
+ "name": "off",
586
+ "flag": "--off",
587
+ "label": "Stop the nightly refresh on every node",
588
+ "type": "boolean"
589
+ },
590
+ {
591
+ "name": "json",
592
+ "flag": "--json",
593
+ "label": "JSON output",
594
+ "type": "boolean"
595
+ }
596
+ ]
597
+ },
598
+ {
599
+ "name": "exclude",
600
+ "description": "List, add or remove repositories the wiki refresh and generate never touch (clones, forks, backups)",
601
+ "effect": "write",
602
+ "args": [
603
+ {
604
+ "name": "targets",
605
+ "type": "string",
606
+ "label": "Registered project name or absolute path",
607
+ "positional": true
608
+ },
609
+ {
610
+ "name": "remove",
611
+ "flag": "--remove",
612
+ "label": "Take the repositories off the list",
613
+ "type": "boolean"
614
+ },
615
+ {
616
+ "name": "json",
617
+ "flag": "--json",
618
+ "label": "JSON output",
619
+ "type": "boolean"
620
+ }
621
+ ]
425
622
  }
426
623
  ]
427
624
  },
@@ -463,5 +660,19 @@
463
660
  "status": {
464
661
  "command": "status",
465
662
  "refresh_seconds": 300
466
- }
663
+ },
664
+ "reuse": {
665
+ "commands": [
666
+ "total-recall auth secret",
667
+ "total-recall ssss status"
668
+ ],
669
+ "owned": "Documentation hub discovery, wiki page auditing, staleness tracking, and OpenWiki knowledge ingestion."
670
+ },
671
+ "skills": [
672
+ {
673
+ "id": "openwiki",
674
+ "path": "./skills/openwiki/SKILL.md",
675
+ "description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall."
676
+ }
677
+ ]
467
678
  }
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: openwiki
3
+ description: "Multi-repo knowledge graph and documentation wiki engine for Total Recall."
4
+ version: 0.4.0
5
+ repo_scoped: false
6
+ ---
7
+
8
+ # OpenWiki
9
+
10
+ Multi-repo knowledge graph and documentation wiki engine for Total Recall.
11
+
12
+ ## When to Use
13
+
14
+ Use the `openwiki` Total Recall plugin (`total-recall openwiki`) instead of ad-hoc scripts or raw commands whenever working on openwiki tasks.
15
+
16
+ ## Commands
17
+
18
+ ```bash
19
+ total-recall openwiki --help
20
+ total-recall openwiki context # Retrieve task-relevant wiki and explicitly linked source evidence with provenance and freshness
21
+ total-recall openwiki search # Search evidence passages in workspace, registered-vault or mesh documentation hubs
22
+ total-recall openwiki graph # Extract documentation link topology as JSON, Mermaid or DOT
23
+ total-recall openwiki generate # Generate documentation for the selected registered repository or current workspace
24
+ total-recall openwiki token # Inspect provider credential status or save a provider key through the shared auth owner
25
+ total-recall openwiki lint # Inspect documentation links and orphaned hubs without rewriting files
26
+ total-recall openwiki status # Show which OpenWiki subcommands are implemented
27
+ total-recall openwiki hubs # List indexed OpenWiki hubs and page counts
28
+ total-recall openwiki ingest # Ingest markdown documentation into the semantic knowledge graph
29
+ total-recall openwiki bootstrap # Create missing OpenWiki documentation pages in explicit brain and project directories
30
+ total-recall openwiki probe # Run HTTP 200 health verification probe
31
+ total-recall openwiki refresh # Report which registered projects' generated OpenWiki pages are stale; --apply updates existing wikis (openwiki/ only), never creates one, and skips excluded repositories
32
+ ```
@@ -6,14 +6,31 @@ import fs from 'node:fs';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
8
 
9
+ // No model default: models and prices change too often to fix one in code, and a stale
10
+ // default (deepseek/deepseek-chat) cost about 5x a current model of the same family.
11
+ // A refresh with no configured model stops before it calls a provider.
12
+ //
13
+ // exclude lists repository roots the refresh never touches: upstream clones,
14
+ // forks, backups and test probes. Registering a repository with Total Recall
15
+ // gives it a brain; it does not make its code something to document.
9
16
  export const DEFAULT_OPENWIKI_CONFIG = Object.freeze({
10
17
  provider: 'openrouter',
11
- model: 'deepseek/deepseek-chat',
12
- fallback_model: 'deepseek/deepseek-chat',
18
+ model: null,
19
+ fallback_model: null,
13
20
  max_tokens: 4096,
14
- timeout_ms: 300000
21
+ timeout_ms: 300000,
22
+ exclude: Object.freeze([])
15
23
  });
16
24
 
25
+ /** Absolute, de-duplicated, sorted repository roots. */
26
+ export function normalizeExclude(list) {
27
+ if (!Array.isArray(list)) return [];
28
+ const roots = list
29
+ .filter((entry) => typeof entry === 'string' && entry.trim() && path.isAbsolute(entry.trim()))
30
+ .map((entry) => path.resolve(entry.trim()));
31
+ return [...new Set(roots)].sort();
32
+ }
33
+
17
34
  export const ALLOWED_CONFIG_KEYS = Object.freeze([
18
35
  'provider',
19
36
  'model',
@@ -41,12 +58,12 @@ export function loadOpenWikiConfig(options = {}) {
41
58
  const filePath = resolveConfigPath(options);
42
59
  try {
43
60
  if (!fs.existsSync(filePath)) {
44
- return { ...DEFAULT_OPENWIKI_CONFIG };
61
+ return { ...DEFAULT_OPENWIKI_CONFIG, exclude: [] };
45
62
  }
46
63
  const raw = fs.readFileSync(filePath, 'utf8');
47
64
  const parsed = JSON.parse(raw);
48
65
  if (!parsed || typeof parsed !== 'object') {
49
- return { ...DEFAULT_OPENWIKI_CONFIG };
66
+ return { ...DEFAULT_OPENWIKI_CONFIG, exclude: [] };
50
67
  }
51
68
  return {
52
69
  provider: typeof parsed.provider === 'string' && parsed.provider ? parsed.provider : DEFAULT_OPENWIKI_CONFIG.provider,
@@ -57,10 +74,11 @@ export function loadOpenWikiConfig(options = {}) {
57
74
  : DEFAULT_OPENWIKI_CONFIG.max_tokens,
58
75
  timeout_ms: Number.isInteger(parsed.timeout_ms) && parsed.timeout_ms >= 30000 && parsed.timeout_ms <= 1800000
59
76
  ? parsed.timeout_ms
60
- : DEFAULT_OPENWIKI_CONFIG.timeout_ms
77
+ : DEFAULT_OPENWIKI_CONFIG.timeout_ms,
78
+ exclude: normalizeExclude(parsed.exclude)
61
79
  };
62
80
  } catch {
63
- return { ...DEFAULT_OPENWIKI_CONFIG };
81
+ return { ...DEFAULT_OPENWIKI_CONFIG, exclude: [] };
64
82
  }
65
83
  }
66
84
 
@@ -69,18 +87,16 @@ export function saveOpenWikiConfig(config, options = {}) {
69
87
  const filePath = resolveConfigPath(options);
70
88
  const validated = {
71
89
  provider: String(config.provider || DEFAULT_OPENWIKI_CONFIG.provider).toLowerCase(),
72
- model: String(config.model || DEFAULT_OPENWIKI_CONFIG.model).trim(),
73
- fallback_model: String(config.fallback_model || DEFAULT_OPENWIKI_CONFIG.fallback_model).trim(),
90
+ model: config.model ? String(config.model).trim() || null : null,
91
+ fallback_model: config.fallback_model ? String(config.fallback_model).trim() || null : null,
74
92
  max_tokens: Number.isInteger(Number(config.max_tokens)) ? Math.max(512, Math.min(131072, Number(config.max_tokens))) : DEFAULT_OPENWIKI_CONFIG.max_tokens,
75
- timeout_ms: Number.isInteger(Number(config.timeout_ms)) ? Math.max(30000, Math.min(1800000, Number(config.timeout_ms))) : DEFAULT_OPENWIKI_CONFIG.timeout_ms
93
+ timeout_ms: Number.isInteger(Number(config.timeout_ms)) ? Math.max(30000, Math.min(1800000, Number(config.timeout_ms))) : DEFAULT_OPENWIKI_CONFIG.timeout_ms,
94
+ exclude: normalizeExclude(config.exclude)
76
95
  };
77
96
 
78
97
  if (!ALLOWED_PROVIDERS.includes(validated.provider)) {
79
98
  throw new Error(`Invalid provider: ${validated.provider}. Allowed: ${ALLOWED_PROVIDERS.join(', ')}`);
80
99
  }
81
- if (!validated.model) {
82
- throw new Error('Model identifier cannot be empty');
83
- }
84
100
 
85
101
  const dir = path.dirname(filePath);
86
102
  if (!fs.existsSync(dir)) {
@@ -123,7 +139,17 @@ export function setOpenWikiConfigValue(key, value, options = {}) {
123
139
  return saveOpenWikiConfig(current, options);
124
140
  }
125
141
 
126
- /** Reset configuration to defaults. */
142
+ /**
143
+ * Reset the generator settings to defaults. The exclude list is kept: a reset
144
+ * must not put clones and backups back into a paid refresh.
145
+ */
127
146
  export function resetOpenWikiConfig(options = {}) {
128
- return saveOpenWikiConfig(DEFAULT_OPENWIKI_CONFIG, options);
147
+ const { exclude } = loadOpenWikiConfig(options);
148
+ return saveOpenWikiConfig({ ...DEFAULT_OPENWIKI_CONFIG, exclude }, options);
149
+ }
150
+
151
+ /** Replace the list of repository roots the refresh never touches. */
152
+ export function setExcludedRoots(roots, options = {}) {
153
+ const current = loadOpenWikiConfig(options);
154
+ return saveOpenWikiConfig({ ...current, exclude: roots }, options);
129
155
  }
@@ -8,7 +8,7 @@ import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { spawn } from 'node:child_process';
10
10
  import { loadOpenWikiConfig } from './config.mjs';
11
- import { readProjects, resolveGenerator, resolveKey } from './refresh.mjs';
11
+ import { buildGeneratorInvocation, ensureDefaultIgnores, readProjects, redact, resolveGenerator, resolveKey } from './refresh.mjs';
12
12
  import { execOnMeshNode, getMeshNodes } from './mesh-vaults.mjs';
13
13
 
14
14
  export async function generateWiki({
@@ -70,6 +70,12 @@ export async function generateWiki({
70
70
  throw new Error(`Target path does not exist: ${resolvedTarget}`);
71
71
  }
72
72
 
73
+ // An excluded repository (an upstream clone, fork, backup or probe) is never
74
+ // documented, even from inside its own checkout.
75
+ if ((loadOpenWikiConfig().exclude || []).includes(path.resolve(resolvedTarget))) {
76
+ throw new Error(`${repoName} is excluded from OpenWiki. To document it, first run: total-recall openwiki exclude --remove ${repoName}`);
77
+ }
78
+
73
79
  const outPath = outputDir ? path.resolve(resolvedTarget, outputDir) : path.join(resolvedTarget, 'openwiki');
74
80
 
75
81
  if (dryRun) {
@@ -92,31 +98,30 @@ export async function generateWiki({
92
98
  }
93
99
 
94
100
  const gen = resolveGenerator();
95
- const generatorArgs = [...gen.args];
96
-
97
- // Pass --init or update mode
98
- const isInit = !fs.existsSync(outPath) || force;
99
- if (isInit) {
100
- generatorArgs.push('--init');
101
+ if (!gen.command) {
102
+ throw new Error('No openwiki generator available (missing bundled generator, local .bin, PATH entry, or npx)');
101
103
  }
102
104
 
103
- const env = {
104
- ...process.env,
105
- OPENROUTER_API_KEY: config.provider === 'openrouter' ? key : process.env.OPENROUTER_API_KEY,
106
- GOOGLE_API_KEY: config.provider === 'google' ? key : process.env.GOOGLE_API_KEY,
107
- OPENWIKI_MODEL: config.model,
108
- OPENWIKI_TIMEOUT: String(config.timeout_ms || 300000),
109
- };
105
+ const mode = (!fs.existsSync(outPath) || force) ? 'init' : 'update';
106
+ const timeoutMs = config.timeout_ms || 300000;
107
+ const { cmdArgs, childEnv } = buildGeneratorInvocation({
108
+ baseArgs: gen.args,
109
+ mode,
110
+ config,
111
+ key,
112
+ });
113
+ ensureDefaultIgnores(resolvedTarget);
110
114
 
111
115
  const startedAt = Date.now();
112
116
  if (typeof onProgress === 'function') {
113
- onProgress(`Starting generator for ${repoName} (${gen.via})...`);
117
+ onProgress(`Starting generator for ${repoName} (${gen.via}, ${mode})...`);
114
118
  }
115
119
 
116
120
  return new Promise((resolve, reject) => {
117
- const child = spawn(gen.command, generatorArgs, {
121
+ const child = spawn(gen.command, cmdArgs, {
118
122
  cwd: resolvedTarget,
119
- env,
123
+ env: childEnv,
124
+ timeout: timeoutMs,
120
125
  stdio: ['ignore', 'pipe', 'pipe'],
121
126
  });
122
127
 
@@ -127,7 +132,7 @@ export async function generateWiki({
127
132
  const str = chunk.toString();
128
133
  stdout += str;
129
134
  if (typeof onProgress === 'function') {
130
- onProgress(str.trim());
135
+ onProgress(redact(str.trim(), key));
131
136
  }
132
137
  });
133
138
 
@@ -135,7 +140,7 @@ export async function generateWiki({
135
140
  stderr += chunk.toString();
136
141
  });
137
142
 
138
- child.on('close', (code) => {
143
+ child.on('close', (code, signal) => {
139
144
  const durationMs = Date.now() - startedAt;
140
145
  if (code === 0) {
141
146
  resolve({
@@ -144,17 +149,19 @@ export async function generateWiki({
144
149
  targetPath: resolvedTarget,
145
150
  outputDir: outPath,
146
151
  generatorVia: gen.via,
152
+ mode,
147
153
  durationMs,
148
154
  credential: from,
149
- stdout: stdout.trim(),
155
+ stdout: redact(stdout.trim(), key),
150
156
  });
151
157
  } else {
152
- reject(new Error(`Generator exited with code ${code}: ${stderr || stdout || 'Unknown error'}`));
158
+ const detail = redact(stderr || stdout || (signal ? `terminated by ${signal}` : 'Unknown error'), key);
159
+ reject(new Error(`Generator exited with code ${code ?? signal ?? 1}: ${detail}`));
153
160
  }
154
161
  });
155
162
 
156
163
  child.on('error', (err) => {
157
- reject(new Error(`Failed to spawn generator: ${err.message}`));
164
+ reject(new Error(`Failed to spawn generator: ${redact(err.message, key)}`));
158
165
  });
159
166
  });
160
167
  }