fux-engine 0.3.1__tar.gz → 0.3.2__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.
- {fux_engine-0.3.1/fux_engine.egg-info → fux_engine-0.3.2}/PKG-INFO +14 -1
- {fux_engine-0.3.1 → fux_engine-0.3.2}/README.md +13 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/__init__.py +1 -1
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/assets/graph_boot.js +62 -22
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/assets/graph_template.html +1 -1
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/costledger.py +33 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/paths.py +6 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/touch.py +4 -3
- {fux_engine-0.3.1 → fux_engine-0.3.2/fux_engine.egg-info}/PKG-INFO +14 -1
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_costledger.py +30 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/LICENSE +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/__main__.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/assets/fux-icon.svg +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/assets/fux-lockup.svg +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/assets/fux-mark.svg +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/astextract.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/bench.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/build.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/capture.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/check.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/cli.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/clicmds.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/cligraph.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/cliquery.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/cliutil.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/community.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/components.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/config.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/context.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/coverage.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/copilot/prompts/fux-plan.prompt.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/copilot/prompts/fux.prompt.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/global/README.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/global/rules/async-everywhere.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/global/rules/doc-per-code-change.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/global/rules/files-max-100-lines.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/global/rules/no-secrets-in-vcs.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/hooks/_common.sh +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/hooks/post_tool_use.sh +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/hooks/session_start.sh +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/hooks/stop.sh +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/hooks/user_prompt_submit.sh +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/packs/indian-markets-tax/pack.toml +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/packs/indian-markets-tax/rules/capital-gains-equity.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/packs/indian-markets-tax/rules/market-hours-nse.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/schema.json +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/skills/adr/SKILL.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/skills/distill/SKILL.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/skills/fetch-rules/SKILL.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/skills/fux/SKILL.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/skills/plan/SKILL.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/skills/savings/SKILL.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/skills/trace/SKILL.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/drift.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/embed.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/explain.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/feedback.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/fetchrules.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/findings.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/fix.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/fmwrite.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/frontmatter.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/gate.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/gitutil.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/globs.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/governance.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/graph.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/graphhtml.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/graphquery.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/hookinstall.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/hookio.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/hooks.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/hybrid.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/impact.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/importer.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/index.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/initcmd.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/lint.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/loader.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/mcpserver.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/mine.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/model.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/narrative.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/pack.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/parity.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/recall.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/report.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/savings.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/scaffold.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/scalars.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/schema.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/seal.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/serve.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/settings.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/stats.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/templates/formula.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/templates/spec.md +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/tour.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/uispec.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/usage.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/verify.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux/vexamples.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux_engine.egg-info/SOURCES.txt +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux_engine.egg-info/dependency_links.txt +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux_engine.egg-info/entry_points.txt +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux_engine.egg-info/requires.txt +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/fux_engine.egg-info/top_level.txt +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/pyproject.toml +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/setup.cfg +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_ast_backend.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_astextract.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_bm25f_expand.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_capture_governance.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_centrality.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_check_fix.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_components.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_crossfile_calls.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_edge_confidence.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_embed_rerank.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_examples.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_feedback.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_fetch_rules.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_frontmatter.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_fuzz_mine.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_globs.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_graph_determinism.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_graphhtml.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_graphhtml_links.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_hookinstall.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_hybrid.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_impact.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_lint_stats_gate.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_mcp.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_mcp_extra.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_pack.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_parity_import.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_recall_build_verify.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_recall_eval.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_resolution.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_savings.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_schema_scaffold_init.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_seal.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_serve_sanitize.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_uispec.py +0 -0
- {fux_engine-0.3.1 → fux_engine-0.3.2}/tests/test_verify_hardening.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fux-engine
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Fux — a portable agent-aware knowledge engine: rules, memory, narrative, and graph in one frontmatter substrate.
|
|
5
5
|
Author-email: arpit arya <arpitarya.me@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -115,6 +115,13 @@ fux serve # local dashboard over the generated views
|
|
|
115
115
|
fux import docs/ # migrate existing markdown → narrative entries
|
|
116
116
|
fux parity # is it safe to retire the old graph/docs/memory?
|
|
117
117
|
fux tour # ordered ONBOARDING.md
|
|
118
|
+
|
|
119
|
+
# Runtime consumers (agents + apps, e.g. Anton's Orff concierge)
|
|
120
|
+
fux components [--scope dir] # component/hook registry for on-the-fly UI generation
|
|
121
|
+
fux validate-spec # validate a declarative UISpec against the registry
|
|
122
|
+
fux feedback # record rejected specs as candidate vocabulary gaps
|
|
123
|
+
fux hook-recall # stdin-JSON recall for agent prompt hooks
|
|
124
|
+
fux query / path / explain # graph traversal: cross-module "how does X relate to Y"
|
|
118
125
|
```
|
|
119
126
|
|
|
120
127
|
**Complete, example-driven guide to everything Fux does:
|
|
@@ -180,6 +187,12 @@ effective ruleset = ~/.claude/fux/global/ (cross-project best practices)
|
|
|
180
187
|
`project` overrides `pack` overrides `global`. `fux check` flags conflicts
|
|
181
188
|
instead of silently shadowing.
|
|
182
189
|
|
|
190
|
+
> Packs are optional. A single-project setup can keep `packs = []` and hold all
|
|
191
|
+
> authored knowledge in the repo's own `.fux/` — version-controlled with the code
|
|
192
|
+
> it governs (see Anton's `knowledge-location` rule for the reasoning). Global
|
|
193
|
+
> rules are seeded from this repo's `fux/data/global/`, so they stay versioned
|
|
194
|
+
> tool code, not loose documents.
|
|
195
|
+
|
|
183
196
|
## Guarantee
|
|
184
197
|
|
|
185
198
|
Every maintenance command is shell/AST/parse — **no LLM calls**. The only paths
|
|
@@ -76,6 +76,13 @@ fux serve # local dashboard over the generated views
|
|
|
76
76
|
fux import docs/ # migrate existing markdown → narrative entries
|
|
77
77
|
fux parity # is it safe to retire the old graph/docs/memory?
|
|
78
78
|
fux tour # ordered ONBOARDING.md
|
|
79
|
+
|
|
80
|
+
# Runtime consumers (agents + apps, e.g. Anton's Orff concierge)
|
|
81
|
+
fux components [--scope dir] # component/hook registry for on-the-fly UI generation
|
|
82
|
+
fux validate-spec # validate a declarative UISpec against the registry
|
|
83
|
+
fux feedback # record rejected specs as candidate vocabulary gaps
|
|
84
|
+
fux hook-recall # stdin-JSON recall for agent prompt hooks
|
|
85
|
+
fux query / path / explain # graph traversal: cross-module "how does X relate to Y"
|
|
79
86
|
```
|
|
80
87
|
|
|
81
88
|
**Complete, example-driven guide to everything Fux does:
|
|
@@ -141,6 +148,12 @@ effective ruleset = ~/.claude/fux/global/ (cross-project best practices)
|
|
|
141
148
|
`project` overrides `pack` overrides `global`. `fux check` flags conflicts
|
|
142
149
|
instead of silently shadowing.
|
|
143
150
|
|
|
151
|
+
> Packs are optional. A single-project setup can keep `packs = []` and hold all
|
|
152
|
+
> authored knowledge in the repo's own `.fux/` — version-controlled with the code
|
|
153
|
+
> it governs (see Anton's `knowledge-location` rule for the reasoning). Global
|
|
154
|
+
> rules are seeded from this repo's `fux/data/global/`, so they stay versioned
|
|
155
|
+
> tool code, not loose documents.
|
|
156
|
+
|
|
144
157
|
## Guarantee
|
|
145
158
|
|
|
146
159
|
Every maintenance command is shell/AST/parse — **no LLM calls**. The only paths
|
|
@@ -42,6 +42,8 @@ for (const e of edges){ const a=byId[e.source], b=byId[e.target];
|
|
|
42
42
|
(govTargets[kn.id]=govTargets[kn.id]||[]).push({id:other.id, type:e.type}); }
|
|
43
43
|
|
|
44
44
|
let view = { x: 0, y: 0, k: 1 }, hidden = new Set(), hiddenE = new Set();
|
|
45
|
+
let vTarget = null; // when set, the camera eases toward it (smooth zoom/fly)
|
|
46
|
+
const VIEW_EASE = 0.2; // per-frame approach fraction toward vTarget
|
|
45
47
|
let selected = null, hover = null, query = "", lens = "know";
|
|
46
48
|
let running = true, showLabels = true, focusSet = null;
|
|
47
49
|
let pathMode = false, pathA = null, pathSet = null, pathEdge = new Set(), pathMD = null;
|
|
@@ -135,7 +137,7 @@ function updateHits(){ if(!query){ $("qhits").innerHTML=""; return; }
|
|
|
135
137
|
+ (m.length>14 ? `<div class="ct" style="padding:5px 7px">+${m.length-14} more</div>` : "");
|
|
136
138
|
$("qhits").querySelectorAll("[data-jump]").forEach(el => el.onclick = () => jumpTo(el.dataset.jump)); }
|
|
137
139
|
function jumpTo(id){ const n=byId[id]; if(!n) return; selected=id; clearPath(); showDetail(n);
|
|
138
|
-
|
|
140
|
+
const k=Math.max(view.k,1); flyTo(-n.x*k, -n.y*k, k); }
|
|
139
141
|
|
|
140
142
|
// ---- copy / governance footer buttons ----
|
|
141
143
|
$("bcopy").onclick = () => { if(pathSet && pathMD) copy(pathMD(), "path copied");
|
|
@@ -151,10 +153,10 @@ $("railtab").onclick = () => { $("right").classList.toggle("collapsed"); applyRi
|
|
|
151
153
|
applyRightState(); // honour the default-collapsed markup on load
|
|
152
154
|
// Micro / Macro both show the real nodes — they just change zoom. Micro zooms in
|
|
153
155
|
// (centred on the selection if any); Macro fits the whole graph as an overview.
|
|
154
|
-
$("bmicro").onclick = () => { focusSet=null;
|
|
155
|
-
|
|
156
|
+
$("bmicro").onclick = () => { focusSet=null; const z=Math.max(view.k,1.3); const p=selected&&byId[selected];
|
|
157
|
+
if(p) flyTo(-p.x*z, -p.y*z, z); else zoomToCenter(z); };
|
|
156
158
|
// Macro = the auto-framed overview; re-enable auto-fit so it stays framed on resize.
|
|
157
|
-
$("bmacro").onclick = () => { focusSet=null; userMoved=false; query=""; $("q").value=""; updateHits(); clearPath();
|
|
159
|
+
$("bmacro").onclick = () => { focusSet=null; userMoved=false; query=""; $("q").value=""; updateHits(); clearPath(); fitAnimated(); };
|
|
158
160
|
|
|
159
161
|
// ---- layout & geometry --------------------------------------------------
|
|
160
162
|
let W=0, H=0;
|
|
@@ -201,7 +203,7 @@ function step(){
|
|
|
201
203
|
if(c){ n.vx+=(c.x-n.x)*COMM_PULL*alpha; n.vy+=(c.y-n.y)*COMM_PULL*alpha; }
|
|
202
204
|
n.vx*=.85; n.vy*=.85; n.x+=n.vx*0.5; n.y+=n.vy*0.5;
|
|
203
205
|
n.vx-=n.x*GRAVITY*alpha; n.vy-=n.y*GRAVITY*alpha; }
|
|
204
|
-
alpha *= ALPHA_DECAY; if(alpha < ALPHA_MIN){ alpha = 0; running = false; if(!userMoved) fit(); }
|
|
206
|
+
alpha *= ALPHA_DECAY; if(alpha < ALPHA_MIN){ alpha = 0; running = false; if(!userMoved && !vTarget) fit(); }
|
|
205
207
|
}
|
|
206
208
|
const TC = (wx,wy) => ({ x: wx*view.k+W/2+view.x, y: wy*view.k+H/2+view.y });
|
|
207
209
|
const T = n => TC(n.x, n.y);
|
|
@@ -213,13 +215,16 @@ const baseR = n => isKnow(n) ? (3 + (n.centrality||0)*3.4 + Math.min((deg[n.id]|
|
|
|
213
215
|
const radius = n => baseR(n) * Math.min(2.4, Math.max(0.7, view.k));
|
|
214
216
|
const neighbors = id => new Set(adj[id].map(([t])=>t));
|
|
215
217
|
|
|
216
|
-
function
|
|
218
|
+
function fitView(){ const vis = nodes.filter(visible); if(!vis.length) return null;
|
|
217
219
|
const xs=vis.map(n=>n.x).sort((a,b)=>a-b), ys=vis.map(n=>n.y).sort((a,b)=>a-b);
|
|
218
220
|
const lo=i=>i[Math.floor(i.length*0.02)], hi=i=>i[Math.floor(i.length*0.98)];
|
|
219
221
|
const minX=lo(xs),maxX=hi(xs),minY=lo(ys),maxY=hi(ys);
|
|
220
222
|
const w=maxX-minX||1, h=maxY-minY||1;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
const k = Math.max(0.2, Math.min(2.5, 0.85*Math.min(W/w, H/h)));
|
|
224
|
+
return { k, x:-(minX+maxX)/2*k, y:-(minY+maxY)/2*k }; }
|
|
225
|
+
// fit() snaps (used by the settling physics loop); fitAnimated() glides (user-driven).
|
|
226
|
+
function fit(){ const t=fitView(); if(t){ view.k=t.k; view.x=t.x; view.y=t.y; vTarget=null; } }
|
|
227
|
+
function fitAnimated(){ const t=fitView(); if(t) vTarget={...t}; }
|
|
223
228
|
function setFocus(id){ focusSet = new Set([id, ...neighbors(id)]); reheat(0.45); }
|
|
224
229
|
function clearFocus(){ focusSet = null; reheat(0.3); }
|
|
225
230
|
function toggleLens(){ const knowOn = focusSet && focusSet._lens;
|
|
@@ -249,11 +254,13 @@ function clearPath(){ pathSet=null; pathEdge=new Set(); pathA=null; pathMD=null;
|
|
|
249
254
|
// ---- render : the Solar pipeline ----------------------------------------
|
|
250
255
|
function draw(){
|
|
251
256
|
_f++;
|
|
257
|
+
easeView();
|
|
252
258
|
if(running && _f % PHYS_STRIDE === 0) step();
|
|
253
259
|
// The layout grows as it settles, so keep re-framing to fit (driven by the draw
|
|
254
260
|
// loop, not events) until the user takes control — this is the real fix for the
|
|
255
|
-
// "graph is cut off until I switch tabs" glitch.
|
|
256
|
-
|
|
261
|
+
// "graph is cut off until I switch tabs" glitch. Suppressed while a camera tween
|
|
262
|
+
// is in flight, so the auto-fit and the glide never fight.
|
|
263
|
+
if(running && !userMoved && !vTarget && _f % 16 === 0) fit();
|
|
257
264
|
ctx.clearRect(0,0,W,H);
|
|
258
265
|
const anchor = selected || hover, near = anchor ? neighbors(anchor) : null;
|
|
259
266
|
const dimCode = lens==="know";
|
|
@@ -370,19 +377,46 @@ function bindMM(mm){ if(!mm) return;
|
|
|
370
377
|
const panTo = ev => { if(!_mmBounds) return; const r=mm.getBoundingClientRect();
|
|
371
378
|
const wx=((ev.clientX-r.left)-_mmBounds.ox)/_mmBounds.s, wy=((ev.clientY-r.top)-_mmBounds.oy)/_mmBounds.s;
|
|
372
379
|
view.x=-wx*view.k; view.y=-wy*view.k; };
|
|
373
|
-
mm.onmousedown = e => { panTo(e); const mv=ev=>panTo(ev);
|
|
380
|
+
mm.onmousedown = e => { vTarget=null; panTo(e); const mv=ev=>panTo(ev);
|
|
374
381
|
const up=()=>{ window.removeEventListener("mousemove",mv); window.removeEventListener("mouseup",up); };
|
|
375
382
|
window.addEventListener("mousemove",mv); window.addEventListener("mouseup",up); }; }
|
|
376
383
|
bindMM($("mm")); bindMM($("fmm"));
|
|
377
384
|
|
|
378
385
|
// ---- zoom well + mode pill ----------------------------------------------
|
|
379
386
|
const K_MIN=0.15, K_MAX=5, LK=Math.log(K_MIN), LKR=Math.log(K_MAX)-LK;
|
|
387
|
+
const clampK = k => Math.max(K_MIN, Math.min(K_MAX, k));
|
|
388
|
+
// Smooth camera. Every interactive zoom/recenter sets `vTarget`; easeView() (run
|
|
389
|
+
// once per frame in draw) glides the live `view` toward it, so nothing snaps.
|
|
390
|
+
// Direct manipulation — panning, node-drag, the zoom slider — cancels the tween so
|
|
391
|
+
// it stays 1:1 with the cursor; the physics auto-fit is suppressed while one runs.
|
|
392
|
+
function easeView(){ if(!vTarget) return;
|
|
393
|
+
view.k += (vTarget.k-view.k)*VIEW_EASE;
|
|
394
|
+
view.x += (vTarget.x-view.x)*VIEW_EASE;
|
|
395
|
+
view.y += (vTarget.y-view.y)*VIEW_EASE;
|
|
396
|
+
if(Math.abs(vTarget.k-view.k)<1e-3 && Math.hypot(vTarget.x-view.x, vTarget.y-view.y)<0.4){
|
|
397
|
+
view.k=vTarget.k; view.x=vTarget.x; view.y=vTarget.y; vTarget=null; } }
|
|
398
|
+
// Zoom keeping the world point under (ex,ey) pinned — compounded in target space so
|
|
399
|
+
// rapid wheel ticks anchor consistently while the previous glide is still in flight.
|
|
400
|
+
function zoomBy(ex, ey, factor){ userMoved=true; const b=vTarget||view; const k=clampK(b.k*factor);
|
|
401
|
+
const wx=(ex-W/2-b.x)/b.k, wy=(ey-H/2-b.y)/b.k;
|
|
402
|
+
vTarget={ k, x: ex-W/2-wx*k, y: ey-H/2-wy*k }; }
|
|
403
|
+
function zoomToCenter(k){ userMoved=true; const b=vTarget||view; k=clampK(k);
|
|
404
|
+
const wx=-b.x/b.k, wy=-b.y/b.k; vTarget={ k, x:-wx*k, y:-wy*k }; }
|
|
405
|
+
function flyTo(x, y, k){ userMoved=true; vTarget={ k:clampK(k), x, y }; }
|
|
380
406
|
function updatePill(){ const macro = view.k < 0.55; // zoomed-out overview = macro
|
|
381
407
|
$("bmacro").classList.toggle("on",macro); $("bmicro").classList.toggle("on",!macro);
|
|
382
|
-
$("zthumb").style.left=((Math.log(
|
|
408
|
+
$("zthumb").style.left=((Math.log(clampK(view.k))-LK)/LKR*100)+"%";
|
|
383
409
|
$("zlabel").textContent = macro ? "macro" : (view.k>1.4 ? "detail" : "micro"); }
|
|
384
|
-
|
|
385
|
-
|
|
410
|
+
// Zoom slider: drag (not just click) the thumb. Writes the camera directly so the
|
|
411
|
+
// thumb tracks the cursor 1:1, recentred on the viewport so the graph scales in place.
|
|
412
|
+
const kFromTrack = clientX => { const r=$("ztrack").getBoundingClientRect();
|
|
413
|
+
return Math.exp(LK + Math.max(0,Math.min(1,(clientX-r.left)/r.width))*LKR); };
|
|
414
|
+
function zoomCenterNow(k){ userMoved=true; vTarget=null; k=clampK(k);
|
|
415
|
+
const wx=-view.x/view.k, wy=-view.y/view.k; view.k=k; view.x=-wx*k; view.y=-wy*k; }
|
|
416
|
+
$("ztrack").onmousedown = e => { e.preventDefault(); zoomCenterNow(kFromTrack(e.clientX));
|
|
417
|
+
const mv=ev=>zoomCenterNow(kFromTrack(ev.clientX));
|
|
418
|
+
const up=()=>{ window.removeEventListener("mousemove",mv); window.removeEventListener("mouseup",up); };
|
|
419
|
+
window.addEventListener("mousemove",mv); window.addEventListener("mouseup",up); };
|
|
386
420
|
|
|
387
421
|
// ---- governance ledger --------------------------------------------------
|
|
388
422
|
(function buildLedger(){
|
|
@@ -404,17 +438,17 @@ $("ztrack").onclick = e => { userMoved=true; const r=$("ztrack").getBoundingClie
|
|
|
404
438
|
if(e.target.closest(".tgt")) return;
|
|
405
439
|
body.querySelectorAll(".lrow").forEach(x=>x.classList.remove("open")); row.classList.add("open");
|
|
406
440
|
const n=byId[row.dataset.rule]; selected=n.id; clearPath(); showDetail(n);
|
|
407
|
-
|
|
441
|
+
const k=Math.max(view.k,1.1); flyTo(-n.x*k, -n.y*k, k); }; });
|
|
408
442
|
body.querySelectorAll(".tgt").forEach(t => { t.onclick = () => { const n=byId[t.dataset.go];
|
|
409
|
-
if(!n) return; selected=n.id; clearPath(); showDetail(n);
|
|
410
|
-
view.
|
|
443
|
+
if(!n) return; selected=n.id; clearPath(); showDetail(n);
|
|
444
|
+
const k=Math.max(view.k,1.1); flyTo(-n.x*k, -n.y*k, k); }; });
|
|
411
445
|
})();
|
|
412
446
|
|
|
413
447
|
// ---- interaction --------------------------------------------------------
|
|
414
448
|
function hit(mx,my){ let best=null,bd=1e9; for(const n of nodes){ if(!visible(n)) continue; const p=T(n);
|
|
415
449
|
const d=Math.hypot(mx-p.x,my-p.y); if(d < radius(n)+4 && d<bd){ bd=d; best=n; } } return best; }
|
|
416
450
|
let drag=null, pan=false, last=null, downAt=null;
|
|
417
|
-
cv.onmousedown = e => { last={x:e.offsetX,y:e.offsetY}; downAt={x:e.offsetX,y:e.offsetY};
|
|
451
|
+
cv.onmousedown = e => { vTarget=null; last={x:e.offsetX,y:e.offsetY}; downAt={x:e.offsetX,y:e.offsetY};
|
|
418
452
|
const n=hit(e.offsetX,e.offsetY);
|
|
419
453
|
if(pathMode){ if(n){ if(!pathA){ pathA=n.id; selected=n.id; showDetail(n); toast("now click the target node"); }
|
|
420
454
|
else { setPath(shortestPath(pathA,n.id)); pathA=null; togglePath(); } } return; }
|
|
@@ -433,13 +467,19 @@ cv.onmousemove = e => {
|
|
|
433
467
|
last={x:e.offsetX,y:e.offsetY}; };
|
|
434
468
|
window.addEventListener("mouseup", () => { drag=null; pan=false; });
|
|
435
469
|
cv.ondblclick = e => { const n=hit(e.offsetX,e.offsetY); if(n){ selected=n.id; setFocus(n.id); showDetail(n); } };
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
470
|
+
// Wheel/trackpad zoom: scale by the *magnitude* of the delta (normalised across
|
|
471
|
+
// pixel/line/page deltaModes) so one mouse notch and a trackpad swipe both feel
|
|
472
|
+
// natural, then glide there. macOS pinch arrives as ctrl+wheel with coarser deltas.
|
|
473
|
+
cv.onwheel = e => { e.preventDefault();
|
|
474
|
+
let d = e.deltaY; if(e.deltaMode===1) d*=16; else if(e.deltaMode===2) d*=(H||500);
|
|
475
|
+
const factor = Math.max(0.4, Math.min(2.5, Math.exp(-d * (e.ctrlKey ? 0.010 : 0.0018))));
|
|
476
|
+
zoomBy(e.offsetX, e.offsetY, factor); };
|
|
439
477
|
window.addEventListener("keydown", e => { if(e.target.tagName==="INPUT"){ if(e.key==="Escape")e.target.blur(); return; }
|
|
440
478
|
const k=e.key.toLowerCase();
|
|
441
479
|
if(k==="/"){ e.preventDefault(); $("q").focus(); }
|
|
442
|
-
else if(k==="f"){ userMoved=false;
|
|
480
|
+
else if(k==="f"){ userMoved=false; fitAnimated(); } else if(k==="r"){ flyTo(0,0,1); }
|
|
481
|
+
else if(k==="+"||k==="="){ zoomToCenter((vTarget?vTarget.k:view.k)*1.3); }
|
|
482
|
+
else if(k==="-"||k==="_"){ zoomToCenter((vTarget?vTarget.k:view.k)/1.3); }
|
|
443
483
|
else if(k===" "){ e.preventDefault(); running ? (running=false) : reheat(0.3); }
|
|
444
484
|
else if(k==="e"){ if(selected) setFocus(selected); }
|
|
445
485
|
else if(k==="l"){ showLabels=!showLabels; }
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
<div class="lab">Edge language</div>
|
|
263
263
|
<div id="efilters"></div>
|
|
264
264
|
</div>
|
|
265
|
-
<div class="keyhint"><span><b>/</b>search</span><span><b>e</b>focus</span><span><b>c</b>macro</span><span><b>p</b>path</span><span><b>l</b>labels</span><span><b>Esc</b>clear</span></div>
|
|
265
|
+
<div class="keyhint"><span><b>/</b>search</span><span><b>±</b>zoom</span><span><b>f</b>fit</span><span><b>e</b>focus</span><span><b>c</b>macro</span><span><b>p</b>path</span><span><b>l</b>labels</span><span><b>Esc</b>clear</span></div>
|
|
266
266
|
</div>
|
|
267
267
|
<div class="zoomwell">
|
|
268
268
|
<span class="lab" style="font-size:10px;letter-spacing:.12em;text-transform:uppercase">Zoom</span>
|
|
@@ -9,6 +9,10 @@ not just a per-call estimate. Opt-in via `cost_tracking`; recorded on each
|
|
|
9
9
|
`tokens_without` = reading the governed source file(s) for the matched rules;
|
|
10
10
|
`tokens_with` = the matched Tier-2 rule(s) only (the realistic later-lookup cost,
|
|
11
11
|
since the Tier-1 INDEX is injected once per session). `saved = without − with`.
|
|
12
|
+
|
|
13
|
+
The summary reports the lifetime total *and* a per-day / per-week / per-month rate
|
|
14
|
+
— `tokens_saved` amortised across the observed span (`first`→`last`, floored at one
|
|
15
|
+
day), then scaled — so the win reads as an ongoing throughput, not just a total.
|
|
12
16
|
"""
|
|
13
17
|
from __future__ import annotations
|
|
14
18
|
|
|
@@ -104,16 +108,45 @@ def overall_ratio(led: dict) -> float:
|
|
|
104
108
|
return (led.get("tokens_without", 0) / w) if w else 0.0
|
|
105
109
|
|
|
106
110
|
|
|
111
|
+
_AVG_MONTH = 365.25 / 12 # mean calendar month, in days
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _date(s: str | None) -> _dt.date | None:
|
|
115
|
+
try:
|
|
116
|
+
return _dt.date.fromisoformat(s) if s else None
|
|
117
|
+
except ValueError:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def span_days(led: dict) -> int:
|
|
122
|
+
"""Calendar days spanned by the recorded lookups (floored at 1, so a same-day
|
|
123
|
+
ledger still yields a finite per-day rate rather than dividing by zero)."""
|
|
124
|
+
a, b = _date(led.get("first")), _date(led.get("last"))
|
|
125
|
+
return max(1, (b - a).days) if a and b else 1
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def rates(led: dict) -> dict:
|
|
129
|
+
"""Average tokens saved per day / week / month over the observed span — the
|
|
130
|
+
lifetime `tokens_saved` amortised across `span_days`, then scaled up."""
|
|
131
|
+
days = span_days(led)
|
|
132
|
+
per_day = led.get("tokens_saved", 0) / days
|
|
133
|
+
return {"days": days, "day": per_day, "week": per_day * 7, "month": per_day * _AVG_MONTH}
|
|
134
|
+
|
|
135
|
+
|
|
107
136
|
def render_summary(led: dict) -> str:
|
|
108
137
|
if not led.get("lookups"):
|
|
109
138
|
return ""
|
|
110
139
|
span = led.get("first") or "?"
|
|
111
140
|
ratio = overall_ratio(led)
|
|
112
141
|
x = f"{ratio:.1f}×" if ratio else "—"
|
|
142
|
+
r = rates(led)
|
|
113
143
|
return "\n".join([
|
|
114
144
|
"",
|
|
115
145
|
f"Cumulative (tracked across {led['lookups']} lookup(s) since {span})",
|
|
116
146
|
f" tokens without Fux: {led['tokens_without']:>10,} tok",
|
|
117
147
|
f" tokens with Fux: {led['tokens_with']:>10,} tok",
|
|
118
148
|
f" tokens saved: {led['tokens_saved']:>10,} tok → {x} overall",
|
|
149
|
+
f" {'≈ saved per day:':<21}{round(r['day']):>10,} tok (avg over {r['days']} day(s))",
|
|
150
|
+
f" {'≈ saved per week:':<21}{round(r['week']):>10,} tok",
|
|
151
|
+
f" {'≈ saved per month:':<21}{round(r['month']):>10,} tok",
|
|
119
152
|
])
|
|
@@ -80,6 +80,12 @@ class Footprint:
|
|
|
80
80
|
def out(self) -> Path:
|
|
81
81
|
return self.base / "out"
|
|
82
82
|
|
|
83
|
+
@property
|
|
84
|
+
def sessions(self) -> Path:
|
|
85
|
+
"""Per-session runtime state (gitignored), kept out of ``out/``'s top level
|
|
86
|
+
so the derived html/reports there stay easy to browse."""
|
|
87
|
+
return self.base / "out" / "sessions"
|
|
88
|
+
|
|
83
89
|
@property
|
|
84
90
|
def config(self) -> Path:
|
|
85
91
|
return self.base / "config.toml"
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Session-aware: rules whose own source was edited this session are skipped, so the
|
|
4
4
|
PostToolUse hook only nags about rules that drifted, not ones you just updated.
|
|
5
|
-
Session state lives in ``.fux/out
|
|
5
|
+
Session state lives in ``.fux/out/sessions/<id>.json`` (gitignored) — tucked in its
|
|
6
|
+
own subdir so it never clutters the derived html/reports at the top of ``out/``.
|
|
6
7
|
"""
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
@@ -15,8 +16,8 @@ from fux.model import Rule
|
|
|
15
16
|
|
|
16
17
|
def _state_path(root: Path, session: str) -> Path:
|
|
17
18
|
fp = paths.Footprint(root)
|
|
18
|
-
fp.
|
|
19
|
-
return fp.
|
|
19
|
+
fp.sessions.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
return fp.sessions / f"{session or 'default'}.json"
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def mark_rule_edited(root: Path, session: str, rule_id: str) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fux-engine
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Fux — a portable agent-aware knowledge engine: rules, memory, narrative, and graph in one frontmatter substrate.
|
|
5
5
|
Author-email: arpit arya <arpitarya.me@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -115,6 +115,13 @@ fux serve # local dashboard over the generated views
|
|
|
115
115
|
fux import docs/ # migrate existing markdown → narrative entries
|
|
116
116
|
fux parity # is it safe to retire the old graph/docs/memory?
|
|
117
117
|
fux tour # ordered ONBOARDING.md
|
|
118
|
+
|
|
119
|
+
# Runtime consumers (agents + apps, e.g. Anton's Orff concierge)
|
|
120
|
+
fux components [--scope dir] # component/hook registry for on-the-fly UI generation
|
|
121
|
+
fux validate-spec # validate a declarative UISpec against the registry
|
|
122
|
+
fux feedback # record rejected specs as candidate vocabulary gaps
|
|
123
|
+
fux hook-recall # stdin-JSON recall for agent prompt hooks
|
|
124
|
+
fux query / path / explain # graph traversal: cross-module "how does X relate to Y"
|
|
118
125
|
```
|
|
119
126
|
|
|
120
127
|
**Complete, example-driven guide to everything Fux does:
|
|
@@ -180,6 +187,12 @@ effective ruleset = ~/.claude/fux/global/ (cross-project best practices)
|
|
|
180
187
|
`project` overrides `pack` overrides `global`. `fux check` flags conflicts
|
|
181
188
|
instead of silently shadowing.
|
|
182
189
|
|
|
190
|
+
> Packs are optional. A single-project setup can keep `packs = []` and hold all
|
|
191
|
+
> authored knowledge in the repo's own `.fux/` — version-controlled with the code
|
|
192
|
+
> it governs (see Anton's `knowledge-location` rule for the reasoning). Global
|
|
193
|
+
> rules are seeded from this repo's `fux/data/global/`, so they stay versioned
|
|
194
|
+
> tool code, not loose documents.
|
|
195
|
+
|
|
183
196
|
## Guarantee
|
|
184
197
|
|
|
185
198
|
Every maintenance command is shell/AST/parse — **no LLM calls**. The only paths
|
|
@@ -49,3 +49,33 @@ def test_reset_and_summary(project):
|
|
|
49
49
|
costledger.reset(project)
|
|
50
50
|
assert costledger.load(project)["lookups"] == 0
|
|
51
51
|
assert costledger.render_summary(costledger.load(project)) == ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_rates_amortise_over_span(project):
|
|
55
|
+
_seed(project)
|
|
56
|
+
from fux import loader, config, paths
|
|
57
|
+
rules = loader.resolve(project, config.load(paths.Footprint(project).config)).active()
|
|
58
|
+
# span 2026-06-05 → 2026-06-13 is 8 days
|
|
59
|
+
costledger.record(project, "a", rules, today=_dt.date(2026, 6, 5))
|
|
60
|
+
costledger.record(project, "b", rules, today=_dt.date(2026, 6, 13))
|
|
61
|
+
led = costledger.load(project)
|
|
62
|
+
assert costledger.span_days(led) == 8
|
|
63
|
+
r = costledger.rates(led)
|
|
64
|
+
assert r["day"] == led["tokens_saved"] / 8
|
|
65
|
+
assert r["week"] == r["day"] * 7
|
|
66
|
+
assert r["month"] > r["week"] > r["day"] > 0 # ascending, all positive
|
|
67
|
+
out = costledger.render_summary(led)
|
|
68
|
+
for label in ("saved per day", "saved per week", "saved per month", "avg over 8 day(s)"):
|
|
69
|
+
assert label in out
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_span_floored_to_one_day(project):
|
|
73
|
+
"""A same-day ledger (first == last) yields span 1, not a divide-by-zero."""
|
|
74
|
+
_seed(project)
|
|
75
|
+
from fux import loader, config, paths
|
|
76
|
+
rules = loader.resolve(project, config.load(paths.Footprint(project).config)).active()
|
|
77
|
+
costledger.record(project, "a", rules, today=_dt.date(2026, 6, 5))
|
|
78
|
+
led = costledger.load(project)
|
|
79
|
+
assert costledger.span_days(led) == 1
|
|
80
|
+
r = costledger.rates(led)
|
|
81
|
+
assert r["day"] == led["tokens_saved"] and r["week"] == led["tokens_saved"] * 7
|
|
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
|
{fux_engine-0.3.1 → fux_engine-0.3.2}/fux/data/packs/indian-markets-tax/rules/market-hours-nse.md
RENAMED
|
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
|
|
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
|