dsh-tiddlywiki 0.27.2 → 0.28.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 (41) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3 -0
  3. package/cordis.patch.yml +13 -13
  4. package/docs/CHANGELOG.md +2 -1
  5. package/docs/code-review-2026-09-17.md +151 -0
  6. package/docs/features.md +5 -2
  7. package/docs/usage.md +34 -3
  8. package/docs/wiki-split.md +97 -0
  9. package/lib/client.js +54 -27
  10. package/lib/index.js +2803 -517
  11. package/lib/index.js.map +1 -1
  12. package/package.json +5 -4
  13. package/skills/wiki-split/SKILL.md +106 -0
  14. package/src/client/endpoints.ts +37 -0
  15. package/src/client/index.ts +14 -0
  16. package/src/client/knowledge-fab.ts +62 -3
  17. package/src/client/markdown-editor.ts +128 -128
  18. package/src/client/panel.ts +2 -1
  19. package/src/client/settings-page.ts +217 -3
  20. package/src/client/state.ts +39 -39
  21. package/src/client/status-cache.ts +17 -0
  22. package/src/client/styles.ts +27 -0
  23. package/src/client/toast.ts +22 -22
  24. package/src/client/tw-frame.ts +28 -4
  25. package/src/client/wiki-focus.ts +70 -0
  26. package/src/client/wiki-scope-dock.ts +116 -0
  27. package/src/host/admin.ts +175 -35
  28. package/src/host/git.ts +50 -5
  29. package/src/host/path-key.ts +35 -0
  30. package/src/host/prompt.ts +24 -0
  31. package/src/host/repo-committers.ts +149 -0
  32. package/src/host/routes.ts +254 -81
  33. package/src/host/seeds.ts +51 -10
  34. package/src/host/session-scope.ts +154 -0
  35. package/src/host/skill-install.ts +121 -0
  36. package/src/host/tools.ts +141 -49
  37. package/src/host/wiki-farm.ts +417 -0
  38. package/src/host/wiki-instance.ts +596 -0
  39. package/src/host/wiki-registry.ts +601 -0
  40. package/src/host/wiki.ts +19 -0
  41. package/src/index.ts +686 -459
@@ -0,0 +1,149 @@
1
+ /**
2
+ * ONE debounced auto-committer per REPOSITORY (v0.28.0).
3
+ *
4
+ * WHY NOT ONE PER WIKI
5
+ * --------------------
6
+ * Two knowledge bases may be subfolders of one repository (the "工作 + 个人 共用现有
7
+ * 仓库、按文件夹物理划分" topology). Git then has ONE index, and `git add -A` run
8
+ * from ANY subdirectory stages the WHOLE working tree — so two per-folder
9
+ * committers would each commit the other's changes, and one of them would
10
+ * forever report `nothing to commit`. The unit that can safely commit is the
11
+ * REPOSITORY, so that is the unit this class keys on.
12
+ *
13
+ * It also decides where the commit runs: at the REPOSITORY ROOT, never in a
14
+ * wiki subfolder (`commit(dir)` runs `git add -A` with `cwd = dir`).
15
+ *
16
+ * THE DIR → REPO LOOKUP IS ASYMMETRICALLY CACHED
17
+ * ----------------------------------------------
18
+ * A POSITIVE answer (this folder belongs to repo X) is cached forever. A
19
+ * NEGATIVE one is not cached at all: `git init` happens moments after the first
20
+ * touch in a fresh install, and a cached "not a repo" would silently disable
21
+ * auto-commit for the rest of the session — exactly the class of bug this
22
+ * repository has paid for more than once.
23
+ *
24
+ * @module dsh-tiddlywiki/host/repo-committers
25
+ */
26
+ import { AutoCommitter, type GitFace } from './git.ts'
27
+ import { pathComparisonKey } from './path-key.ts'
28
+
29
+ /** The effective auto-commit settings (re-read so a settings save applies live). */
30
+ export interface RepoCommitSettings {
31
+ autoCommit: boolean
32
+ debounceMs: number
33
+ }
34
+
35
+ export interface RepoCommittersOptions {
36
+ git: GitFace
37
+ /**
38
+ * The CURRENT effective settings FOR ONE REPOSITORY (v0.28.0).
39
+ *
40
+ * Per repository, not per wiki: several knowledge bases may share one
41
+ * repository, and their `git.*` would otherwise be read from whichever wiki
42
+ * happens to be the default — so the books repository could be auto-committed
43
+ * with the notes wiki's debounce (or not at all).
44
+ */
45
+ settings: (repoRoot: string) => RepoCommitSettings
46
+ /** Commit message factory. */
47
+ message: () => string
48
+ log?: (message: string) => void
49
+ }
50
+
51
+ interface RepoState {
52
+ repoRoot: string
53
+ committer: AutoCommitter
54
+ }
55
+
56
+ export class RepoCommitters {
57
+ /** Keyed by `pathComparisonKey(repoRoot)`. */
58
+ private readonly repos = new Map<string, RepoState>()
59
+ /** Positive `dir → repoRoot` cache only (see the module header). */
60
+ private readonly dirToRepo = new Map<string, string>()
61
+ private readonly log: (message: string) => void
62
+
63
+ constructor(private readonly options: RepoCommittersOptions) {
64
+ this.log = options.log ?? ((message) => { console.warn('[dsh-tiddlywiki]', message) })
65
+ }
66
+
67
+ /** Repositories with a live committer (diagnostics / tests). */
68
+ repoRoots(): string[] {
69
+ return [...this.repos.values()].map((state) => state.repoRoot)
70
+ }
71
+
72
+ /** The repository root owning `dir`, or undefined when it is not in one. */
73
+ async repoRootOf(dir: string): Promise<string | undefined> {
74
+ const key = pathComparisonKey(dir)
75
+ const cached = this.dirToRepo.get(key)
76
+ if (cached !== undefined) return cached
77
+ const root = await this.options.git.repoRoot(dir)
78
+ if (root !== undefined) this.dirToRepo.set(key, root)
79
+ return root
80
+ }
81
+
82
+ /**
83
+ * Debounced commit for the repository owning `dir`.
84
+ *
85
+ * Fire-and-forget friendly (the fs watcher calls it synchronously): the first
86
+ * call for a folder pays one `git rev-parse`, every later one is a Map hit.
87
+ */
88
+ async touch(dir: string): Promise<void> {
89
+ const state = await this.stateFor(dir)
90
+ state?.committer.touch()
91
+ }
92
+
93
+ /** Commit now. Omit `dir` to flush every repository (teardown / switch). */
94
+ async flush(dir?: string): Promise<void> {
95
+ if (dir === undefined) {
96
+ for (const state of this.repos.values()) {
97
+ try { await state.committer.flush() } catch { /* best-effort */ }
98
+ }
99
+ return
100
+ }
101
+ const root = await this.repoRootOf(dir)
102
+ if (root === undefined) return
103
+ try { await this.repos.get(pathComparisonKey(root))?.committer.flush() } catch { /* best-effort */ }
104
+ }
105
+
106
+ /**
107
+ * Rebuild every committer from the CURRENT settings (v0.25.0's "git.* takes
108
+ * effect on the running plugin"): `AutoCommitter` snapshots `enabled` and
109
+ * `debounceMs` at construction, so a settings-page save has to rebuild.
110
+ */
111
+ rebuild(): void {
112
+ for (const [key, state] of [...this.repos]) {
113
+ state.committer.dispose()
114
+ this.repos.set(key, { repoRoot: state.repoRoot, committer: this.create(state.repoRoot) })
115
+ }
116
+ }
117
+
118
+ /** Release every committer (plugin teardown). */
119
+ dispose(): void {
120
+ for (const state of this.repos.values()) state.committer.dispose()
121
+ this.repos.clear()
122
+ this.dirToRepo.clear()
123
+ }
124
+
125
+ private create(repoRoot: string): AutoCommitter {
126
+ const settings = this.options.settings(repoRoot)
127
+ return new AutoCommitter({
128
+ git: this.options.git,
129
+ // The COMMIT runs at the repository root, never in a wiki subfolder.
130
+ dir: repoRoot,
131
+ enabled: settings.autoCommit,
132
+ debounceMs: settings.debounceMs,
133
+ message: this.options.message,
134
+ onError: (err) => this.log(`autocommit(${repoRoot}): ${err instanceof Error ? err.message : String(err)}`),
135
+ })
136
+ }
137
+
138
+ private async stateFor(dir: string): Promise<RepoState | undefined> {
139
+ const root = await this.repoRootOf(dir)
140
+ if (root === undefined) return undefined
141
+ const key = pathComparisonKey(root)
142
+ const existing = this.repos.get(key)
143
+ if (existing !== undefined) return existing
144
+ const state: RepoState = { repoRoot: root, committer: this.create(root) }
145
+ this.repos.set(key, state)
146
+ this.log(`自动提交按仓库分组:${root}`)
147
+ return state
148
+ }
149
+ }