fusion-cli 1.1.1__tar.gz → 1.2.1__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.1}/PKG-INFO +2 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/README.md +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/pyproject.toml +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-analyst/SKILL.md +6 -3
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-analyst/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/SKILL.md +6 -3
- {fusion_cli-1.1.1/skills/fusion-librarian → fusion_cli-1.2.1/skills/fusion-intake}/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/SKILL.md +6 -3
- {fusion_cli-1.1.1/skills/fusion-planner → fusion_cli-1.2.1/skills/fusion-librarian}/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-planner/SKILL.md +6 -3
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-planner/references/create-activity.md +21 -0
- {fusion_cli-1.1.1/skills/fusion-intake → fusion_cli-1.2.1/skills/fusion-planner}/references/fusion-conventions.md +13 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/checker.py +29 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/cli.py +59 -10
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/scaffold.py +11 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/setup.py +67 -3
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/views.py +12 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_checker_warnings.py +30 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_cli.py +42 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_hub_bucket.py +2 -2
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_ledger.py +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_roundtrip.py +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_scaffold.py +10 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_setup_agents.py +119 -26
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_views.py +11 -7
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/uv.lock +1 -1
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/.gitignore +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/hatch_build.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-analyst/references/assess.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-analyst/references/compare.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-analyst/references/export.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-analyst/references/report.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-analyst/scripts/export.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/references/convert.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/references/delivery.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/references/gate.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/scripts/convert.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/scripts/gate.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/tests/conftest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/tests/make_fixtures.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/tests/test_convert.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/tests/test_gate.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-intake/tests/test_integration.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/archive.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/create.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/cross-reference.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/promote.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/query.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/reflect.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/restructure.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/references/tag.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/scripts/link-repair.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/tests/conftest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-librarian/tests/test_link_repair.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-planner/references/close.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/skills/fusion-planner/references/horizon.md +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/__init__.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/bucket.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/document.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/hub.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/indexer.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/ledger.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/src/fusion/manifest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/__init__.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/conftest.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_build_payload.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_checker_errors.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_cli_encoding.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_cli_setup.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_cli_version.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_document.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_indexer.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_line_endings.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_setup_core.py +0 -0
- {fusion_cli-1.1.1 → fusion_cli-1.2.1}/tests/test_skill_family.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fusion-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: The Fusion reference CLI — the notary of the Fusion Convention.
|
|
5
5
|
Author: Bluewaves Boutique
|
|
6
6
|
License-Expression: MIT
|
|
@@ -50,7 +50,7 @@ fusion --version
|
|
|
50
50
|
| `fusion status [path]` | One bucket at a glance |
|
|
51
51
|
| `fusion today` | The composed day, across every bucket in the hub |
|
|
52
52
|
| `fusion agenda` | The wider horizon — dated and active, across the hub |
|
|
53
|
-
| `fusion setup` | Install the skills
|
|
53
|
+
| `fusion setup` | Install the skills to `~/.agents/skills` and serve every detected agent (links only where needed) — the installer's brain. `--remove` undoes it |
|
|
54
54
|
|
|
55
55
|
Every command takes `--json` (agents parse, never scrape). `log`, `new`,
|
|
56
56
|
and `index` take `--as <actor>`; the pen defaults to `FUSION_ACTOR`, then
|
|
@@ -40,7 +40,7 @@ fusion --version
|
|
|
40
40
|
| `fusion status [path]` | One bucket at a glance |
|
|
41
41
|
| `fusion today` | The composed day, across every bucket in the hub |
|
|
42
42
|
| `fusion agenda` | The wider horizon — dated and active, across the hub |
|
|
43
|
-
| `fusion setup` | Install the skills
|
|
43
|
+
| `fusion setup` | Install the skills to `~/.agents/skills` and serve every detected agent (links only where needed) — the installer's brain. `--remove` undoes it |
|
|
44
44
|
|
|
45
45
|
Every command takes `--json` (agents parse, never scrape). `log`, `new`,
|
|
46
46
|
and `index` take `--as <actor>`; the pen defaults to `FUSION_ACTOR`, then
|
|
@@ -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.1}/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
|
|
|
@@ -357,8 +404,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
357
404
|
|
|
358
405
|
p = sub.add_parser("setup", help="install skills into agents; --remove undoes it",
|
|
359
406
|
description="Install the bundled skills to the canonical "
|
|
360
|
-
"skills directory and
|
|
361
|
-
"
|
|
407
|
+
"skills directory (~/.agents/skills) and make them available "
|
|
408
|
+
"to every detected agent — most read that directory "
|
|
409
|
+
"natively; agents that do not (Claude Code) get symlinks. "
|
|
410
|
+
"--remove undoes exactly what setup can prove it created.")
|
|
362
411
|
p.add_argument("--force", action="store_true",
|
|
363
412
|
help="replace foreign entries setup would otherwise leave")
|
|
364
413
|
p.add_argument("--remove", action="store_true",
|
|
@@ -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
|
|
|
@@ -99,15 +99,20 @@ def install_canonical(payload: Path, skills_dir: Path,
|
|
|
99
99
|
# ~/.agents/skills dir, so each fusion-* skill gets a symlink (or copy)
|
|
100
100
|
# in its own skills dir. mode "standard": the agent reads the standard
|
|
101
101
|
# dir — creating links there too would load every skill twice.
|
|
102
|
+
# legacy_subdir: a dir an earlier fusion-cli release linked into before
|
|
103
|
+
# the agent's native standard-dir support was verified; setup sweeps
|
|
104
|
+
# OUR leftovers out of it (attribution-checked, like everything else).
|
|
102
105
|
AGENTS = [
|
|
103
106
|
{"name": "Claude Code", "marker": ".claude",
|
|
104
107
|
"skills_subdir": ".claude/skills", "mode": "link",
|
|
105
108
|
"docs_url": "https://code.claude.com/docs/en/skills"},
|
|
106
109
|
{"name": "Codex", "marker": ".codex",
|
|
107
|
-
"skills_subdir": ".
|
|
110
|
+
"skills_subdir": ".agents/skills", "mode": "standard",
|
|
111
|
+
"legacy_subdir": ".codex/skills",
|
|
108
112
|
"docs_url": "https://developers.openai.com/codex/skills"},
|
|
109
113
|
{"name": "Pi", "marker": ".pi",
|
|
110
|
-
"skills_subdir": ".
|
|
114
|
+
"skills_subdir": ".agents/skills", "mode": "standard",
|
|
115
|
+
"legacy_subdir": ".pi/agent/skills",
|
|
111
116
|
"docs_url": "https://pi.dev/docs/latest/skills"},
|
|
112
117
|
{"name": "Cursor", "marker": ".cursor",
|
|
113
118
|
"skills_subdir": ".agents/skills", "mode": "standard",
|
|
@@ -128,7 +133,10 @@ def detect_agents(home: Path) -> list[dict]:
|
|
|
128
133
|
found = []
|
|
129
134
|
for row in AGENTS:
|
|
130
135
|
if (home / row["marker"]).is_dir():
|
|
131
|
-
|
|
136
|
+
entry = {**row, "skills_dir": home / row["skills_subdir"]}
|
|
137
|
+
if "legacy_subdir" in row:
|
|
138
|
+
entry["legacy_dir"] = home / row["legacy_subdir"]
|
|
139
|
+
found.append(entry)
|
|
132
140
|
return found
|
|
133
141
|
|
|
134
142
|
|
|
@@ -139,11 +147,51 @@ def _points_into(link: Path, canonical: Path) -> bool:
|
|
|
139
147
|
return False
|
|
140
148
|
|
|
141
149
|
|
|
150
|
+
def _sweep_legacy(agent: dict, canonical: Path) -> list[dict]:
|
|
151
|
+
"""Remove OUR leftovers from a dir an earlier release linked into.
|
|
152
|
+
|
|
153
|
+
fusion-cli <= 1.2.0 symlinked (or copy-fell-back) into Pi's and
|
|
154
|
+
Codex's own dirs; both agents read the canonical dir natively, so
|
|
155
|
+
those entries double-loaded every skill. Attribution-checked:
|
|
156
|
+
symlinks into the canonical and sentinel-marked copies are ours;
|
|
157
|
+
anything else is left with a report row. If the legacy dir is
|
|
158
|
+
itself aliased at the canonical, there is nothing to sweep.
|
|
159
|
+
"""
|
|
160
|
+
legacy = agent.get("legacy_dir")
|
|
161
|
+
if legacy is None or not legacy.is_dir():
|
|
162
|
+
return []
|
|
163
|
+
if _points_into(legacy, canonical):
|
|
164
|
+
return [] # the legacy dir IS the canonical through an alias —
|
|
165
|
+
# sweeping it would reach canonical entries
|
|
166
|
+
results = []
|
|
167
|
+
for entry in sorted(legacy.glob("fusion-*")):
|
|
168
|
+
row = {"agent": agent["name"], "skill": entry.name}
|
|
169
|
+
if entry.is_symlink() and _points_into(entry, canonical):
|
|
170
|
+
entry.unlink()
|
|
171
|
+
results.append({**row, "action": "unlinked",
|
|
172
|
+
"detail": f"{entry} — this agent reads the "
|
|
173
|
+
f"standard dir; the extra link "
|
|
174
|
+
f"double-loaded every skill"})
|
|
175
|
+
elif (entry.is_dir() and not entry.is_symlink()
|
|
176
|
+
and (entry / ".fusion-setup").is_file()):
|
|
177
|
+
shutil.rmtree(entry)
|
|
178
|
+
results.append({**row, "action": "unlinked",
|
|
179
|
+
"detail": f"{entry} — copy from an earlier "
|
|
180
|
+
f"fusion-cli; this agent reads the "
|
|
181
|
+
f"standard dir"})
|
|
182
|
+
else:
|
|
183
|
+
results.append({**row, "action": "left",
|
|
184
|
+
"detail": f"{entry} is not attributable to "
|
|
185
|
+
f"setup — left in place"})
|
|
186
|
+
return results
|
|
187
|
+
|
|
188
|
+
|
|
142
189
|
def fan_out(canonical: Path, agents: list[dict], force: bool) -> list[dict]:
|
|
143
190
|
results = []
|
|
144
191
|
skills = sorted(canonical.glob("fusion-*"))
|
|
145
192
|
for agent in agents:
|
|
146
193
|
if agent["mode"] == "standard":
|
|
194
|
+
results.extend(_sweep_legacy(agent, canonical))
|
|
147
195
|
if canonical.resolve() != agent["skills_dir"].resolve():
|
|
148
196
|
detail = (f"reads ~/.agents/skills — but your skills are "
|
|
149
197
|
f"at {canonical}, which this agent does not read")
|
|
@@ -152,6 +200,13 @@ def fan_out(canonical: Path, agents: list[dict], force: bool) -> list[dict]:
|
|
|
152
200
|
results.append({"agent": agent["name"], "skill": "*",
|
|
153
201
|
"action": "standard", "detail": detail})
|
|
154
202
|
continue
|
|
203
|
+
if _points_into(agent["skills_dir"], canonical):
|
|
204
|
+
results.append({
|
|
205
|
+
"agent": agent["name"], "skill": "*", "action": "served",
|
|
206
|
+
"detail": f"{agent['skills_dir']} resolves into "
|
|
207
|
+
f"{canonical} — the canonical install already "
|
|
208
|
+
f"serves this agent; nothing to link"})
|
|
209
|
+
continue
|
|
155
210
|
agent["skills_dir"].mkdir(parents=True, exist_ok=True)
|
|
156
211
|
for skill in skills:
|
|
157
212
|
target = agent["skills_dir"] / skill.name
|
|
@@ -170,6 +225,11 @@ def fan_out(canonical: Path, agents: list[dict], force: bool) -> list[dict]:
|
|
|
170
225
|
target.unlink()
|
|
171
226
|
replaced = True
|
|
172
227
|
elif target.is_dir():
|
|
228
|
+
if target.resolve() == skill.resolve():
|
|
229
|
+
results.append({**row, "action": "served",
|
|
230
|
+
"detail": f"{target} already resolves "
|
|
231
|
+
f"to the canonical skill"})
|
|
232
|
+
continue
|
|
173
233
|
if tree_digest(target) == tree_digest(skill):
|
|
174
234
|
pass # our current copy, refresh below keeps it current
|
|
175
235
|
elif (target / ".fusion-setup").is_file():
|
|
@@ -211,7 +271,11 @@ def remove_all(canonical: Path, home: Path) -> list[dict]:
|
|
|
211
271
|
results = []
|
|
212
272
|
for agent in detect_agents(home):
|
|
213
273
|
if agent["mode"] == "standard":
|
|
274
|
+
results.extend(_sweep_legacy(agent, canonical))
|
|
214
275
|
continue
|
|
276
|
+
if _points_into(agent["skills_dir"], canonical):
|
|
277
|
+
continue # entries here ARE the canonical skills; the
|
|
278
|
+
# canonical phase below removes them exactly once
|
|
215
279
|
entries = (sorted(agent["skills_dir"].glob("fusion-*"))
|
|
216
280
|
if agent["skills_dir"].is_dir() else [])
|
|
217
281
|
for entry in entries:
|