fusion-cli 1.1.1__tar.gz → 1.2.0__tar.gz
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.
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/PKG-INFO +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/pyproject.toml +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/SKILL.md +6 -3
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/SKILL.md +6 -3
- {fusion_cli-1.1.1/skills/fusion-librarian → fusion_cli-1.2.0/skills/fusion-intake}/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/SKILL.md +6 -3
- {fusion_cli-1.1.1/skills/fusion-planner → fusion_cli-1.2.0/skills/fusion-librarian}/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-planner/SKILL.md +6 -3
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-planner/references/create-activity.md +21 -0
- {fusion_cli-1.1.1/skills/fusion-intake → fusion_cli-1.2.0/skills/fusion-planner}/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/checker.py +29 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/cli.py +55 -8
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/scaffold.py +11 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/views.py +12 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_checker_warnings.py +30 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_cli.py +42 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_hub_bucket.py +2 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_ledger.py +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_roundtrip.py +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_scaffold.py +10 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_views.py +11 -7
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/uv.lock +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/.gitignore +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/README.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/hatch_build.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/references/assess.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/references/compare.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/references/export.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/references/report.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/scripts/export.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/references/convert.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/references/delivery.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/references/gate.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/scripts/convert.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/scripts/gate.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/tests/conftest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/tests/make_fixtures.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/tests/test_convert.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/tests/test_gate.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-intake/tests/test_integration.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/archive.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/create.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/cross-reference.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/promote.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/query.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/reflect.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/restructure.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/references/tag.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/scripts/link-repair.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/tests/conftest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-librarian/tests/test_link_repair.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-planner/references/close.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-planner/references/horizon.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/__init__.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/bucket.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/document.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/hub.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/indexer.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/ledger.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/manifest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/src/fusion/setup.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/__init__.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/conftest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_build_payload.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_checker_errors.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_cli_encoding.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_cli_setup.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_cli_version.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_document.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_indexer.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_line_endings.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_setup_agents.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_setup_core.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.0}/tests/test_skill_family.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fusion-analyst
|
|
3
|
-
description: "The Fusion analyst — deliverables out of the bucket. Four gears: report (multi-section analysis from library + activities — the default), assess (scored assessment with explicit criteria and scale), compare (side-by-side matrix with deltas and red flags), export (CSV/JSON/XLSX data extracts). Everything lands in output/ as a summary-first document, cites every source path in data_sources, and ships signed in the ledger. Use for 'report on', 'analyze and write up', 'brief', 'assess', 'evaluate', 'score', 'compare X and Y', 'export as csv/excel'. For searching without producing a deliverable use fusion-librarian's query; for new documents of record use fusion-librarian's create. 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,
|
|
3
|
+
description: "The Fusion analyst — deliverables out of the bucket. Four gears: report (multi-section analysis from library + activities — the default), assess (scored assessment with explicit criteria and scale), compare (side-by-side matrix with deltas and red flags), export (CSV/JSON/XLSX data extracts). Everything lands in output/ as a summary-first document, cites every source path in data_sources, and ships signed in the ledger. Use for 'report on', 'analyze and write up', 'brief', 'assess', 'evaluate', 'score', 'compare X and Y', 'export as csv/excel'. For searching without producing a deliverable use fusion-librarian's query; for new documents of record use fusion-librarian's create. 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, offer to create one with `fusion new` instead of improvising structure."
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: "Requires the fusion CLI on PATH; uv for the export script (PEP 723: openpyxl)."
|
|
6
6
|
---
|
|
@@ -13,8 +13,11 @@ document cites the exact paths it was built from, and ships with a
|
|
|
13
13
|
|
|
14
14
|
Read `references/fusion-conventions.md` once per session; read
|
|
15
15
|
`BUCKET.md ## Conventions` before acting. No `BUCKET.md` up the tree and
|
|
16
|
-
none named?
|
|
17
|
-
applies
|
|
16
|
+
none named? Then nothing here is a Fusion bucket and no Fusion skill
|
|
17
|
+
applies — but offer the door instead of walking away: `fusion new
|
|
18
|
+
<path>` scaffolds a conformant bucket and registers it in the hub
|
|
19
|
+
(`fusion hub` lists the ones that exist). Buckets are life-domains —
|
|
20
|
+
few and bold — never improvised by hand.
|
|
18
21
|
|
|
19
22
|
## Pick the gear
|
|
20
23
|
|
{fusion_cli-1.1.1 → fusion_cli-1.2.0}/skills/fusion-analyst/references/fusion-conventions.md
RENAMED
|
@@ -107,6 +107,11 @@ shell. The ledger verbs (closed set of eleven): `created`, `converted`,
|
|
|
107
107
|
`shipped`, `reflected`, `noted`. Sign with your agent name (`--as claude`,
|
|
108
108
|
or set `FUSION_ACTOR`).
|
|
109
109
|
|
|
110
|
+
On a bucket that lives on more than one machine, git's union driver
|
|
111
|
+
(`.gitattributes`, written at bucket birth) merges parallel `LEDGER.md`
|
|
112
|
+
and `MANIFEST.md` appends. After any git merge: `fusion index`, then
|
|
113
|
+
`fusion check`.
|
|
114
|
+
|
|
110
115
|
## The CLI crib
|
|
111
116
|
|
|
112
117
|
| Command | What it does |
|
|
@@ -116,6 +121,7 @@ or set `FUSION_ACTOR`).
|
|
|
116
121
|
| `fusion log <verb> <object>` | append a signed ledger entry |
|
|
117
122
|
| `fusion index` | regenerate INDEX files (logs `indexed` when changed) |
|
|
118
123
|
| `fusion check [path]` | conformance: errors, warnings, honest exit codes |
|
|
124
|
+
| `fusion check --hub` | is every registered bucket where the hub says? |
|
|
119
125
|
| `fusion status [--since …]` | one bucket at a glance |
|
|
120
126
|
| `fusion today` | the composed morning across all hub buckets |
|
|
121
127
|
| `fusion agenda` | dated + active items across buckets |
|
|
@@ -127,8 +133,14 @@ green before you call the work done.**
|
|
|
127
133
|
|
|
128
134
|
## When you're blocked
|
|
129
135
|
|
|
136
|
+
- No bucket in play anywhere: never improvise Fusion structure by hand.
|
|
137
|
+
Offer `fusion new <path> --kind <kind> --description "…"` — six zones,
|
|
138
|
+
BUCKET.md, an opened ledger, a merge-safe `.gitattributes`, registered
|
|
139
|
+
in the hub. Guide the human toward few, bold, life-domain buckets
|
|
140
|
+
(personal, one per company, a studio).
|
|
130
141
|
- `fusion` not on PATH: stop and tell the human — the install is
|
|
131
|
-
`uv tool install
|
|
142
|
+
`uv tool install fusion-cli` (or, from a clone of the Fusion
|
|
143
|
+
repository, `uv tool install ./fusion/cli`).
|
|
132
144
|
Never imitate the notary by hand: no register writes while the CLI is
|
|
133
145
|
missing.
|
|
134
146
|
- `fusion check` red and you cannot fix it: stop, show the findings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fusion-intake
|
|
3
|
-
description: "The Fusion intake gate — everything that enters a bucket enters through it, losslessly. Classify what landed in inbox/ (new, updated, duplicate, conflicting), preserve the original in sources/ with its sha256 in the MANIFEST, convert to a faithful summary-first document (xlsx, csv, docx, pptx, pdf, html, images, .eml mail, markdown/text exports), propose type and aurora, sign the ledger, clear the inbox. Use when the user says 'process the inbox', 'intake', 'ingest', 'convert this file', or drops files into a Fusion bucket's inbox/. For placement, curation, or restructuring of what is already inside, use fusion-librarian; for deliverables out of the bucket, 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,
|
|
3
|
+
description: "The Fusion intake gate — everything that enters a bucket enters through it, losslessly. Classify what landed in inbox/ (new, updated, duplicate, conflicting), preserve the original in sources/ with its sha256 in the MANIFEST, convert to a faithful summary-first document (xlsx, csv, docx, pptx, pdf, html, images, .eml mail, markdown/text exports), propose type and aurora, sign the ledger, clear the inbox. Use when the user says 'process the inbox', 'intake', 'ingest', 'convert this file', or drops files into a Fusion bucket's inbox/. For placement, curation, or restructuring of what is already inside, use fusion-librarian; for deliverables out of the bucket, 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, offer to create one with `fusion new` instead of improvising structure."
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: "Requires the fusion CLI on PATH and uv. LibreOffice (soffice on PATH) required for docx/pptx/legacy office/html formats — fails fast when missing, never silently degrades. Script deps via PEP 723 (openpyxl, PyYAML, pymupdf)."
|
|
6
6
|
---
|
|
@@ -14,8 +14,11 @@ that isn't clean and new.
|
|
|
14
14
|
Read `references/fusion-conventions.md` once per session. Then, before
|
|
15
15
|
touching anything: read the bucket's `BUCKET.md` — `## Conventions` may
|
|
16
16
|
contain filing rules and standing delegations that bind this whole run.
|
|
17
|
-
No `BUCKET.md` up the tree and none named?
|
|
18
|
-
bucket
|
|
17
|
+
No `BUCKET.md` up the tree and none named? Then nothing here is a
|
|
18
|
+
Fusion bucket and no Fusion skill applies — but offer the door instead
|
|
19
|
+
of walking away: `fusion new <path>` scaffolds a conformant bucket and
|
|
20
|
+
registers it in the hub (`fusion hub` lists the ones that exist).
|
|
21
|
+
Buckets are life-domains — few and bold — never improvised by hand.
|
|
19
22
|
|
|
20
23
|
## The pipeline
|
|
21
24
|
|
|
@@ -107,6 +107,11 @@ shell. The ledger verbs (closed set of eleven): `created`, `converted`,
|
|
|
107
107
|
`shipped`, `reflected`, `noted`. Sign with your agent name (`--as claude`,
|
|
108
108
|
or set `FUSION_ACTOR`).
|
|
109
109
|
|
|
110
|
+
On a bucket that lives on more than one machine, git's union driver
|
|
111
|
+
(`.gitattributes`, written at bucket birth) merges parallel `LEDGER.md`
|
|
112
|
+
and `MANIFEST.md` appends. After any git merge: `fusion index`, then
|
|
113
|
+
`fusion check`.
|
|
114
|
+
|
|
110
115
|
## The CLI crib
|
|
111
116
|
|
|
112
117
|
| Command | What it does |
|
|
@@ -116,6 +121,7 @@ or set `FUSION_ACTOR`).
|
|
|
116
121
|
| `fusion log <verb> <object>` | append a signed ledger entry |
|
|
117
122
|
| `fusion index` | regenerate INDEX files (logs `indexed` when changed) |
|
|
118
123
|
| `fusion check [path]` | conformance: errors, warnings, honest exit codes |
|
|
124
|
+
| `fusion check --hub` | is every registered bucket where the hub says? |
|
|
119
125
|
| `fusion status [--since …]` | one bucket at a glance |
|
|
120
126
|
| `fusion today` | the composed morning across all hub buckets |
|
|
121
127
|
| `fusion agenda` | dated + active items across buckets |
|
|
@@ -127,8 +133,14 @@ green before you call the work done.**
|
|
|
127
133
|
|
|
128
134
|
## When you're blocked
|
|
129
135
|
|
|
136
|
+
- No bucket in play anywhere: never improvise Fusion structure by hand.
|
|
137
|
+
Offer `fusion new <path> --kind <kind> --description "…"` — six zones,
|
|
138
|
+
BUCKET.md, an opened ledger, a merge-safe `.gitattributes`, registered
|
|
139
|
+
in the hub. Guide the human toward few, bold, life-domain buckets
|
|
140
|
+
(personal, one per company, a studio).
|
|
130
141
|
- `fusion` not on PATH: stop and tell the human — the install is
|
|
131
|
-
`uv tool install
|
|
142
|
+
`uv tool install fusion-cli` (or, from a clone of the Fusion
|
|
143
|
+
repository, `uv tool install ./fusion/cli`).
|
|
132
144
|
Never imitate the notary by hand: no register writes while the CLI is
|
|
133
145
|
missing.
|
|
134
146
|
- `fusion check` red and you cannot fix it: stop, show the findings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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,
|
|
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, offer to create one with `fusion new` instead of improvising structure."
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: "Requires the fusion CLI on PATH."
|
|
6
6
|
---
|
|
@@ -14,8 +14,11 @@ ledger with its reasons.
|
|
|
14
14
|
Read `references/fusion-conventions.md` once per session. Before any gear:
|
|
15
15
|
read the bucket's `BUCKET.md` — `### Rules` bind how this bucket files
|
|
16
16
|
things; `### Delegations` say what you may do without asking. No
|
|
17
|
-
`BUCKET.md` up the tree and none named?
|
|
18
|
-
bucket
|
|
17
|
+
`BUCKET.md` up the tree and none named? Then nothing here is a Fusion
|
|
18
|
+
bucket and no Fusion skill applies — but offer the door instead of
|
|
19
|
+
walking away: `fusion new <path>` scaffolds a conformant bucket and
|
|
20
|
+
registers it in the hub (`fusion hub` lists the ones that exist).
|
|
21
|
+
Buckets are life-domains — few and bold — never improvised by hand.
|
|
19
22
|
|
|
20
23
|
## Pick the gear
|
|
21
24
|
|
|
@@ -107,6 +107,11 @@ shell. The ledger verbs (closed set of eleven): `created`, `converted`,
|
|
|
107
107
|
`shipped`, `reflected`, `noted`. Sign with your agent name (`--as claude`,
|
|
108
108
|
or set `FUSION_ACTOR`).
|
|
109
109
|
|
|
110
|
+
On a bucket that lives on more than one machine, git's union driver
|
|
111
|
+
(`.gitattributes`, written at bucket birth) merges parallel `LEDGER.md`
|
|
112
|
+
and `MANIFEST.md` appends. After any git merge: `fusion index`, then
|
|
113
|
+
`fusion check`.
|
|
114
|
+
|
|
110
115
|
## The CLI crib
|
|
111
116
|
|
|
112
117
|
| Command | What it does |
|
|
@@ -116,6 +121,7 @@ or set `FUSION_ACTOR`).
|
|
|
116
121
|
| `fusion log <verb> <object>` | append a signed ledger entry |
|
|
117
122
|
| `fusion index` | regenerate INDEX files (logs `indexed` when changed) |
|
|
118
123
|
| `fusion check [path]` | conformance: errors, warnings, honest exit codes |
|
|
124
|
+
| `fusion check --hub` | is every registered bucket where the hub says? |
|
|
119
125
|
| `fusion status [--since …]` | one bucket at a glance |
|
|
120
126
|
| `fusion today` | the composed morning across all hub buckets |
|
|
121
127
|
| `fusion agenda` | dated + active items across buckets |
|
|
@@ -127,8 +133,14 @@ green before you call the work done.**
|
|
|
127
133
|
|
|
128
134
|
## When you're blocked
|
|
129
135
|
|
|
136
|
+
- No bucket in play anywhere: never improvise Fusion structure by hand.
|
|
137
|
+
Offer `fusion new <path> --kind <kind> --description "…"` — six zones,
|
|
138
|
+
BUCKET.md, an opened ledger, a merge-safe `.gitattributes`, registered
|
|
139
|
+
in the hub. Guide the human toward few, bold, life-domain buckets
|
|
140
|
+
(personal, one per company, a studio).
|
|
130
141
|
- `fusion` not on PATH: stop and tell the human — the install is
|
|
131
|
-
`uv tool install
|
|
142
|
+
`uv tool install fusion-cli` (or, from a clone of the Fusion
|
|
143
|
+
repository, `uv tool install ./fusion/cli`).
|
|
132
144
|
Never imitate the notary by hand: no register writes while the CLI is
|
|
133
145
|
missing.
|
|
134
146
|
- `fusion check` red and you cannot fix it: stop, show the findings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fusion-planner
|
|
3
|
-
description: "The Fusion planner — the horizon keeper. The human drives activities; the planner structures them. Three gears: create-activity (a new activity folder with a stateful plan document — works for plans, campaigns, programs, agendas alike), horizon (review what's live: stalled activities, expiring commitments, what fusion today will show tomorrow), close (finish an activity: status done, archived, out of the way). Use for 'new project/activity/campaign', 'plan this', 'what's on my plate', 'review my activities', 'what's stalled', 'agenda', 'close/finish this activity'. For placing knowledge use fusion-librarian; for deliverables 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,
|
|
3
|
+
description: "The Fusion planner — the horizon keeper. The human drives activities; the planner structures them. Three gears: create-activity (a new activity folder with a stateful plan document — works for plans, campaigns, programs, agendas alike), horizon (review what's live: stalled activities, expiring commitments, what fusion today will show tomorrow), close (finish an activity: status done, archived, out of the way). Use for 'new project/activity/campaign', 'plan this', 'what's on my plate', 'review my activities', 'what's stalled', 'agenda', 'close/finish this activity'. For placing knowledge use fusion-librarian; for deliverables 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, offer to create one with `fusion new` instead of improvising structure."
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: "Requires the fusion CLI on PATH."
|
|
6
6
|
---
|
|
@@ -13,8 +13,11 @@ composes tomorrow morning is this skill's report card.
|
|
|
13
13
|
|
|
14
14
|
Read `references/fusion-conventions.md` once per session; read the
|
|
15
15
|
bucket's `BUCKET.md ## Conventions` before acting. No `BUCKET.md` up the
|
|
16
|
-
tree and none named?
|
|
17
|
-
skill applies
|
|
16
|
+
tree and none named? Then nothing here is a Fusion bucket and no
|
|
17
|
+
Fusion skill applies — but offer the door instead of walking away:
|
|
18
|
+
`fusion new <path>` scaffolds a conformant bucket and registers it in
|
|
19
|
+
the hub (`fusion hub` lists the ones that exist). Buckets are
|
|
20
|
+
life-domains — few and bold — never improvised by hand.
|
|
18
21
|
|
|
19
22
|
## Pick the gear
|
|
20
23
|
|
|
@@ -36,3 +36,24 @@
|
|
|
36
36
|
4. `fusion log created "activities/<slug>/plan.md" --bucket <root>
|
|
37
37
|
--as <you>` · `fusion index <root>` · `fusion check <root>` green.
|
|
38
38
|
Then show the human what `fusion today` now includes.
|
|
39
|
+
|
|
40
|
+
## The external project
|
|
41
|
+
|
|
42
|
+
An activity about building something that is code — an app, a site, a
|
|
43
|
+
tool — keeps the repository OUTSIDE the bucket (Fusion holds knowledge
|
|
44
|
+
and work, never code). Point at it instead:
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
title: <name>
|
|
48
|
+
type: plan
|
|
49
|
+
aurora: focus
|
|
50
|
+
status: active
|
|
51
|
+
created: <today>
|
|
52
|
+
resource: <path or URL of the repository>
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
The repo keeps its own history; the activity keeps what the code cannot
|
|
56
|
+
remember — the plan, the decisions, the progress log, links to the
|
|
57
|
+
bucket documents it draws on. The normative example:
|
|
58
|
+
`examples/crazy-ones/activities/band-site/plan.md` in the Fusion
|
|
59
|
+
repository.
|
|
@@ -107,6 +107,11 @@ shell. The ledger verbs (closed set of eleven): `created`, `converted`,
|
|
|
107
107
|
`shipped`, `reflected`, `noted`. Sign with your agent name (`--as claude`,
|
|
108
108
|
or set `FUSION_ACTOR`).
|
|
109
109
|
|
|
110
|
+
On a bucket that lives on more than one machine, git's union driver
|
|
111
|
+
(`.gitattributes`, written at bucket birth) merges parallel `LEDGER.md`
|
|
112
|
+
and `MANIFEST.md` appends. After any git merge: `fusion index`, then
|
|
113
|
+
`fusion check`.
|
|
114
|
+
|
|
110
115
|
## The CLI crib
|
|
111
116
|
|
|
112
117
|
| Command | What it does |
|
|
@@ -116,6 +121,7 @@ or set `FUSION_ACTOR`).
|
|
|
116
121
|
| `fusion log <verb> <object>` | append a signed ledger entry |
|
|
117
122
|
| `fusion index` | regenerate INDEX files (logs `indexed` when changed) |
|
|
118
123
|
| `fusion check [path]` | conformance: errors, warnings, honest exit codes |
|
|
124
|
+
| `fusion check --hub` | is every registered bucket where the hub says? |
|
|
119
125
|
| `fusion status [--since …]` | one bucket at a glance |
|
|
120
126
|
| `fusion today` | the composed morning across all hub buckets |
|
|
121
127
|
| `fusion agenda` | dated + active items across buckets |
|
|
@@ -127,8 +133,14 @@ green before you call the work done.**
|
|
|
127
133
|
|
|
128
134
|
## When you're blocked
|
|
129
135
|
|
|
136
|
+
- No bucket in play anywhere: never improvise Fusion structure by hand.
|
|
137
|
+
Offer `fusion new <path> --kind <kind> --description "…"` — six zones,
|
|
138
|
+
BUCKET.md, an opened ledger, a merge-safe `.gitattributes`, registered
|
|
139
|
+
in the hub. Guide the human toward few, bold, life-domain buckets
|
|
140
|
+
(personal, one per company, a studio).
|
|
130
141
|
- `fusion` not on PATH: stop and tell the human — the install is
|
|
131
|
-
`uv tool install
|
|
142
|
+
`uv tool install fusion-cli` (or, from a clone of the Fusion
|
|
143
|
+
repository, `uv tool install ./fusion/cli`).
|
|
132
144
|
Never imitate the notary by hand: no register writes while the CLI is
|
|
133
145
|
missing.
|
|
134
146
|
- `fusion check` red and you cannot fix it: stop, show the findings
|
|
@@ -6,7 +6,7 @@ import time
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from . import indexer, ledger, manifest
|
|
9
|
+
from . import hub, indexer, ledger, manifest
|
|
10
10
|
from .bucket import DOC_ZONES, REQUIRED_BUCKET_FIELDS, ZONES, load, iter_documents
|
|
11
11
|
from .document import AURORAS, FILENAME_RE, MAX_STEM
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ EXPORT_FILENAME_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+$")
|
|
|
19
19
|
@dataclass
|
|
20
20
|
class Finding:
|
|
21
21
|
level: str # "error" | "warning"
|
|
22
|
-
code: str # E1..E8, W1..W5
|
|
22
|
+
code: str # E1..E8, W1..W5 — plus H1..H2 from check_hub (CLI vocabulary, not SPEC)
|
|
23
23
|
path: str # bucket-relative, "" when bucket-wide
|
|
24
24
|
message: str
|
|
25
25
|
|
|
@@ -246,3 +246,30 @@ def _w5_untouched_activities(root: Path) -> list[Finding]:
|
|
|
246
246
|
"active activity with no ledger mention across the last "
|
|
247
247
|
"reflection window"))
|
|
248
248
|
return findings
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def check_hub() -> list[Finding]:
|
|
252
|
+
"""The hub's own audit — dangling entries and name drift.
|
|
253
|
+
|
|
254
|
+
H-codes are CLI vocabulary, not SPEC conformance codes: the hub is a
|
|
255
|
+
per-machine register and consumers must tolerate anything in it. All
|
|
256
|
+
findings are warnings; a hub audit never fails.
|
|
257
|
+
"""
|
|
258
|
+
findings: list[Finding] = []
|
|
259
|
+
for entry in hub.load():
|
|
260
|
+
root = hub.resolve(entry)
|
|
261
|
+
if not (root / "BUCKET.md").is_file():
|
|
262
|
+
findings.append(Finding(
|
|
263
|
+
"warning", "H1", entry.path,
|
|
264
|
+
f"'{entry.name}' — no bucket at this path; clone it "
|
|
265
|
+
f"there, or `fusion hub remove {entry.name}`",
|
|
266
|
+
))
|
|
267
|
+
continue
|
|
268
|
+
name = load(root).name
|
|
269
|
+
if name and name != entry.name:
|
|
270
|
+
findings.append(Finding(
|
|
271
|
+
"warning", "H2", entry.path,
|
|
272
|
+
f"hub says '{entry.name}' but BUCKET.md says '{name}' — "
|
|
273
|
+
"re-register: `fusion hub remove` then `fusion hub add`",
|
|
274
|
+
))
|
|
275
|
+
return findings
|
|
@@ -88,11 +88,20 @@ def cmd_hub(args) -> int:
|
|
|
88
88
|
f"'{args.name}' retired from the hub. The files live on.")
|
|
89
89
|
return 0
|
|
90
90
|
entries = hub.load()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
flagged = [
|
|
92
|
+
(e, not (hub.resolve(e) / "BUCKET.md").is_file()) for e in entries
|
|
93
|
+
]
|
|
94
|
+
lines = []
|
|
95
|
+
for e, missing in flagged:
|
|
96
|
+
lines.append(f"- **{e.name}** · {e.kind} · {e.path} — {e.description}")
|
|
97
|
+
if missing:
|
|
98
|
+
lines.append(
|
|
99
|
+
f" ⚠ no bucket at that path — clone it there, "
|
|
100
|
+
f"or `fusion hub remove {e.name}`"
|
|
101
|
+
)
|
|
102
|
+
human = "\n".join(lines) or "the hub is empty — `fusion new` a bucket."
|
|
103
|
+
_emit([{**asdict(e), "missing": missing} for e, missing in flagged],
|
|
104
|
+
args.json, human)
|
|
96
105
|
return 0
|
|
97
106
|
|
|
98
107
|
|
|
@@ -140,12 +149,36 @@ def cmd_index(args) -> int:
|
|
|
140
149
|
return 0
|
|
141
150
|
|
|
142
151
|
|
|
152
|
+
def _check_hub(args) -> int:
|
|
153
|
+
findings = checker.check_hub()
|
|
154
|
+
entries = hub.load()
|
|
155
|
+
if args.json:
|
|
156
|
+
_emit({
|
|
157
|
+
"hub": str(hub.hub_path()),
|
|
158
|
+
"buckets": len(entries),
|
|
159
|
+
"ok": not findings,
|
|
160
|
+
"warnings": [asdict(f) for f in findings],
|
|
161
|
+
}, True, "")
|
|
162
|
+
else:
|
|
163
|
+
for f in findings:
|
|
164
|
+
print(f" {f.code} · {f.path} — {f.message}")
|
|
165
|
+
n, w = len(entries), len(findings)
|
|
166
|
+
verdict = ("every bucket answers." if not findings
|
|
167
|
+
else "some buckets are not where the hub says.")
|
|
168
|
+
print(f"hub: {n} bucket{'s' if n != 1 else ''} · "
|
|
169
|
+
f"{w} warning{'s' if w != 1 else ''} — {verdict}")
|
|
170
|
+
return 0
|
|
171
|
+
|
|
172
|
+
|
|
143
173
|
def cmd_check(args) -> int:
|
|
174
|
+
if getattr(args, "hub", False):
|
|
175
|
+
return _check_hub(args)
|
|
144
176
|
root = _root_from(args)
|
|
145
177
|
if root is None:
|
|
146
178
|
given = getattr(args, "path", None)
|
|
147
|
-
|
|
148
|
-
|
|
179
|
+
if given is None:
|
|
180
|
+
return _check_hub(args) # outside any bucket: audit the hub
|
|
181
|
+
return _fail(f"no bucket at: {given}", args.json)
|
|
149
182
|
findings = checker.check(root)
|
|
150
183
|
errors = [f for f in findings if f.level == "error"]
|
|
151
184
|
warnings = [f for f in findings if f.level == "warning"]
|
|
@@ -207,6 +240,12 @@ def _render_items(items) -> list[str]:
|
|
|
207
240
|
|
|
208
241
|
def cmd_today(args) -> int:
|
|
209
242
|
t = views.today()
|
|
243
|
+
for m in t["missing"]:
|
|
244
|
+
print(
|
|
245
|
+
f"note: hub bucket '{m['name']}' is not at {m['path']} — "
|
|
246
|
+
f"clone it there, or `fusion hub remove {m['name']}`",
|
|
247
|
+
file=sys.stderr,
|
|
248
|
+
)
|
|
210
249
|
lines = [f"Today across {len(t['buckets'])} bucket"
|
|
211
250
|
f"{'s' if len(t['buckets']) != 1 else ''}"]
|
|
212
251
|
for aurora, items in t["groups"].items():
|
|
@@ -220,6 +259,12 @@ def cmd_today(args) -> int:
|
|
|
220
259
|
|
|
221
260
|
def cmd_agenda(args) -> int:
|
|
222
261
|
a = views.agenda()
|
|
262
|
+
for m in a["missing"]:
|
|
263
|
+
print(
|
|
264
|
+
f"note: hub bucket '{m['name']}' is not at {m['path']} — "
|
|
265
|
+
f"clone it there, or `fusion hub remove {m['name']}`",
|
|
266
|
+
file=sys.stderr,
|
|
267
|
+
)
|
|
223
268
|
lines = []
|
|
224
269
|
if a["dated"]:
|
|
225
270
|
lines.append("dated:")
|
|
@@ -336,8 +381,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
336
381
|
flag_as(p); flag_json(p)
|
|
337
382
|
p.set_defaults(func=cmd_index)
|
|
338
383
|
|
|
339
|
-
p = sub.add_parser("check", help="audit a bucket against the convention", description="Audit a bucket against the convention (SPEC §11). Exit 1 on errors; warnings never fail the check.")
|
|
384
|
+
p = sub.add_parser("check", help="audit a bucket against the convention", description="Audit a bucket against the convention (SPEC §11). Exit 1 on errors; warnings never fail the check. --hub (or running outside any bucket) audits the hub instead: every registered bucket present and answering to its name.")
|
|
340
385
|
p.add_argument("path", nargs="?", help="bucket root (default: walk up from the current directory)")
|
|
386
|
+
p.add_argument("--hub", action="store_true",
|
|
387
|
+
help="audit the hub instead: every entry answers at its path")
|
|
341
388
|
flag_json(p)
|
|
342
389
|
p.set_defaults(func=cmd_check)
|
|
343
390
|
|
|
@@ -43,6 +43,14 @@ MANIFEST_HEADER = (
|
|
|
43
43
|
"|---|---|---|---|---|\n"
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
GITATTRIBUTES = (
|
|
47
|
+
"# written by fusion new — multi-machine merges stay safe (SPEC §6)\n"
|
|
48
|
+
"* text=auto eol=lf\n"
|
|
49
|
+
"sources/** -text\n"
|
|
50
|
+
"LEDGER.md merge=union\n"
|
|
51
|
+
"sources/MANIFEST.md merge=union\n"
|
|
52
|
+
)
|
|
53
|
+
|
|
46
54
|
|
|
47
55
|
class ScaffoldError(Exception):
|
|
48
56
|
pass
|
|
@@ -85,6 +93,9 @@ def new_bucket(
|
|
|
85
93
|
(root / "sources" / "MANIFEST.md").write_text(
|
|
86
94
|
MANIFEST_HEADER, encoding="utf-8", newline="\n"
|
|
87
95
|
)
|
|
96
|
+
(root / ".gitattributes").write_text(
|
|
97
|
+
GITATTRIBUTES, encoding="utf-8", newline="\n"
|
|
98
|
+
)
|
|
88
99
|
ledger.append(root, actor, "created", "BUCKET.md", note="bucket born", at=at)
|
|
89
100
|
indexer.write_indexes(root, actor=None)
|
|
90
101
|
|
|
@@ -62,6 +62,16 @@ def _hub_documents():
|
|
|
62
62
|
yield name, zone, rel, doc
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def missing_hub_entries() -> list[dict]:
|
|
66
|
+
"""Hub entries whose path holds no readable BUCKET.md — a bucket not
|
|
67
|
+
yet cloned to this machine, moved, or gone."""
|
|
68
|
+
return [
|
|
69
|
+
{"name": entry.name, "path": entry.path}
|
|
70
|
+
for entry in hub.load()
|
|
71
|
+
if not (hub.resolve(entry) / "BUCKET.md").is_file()
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
65
75
|
def _item(name: str, zone: str, rel, doc, date=None) -> dict:
|
|
66
76
|
return {
|
|
67
77
|
"bucket": name,
|
|
@@ -92,7 +102,7 @@ def today() -> dict:
|
|
|
92
102
|
for aurora, items in groups.items():
|
|
93
103
|
if aurora not in ordered:
|
|
94
104
|
ordered[aurora] = items
|
|
95
|
-
return {"buckets": buckets, "groups": ordered}
|
|
105
|
+
return {"buckets": buckets, "groups": ordered, "missing": missing_hub_entries()}
|
|
96
106
|
|
|
97
107
|
|
|
98
108
|
def agenda() -> dict:
|
|
@@ -108,4 +118,4 @@ def agenda() -> dict:
|
|
|
108
118
|
elif zone == "activities" and doc.status == "active":
|
|
109
119
|
active.append(_item(name, zone, rel, doc))
|
|
110
120
|
dated.sort(key=lambda i: i["date"])
|
|
111
|
-
return {"dated": dated, "active": active}
|
|
121
|
+
return {"dated": dated, "active": active, "missing": missing_hub_entries()}
|
|
@@ -225,3 +225,33 @@ def test_w5_directory_mention_suppresses(make_bucket):
|
|
|
225
225
|
from fusion import indexer
|
|
226
226
|
indexer.write_indexes(root)
|
|
227
227
|
assert not [f for f in warnings(root) if f.code == "W5"]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_check_hub_dangling_and_drift(tmp_path, monkeypatch):
|
|
231
|
+
from fusion import checker, hub
|
|
232
|
+
from fusion.scaffold import new_bucket
|
|
233
|
+
|
|
234
|
+
monkeypatch.setenv("FUSION_HUB", str(tmp_path / "hub.md"))
|
|
235
|
+
new_bucket(tmp_path / "here", description="d", actor="test")
|
|
236
|
+
hub.add(hub.HubEntry("ghost", "personal", str(tmp_path / "gone"), "x"))
|
|
237
|
+
drifted, _ = new_bucket(tmp_path / "drift", name="oldname",
|
|
238
|
+
description="d", actor="test")
|
|
239
|
+
text = (drifted / "BUCKET.md").read_text(encoding="utf-8")
|
|
240
|
+
(drifted / "BUCKET.md").write_text(
|
|
241
|
+
text.replace("name: oldname", "name: newname"), encoding="utf-8"
|
|
242
|
+
)
|
|
243
|
+
findings = checker.check_hub()
|
|
244
|
+
assert [(f.code, f.level) for f in findings] == [
|
|
245
|
+
("H1", "warning"), ("H2", "warning")
|
|
246
|
+
]
|
|
247
|
+
assert "ghost" in findings[0].message
|
|
248
|
+
assert "newname" in findings[1].message
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_check_hub_all_present_is_empty(tmp_path, monkeypatch):
|
|
252
|
+
from fusion import checker
|
|
253
|
+
from fusion.scaffold import new_bucket
|
|
254
|
+
|
|
255
|
+
monkeypatch.setenv("FUSION_HUB", str(tmp_path / "hub.md"))
|
|
256
|
+
new_bucket(tmp_path / "here", description="d", actor="test")
|
|
257
|
+
assert checker.check_hub() == []
|
|
@@ -89,11 +89,11 @@ def test_index_logs_when_changed(tmp_path, capsys):
|
|
|
89
89
|
|
|
90
90
|
def test_status_today_agenda_json(fixture_bucket, capsys):
|
|
91
91
|
assert main(["status", str(fixture_bucket), "--json"]) == 0
|
|
92
|
-
assert out_json(capsys)["documents"] ==
|
|
92
|
+
assert out_json(capsys)["documents"] == 7
|
|
93
93
|
assert main(["today", "--json"]) == 0
|
|
94
94
|
assert out_json(capsys)["groups"] == {} # empty hub, empty day
|
|
95
95
|
assert main(["agenda", "--json"]) == 0
|
|
96
|
-
assert out_json(capsys) == {"dated": [], "active": []}
|
|
96
|
+
assert out_json(capsys) == {"dated": [], "active": [], "missing": []}
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def test_every_command_has_help(capsys):
|
|
@@ -223,3 +223,43 @@ def test_parent_listing_has_one_liners(capsys):
|
|
|
223
223
|
output = capsys.readouterr().out
|
|
224
224
|
for cmd, oneliner in hub_sub_help.items():
|
|
225
225
|
assert oneliner in output, f"Hub subcommand help missing for '{cmd}': '{oneliner}'"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_hub_list_flags_missing(tmp_path, capsys, monkeypatch):
|
|
229
|
+
from fusion import hub
|
|
230
|
+
|
|
231
|
+
monkeypatch.setenv("FUSION_HUB", str(tmp_path / "hub.md"))
|
|
232
|
+
hub.add(hub.HubEntry("ghost", "personal", str(tmp_path / "gone"), "x"))
|
|
233
|
+
assert main(["hub"]) == 0
|
|
234
|
+
out = capsys.readouterr().out
|
|
235
|
+
assert "ghost" in out and "fusion hub remove ghost" in out
|
|
236
|
+
assert main(["hub", "--json"]) == 0
|
|
237
|
+
assert out_json(capsys)[0]["missing"] is True
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def test_check_hub_mode(tmp_path, capsys, monkeypatch):
|
|
241
|
+
from fusion import hub
|
|
242
|
+
|
|
243
|
+
monkeypatch.setenv("FUSION_HUB", str(tmp_path / "hub.md"))
|
|
244
|
+
hub.add(hub.HubEntry("ghost", "personal", str(tmp_path / "gone"), "x"))
|
|
245
|
+
assert main(["check", "--hub", "--json"]) == 0 # warnings never fail
|
|
246
|
+
payload = out_json(capsys)
|
|
247
|
+
assert payload["ok"] is False
|
|
248
|
+
assert payload["warnings"][0]["code"] == "H1"
|
|
249
|
+
# outside any bucket, path-less check falls back to the hub audit
|
|
250
|
+
monkeypatch.chdir(tmp_path)
|
|
251
|
+
assert main(["check"]) == 0
|
|
252
|
+
assert "H1" in capsys.readouterr().out
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_today_agenda_note_missing_buckets(tmp_path, capsys, monkeypatch):
|
|
256
|
+
from fusion import hub
|
|
257
|
+
|
|
258
|
+
monkeypatch.setenv("FUSION_HUB", str(tmp_path / "hub.md"))
|
|
259
|
+
hub.add(hub.HubEntry("ghost", "personal", str(tmp_path / "gone"), "x"))
|
|
260
|
+
assert main(["today"]) == 0
|
|
261
|
+
assert "ghost" in capsys.readouterr().err
|
|
262
|
+
assert main(["agenda", "--json"]) == 0
|
|
263
|
+
captured = capsys.readouterr()
|
|
264
|
+
assert "ghost" in captured.err
|
|
265
|
+
assert json.loads(captured.out)["missing"][0]["name"] == "ghost"
|
|
@@ -79,10 +79,10 @@ def test_load_bucket(fixture_bucket):
|
|
|
79
79
|
|
|
80
80
|
def test_iter_documents_fixture(fixture_bucket):
|
|
81
81
|
docs = list(bucket.iter_documents(fixture_bucket))
|
|
82
|
-
assert len(docs) ==
|
|
82
|
+
assert len(docs) == 7
|
|
83
83
|
from collections import Counter
|
|
84
84
|
zone_counts = Counter(zone for zone, _, _ in docs)
|
|
85
|
-
assert zone_counts == {"library": 3, "activities":
|
|
85
|
+
assert zone_counts == {"library": 3, "activities": 3, "output": 1}
|
|
86
86
|
names = {rel.name for _, rel, _ in docs}
|
|
87
87
|
assert "INDEX.md" not in names
|
|
88
88
|
# yields (zone, path relative to the zone, Document)
|
|
@@ -15,7 +15,7 @@ def test_verbs_are_the_eleven_verbatim():
|
|
|
15
15
|
|
|
16
16
|
def test_parse_fixture(fixture_bucket):
|
|
17
17
|
entries = ledger.read(fixture_bucket)
|
|
18
|
-
assert len(entries) ==
|
|
18
|
+
assert len(entries) == 19
|
|
19
19
|
first = entries[0]
|
|
20
20
|
assert (first.date, first.time, first.actor, first.verb) == (
|
|
21
21
|
"2026-07-08", "09:00", "bertrand", "created"
|
|
@@ -63,4 +63,4 @@ def test_round_trip(tmp_path, capsys):
|
|
|
63
63
|
today = out_json(capsys)
|
|
64
64
|
assert today["buckets"] == ["scratch"]
|
|
65
65
|
assert main(["agenda", "--json"]) == 0
|
|
66
|
-
assert out_json(capsys) == {"dated": [], "active": []}
|
|
66
|
+
assert out_json(capsys) == {"dated": [], "active": [], "missing": []}
|
|
@@ -107,3 +107,13 @@ def test_hub_failure_is_a_warning_not_an_error(tmp_path, monkeypatch):
|
|
|
107
107
|
root, warnings = new_bucket(tmp_path / "b", description="d", actor="c")
|
|
108
108
|
assert root.exists()
|
|
109
109
|
assert any("hub registration failed" in w for w in warnings)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_new_bucket_writes_gitattributes(tmp_path):
|
|
113
|
+
root, _ = new_bucket(tmp_path / "b", description="d", actor="test")
|
|
114
|
+
text = (root / ".gitattributes").read_text(encoding="utf-8")
|
|
115
|
+
assert "LEDGER.md merge=union" in text
|
|
116
|
+
assert "sources/MANIFEST.md merge=union" in text
|
|
117
|
+
assert "* text=auto eol=lf" in text
|
|
118
|
+
assert "sources/** -text" in text
|
|
119
|
+
assert "\r" not in text
|
|
@@ -7,17 +7,17 @@ from fusion.ledger import Entry
|
|
|
7
7
|
def test_status_fixture(fixture_bucket):
|
|
8
8
|
s = views.status(fixture_bucket)
|
|
9
9
|
assert s["bucket"] == "crazy-ones"
|
|
10
|
-
assert s["documents"] ==
|
|
11
|
-
assert s["auroras"] == {"archive": 1, "collab": 1, "focus":
|
|
12
|
-
assert s["types"] == {"instrument": 2, "plan":
|
|
13
|
-
assert s["activities"] == {"active":
|
|
14
|
-
assert len(s["ledger"]) == 10 # last ten of
|
|
10
|
+
assert s["documents"] == 7
|
|
11
|
+
assert s["auroras"] == {"archive": 1, "collab": 1, "focus": 2, "library": 3}
|
|
12
|
+
assert s["types"] == {"instrument": 2, "plan": 3, "press-kit": 1, "recipe": 1}
|
|
13
|
+
assert s["activities"] == {"active": 2, "done": 1}
|
|
14
|
+
assert len(s["ledger"]) == 10 # last ten of nineteen
|
|
15
15
|
assert s["ledger"][-1]["verb"] == "reflected"
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def test_status_since_date(fixture_bucket):
|
|
19
19
|
s = views.status(fixture_bucket, since="2026-07-10")
|
|
20
|
-
assert len(s["ledger"]) ==
|
|
20
|
+
assert len(s["ledger"]) == 7
|
|
21
21
|
assert all(e["date"] == "2026-07-10" for e in s["ledger"])
|
|
22
22
|
|
|
23
23
|
|
|
@@ -106,7 +106,11 @@ def test_today_skips_hub_entries_without_bucket(tmp_path, monkeypatch):
|
|
|
106
106
|
|
|
107
107
|
monkeypatch.setenv("FUSION_HUB", str(tmp_path / "hub.md"))
|
|
108
108
|
hub.add(hub.HubEntry("ghost", "personal", str(tmp_path / "gone"), "missing"))
|
|
109
|
-
|
|
109
|
+
t = views.today()
|
|
110
|
+
assert t["buckets"] == []
|
|
111
|
+
assert t["missing"] == [
|
|
112
|
+
{"name": "ghost", "path": str(tmp_path / "gone")}
|
|
113
|
+
]
|
|
110
114
|
|
|
111
115
|
|
|
112
116
|
def test_views_exclude_archive_aurora_off_archive_paths(two_bucket_hub):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|