fusion-cli 1.1.0__py3-none-any.whl

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 (46) hide show
  1. fusion/__init__.py +3 -0
  2. fusion/_skills/fusion-analyst/SKILL.md +50 -0
  3. fusion/_skills/fusion-analyst/references/assess.md +12 -0
  4. fusion/_skills/fusion-analyst/references/compare.md +12 -0
  5. fusion/_skills/fusion-analyst/references/export.md +18 -0
  6. fusion/_skills/fusion-analyst/references/fusion-conventions.md +149 -0
  7. fusion/_skills/fusion-analyst/references/report.md +13 -0
  8. fusion/_skills/fusion-analyst/scripts/export.py +64 -0
  9. fusion/_skills/fusion-intake/SKILL.md +128 -0
  10. fusion/_skills/fusion-intake/references/convert.md +104 -0
  11. fusion/_skills/fusion-intake/references/delivery.md +107 -0
  12. fusion/_skills/fusion-intake/references/fusion-conventions.md +149 -0
  13. fusion/_skills/fusion-intake/references/gate.md +107 -0
  14. fusion/_skills/fusion-intake/scripts/convert.py +836 -0
  15. fusion/_skills/fusion-intake/scripts/gate.py +267 -0
  16. fusion/_skills/fusion-librarian/SKILL.md +64 -0
  17. fusion/_skills/fusion-librarian/references/archive.md +21 -0
  18. fusion/_skills/fusion-librarian/references/create.md +14 -0
  19. fusion/_skills/fusion-librarian/references/cross-reference.md +45 -0
  20. fusion/_skills/fusion-librarian/references/fusion-conventions.md +149 -0
  21. fusion/_skills/fusion-librarian/references/promote.md +24 -0
  22. fusion/_skills/fusion-librarian/references/query.md +17 -0
  23. fusion/_skills/fusion-librarian/references/reflect.md +47 -0
  24. fusion/_skills/fusion-librarian/references/restructure.md +20 -0
  25. fusion/_skills/fusion-librarian/references/tag.md +13 -0
  26. fusion/_skills/fusion-librarian/scripts/link-repair.py +371 -0
  27. fusion/_skills/fusion-planner/SKILL.md +62 -0
  28. fusion/_skills/fusion-planner/references/close.md +12 -0
  29. fusion/_skills/fusion-planner/references/create-activity.md +38 -0
  30. fusion/_skills/fusion-planner/references/fusion-conventions.md +149 -0
  31. fusion/_skills/fusion-planner/references/horizon.md +20 -0
  32. fusion/bucket.py +77 -0
  33. fusion/checker.py +248 -0
  34. fusion/cli.py +406 -0
  35. fusion/document.py +155 -0
  36. fusion/hub.py +78 -0
  37. fusion/indexer.py +75 -0
  38. fusion/ledger.py +106 -0
  39. fusion/manifest.py +33 -0
  40. fusion/scaffold.py +120 -0
  41. fusion/setup.py +300 -0
  42. fusion/views.py +111 -0
  43. fusion_cli-1.1.0.dist-info/METADATA +67 -0
  44. fusion_cli-1.1.0.dist-info/RECORD +46 -0
  45. fusion_cli-1.1.0.dist-info/WHEEL +4 -0
  46. fusion_cli-1.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = []
5
+ # ///
6
+ """fusion-intake Stage 1 — the deterministic gate classifier.
7
+
8
+ Hashes every file in inbox/, compares against the sources/ tree, normalizes
9
+ filenames, scores content similarity, and writes a gate manifest with six
10
+ buckets: exact_dups, near_dups, update_candidates, clean_new, containers,
11
+ inbox_dups. It classifies and reports — it never writes to sources/ or
12
+ library/. Stage 2 (the agent, references/gate.md) assigns the final
13
+ class and asks the human where the locked rule requires it.
14
+
15
+ Usage:
16
+ uv run <skill>/scripts/gate.py --bucket <bucket-root> [--out <path>]
17
+ """
18
+ import argparse
19
+ import hashlib
20
+ import json
21
+ import re
22
+ import subprocess
23
+ import uuid
24
+ from collections import defaultdict
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+
28
+ # Locked lineage thresholds (doc-converter, kept verbatim).
29
+ NEAR_DUP_THRESHOLD = 0.85 # best content sim >= this (and not exact) -> near-dup
30
+ UPDATE_SIM_FLOOR = 0.30 # [floor, near): name match -> update candidate
31
+ SHINGLE_K = 3 # word-shingle size for similarity
32
+
33
+ SIMILARITY_READ_CAP = 512 * 1024 # bytes of text considered for similarity — enough to catch any real re-export/update
34
+
35
+ _DATE_PREFIX = re.compile(r"^(?:\d{4}-\d{2}-\d{2}|\d{8})[_-]")
36
+ _SEP_RUN = re.compile(r"[\s_-]+")
37
+ _WORD = re.compile(r"\w+")
38
+
39
+ SKIP_NAMES = {"MANIFEST.md", "README.md"}
40
+
41
+ # Containers are delivery vehicles, not originals (skills/fusion-intake
42
+ # scripts/convert.py `unpack`) — the gate reports them and skips all other
43
+ # classification, before hashing (a 144MB zip should not be shingled).
44
+ CONTAINER_EXTS = {".zip", ".athena"}
45
+
46
+
47
+ def normalize_filename(name: str) -> str:
48
+ """Strip a leading date prefix, lowercase, collapse separators to '-',
49
+ drop the extension."""
50
+ stem = Path(name).stem
51
+ stem = _DATE_PREFIX.sub("", stem)
52
+ stem = stem.lower()
53
+ stem = _SEP_RUN.sub("-", stem).strip("-")
54
+ return stem
55
+
56
+
57
+ def sha256_of(path: Path) -> str:
58
+ h = hashlib.sha256()
59
+ with open(path, "rb") as fh:
60
+ for chunk in iter(lambda: fh.read(65536), b""):
61
+ h.update(chunk)
62
+ return h.hexdigest()
63
+
64
+
65
+ def extract_text(path: Path) -> str:
66
+ """Text for similarity scoring. Binary formats decode to noise and
67
+ effectively compare on filename only — full content comparison is
68
+ Stage 2's judgment work. Reads at most SIMILARITY_READ_CAP bytes: a
69
+ 144MB delivery must not be fully decoded just to shingle it."""
70
+ try:
71
+ with path.open("rb") as fh:
72
+ data = fh.read(SIMILARITY_READ_CAP)
73
+ return data.decode("utf-8", errors="ignore")
74
+ except OSError:
75
+ return ""
76
+
77
+
78
+ @dataclass
79
+ class SourceIndex:
80
+ by_hash: dict = field(default_factory=lambda: defaultdict(list))
81
+ by_name: dict = field(default_factory=lambda: defaultdict(list))
82
+ text_by_path: dict = field(default_factory=dict)
83
+
84
+
85
+ def _iter_files(root: Path):
86
+ for p in sorted(root.rglob("*")):
87
+ if not p.is_file() or p.name in SKIP_NAMES:
88
+ continue
89
+ rel = p.relative_to(root)
90
+ if any(part.startswith(".") for part in rel.parts):
91
+ continue
92
+ yield p
93
+
94
+
95
+ def index_sources(sources_dir: Path) -> SourceIndex:
96
+ idx = SourceIndex()
97
+ sources_dir = Path(sources_dir)
98
+ for p in _iter_files(sources_dir):
99
+ rel = str(p.relative_to(sources_dir))
100
+ idx.by_hash[sha256_of(p)].append(rel)
101
+ idx.by_name[normalize_filename(p.name)].append(rel)
102
+ idx.text_by_path[rel] = extract_text(p)
103
+ return idx
104
+
105
+
106
+ def _shingles(text, k: int = SHINGLE_K) -> set:
107
+ """Produce word k-shingles from text. Texts below k word tokens yield NO
108
+ shingles—there is no linguistic evidence at sub-k thresholds by design.
109
+ Idempotent on an already-shingled set (cheap passthrough) so callers can
110
+ thread precomputed/cached shingles through the same call sites that
111
+ otherwise take raw text."""
112
+ if isinstance(text, set):
113
+ return text
114
+ tokens = _WORD.findall(text.lower())
115
+ if len(tokens) < k:
116
+ return set()
117
+ return {" ".join(tokens[i:i + k]) for i in range(len(tokens) - k + 1)}
118
+
119
+
120
+ def similarity(a, b) -> float:
121
+ """Jaccard over word k-shingles: 0.0 disjoint .. 1.0 identical. Texts
122
+ under SHINGLE_K words always score 0.0—no evidence, not identity. Exact
123
+ duplicates are sha256's catch upstream, not this function's. Accepts
124
+ raw text or a precomputed shingle set (idempotent via _shingles) so
125
+ the gate's cached best-match path can reuse shingles across sources."""
126
+ sa, sb = _shingles(a), _shingles(b)
127
+ if not sa or not sb:
128
+ return 0.0
129
+ union = len(sa | sb)
130
+ return len(sa & sb) / union if union else 0.0
131
+
132
+
133
+ def git_history(path: Path, cwd: Path, limit: int = 10) -> list:
134
+ """Prior-version evidence via git log --follow; [] outside a repo or on
135
+ any failure (liberal reader)."""
136
+ try:
137
+ out = subprocess.run(
138
+ ["git", "log", "--follow", "--date=short",
139
+ f"-n{limit}", "--format=%ad\t%s", "--", str(path)],
140
+ cwd=str(cwd), capture_output=True, text=True, timeout=30,
141
+ )
142
+ except (OSError, subprocess.SubprocessError):
143
+ return []
144
+ if out.returncode != 0:
145
+ return []
146
+ history = []
147
+ for line in out.stdout.splitlines():
148
+ date, _, subject = line.partition("\t")
149
+ if date:
150
+ history.append({"date": date, "subject": subject})
151
+ return history
152
+
153
+
154
+ def _best_match(incoming_shingles: set, idx: SourceIndex, shingle_cache: dict):
155
+ """Best (path, sim) over ALL sources — catches renamed near-dups.
156
+ Source shingles are cached on first use (shingle_cache, one dict per
157
+ run) so a batch with many incoming files never re-shingles the same
158
+ source text twice."""
159
+ best_path, best_sim = None, 0.0
160
+ for path, src_text in idx.text_by_path.items():
161
+ if path not in shingle_cache:
162
+ shingle_cache[path] = _shingles(src_text)
163
+ s = similarity(incoming_shingles, shingle_cache[path])
164
+ if s >= best_sim:
165
+ best_path, best_sim = path, s
166
+ return best_path, best_sim
167
+
168
+
169
+ def classify_intake(inbox_dir: Path, sources_dir: Path, idx: SourceIndex) -> dict:
170
+ result = {"exact_dups": [], "near_dups": [],
171
+ "update_candidates": [], "clean_new": [], "inbox_dups": [],
172
+ "containers": []}
173
+ seen_in_batch: dict = {}
174
+ shingle_cache: dict = {} # source path -> shingle set, cached for this run only
175
+ for f in _iter_files(Path(inbox_dir)):
176
+ rel = str(f.relative_to(inbox_dir))
177
+
178
+ if f.suffix.lower() in CONTAINER_EXTS:
179
+ result["containers"].append({"incoming": rel, "size": f.stat().st_size})
180
+ continue
181
+
182
+ h = sha256_of(f)
183
+
184
+ if h in seen_in_batch:
185
+ result["inbox_dups"].append({
186
+ "incoming": rel,
187
+ "duplicate_of": seen_in_batch[h],
188
+ "hash": h,
189
+ "auto_skip": True,
190
+ })
191
+ continue
192
+ seen_in_batch[h] = rel
193
+
194
+ if h in idx.by_hash:
195
+ result["exact_dups"].append({
196
+ "incoming": rel,
197
+ "matched_source": idx.by_hash[h][0],
198
+ "hash": h,
199
+ "auto_skip": True,
200
+ })
201
+ continue
202
+
203
+ name_match = normalize_filename(f.name) in idx.by_name
204
+ incoming_shingles = _shingles(extract_text(f))
205
+ if incoming_shingles:
206
+ match_path, sim = _best_match(incoming_shingles, idx, shingle_cache)
207
+ else:
208
+ # No word-level evidence (binary/empty) — hash matching already
209
+ # ran above, and empty shingles can never score above 0.0, so
210
+ # skip the whole sources scan rather than pay for it.
211
+ match_path, sim = None, 0.0
212
+
213
+ if match_path is not None and sim >= NEAR_DUP_THRESHOLD:
214
+ result["near_dups"].append({
215
+ "incoming": rel, "matched_source": match_path,
216
+ "similarity": round(sim, 4), "auto_skip": False,
217
+ })
218
+ elif match_path is not None and sim >= UPDATE_SIM_FLOOR and name_match:
219
+ result["update_candidates"].append({
220
+ "incoming": rel, "matched_source": match_path,
221
+ "similarity": round(sim, 4),
222
+ "history": git_history(Path("sources") / match_path,
223
+ Path(sources_dir).parent),
224
+ "auto_skip": False,
225
+ })
226
+ elif match_path is not None and sim >= UPDATE_SIM_FLOOR:
227
+ result["near_dups"].append({
228
+ "incoming": rel, "matched_source": match_path,
229
+ "similarity": round(sim, 4), "auto_skip": False,
230
+ })
231
+ else:
232
+ result["clean_new"].append({"incoming": rel})
233
+ return result
234
+
235
+
236
+ def main(argv=None) -> int:
237
+ ap = argparse.ArgumentParser(
238
+ description="fusion-intake Stage-1 gate classifier")
239
+ ap.add_argument("--bucket", required=True, help="bucket root")
240
+ ap.add_argument("--out", help="manifest path "
241
+ "(default: <bucket>/workbench/.intake/gate-<runid>.json)")
242
+ args = ap.parse_args(argv)
243
+
244
+ root = Path(args.bucket).expanduser().resolve()
245
+ inbox, sources = root / "inbox", root / "sources"
246
+ if not inbox.is_dir() or not sources.is_dir():
247
+ print(f"not a bucket (no inbox/ + sources/): {root}")
248
+ return 1
249
+
250
+ idx = index_sources(sources)
251
+ buckets = classify_intake(inbox, sources, idx)
252
+ manifest = {
253
+ "version": 1,
254
+ "counts": {k: len(v) for k, v in buckets.items()},
255
+ "buckets": buckets,
256
+ }
257
+ out = Path(args.out) if args.out else (
258
+ root / "workbench" / ".intake" / f"gate-{uuid.uuid4().hex[:12]}.json")
259
+ out.parent.mkdir(parents=True, exist_ok=True)
260
+ out.write_text(json.dumps(manifest, indent=2), encoding="utf-8",
261
+ newline="\n")
262
+ print(f"gate: {manifest['counts']} -> {out}")
263
+ return 0
264
+
265
+
266
+ if __name__ == "__main__":
267
+ raise SystemExit(main())
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: fusion-librarian
3
+ description: "The Fusion librarian — the accountable owner of a bucket's order. One entry, eight gears: query (natural-language search over the bucket — the default), create (a new conformant document), tag (bulk frontmatter metadata), cross-reference (map what relates to what), promote (workbench → a real zone, validated), archive (out of the way, path + aurora agreeing), restructure (reorganize and sign your reasons), reflect (the reflection ritual: introspect the ledger, propose curation, land what was learned). Use for 'find', 'search', 'where is', 'create a document', 'tag', 'what links to', 'promote', 'archive this', 'reorganize', 'restructure', 'run a reflection'. For files arriving from outside use fusion-intake; for activity planning use fusion-planner; for reports and exports use fusion-analyst. Applies only inside a Fusion bucket — a directory tree with BUCKET.md and LEDGER.md at its root; if there is no such bucket in play, this skill does not apply."
4
+ license: MIT
5
+ compatibility: "Requires the fusion CLI on PATH."
6
+ ---
7
+
8
+ # fusion-librarian — the owner
9
+
10
+ The agent is the librarian: not a search box, an accountable owner. Clean,
11
+ bold, opinionated structures — and every structural act signed in the
12
+ ledger with its reasons.
13
+
14
+ Read `references/fusion-conventions.md` once per session. Before any gear:
15
+ read the bucket's `BUCKET.md` — `### Rules` bind how this bucket files
16
+ things; `### Delegations` say what you may do without asking. No
17
+ `BUCKET.md` up the tree and none named? Stop — this is not a Fusion
18
+ bucket, and no Fusion skill applies (`fusion hub` lists the real ones).
19
+
20
+ ## Pick the gear
21
+
22
+ Explicit intent beats inference. Bare or ambiguous intent defaults to
23
+ **query** — the one gear that changes nothing.
24
+
25
+ | Signal | Gear | Load |
26
+ |---|---|---|
27
+ | find / search / where is / what do we know about | query (default) | references/query.md |
28
+ | create / new document / write down | create | references/create.md |
29
+ | tag / label / set field across docs | tag | references/tag.md |
30
+ | what links to / related / map connections | cross-reference | references/cross-reference.md |
31
+ | promote / finalize / move out of workbench | promote | references/promote.md |
32
+ | archive / put away (documents — a finished *activity* closes via fusion-planner) | archive | references/archive.md |
33
+ | reorganize / restructure / new taxonomy | restructure | references/restructure.md |
34
+ | reflect / reflection / review the bucket | reflect | references/reflect.md |
35
+
36
+ Destructive gears — promote, archive, restructure — are never reached by
37
+ near-miss inference: if you arrived at one without the user naming it,
38
+ stop and confirm by name. reflect proposes destructive acts but executes
39
+ them only on a yes (or a standing delegation).
40
+
41
+ ## Ledger discipline (which hat, which verb)
42
+
43
+ | Gear | Verb logged |
44
+ |---|---|
45
+ | create | `created` |
46
+ | tag | `classified` |
47
+ | promote | `promoted` |
48
+ | archive | `archived` |
49
+ | restructure | `restructured` — always with a `--note` giving the reasons |
50
+ | reflect | `reflected` to sign the cycle; `noted` for each convention change |
51
+ | query | nothing — reading is free |
52
+ | cross-reference | nothing — unless approved link edits are applied, then `noted` |
53
+
54
+ All writes via `fusion log … --bucket <root> --as <you>`. After any gear
55
+ that adds, moves, or edits documents: `fusion index <root>`, then
56
+ `fusion check <root>` — green before you report done.
57
+
58
+ ## Never
59
+
60
+ - Never hand-edit `LEDGER.md`, `INDEX.md`, or `MANIFEST.md`.
61
+ - Never touch `sources/` — originals are immutable.
62
+ - Never restructure or archive without either a yes or a written
63
+ delegation in BUCKET.md.
64
+ - Never leave `fusion check` red behind you.
@@ -0,0 +1,21 @@
1
+ # archive — out of the way, never out of reach
2
+
3
+ Path is the truth, aurora is the signal — both, always, in one move.
4
+
5
+ 1. Confirm the target and the authority: explicit ask, or a standing
6
+ delegation in BUCKET.md `### Delegations` that covers this case
7
+ (cite it in the note when you use it).
8
+ 2. Move the document to `<its-zone>/archive/<same-folder-structure>/`.
9
+ 3. Repair what pointed at it: run `uv run <skill>/scripts/link-repair.py
10
+ scan --bucket <root>` and apply exact-confidence repairs per the
11
+ sweep protocol in [cross-reference.md](cross-reference.md) (or a
12
+ standing delegation); fuzzy always asks.
13
+ 4. Edit its frontmatter: `aurora: archive`.
14
+ 5. `fusion log archived "<zone>/<old> → <zone>/archive/<new>"
15
+ [--note "delegation: <rule>"] --bucket <root> --as <you>` ·
16
+ `fusion index <root>` · `fusion check <root>` — the checker's W3
17
+ watches exactly this agreement.
18
+
19
+ Activities are the planner's: closing or archiving one is
20
+ fusion-planner's close gear (it also writes the final Log line in
21
+ plan.md). This gear archives library/ and output/ documents.
@@ -0,0 +1,14 @@
1
+ # create — a document born conformant
2
+
3
+ 1. Establish title, type, aurora, and destination zone/folder. Infer type
4
+ and folder from the bucket's existing taxonomy and BUCKET.md Rules;
5
+ ask only when genuinely ambiguous. Aurora is the human-attention call —
6
+ propose, and let an explicit instruction override. Before writing,
7
+ triage `library/INDEX.md` for an existing document on the same
8
+ subject — extending or reconciling one beats creating a twin; when a
9
+ candidate exists, propose the edit instead and let the human choose.
10
+ 2. Write the document: the three required fields (plus `created:` today,
11
+ `tags:` when useful), `## Summary` (2–3 lines), `---`, then the body.
12
+ Filename: lowercase-hyphen slug, ≤60 chars.
13
+ 3. `fusion log created "<zone>/<path>" --bucket <root> --as <you>` ·
14
+ `fusion index <root>` · `fusion check <root>` green.
@@ -0,0 +1,45 @@
1
+ # cross-reference — map what relates to what
2
+
3
+ Read-only. For a target document or topic:
4
+
5
+ 1. Extract its entities (names, projects, products, periods).
6
+ 2. Sweep the bucket: direct mentions (grep), shared frontmatter values
7
+ (type, tags, aurora), thematic kinship (summaries that talk about the
8
+ same thing).
9
+ 3. Report three buckets, every line carrying a path:
10
+ **Direct references** · **Shared attributes** · **Thematic connections**.
11
+ 4. Offer (don't apply) link edits: "these two documents should point at
12
+ each other" — applying them is a content change the user approves.
13
+ On a yes: edit, then sign the write —
14
+ `fusion log noted "<path> — cross-linked to <other>" --bucket <root>
15
+ --as <you>` — then `fusion index <root>`, `fusion check <root>`.
16
+
17
+ ## Repair sweep — broken links, scan proposes, you approve, apply signs off
18
+
19
+ `scripts/link-repair.py` productizes the pattern above for the mechanical
20
+ case: a link that used to resolve and no longer does (a document moved, a
21
+ source got re-filed). It never guesses between candidates — the human
22
+ gate stays in the middle.
23
+
24
+ 1. `uv run <skill>/scripts/link-repair.py scan --bucket <root> >
25
+ proposals.json` — walks `library/` and `activities/`, tests every
26
+ relative link from the doc's own directory, and for each broken one
27
+ searches `sources/` and the doc zones by basename. JSON goes to
28
+ stdout, a human-readable table to stderr: **EXACT** (unique basename
29
+ match — safe to apply as-is), **FUZZY** (unique only after
30
+ lowercase/hyphen-insensitive normalization — confirm per group, never
31
+ silently folded in with the exact ones), **UNREPAIRABLE** (zero or
32
+ two-or-more candidates — never guessed, hand-fix or leave it).
33
+ 2. Show the human the table. Exact and fuzzy are always separated; a
34
+ fuzzy group is applied only on an explicit yes.
35
+ 3. Edit `proposals.json` down to the approved subset (delete the
36
+ proposals nobody confirmed; `unrepairable` entries are informational,
37
+ never fed to apply).
38
+ 4. `uv run <skill>/scripts/link-repair.py apply --bucket <root>
39
+ --proposals proposals.json` — rewrites only the approved pairs,
40
+ bumps (or adds) `updated:` on the documents it touched, prints the
41
+ count. It writes no ledger entry.
42
+ 5. Sign the pass yourself: `fusion log noted "link-repair sweep — <N>
43
+ links repaired (<E> exact, <F> fuzzy)" --bucket <root> --as <you>` —
44
+ then `fusion index <root>`, `fusion check <root>` green before you
45
+ report done.
@@ -0,0 +1,149 @@
1
+ # The Fusion Conventions — operator's card
2
+
3
+ > Carried byte-identical by all four Fusion skills. SPEC.md in the Fusion
4
+ > repository is normative; this card is the working summary. When they
5
+ > disagree, SPEC.md wins and this card has a bug.
6
+
7
+ **The contract:** the human judges, the AI operates, the files remember.
8
+
9
+ **Liberal reader, strict writer.** Never refuse to read a bucket because
10
+ something is missing or unknown. Never write into `library/`, `activities/`,
11
+ `output/`, or a register without satisfying every rule below first.
12
+
13
+ ## The bucket
14
+
15
+ ```
16
+ <bucket>/
17
+ ├── BUCKET.md # identity card + learned conventions
18
+ ├── LEDGER.md # append-only collaboration record
19
+ ├── inbox/ # drop zone — things arrive, nothing lives here
20
+ ├── sources/ # immutable originals + MANIFEST.md
21
+ ├── library/ # settled knowledge — documents
22
+ ├── activities/ # live work — documents + status
23
+ ├── workbench/ # ephemeral human+AI space — NO format rules
24
+ └── output/ # finished deliverables — documents
25
+ ```
26
+
27
+ - `sources/` is immutable: never modify, rename, or delete a registered file.
28
+ - `workbench/` has no rules; leaving it (promotion) is a deliberate,
29
+ ledger-logged act.
30
+ - Fusion holds knowledge and work, never media or code — documents point
31
+ (`resource:`) at big things, they never swallow them.
32
+ - `output/` may also hold non-markdown deliverable files (exports); their
33
+ names are still lowercase-hyphen slugs with a lowercase extension.
34
+
35
+ ## Before acting — always
36
+
37
+ 1. Read `BUCKET.md`: the identity card, then `## Conventions` — `### Rules`
38
+ are how this bucket works; `### Delegations` are your standing autonomy
39
+ grants. They bind you.
40
+ 2. Triage through `library/INDEX.md` and `activities/INDEX.md` plus document
41
+ summaries before opening bodies.
42
+
43
+ ## The document format
44
+
45
+ Every `.md` in `library/`, `activities/`, `output/` (except INDEX.md):
46
+
47
+ ```markdown
48
+ ---
49
+ title: Human-readable name
50
+ type: what-it-is # open vocabulary, curated per bucket
51
+ aurora: library # one of the eight — closed set
52
+ ---
53
+
54
+ ## Summary
55
+
56
+ Two or three lines a human or agent reads in two seconds.
57
+
58
+ ---
59
+
60
+ Full body. Cross-links are plain relative markdown links.
61
+ ```
62
+
63
+ - Required: `title`, `type`, `aurora` — exactly three.
64
+ - Optional: `tags`, `created`/`updated` (ISO dates), `due` (ISO date the
65
+ thing falls due — `fusion agenda` surfaces it), `source` (path into
66
+ `sources/`), `resource` (URI or bucket path of the thing this document
67
+ describes), `status` (`active`|`done`|`dormant`, activities only),
68
+ `data_sources` (paths list, output only).
69
+ - Body MUST be summary-first: `## Summary`, the lines, a `---` separator,
70
+ then everything else.
71
+ - Filenames: lowercase, hyphen-separated, `.md`, stem ≤60 chars.
72
+ - Preserve frontmatter keys you don't recognize.
73
+
74
+ ## Aurora — the eight (closed)
75
+
76
+ | Aurora | Meaning |
77
+ |---|---|
78
+ | `commitments` | Obligations, promises, deadlines |
79
+ | `focus` | Deep work, what deserves full attention |
80
+ | `ops` | Operations, process, the recurring |
81
+ | `collab` | Shared work, other people involved |
82
+ | `life` | Personal, wellbeing, the non-work |
83
+ | `explore` | Curiosity, research, the not-yet-settled |
84
+ | `archive` | Done, kept, out of the way |
85
+ | `library` | Reference, the settled knowledge |
86
+
87
+ Aurora says what a document means for the human's attention — never invent
88
+ a ninth value.
89
+
90
+ ## Archive
91
+
92
+ No archive zone: archived items move to an `archive/` subfolder inside their
93
+ zone AND take `aurora: archive`. Path is the truth, aurora is the signal —
94
+ both, always.
95
+
96
+ ## The registers — single writers, no exceptions
97
+
98
+ | File | Only writer | Your move |
99
+ |---|---|---|
100
+ | `LEDGER.md` | `fusion log` | `fusion log <verb> "<object>" [--note "…"] --as <you>` |
101
+ | `library/INDEX.md`, `activities/INDEX.md` | `fusion index` | run it after any add/move/edit that changes titles or summaries |
102
+ | `sources/MANIFEST.md` | fusion-intake's `scripts/convert.py` | everything enters `sources/` through the intake gate |
103
+
104
+ Never edit these three files by hand — not with an editor tool, not with
105
+ shell. The ledger verbs (closed set of eleven): `created`, `converted`,
106
+ `classified`, `indexed`, `moved`, `promoted`, `archived`, `restructured`,
107
+ `shipped`, `reflected`, `noted`. Sign with your agent name (`--as claude`,
108
+ or set `FUSION_ACTOR`).
109
+
110
+ ## The CLI crib
111
+
112
+ | Command | What it does |
113
+ |---|---|
114
+ | `fusion new <path>` | scaffold a conformant bucket |
115
+ | `fusion hub [add\|remove]` | list / register / retire buckets |
116
+ | `fusion log <verb> <object>` | append a signed ledger entry |
117
+ | `fusion index` | regenerate INDEX files (logs `indexed` when changed) |
118
+ | `fusion check [path]` | conformance: errors, warnings, honest exit codes |
119
+ | `fusion status [--since …]` | one bucket at a glance |
120
+ | `fusion today` | the composed morning across all hub buckets |
121
+ | `fusion agenda` | dated + active items across buckets |
122
+ | `fusion setup` | install/refresh the skills into detected agents |
123
+
124
+ All take `--json`. `--since last-reflection` scopes to the current
125
+ reflection window. **Exit gate for every skill scenario: `fusion check`
126
+ green before you call the work done.**
127
+
128
+ ## When you're blocked
129
+
130
+ - `fusion` not on PATH: stop and tell the human — the install is
131
+ `uv tool install ./fusion/cli` from a clone of the Fusion repository.
132
+ Never imitate the notary by hand: no register writes while the CLI is
133
+ missing.
134
+ - `fusion check` red and you cannot fix it: stop, show the findings
135
+ verbatim, leave the bucket as it stands, and sign nothing that claims
136
+ the work is done. A bucket is a git repo — nothing is unrecoverable.
137
+ - The human rejects a proposal: that is a result, not a failure. Record
138
+ it if the gear's protocol says to (`noted`), and move on.
139
+
140
+ ## The four accountabilities
141
+
142
+ | Skill | Owns |
143
+ |---|---|
144
+ | fusion-intake | The gate. Everything that enters, enters through it. |
145
+ | fusion-librarian | The order. Placement, curation, restructuring, reflection. |
146
+ | fusion-planner | The horizon. Activities, agendas, what today looks like. |
147
+ | fusion-analyst | The output. Deliverables that cite their sources. |
148
+
149
+ One skill, one accountability — the ledger says which hat was worn.
@@ -0,0 +1,24 @@
1
+ # promote — leaving the workbench is a deliberate act
2
+
3
+ workbench/ has no rules; the zones do. Promotion is the moment the rules
4
+ attach.
5
+
6
+ Pre-flight (all three, before anything moves):
7
+ 1. Explicit invocation confirmed — arrived here by inference? Stop, ask.
8
+ 2. Source is in `workbench/`; destination zone/folder named or clearly
9
+ inferable from the document's nature (`library/` knowledge,
10
+ `activities/` live work, `output/` deliverables). Ask if unclear.
11
+ 3. State the plan in one sentence: "Promoting workbench/<x> to
12
+ <zone>/<folder>/<slug>.md."
13
+
14
+ Validate BEFORE moving (strict writer — a failed check stops the
15
+ promotion, nothing moves):
16
+ - frontmatter parses; `title`, `type`, `aurora` present; aurora one of
17
+ the eight; summary-first body; slug filename ≤60.
18
+ - activities documents: `status` present. output documents: encourage
19
+ `data_sources`.
20
+ Fix what the user wants fixed, or leave it in the workbench.
21
+
22
+ Then: move the file (Write new + delete old, or `mv`), then
23
+ `fusion log promoted "workbench/<x> → <zone>/<path>" --bucket <root>
24
+ --as <you>` · `fusion index <root>` · `fusion check <root>` green.
@@ -0,0 +1,17 @@
1
+ # query — answer from the bucket, cited
2
+
3
+ Read-only. Changes nothing, logs nothing.
4
+
5
+ 1. Parse the question: entities, concepts, constraints (type, aurora,
6
+ dates, status).
7
+ 2. Triage via `library/INDEX.md` + `activities/INDEX.md` — titles and
8
+ summary lines first. `fusion status` helps scope what exists.
9
+ 3. Grep frontmatter for structured hits (`type:`, `aurora:`, `tags:`),
10
+ then content for the rest.
11
+ 4. Read the most relevant documents — summaries first, bodies only for
12
+ the finalists (keep it under ~10 full reads).
13
+ 5. Answer with a `## Sources` table: document path · what it contributed.
14
+ Every claim cites a path. If the bucket is silent on part of the
15
+ question, say what is known and what is missing — never fill gaps
16
+ from your own general knowledge without labelling it as outside the
17
+ bucket.
@@ -0,0 +1,47 @@
1
+ # reflect — the metabolism (SPEC §10)
2
+
3
+ The files remember → you reflect → the human judges → the system learns.
4
+ Run on the bucket's `reflection_cadence`, or when asked.
5
+
6
+ ## 1. Introspect
7
+
8
+ ```bash
9
+ fusion log --since last-reflection --bucket <root> # what happened
10
+ fusion status <root> --since last-reflection # counts, auroras…
11
+ fusion check <root> # drift: stale INDEX, W1 inbox age…
12
+ ```
13
+
14
+ Read the window's ledger like telemetry: what got converted, promoted,
15
+ touched — and what never did.
16
+
17
+ ## 2. Curate & prune — the proposal list
18
+
19
+ Propose, with paths and evidence, whichever apply:
20
+ - workbench items older than the window → promote or expire;
21
+ - activities with `status: active` and no ledger touch → `dormant` or
22
+ archive (W5 already names them);
23
+ - superseded or stale library documents → `archive/`;
24
+ - duplicates to merge, fat documents to split;
25
+ - summaries that no longer match their bodies;
26
+ - taxonomy that stopped serving → a restructure proposal;
27
+ - rules the window taught: "we always filed X under Y — make it a Rule?"
28
+ - delegations earned: acts the human approved repeatedly this window.
29
+
30
+ ## 3. Judge
31
+
32
+ Present the list. Destructive and archival acts need a yes — EXCEPT where
33
+ `### Delegations` already grants it (cite the delegation, act, and note
34
+ it). Never bundle: the human can take proposal 3 and refuse proposal 4.
35
+
36
+ ## 4. Learn & sign
37
+
38
+ - Approved rule/delegation changes: edit `BUCKET.md ## Conventions`, one
39
+ `fusion log noted "BUCKET.md — <the change>" --bucket <root> --as <you>`
40
+ per change.
41
+ - Execute approved acts via their own gears (archive, restructure…) so
42
+ each carries its own verb.
43
+ - Sign the cycle LAST:
44
+ `fusion log reflected "<bucket> — <N> proposals, <M> adopted"
45
+ --bucket <root> --as <you>`
46
+ - Episodic reports are ephemeral: the proposal list lives in workbench/
47
+ and can die there; only conventions and ledger entries persist.
@@ -0,0 +1,20 @@
1
+ # restructure — ownership means showing your reasons
2
+
3
+ The librarian's distinctive power: reorganizing the bucket when the
4
+ taxonomy stopped serving. Also the most destructive gear — so it runs as
5
+ propose → confirm → execute → sign.
6
+
7
+ 1. **Propose.** Read the zone's INDEX + folder shape. Write the plan:
8
+ every move (`old → new`), every folder created or retired, and the
9
+ REASON in one paragraph. Check `BUCKET.md ### Rules` — a restructure
10
+ that contradicts a Rule needs the Rule changed first (that is a
11
+ reflect-gear conversation).
12
+ 2. **Confirm.** The human says yes (or a Delegation explicitly covers
13
+ it — rare for restructures; cite it).
14
+ 3. **Execute.** Move files; update relative links in affected documents
15
+ (grep for the old paths); keep filenames conformant.
16
+ 4. **Sign.** One ledger entry for the operation:
17
+ `fusion log restructured "<zone>/<scope>" --note "<the reason>"
18
+ --bucket <root> --as <you>` — the note is not optional here. Then
19
+ `fusion index <root>` and `fusion check <root>` (W4 watches the links
20
+ you just moved).