@ctxr/skill-llm-wiki 1.0.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 (75) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/LICENSE +21 -0
  3. package/README.md +484 -0
  4. package/SKILL.md +252 -0
  5. package/guide/basics/concepts.md +74 -0
  6. package/guide/basics/index.md +45 -0
  7. package/guide/basics/schema.md +140 -0
  8. package/guide/cli.md +256 -0
  9. package/guide/correctness/index.md +45 -0
  10. package/guide/correctness/invariants.md +89 -0
  11. package/guide/correctness/safety.md +96 -0
  12. package/guide/history/diff.md +110 -0
  13. package/guide/history/hidden-git.md +130 -0
  14. package/guide/history/index.md +52 -0
  15. package/guide/history/remote-sync.md +113 -0
  16. package/guide/index.md +134 -0
  17. package/guide/isolation/coexistence.md +134 -0
  18. package/guide/isolation/index.md +44 -0
  19. package/guide/isolation/scale.md +251 -0
  20. package/guide/layout/in-place-mode.md +97 -0
  21. package/guide/layout/index.md +53 -0
  22. package/guide/layout/layout-contract.md +131 -0
  23. package/guide/layout/layout-modes.md +115 -0
  24. package/guide/operations/index.md +76 -0
  25. package/guide/operations/ingest/build.md +75 -0
  26. package/guide/operations/ingest/extend.md +61 -0
  27. package/guide/operations/ingest/index.md +54 -0
  28. package/guide/operations/ingest/join.md +65 -0
  29. package/guide/operations/maintain/fix.md +66 -0
  30. package/guide/operations/maintain/index.md +47 -0
  31. package/guide/operations/maintain/rebuild.md +86 -0
  32. package/guide/operations/validate.md +48 -0
  33. package/guide/substrate/index.md +47 -0
  34. package/guide/substrate/operators.md +96 -0
  35. package/guide/substrate/tiered-ai.md +363 -0
  36. package/guide/ux/index.md +44 -0
  37. package/guide/ux/preflight.md +150 -0
  38. package/guide/ux/user-intent.md +135 -0
  39. package/package.json +55 -0
  40. package/scripts/cli.mjs +893 -0
  41. package/scripts/commands/remote.mjs +93 -0
  42. package/scripts/commands/review.mjs +253 -0
  43. package/scripts/commands/sync.mjs +84 -0
  44. package/scripts/lib/chunk.mjs +421 -0
  45. package/scripts/lib/cluster-detect.mjs +516 -0
  46. package/scripts/lib/decision-log.mjs +343 -0
  47. package/scripts/lib/draft.mjs +158 -0
  48. package/scripts/lib/embeddings.mjs +366 -0
  49. package/scripts/lib/frontmatter.mjs +497 -0
  50. package/scripts/lib/git-commands.mjs +155 -0
  51. package/scripts/lib/git.mjs +486 -0
  52. package/scripts/lib/gitignore.mjs +62 -0
  53. package/scripts/lib/history.mjs +331 -0
  54. package/scripts/lib/indices.mjs +510 -0
  55. package/scripts/lib/ingest.mjs +258 -0
  56. package/scripts/lib/intent.mjs +713 -0
  57. package/scripts/lib/interactive.mjs +99 -0
  58. package/scripts/lib/migrate.mjs +126 -0
  59. package/scripts/lib/nest-applier.mjs +260 -0
  60. package/scripts/lib/operators.mjs +1365 -0
  61. package/scripts/lib/orchestrator.mjs +718 -0
  62. package/scripts/lib/paths.mjs +197 -0
  63. package/scripts/lib/preflight.mjs +213 -0
  64. package/scripts/lib/provenance.mjs +672 -0
  65. package/scripts/lib/quality-metric.mjs +269 -0
  66. package/scripts/lib/query-fixture.mjs +71 -0
  67. package/scripts/lib/rollback.mjs +95 -0
  68. package/scripts/lib/shape-check.mjs +172 -0
  69. package/scripts/lib/similarity-cache.mjs +126 -0
  70. package/scripts/lib/similarity.mjs +230 -0
  71. package/scripts/lib/snapshot.mjs +54 -0
  72. package/scripts/lib/source-frontmatter.mjs +85 -0
  73. package/scripts/lib/tier2-protocol.mjs +470 -0
  74. package/scripts/lib/tiered.mjs +453 -0
  75. package/scripts/lib/validate.mjs +362 -0
@@ -0,0 +1,251 @@
1
+ ---
2
+ id: scale
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: chunked iteration, bounded memory, context-window hygiene, and how the skill handles multi-megabyte corpora
6
+ parents:
7
+ - index.md
8
+ covers:
9
+ - "iterEntries yields one entry at a time with a lazy loadBody() thunk"
10
+ - "frontmatter reads are bounded (max 256 KB per entry) via a streaming fs reader"
11
+ - operator-convergence reads frontmatter only — no body touches memory during detection
12
+ - "streaming consumer pattern: load → process → releaseBody → next — keeps peak at 1"
13
+ - listChildren was rewired to use readFrontmatterStreaming so rebuildAllIndices scales
14
+ - "per-phase commits plus diff --op <id> let you inspect large operations without loading the whole tree"
15
+ - the wiki-runner sub-agent auto-compacts its own context between phases; phase commits are the durable checkpoint
16
+ - "Tier 2 fan-out to per-decision sub-agents keeps the wiki-runner's own window lean"
17
+ tags:
18
+ - scale
19
+ - performance
20
+ activation:
21
+ keyword_matches:
22
+ - large
23
+ - big
24
+ - megabytes
25
+ - thousands
26
+ - out of memory
27
+ - out of context
28
+ - too big
29
+ - heap
30
+ - bounded
31
+ tag_matches:
32
+ - scale
33
+ escalation_from:
34
+ - build
35
+ - rebuild
36
+ - fix
37
+ - operator-convergence
38
+ source:
39
+ origin: file
40
+ path: scale.md
41
+ hash: "sha256:39cb1561005bb6836eb2ef3b5834cd3b4701623c8811750bc0bcada9b7ee75ae"
42
+ ---
43
+
44
+ # Scale-aware processing
45
+
46
+ The skill's phased operations (Build, Extend, Rebuild, Fix, Join) all
47
+ read wiki entries through a single chokepoint: `iterEntries` in
48
+ `scripts/lib/chunk.mjs`. That chokepoint enforces two bounded-memory
49
+ guarantees:
50
+
51
+ ## 1. Frontmatter reads are bounded
52
+
53
+ Every entry's frontmatter is read via `readFrontmatterStreaming`,
54
+ which opens the file, reads in 4 KB chunks, and stops as soon as it
55
+ finds the closing `---\n` fence. It raises a loud error if a fence
56
+ is not found within **256 KB** — that's a pathological frontmatter
57
+ ceiling, not a normal case. The body of the file is never loaded
58
+ during detection phases.
59
+
60
+ Result: for a wiki with 10,000 leaves × 50 KB average body, the
61
+ memory cost of walking every entry's frontmatter is ~40 MB
62
+ (4 KB × 10,000) instead of ~500 MB (50 KB × 10,000).
63
+
64
+ ## 2. Body access is lazy and explicit
65
+
66
+ The iterator yields `{ path, relPath, data, type, loadBody }`. The
67
+ `loadBody()` method is a thunk — calling it reads the file fresh
68
+ and returns the body string. The iterator never caches bodies, so
69
+ the caller controls exactly how long a body stays in memory.
70
+
71
+ The **streaming consumer pattern** is the load-bearing discipline:
72
+
73
+ ```js
74
+ for (const entry of iterEntries(wikiRoot)) {
75
+ const body = await entry.loadBody();
76
+ // ...do work with body...
77
+ releaseBody(); // balances the loadBody discipline counter
78
+ }
79
+ ```
80
+
81
+ `releaseBody()` decrements a process-global **discipline counter**
82
+ that tracks caller hygiene — NOT actual memory residency. V8 has no
83
+ cheap JavaScript-side hook for "is this string still alive?", so
84
+ the counter measures whether consumers follow the load-process-
85
+ release pattern, not whether the body is really reclaimed. A
86
+ caller that calls `releaseBody()` while still holding a reference
87
+ to `body` does not free the memory; V8 does that when GC runs and
88
+ the reference falls out of scope.
89
+
90
+ The counter catches a specific class of bug: consumers that
91
+ accidentally *accumulate* bodies in an array or closure across
92
+ iterations. In that case `peakInFlightBodies` grows to N and any
93
+ regression test can flag it. For real memory-residency questions
94
+ run under `node --expose-gc` and measure `process.memoryUsage()`
95
+ directly.
96
+
97
+ An imbalanced `releaseBody()` (more releases than loads) throws
98
+ loudly, so discipline bugs surface at the offending call site
99
+ instead of silently muddying the counter.
100
+
101
+ ## 3. Operator-convergence is frontmatter-only by construction
102
+
103
+ The methodology (sections 3.5, 3.6, 8.5) mandates that operator
104
+ detection runs on frontmatter alone. The chunk API enforces this
105
+ mechanically: `iterEntries` does not return a body field, only a
106
+ thunk. A detection phase that never calls `loadBody()` cannot
107
+ accidentally allocate body bytes. Phase 6's tiered AI ladder
108
+ (TF-IDF → embeddings → Claude) operates on the same frontmatter-only
109
+ surface, so the scale guarantee extends through classify and
110
+ rebuild-plan-review as well.
111
+
112
+ ## 4. Index regeneration respects the bound
113
+
114
+ `scripts/lib/indices.mjs`'s `listChildren` was rewired to use
115
+ `readFrontmatterStreaming` directly. A `rebuildAllIndices` call on a
116
+ 10 k-entry wiki allocates bounded frontmatter bytes per leaf, not
117
+ full files. The scale e2e test measures this via the `totalBodyLoads`
118
+ metric and asserts it stays at zero during a whole-tree rebuild.
119
+
120
+ ## 5. diff --op at scale
121
+
122
+ Large operations produce many commits — the orchestrator makes one
123
+ commit per phase, so a single build creates ~6 commits regardless of
124
+ corpus size. `skill-llm-wiki diff <wiki> --op <id> --stat` reads the
125
+ git object database, not the working tree — so the diff cost is
126
+ proportional to what *changed* in the operation, not to the total
127
+ wiki size. A 10,000-file wiki whose build made three operator
128
+ applications renders its diff in tens of milliseconds.
129
+
130
+ ## 6. Context-window management in the wiki-runner
131
+
132
+ Byte-level memory is not the only bounded resource in a large
133
+ build. The wiki-runner sub-agent has its own **context window**, and
134
+ a 10k-entry build that fans out Tier 2 calls and stitches
135
+ progress back into the main transcript will overflow that window
136
+ long before it runs out of heap. The skill handles this with three
137
+ rules that together let the wiki-runner survive wikis of any
138
+ size.
139
+
140
+ ### Rule 1 — Phase commits are the durable checkpoint
141
+
142
+ Every phase the orchestrator runs ends with a git commit in
143
+ `<wiki>/.llmwiki/git/`. Once `phase draft-frontmatter` has
144
+ committed its output, the wiki-runner's in-memory knowledge of
145
+ **what each entry's frontmatter looks like right now** is
146
+ redundant: `git show HEAD:<path>` can reconstruct it on demand.
147
+ The same applies to every operator-convergence iteration commit,
148
+ every index-generation commit, and the pre-op snapshot itself.
149
+
150
+ This means the wiki-runner can treat any phase's conversation
151
+ history as discardable once the phase's closing commit lands. A
152
+ thousand entries' worth of draft-frontmatter Tier 2 prompts that
153
+ lived inside draft-frontmatter never need to be re-read from the
154
+ wiki-runner's transcript — they're in the decision log and (for
155
+ the chosen frontmatter) in the commit tree.
156
+
157
+ ### Rule 2 — Tier 2 work fans out to per-decision sub-agents
158
+
159
+ Every Tier 2 call is a separate sub-agent (see
160
+ `guide/tiered-ai.md` "Tier 2 execution via dedicated sub-agents").
161
+ The wiki-runner sees only the final decision, not the sub-agent's
162
+ prompt or response body. A 10k-entry wiki with 500 mid-band pairs
163
+ produces 500 Tier 2 sub-agents, but the wiki-runner's own window
164
+ absorbs only 500 one-line decisions (plus whatever the
165
+ similarity-cache served without a Claude call at all).
166
+
167
+ ### Rule 3 — Self-monitor and auto-compact
168
+
169
+ The wiki-runner periodically checks its remaining context budget.
170
+ When the budget falls below a safety threshold (typically around
171
+ 20–25% remaining), it performs an **auto-compact** before
172
+ starting the next phase:
173
+
174
+ 1. **Cut to the last clean checkpoint.** The most recent phase
175
+ commit is the earliest safe point to resume from. Everything
176
+ in the conversation after that commit is summarisable.
177
+ 2. **Summarise what remains.** Produce a short paragraph per
178
+ completed phase — what it did, which op-id it committed under,
179
+ any warnings worth carrying forward. This summary lives in
180
+ the compacted transcript instead of the full per-phase chatter.
181
+ 3. **Re-anchor the plan.** State the current phase, the next
182
+ phase, the remaining work (entries pending / iterations
183
+ remaining), and the pre-op tag. A resume from this compacted
184
+ state must be able to pick up exactly where the pre-compact
185
+ run left off.
186
+ 4. **Drop Tier 2 transcripts.** Per-decision sub-agent transcripts
187
+ are already not in the wiki-runner's window (Rule 2), but any
188
+ lingering summaries can be replaced by a single line: "Tier 2
189
+ decisions landed: N same, M different, K undecidable (see
190
+ decisions.yaml)."
191
+ 5. **Verify state against git.** Run `skill-llm-wiki log --op
192
+ <id>` to confirm the expected phase commits are reachable
193
+ from HEAD — this catches the rare case where the auto-compact
194
+ dropped a commit the agent thought had landed.
195
+
196
+ Steps 1–5 are cheap: they're all reads against the private git,
197
+ no mutation happens. Auto-compaction is idempotent — running it
198
+ twice in a row is safe.
199
+
200
+ ### What the wiki-runner does NOT do
201
+
202
+ - **Pre-emptive compaction.** Auto-compact fires only when budget
203
+ pressure is real. Compacting prematurely wastes the headroom
204
+ you'd need to correctly summarise the phases in the first place.
205
+ - **Mid-phase compaction.** Auto-compact runs between phase
206
+ boundaries, never in the middle of a phase. A half-committed
207
+ phase is not a resumable state; waiting for the phase commit
208
+ is the right protocol.
209
+ - **Main-session compaction.** The main session is not the
210
+ wiki-runner. If the main session's context is under pressure,
211
+ that's a separate concern the main session handles on its own.
212
+ See SKILL.md's "Agent delegation contract" for why the main
213
+ session should never be holding wiki content in its window in
214
+ the first place.
215
+
216
+ ### Budget-driven fallbacks
217
+
218
+ If auto-compaction still leaves the wiki-runner under budget
219
+ pressure — e.g. a pathologically large corpus with thousands of
220
+ simultaneously-pending Tier 2 decisions — the wiki-runner should:
221
+
222
+ 1. **Split the remaining operation at a phase boundary.** Roll back
223
+ to the pre-op tag via `skill-llm-wiki rollback`, then launch a
224
+ fresh wiki-runner sub-agent whose prompt picks up from the
225
+ last known-good commit SHA with a clean context.
226
+ 2. **Narrow the Tier 2 fan-out.** Raise the Tier 1 escalation
227
+ threshold so fewer pairs reach Tier 2, with a note to the
228
+ user that the run was downgraded. This trades quality for
229
+ completability.
230
+ 3. **Stop and report.** If neither of the above fits, surface the
231
+ situation to the main session and ask the user whether to
232
+ continue with a narrower run or wait for a `--quality-mode
233
+ claude-first` pass on a smaller slice.
234
+
235
+ The rule is: **never silently drop quality, never silently
236
+ abort.** Either the operation completes end-to-end at the
237
+ requested quality level, or the wiki-runner returns control to
238
+ the user with a clear explanation of what it couldn't finish.
239
+
240
+ ## What this does NOT do
241
+
242
+ - Tune garbage collection. V8 handles its own heap; the chunk
243
+ iterator just ensures it gets small objects to collect.
244
+ - Stream *writes*. The orchestrator still does one `writeFileSync`
245
+ per leaf during build. Phase 6 may revisit this if a prose-heavy
246
+ corpus produces write pressure, but today the read path was the
247
+ dominant cost.
248
+ - Cache bodies across iterations. Each convergence pass re-invokes
249
+ `iterEntries` with fresh reads, so frontmatter edits from the
250
+ previous pass land correctly. A caching layer would break this
251
+ and is deliberately omitted.
@@ -0,0 +1,97 @@
1
+ ---
2
+ id: in-place-mode
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: converting an existing folder into a wiki in place, lossless and reversible
6
+ parents:
7
+ - index.md
8
+ covers:
9
+ - "--layout-mode in-place never runs without explicit user opt-in"
10
+ - pre-op snapshot captures every byte into the private git repo before mutation
11
+ - "rollback --to pre-<op-id> restores byte-exact pre-operation state"
12
+ - "the user's own git repository is never touched, even when the wiki is inside it"
13
+ - wiki-local .gitignore hides our private metadata from any ancestor user repo
14
+ - cannot combine with --target — the source IS the target in this mode
15
+ tags:
16
+ - layout
17
+ - in-place
18
+ - conversion
19
+ activation:
20
+ keyword_matches:
21
+ - in place
22
+ - in-place
23
+ - overwrite
24
+ - transform my folder
25
+ - convert this directory
26
+ - make my docs into a wiki
27
+ tag_matches:
28
+ - layout
29
+ - conversion
30
+ escalation_from:
31
+ - build
32
+ - fix
33
+ source:
34
+ origin: file
35
+ path: in-place-mode.md
36
+ hash: "sha256:38dd2158263b6cdb006ff86d53035d88172e27be63867116e74100c85ab149f1"
37
+ ---
38
+
39
+ # In-place mode
40
+
41
+ Pass `--layout-mode in-place` when the user explicitly says "transform this
42
+ folder into a wiki" or "convert my docs in place". The source folder becomes
43
+ the wiki. History lives in `<source>/.llmwiki/git/` from the first operation
44
+ onward, and every operation tags a rollback anchor before mutating.
45
+
46
+ ## Safety envelope
47
+
48
+ Phase 1 of every operation (including the first build) runs `preOpSnapshot`:
49
+
50
+ 1. Lazily initialise `<source>/.llmwiki/git/` if missing, commit a `genesis`
51
+ tag of the empty tree.
52
+ 2. Write `<source>/.gitignore` with the three skill-internal entries so the
53
+ user's ancestor git repository (if any) ignores our private metadata.
54
+ 3. `git add -A` — stage every file in the working tree.
55
+ 4. If anything is staged, commit with message `pre-op <op-id>`.
56
+ 5. Tag the commit as `pre-op/<op-id>` (loud on collision).
57
+
58
+ The `pre-op/<op-id>` tag is the rollback anchor and lives in a separate
59
+ ref namespace from the final `op/<op-id>` tag so git's ref hierarchy
60
+ never collides. Even a SIGKILL between steps 2 and 5 leaves the tag from
61
+ the previous operation reachable. Rollback is:
62
+
63
+ ```text
64
+ skill-llm-wiki rollback <source> --to pre-<op-id>
65
+ ```
66
+
67
+ This runs `git reset --hard <tag> && git clean -fd` against the private repo,
68
+ returning the working tree to byte-identical pre-operation state. `.work/` and
69
+ `.shape/history/*/work/` are preserved through rollback (protected by
70
+ `.llmwiki/git/info/exclude`); every other untracked change is wiped.
71
+
72
+ ## When to choose in-place vs sibling
73
+
74
+ Ask the user if the request is ambiguous. Do not guess. Typical signals:
75
+
76
+ | User says | Mode |
77
+ |-----------|------|
78
+ | "build a wiki from my docs" | sibling (default `<source>.wiki/`) |
79
+ | "convert my docs to a wiki" | **ask** — could mean either |
80
+ | "transform this folder in place" | in-place |
81
+ | "overwrite ./docs with a wiki structure" | in-place |
82
+ | "I want the wiki files alongside ./docs" | sibling |
83
+ | "put it in my memory folder" | hosted with `--target ./memory` |
84
+
85
+ When ambiguous, Claude should say something like: "I can either build a new
86
+ `./docs.wiki/` sibling (default, reversible, leaves `./docs` untouched) or
87
+ transform `./docs` itself with `--layout-mode in-place` (reversible via git
88
+ rollback). Which do you prefer?"
89
+
90
+ ## Coexistence with a user git repository
91
+
92
+ If the user's source folder is already tracked by their own git repo, the
93
+ first in-place operation writes `.gitignore` with `.llmwiki/`, `.work/`,
94
+ `.shape/history/*/work/`. The user's git sees those paths as ignored. Our
95
+ private repo's operations never touch the user's `.git/` — see
96
+ [guide/coexistence.md](coexistence.md) for the full coexistence story and
97
+ proof-of-isolation tests.
@@ -0,0 +1,53 @@
1
+ ---
2
+ id: layout
3
+ type: index
4
+ depth_role: subcategory
5
+ depth: 1
6
+ focus: Layout modes, hosted-mode contract, and in-place conversion of source folders.
7
+ parents:
8
+ - "../index.md"
9
+ shared_covers: []
10
+ entries:
11
+ - id: in-place-mode
12
+ file: in-place-mode.md
13
+ type: primary
14
+ focus: converting an existing folder into a wiki in place, lossless and reversible
15
+ tags:
16
+ - layout
17
+ - in-place
18
+ - conversion
19
+ - id: layout-contract
20
+ file: layout-contract.md
21
+ type: primary
22
+ focus: hosted-mode layout contract schema, validation, and conflict-resolution rules
23
+ tags:
24
+ - hosted-mode
25
+ - layout-contract
26
+ - schema
27
+ - id: layout-modes
28
+ file: layout-modes.md
29
+ type: primary
30
+ focus: "choosing between sibling (default), in-place, and hosted layout modes"
31
+ tags:
32
+ - layout
33
+ - operation
34
+ children: []
35
+ ---
36
+ <!-- BEGIN AUTO-GENERATED NAVIGATION -->
37
+
38
+ # Layout
39
+
40
+ **Focus:** Layout modes, hosted-mode contract, and in-place conversion of source folders.
41
+
42
+ ## Children
43
+
44
+ | File | Type | Focus |
45
+ |------|------|-------|
46
+ | [in-place-mode.md](in-place-mode.md) | 📄 primary | converting an existing folder into a wiki in place, lossless and reversible |
47
+ | [layout-contract.md](layout-contract.md) | 📄 primary | hosted-mode layout contract schema, validation, and conflict-resolution rules |
48
+ | [layout-modes.md](layout-modes.md) | 📄 primary | choosing between sibling (default), in-place, and hosted layout modes |
49
+
50
+ <!-- END AUTO-GENERATED NAVIGATION -->
51
+
52
+ <!-- BEGIN AUTHORED ORIENTATION -->
53
+ <!-- END AUTHORED ORIENTATION -->
@@ -0,0 +1,131 @@
1
+ ---
2
+ id: layout-contract
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: hosted-mode layout contract schema, validation, and conflict-resolution rules
6
+ parents:
7
+ - index.md
8
+ covers:
9
+ - "full contract schema (mode, versioning, purpose, global_invariants, layout with children and dynamic_subdirs)"
10
+ - field semantics for path, purpose, content_rules, allow_entry_types, max_depth, dynamic_subdirs.template placeholders
11
+ - contract validation rules applied before every operation
12
+ - "conflict resolution: the contract always wins over methodology defaults when they disagree"
13
+ - "authoring contracts on behalf of the user (always confirm before writing)"
14
+ tags:
15
+ - hosted-mode
16
+ - layout-contract
17
+ - schema
18
+ activation:
19
+ keyword_matches:
20
+ - hosted
21
+ - contract
22
+ - layout.yaml
23
+ - llmwiki.layout
24
+ - in-place
25
+ tag_matches:
26
+ - hosted-mode
27
+ source:
28
+ origin: file
29
+ path: layout-contract.md
30
+ hash: "sha256:78213fe390d9f5e0dd58f23c14bdeaa8136831a3c0987e53f02814c37640f4f2"
31
+ ---
32
+
33
+ # Layout contract (hosted mode)
34
+
35
+ A layout contract is a YAML file at the hosted-mode target's root: `<target>/.llmwiki.layout.yaml`. Presence of this file is the sole signal that enters hosted mode. When you read this file, you are operating on a hosted-mode target and must honor every rule below.
36
+
37
+ ## Full schema
38
+
39
+ ```yaml
40
+ mode: hosted
41
+
42
+ versioning:
43
+ style: in-place # or: sibling-versioned
44
+ backup_before_mutate: true
45
+ backup_dir: .llmwiki.backups # relative to target root
46
+
47
+ purpose: "Persistent memory for the agent across sessions"
48
+
49
+ # Additional hard invariants enforced by Validate and Fix on top of
50
+ # the methodology's defaults.
51
+ global_invariants:
52
+ - "every leaf must declare a source.origin field"
53
+ - "no leaf exceeds 300 lines"
54
+
55
+ layout:
56
+ - path: knowledge
57
+ purpose: "long-lived factual knowledge and reference entries"
58
+ content_rules:
59
+ - "each leaf is a self-contained fact"
60
+ - "covers[] lists concrete concerns, not vague topics"
61
+ allow_entry_types: [primary]
62
+ max_depth: 3
63
+
64
+ - path: daily
65
+ purpose: "time-series daily journal"
66
+ dynamic_subdirs:
67
+ template: "{yyyy}-{mm}-{dd}"
68
+ purpose: "entries from a single day"
69
+ allow_entry_types: [primary]
70
+ content_rules:
71
+ - "one leaf per event or observation"
72
+ - "past days are read-only except via explicit Fix"
73
+
74
+ - path: policies
75
+ purpose: "rules the agent must follow"
76
+ allow_entry_types: [primary, overlay]
77
+
78
+ - path: projects
79
+ purpose: "active and archived project workspaces"
80
+ children:
81
+ - path: active
82
+ purpose: "in-flight projects"
83
+ - path: archive
84
+ purpose: "completed or abandoned projects"
85
+ ```
86
+
87
+ ## Field semantics
88
+
89
+ - **`mode`** — must be `hosted`.
90
+ - **`versioning.style`** — `in-place` writes directly into the target; `sibling-versioned` produces `.llmwiki.vN` siblings but respects contract structure inside each version.
91
+ - **`versioning.backup_before_mutate`** (default true) — snapshot target to `backup_dir/<timestamp>/` before any structural mutation.
92
+ - **`versioning.backup_dir`** (default `.llmwiki.backups`) — relative to target.
93
+ - **`purpose`** (optional) — becomes the root index's `focus` if not authored otherwise.
94
+ - **`global_invariants`** (optional) — additional hard invariants enforced on top of the methodology defaults.
95
+ - **`layout`** (required) — array of top-level subdirectory specs.
96
+ - **`layout[].path`** (required) — directory name relative to target root. No `/`, no `..`.
97
+ - **`layout[].purpose`** (required) — becomes the directory index's `focus`.
98
+ - **`layout[].content_rules`** (optional) — per-leaf rules checked as soft signals by Validate.
99
+ - **`layout[].allow_entry_types`** (optional, default all) — which entry types are permitted.
100
+ - **`layout[].max_depth`** (optional) — hard cap on nesting within this subtree.
101
+ - **`layout[].dynamic_subdirs`** (optional) — marks the directory as a container for dynamically-named subdirectories.
102
+ - **`layout[].dynamic_subdirs.template`** (required within dynamic_subdirs) — placeholder template. Supported placeholders: `{yyyy}` `{mm}` `{dd}` `{hh}` `{mi}` `{ss}` `{iso}` `{slug}`. Resolved against the clock (or a user-supplied slug) at write time.
103
+ - **`layout[].dynamic_subdirs.purpose`** / `content_rules` / `allow_entry_types` — inherited by dynamically-created subdirectories.
104
+ - **`layout[].children`** (optional) — nested directory specs for fixed deeper structure.
105
+
106
+ ## Contract validation
107
+
108
+ Before any operation in hosted mode, parse the contract and verify:
109
+
110
+ - `mode: hosted` is present.
111
+ - Every `path` is a legal single-segment directory name.
112
+ - No duplicate paths at the same level.
113
+ - `dynamic_subdirs.template` uses only supported placeholders.
114
+ - `children` nesting is well-formed (no cycles).
115
+ - `versioning.style: sibling-versioned` is only used where sibling naming is possible.
116
+
117
+ A contract that fails these checks aborts the operation with a clear error. Tell the user exactly which rule failed and where.
118
+
119
+ ## Conflict resolution: contract always wins
120
+
121
+ When methodology defaults and the contract disagree, the contract wins:
122
+
123
+ - Rewrite operators cannot override contract structure.
124
+ - The narrowing chain is still enforced, but root `focus` comes from the contract's `purpose` if provided.
125
+ - Validation adds contract invariants on top of methodology invariants; it never removes methodology invariants.
126
+
127
+ A hosted wiki is strictly at least as constrained as a free wiki — never less. Quality is never compromised because the contract adds structure; the methodology's guarantees remain intact.
128
+
129
+ ## Writing layout contracts on behalf of the user
130
+
131
+ If the user describes a desired hosted structure in natural language ("I want a memory folder with knowledge, daily entries per day, and projects with active/archive"), draft a `.llmwiki.layout.yaml` matching the description and **show it to the user for confirmation before writing the file**. Getting the contract wrong at Build time means every subsequent operation is constrained by the mistake, so always confirm.
@@ -0,0 +1,115 @@
1
+ ---
2
+ id: layout-modes
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: "choosing between sibling (default), in-place, and hosted layout modes"
6
+ parents:
7
+ - index.md
8
+ covers:
9
+ - "sibling mode writes to <source>.wiki/ by default; no version-numbered folders"
10
+ - "in-place mode transforms the user's source folder itself; requires explicit opt-in"
11
+ - hosted mode writes under a pre-declared .llmwiki.layout.yaml contract at --target
12
+ - "private git repo at <wiki>/.llmwiki/git/ carries history and rollback anchors across all modes"
13
+ - "sibling default collision handling refuses on INT-01 / INT-03 rather than guessing"
14
+ - "legacy <source>.llmwiki.v<N>/ is detected via INT-04 and requires explicit migrate"
15
+ tags:
16
+ - layout
17
+ - operation
18
+ activation:
19
+ keyword_matches:
20
+ - layout
21
+ - mode
22
+ - sibling
23
+ - in-place
24
+ - in place
25
+ - hosted
26
+ - .wiki
27
+ - target folder
28
+ - default
29
+ tag_matches:
30
+ - layout
31
+ - any-op
32
+ escalation_from:
33
+ - build
34
+ - extend
35
+ - rebuild
36
+ - fix
37
+ source:
38
+ origin: file
39
+ path: layout-modes.md
40
+ hash: "sha256:5514b435d94710045b2669167f617ed641bd8697ce3300420078c39f2f27f02e"
41
+ ---
42
+
43
+ # Layout modes
44
+
45
+ Every top-level operation (build / extend / rebuild / fix / join) accepts
46
+ `--layout-mode <sibling|in-place|hosted>`. The default is **sibling**. Pick the
47
+ mode that matches the user's stated intent — never guess.
48
+
49
+ ## Sibling (default)
50
+
51
+ Writes to a sibling directory named `<source>.wiki/` next to the source.
52
+
53
+ - `build ./docs` → creates `./docs.wiki/`
54
+ - History lives in `./docs.wiki/.llmwiki/git/`, not in separate versioned folders
55
+ - No `<source>.llmwiki.v1/`, `.v2/`, etc. anywhere
56
+ - Safe to re-run: second `build ./docs` exits with **INT-03** and prompts for
57
+ `rebuild` / `extend` / `fix` instead of silently overwriting
58
+
59
+ Use when: the user says "build a wiki from my docs folder" without specifying
60
+ a target. This is the default because it is the cheapest reversible outcome
61
+ — the source is untouched and the wiki is visibly separate.
62
+
63
+ ## In-place
64
+
65
+ Transforms the source folder itself into a wiki. The private git repo lives
66
+ at `<source>/.llmwiki/git/`, the pre-op snapshot captures every byte, and
67
+ `rollback --to pre-<op-id>` restores byte-exact state.
68
+
69
+ - Must be opted into with `--layout-mode in-place`
70
+ - Cannot be combined with `--target` (triggers **INT-09a**)
71
+ - First run creates `<source>/.gitignore` so an ancestor user git repo ignores
72
+ the private metadata
73
+
74
+ Use when: the user explicitly says "convert my docs folder in place", "transform
75
+ this directory", or "I want the wiki files where my docs are". If the user
76
+ says merely "convert", **ask** whether they mean sibling or in-place before
77
+ running.
78
+
79
+ ## Hosted
80
+
81
+ Writes under a pre-existing `.llmwiki.layout.yaml` contract at `--target <path>`.
82
+ The contract defines where entries live, what naming conventions apply, and
83
+ any site-specific rules. Use this when the wiki has a fixed home (like
84
+ `./memory/knowledge/`) and the user wants the skill to respect that layout.
85
+
86
+ - Requires both `--layout-mode hosted` and `--target <path>`. Missing
87
+ `--target` triggers **INT-09b**; a non-empty target without a layout
88
+ contract triggers **INT-01b** (override with `--accept-foreign-target`
89
+ only after confirming with the user).
90
+ - The target must carry a `.llmwiki.layout.yaml` contract **or** be a fresh
91
+ directory the first operation will initialise
92
+
93
+ ## Collision handling
94
+
95
+ The CLI refuses to guess when the layout is ambiguous:
96
+
97
+ | Code | Situation | Resolving flag |
98
+ |------|-----------|----------------|
99
+ | INT-01 | Default sibling target exists as a foreign directory | `--target <other>` or `--layout-mode in-place` |
100
+ | INT-01b | Explicit `--target` is a non-empty foreign directory | `--target <other>` or `--accept-foreign-target` |
101
+ | INT-02 | Source is already a managed wiki | pick `extend` / `rebuild` / `fix`, or `build --layout-mode in-place` |
102
+ | INT-03 | Default sibling target is already a managed wiki | pick `extend` / `rebuild` / `fix` |
103
+ | INT-04 | Source is a legacy `<name>.llmwiki.v<N>` folder | `skill-llm-wiki migrate <legacy>` |
104
+
105
+ For each code the CLI prints numbered options; Claude surfaces those to the
106
+ user verbatim so the human makes the call.
107
+
108
+ ## Legacy migration
109
+
110
+ Pre–Phase 2 wikis used the naming convention `<source>.llmwiki.v1/`, `.v2/`, ...
111
+ The skill no longer uses that pattern. When the skill detects a legacy folder
112
+ it refuses every operation (INT-04) and prompts for `skill-llm-wiki migrate`,
113
+ which copies the latest version's content into the new `<source>.wiki/`
114
+ sibling, initialises the private git repo, and records the migration lineage
115
+ in `.llmwiki/op-log.yaml`. The legacy folder is left byte-identical on disk.